mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-07 22:24:06 -05:00
improvement(copilot): ui/ux; refactor: store dimensions (#2636)
This commit is contained in:
@@ -5,13 +5,16 @@
|
|||||||
/**
|
/**
|
||||||
* CSS-based sidebar and panel widths to prevent SSR hydration mismatches.
|
* CSS-based sidebar and panel widths to prevent SSR hydration mismatches.
|
||||||
* Default widths are set here and updated via blocking script before React hydrates.
|
* Default widths are set here and updated via blocking script before React hydrates.
|
||||||
|
*
|
||||||
|
* @important These values must stay in sync with stores/constants.ts
|
||||||
|
* @see stores/constants.ts for the source of truth
|
||||||
*/
|
*/
|
||||||
:root {
|
:root {
|
||||||
--sidebar-width: 232px;
|
--sidebar-width: 232px; /* SIDEBAR_WIDTH.DEFAULT */
|
||||||
--panel-width: 260px;
|
--panel-width: 290px; /* PANEL_WIDTH.DEFAULT */
|
||||||
--toolbar-triggers-height: 300px;
|
--toolbar-triggers-height: 300px; /* TOOLBAR_TRIGGERS_HEIGHT.DEFAULT */
|
||||||
--editor-connections-height: 200px;
|
--editor-connections-height: 172px; /* EDITOR_CONNECTIONS_HEIGHT.DEFAULT */
|
||||||
--terminal-height: 155px;
|
--terminal-height: 155px; /* TERMINAL_HEIGHT.DEFAULT */
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-container {
|
.sidebar-container {
|
||||||
|
|||||||
@@ -41,7 +41,12 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Workspace layout dimensions: set CSS vars before hydration to avoid layout jump */}
|
{/*
|
||||||
|
Workspace layout dimensions: set CSS vars before hydration to avoid layout jump.
|
||||||
|
|
||||||
|
IMPORTANT: These hardcoded values must stay in sync with stores/constants.ts
|
||||||
|
We cannot use imports here since this is a blocking script that runs before React.
|
||||||
|
*/}
|
||||||
<script
|
<script
|
||||||
id='workspace-layout-dimensions'
|
id='workspace-layout-dimensions'
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
@@ -84,7 +89,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|||||||
var panelWidth = panelState && panelState.panelWidth;
|
var panelWidth = panelState && panelState.panelWidth;
|
||||||
var maxPanelWidth = window.innerWidth * 0.4;
|
var maxPanelWidth = window.innerWidth * 0.4;
|
||||||
|
|
||||||
if (panelWidth >= 260 && panelWidth <= maxPanelWidth) {
|
if (panelWidth >= 290 && panelWidth <= maxPanelWidth) {
|
||||||
document.documentElement.style.setProperty('--panel-width', panelWidth + 'px');
|
document.documentElement.style.setProperty('--panel-width', panelWidth + 'px');
|
||||||
} else if (panelWidth > maxPanelWidth) {
|
} else if (panelWidth > maxPanelWidth) {
|
||||||
document.documentElement.style.setProperty('--panel-width', maxPanelWidth + 'px');
|
document.documentElement.style.setProperty('--panel-width', maxPanelWidth + 'px');
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export const FileAttachmentDisplay = memo(({ fileAttachments }: FileAttachmentDi
|
|||||||
{fileAttachments.map((file) => (
|
{fileAttachments.map((file) => (
|
||||||
<div
|
<div
|
||||||
key={file.id}
|
key={file.id}
|
||||||
className='group relative h-16 w-16 cursor-pointer overflow-hidden rounded-md border border-border/50 bg-muted/20 transition-all hover:bg-muted/40'
|
className='group relative h-16 w-16 cursor-pointer overflow-hidden rounded-md border border-[var(--border-1)] bg-muted/20 transition-all hover:bg-muted/40'
|
||||||
onClick={() => handleFileClick(file)}
|
onClick={() => handleFileClick(file)}
|
||||||
title={`${file.filename} (${formatFileSize(file.size)})`}
|
title={`${file.filename} (${formatFileSize(file.size)})`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ const CopilotMessage: FC<CopilotMessageProps> = memo(
|
|||||||
onClick={handleMessageClick}
|
onClick={handleMessageClick}
|
||||||
onMouseEnter={() => setIsHoveringMessage(true)}
|
onMouseEnter={() => setIsHoveringMessage(true)}
|
||||||
onMouseLeave={() => setIsHoveringMessage(false)}
|
onMouseLeave={() => setIsHoveringMessage(false)}
|
||||||
className='group relative w-full cursor-pointer rounded-[4px] border border-[var(--border-1)] bg-[var(--surface-5)] px-[6px] py-[6px] transition-all duration-200 hover:border-[var(--surface-7)] hover:bg-[var(--surface-5)] dark:bg-[var(--surface-5)] dark:hover:border-[var(--surface-7)] dark:hover:bg-[var(--border-1)]'
|
className='group relative w-full cursor-pointer rounded-[4px] border border-[var(--border-1)] bg-[var(--surface-4)] px-[6px] py-[6px] transition-all duration-200 hover:border-[var(--surface-7)] hover:bg-[var(--surface-4)] dark:bg-[var(--surface-4)] dark:hover:border-[var(--surface-7)] dark:hover:bg-[var(--border-1)]'
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
ref={messageContentRef}
|
ref={messageContentRef}
|
||||||
@@ -350,7 +350,7 @@ const CopilotMessage: FC<CopilotMessageProps> = memo(
|
|||||||
nodes.push(
|
nodes.push(
|
||||||
<span
|
<span
|
||||||
key={`mention-${i}-${lastIndex}`}
|
key={`mention-${i}-${lastIndex}`}
|
||||||
className='rounded-[6px] bg-[rgba(142,76,251,0.65)]'
|
className='rounded-[4px] bg-[rgba(50,189,126,0.65)] py-[1px]'
|
||||||
>
|
>
|
||||||
{mention}
|
{mention}
|
||||||
</span>
|
</span>
|
||||||
@@ -365,7 +365,7 @@ const CopilotMessage: FC<CopilotMessageProps> = memo(
|
|||||||
|
|
||||||
{/* Gradient fade when truncated - applies to entire message box */}
|
{/* Gradient fade when truncated - applies to entire message box */}
|
||||||
{!isExpanded && needsExpansion && (
|
{!isExpanded && needsExpansion && (
|
||||||
<div className='pointer-events-none absolute right-0 bottom-0 left-0 h-6 bg-gradient-to-t from-0% from-[var(--surface-5)] via-40% via-[var(--surface-5)]/70 to-100% to-transparent group-hover:from-[var(--surface-5)] group-hover:via-[var(--surface-5)]/70 dark:from-[var(--surface-5)] dark:via-[var(--surface-5)]/70 dark:group-hover:from-[var(--border-1)] dark:group-hover:via-[var(--border-1)]/70' />
|
<div className='pointer-events-none absolute right-0 bottom-0 left-0 h-6 bg-gradient-to-t from-0% from-[var(--surface-4)] via-25% via-[var(--surface-4)] to-100% to-transparent opacity-40 group-hover:opacity-30 dark:from-[var(--surface-4)] dark:via-[var(--surface-4)] dark:group-hover:from-[var(--border-1)] dark:group-hover:via-[var(--border-1)]' />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Abort button when hovering and response is generating (only on last user message) */}
|
{/* Abort button when hovering and response is generating (only on last user message) */}
|
||||||
@@ -376,13 +376,12 @@ const CopilotMessage: FC<CopilotMessageProps> = memo(
|
|||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
abortMessage()
|
abortMessage()
|
||||||
}}
|
}}
|
||||||
className='h-[20px] w-[20px] rounded-full bg-[var(--c-C0C0C0)] p-0 transition-colors hover:bg-[var(--c-D0D0D0)]'
|
className='h-[20px] w-[20px] rounded-full border-0 bg-[var(--c-383838)] p-0 transition-colors hover:bg-[var(--c-575757)] dark:bg-[var(--c-E0E0E0)] dark:hover:bg-[var(--c-CFCFCF)]'
|
||||||
title='Stop generation'
|
title='Stop generation'
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
className='block h-[13px] w-[13px]'
|
className='block h-[13px] w-[13px] fill-white dark:fill-black'
|
||||||
viewBox='0 0 24 24'
|
viewBox='0 0 24 24'
|
||||||
fill='black'
|
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
>
|
>
|
||||||
<rect x='4' y='4' width='16' height='16' rx='3' ry='3' />
|
<rect x='4' y='4' width='16' height='16' rx='3' ry='3' />
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ export function ModelSelector({ selectedModel, isNearTop, onModelSelect }: Model
|
|||||||
{getModelIconComponent(option.value)}
|
{getModelIconComponent(option.value)}
|
||||||
<span>{option.label}</span>
|
<span>{option.label}</span>
|
||||||
{isMaxModel(option.value) && (
|
{isMaxModel(option.value) && (
|
||||||
<Badge variant='default' className='ml-auto px-[6px] py-[1px] text-[10px]'>
|
<Badge size='sm' className='ml-auto'>
|
||||||
MAX
|
MAX
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -590,7 +590,7 @@ const UserInput = forwardRef<UserInputRef, UserInputProps>(
|
|||||||
elements.push(
|
elements.push(
|
||||||
<span
|
<span
|
||||||
key={`mention-${i}-${range.start}-${range.end}`}
|
key={`mention-${i}-${range.start}-${range.end}`}
|
||||||
className='rounded-[6px] bg-[rgba(142,76,251,0.65)]'
|
className='rounded-[4px] bg-[rgba(50,189,126,0.65)] py-[1px]'
|
||||||
>
|
>
|
||||||
{mentionText}
|
{mentionText}
|
||||||
</span>
|
</span>
|
||||||
@@ -619,7 +619,7 @@ const UserInput = forwardRef<UserInputRef, UserInputProps>(
|
|||||||
<div
|
<div
|
||||||
ref={setInputContainerRef}
|
ref={setInputContainerRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative w-full rounded-[4px] border border-[var(--border-1)] bg-[var(--surface-5)] px-[6px] py-[6px] transition-colors dark:bg-[var(--surface-5)]',
|
'relative w-full rounded-[4px] border border-[var(--border-1)] bg-[var(--surface-4)] px-[6px] py-[6px] transition-colors dark:bg-[var(--surface-4)]',
|
||||||
fileAttachments.isDragging && 'ring-[1.75px] ring-[var(--brand-secondary)]'
|
fileAttachments.isDragging && 'ring-[1.75px] ring-[var(--brand-secondary)]'
|
||||||
)}
|
)}
|
||||||
onDragEnter={fileAttachments.handleDragEnter}
|
onDragEnter={fileAttachments.handleDragEnter}
|
||||||
@@ -746,7 +746,7 @@ const UserInput = forwardRef<UserInputRef, UserInputProps>(
|
|||||||
onClick={fileAttachments.handleFileSelect}
|
onClick={fileAttachments.handleFileSelect}
|
||||||
title='Attach file'
|
title='Attach file'
|
||||||
className={cn(
|
className={cn(
|
||||||
'cursor-pointer rounded-[6px] bg-transparent p-[0px] dark:bg-transparent',
|
'cursor-pointer rounded-[6px] border-0 bg-transparent p-[0px] dark:bg-transparent',
|
||||||
(disabled || isLoading) && 'cursor-not-allowed opacity-50'
|
(disabled || isLoading) && 'cursor-not-allowed opacity-50'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -758,20 +758,19 @@ const UserInput = forwardRef<UserInputRef, UserInputProps>(
|
|||||||
onClick={handleAbort}
|
onClick={handleAbort}
|
||||||
disabled={isAborting}
|
disabled={isAborting}
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-[20px] w-[20px] rounded-full p-0 transition-colors',
|
'h-[20px] w-[20px] rounded-full border-0 p-0 transition-colors',
|
||||||
!isAborting
|
!isAborting
|
||||||
? 'bg-[var(--c-C0C0C0)] hover:bg-[var(--c-D0D0D0)]'
|
? 'bg-[var(--c-383838)] hover:bg-[var(--c-575757)] dark:bg-[var(--c-E0E0E0)] dark:hover:bg-[var(--c-CFCFCF)]'
|
||||||
: 'bg-[var(--c-C0C0C0)]'
|
: 'bg-[var(--c-383838)] dark:bg-[var(--c-E0E0E0)]'
|
||||||
)}
|
)}
|
||||||
title='Stop generation'
|
title='Stop generation'
|
||||||
>
|
>
|
||||||
{isAborting ? (
|
{isAborting ? (
|
||||||
<Loader2 className='block h-[13px] w-[13px] animate-spin text-black' />
|
<Loader2 className='block h-[13px] w-[13px] animate-spin text-white dark:text-black' />
|
||||||
) : (
|
) : (
|
||||||
<svg
|
<svg
|
||||||
className='block h-[13px] w-[13px]'
|
className='block h-[13px] w-[13px] fill-white dark:fill-black'
|
||||||
viewBox='0 0 24 24'
|
viewBox='0 0 24 24'
|
||||||
fill='black'
|
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
>
|
>
|
||||||
<rect x='4' y='4' width='16' height='16' rx='3' ry='3' />
|
<rect x='4' y='4' width='16' height='16' rx='3' ry='3' />
|
||||||
@@ -785,16 +784,19 @@ const UserInput = forwardRef<UserInputRef, UserInputProps>(
|
|||||||
}}
|
}}
|
||||||
disabled={!canSubmit}
|
disabled={!canSubmit}
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-[22px] w-[22px] rounded-full p-0 transition-colors',
|
'h-[22px] w-[22px] rounded-full border-0 p-0 transition-colors',
|
||||||
canSubmit
|
canSubmit
|
||||||
? 'bg-[var(--c-C0C0C0)] hover:bg-[var(--c-D0D0D0)]'
|
? 'bg-[var(--c-383838)] hover:bg-[var(--c-575757)] dark:bg-[var(--c-E0E0E0)] dark:hover:bg-[var(--c-CFCFCF)]'
|
||||||
: 'bg-[var(--c-C0C0C0)]'
|
: 'bg-[var(--c-808080)] dark:bg-[var(--c-808080)]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Loader2 className='block h-3.5 w-3.5 animate-spin text-black' />
|
<Loader2 className='block h-3.5 w-3.5 animate-spin text-white dark:text-black' />
|
||||||
) : (
|
) : (
|
||||||
<ArrowUp className='block h-3.5 w-3.5 text-black' strokeWidth={2.25} />
|
<ArrowUp
|
||||||
|
className='block h-3.5 w-3.5 text-white dark:text-black'
|
||||||
|
strokeWidth={2.25}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
import { PANEL_WIDTH } from '@/stores/constants'
|
||||||
import { usePanelStore } from '@/stores/panel/store'
|
import { usePanelStore } from '@/stores/panel/store'
|
||||||
|
|
||||||
/**
|
|
||||||
* Constants for panel sizing
|
|
||||||
*/
|
|
||||||
const MIN_WIDTH = 244
|
|
||||||
const MAX_WIDTH_PERCENTAGE = 0.4 // 40% of viewport width
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom hook to handle panel resize functionality.
|
* Custom hook to handle panel resize functionality.
|
||||||
* Manages mouse events for resizing and enforces min/max width constraints.
|
* Manages mouse events for resizing and enforces min/max width constraints.
|
||||||
@@ -35,9 +30,9 @@ export function usePanelResize() {
|
|||||||
const handleMouseMove = (e: MouseEvent) => {
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
// Calculate width from the right edge of the viewport
|
// Calculate width from the right edge of the viewport
|
||||||
const newWidth = window.innerWidth - e.clientX
|
const newWidth = window.innerWidth - e.clientX
|
||||||
const maxWidth = window.innerWidth * MAX_WIDTH_PERCENTAGE
|
const maxWidth = window.innerWidth * PANEL_WIDTH.MAX_PERCENTAGE
|
||||||
|
|
||||||
if (newWidth >= MIN_WIDTH && newWidth <= maxWidth) {
|
if (newWidth >= PANEL_WIDTH.MIN && newWidth <= maxWidth) {
|
||||||
setPanelWidth(newWidth)
|
setPanelWidth(newWidth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
import { OUTPUT_PANEL_WIDTH } from '@/stores/constants'
|
||||||
import { useTerminalStore } from '@/stores/terminal'
|
import { useTerminalStore } from '@/stores/terminal'
|
||||||
|
|
||||||
const MIN_WIDTH = 440
|
|
||||||
const BLOCK_COLUMN_WIDTH = 240
|
const BLOCK_COLUMN_WIDTH = 240
|
||||||
|
|
||||||
export function useOutputPanelResize() {
|
export function useOutputPanelResize() {
|
||||||
@@ -26,7 +26,7 @@ export function useOutputPanelResize() {
|
|||||||
const newWidth = window.innerWidth - e.clientX - panelWidth
|
const newWidth = window.innerWidth - e.clientX - panelWidth
|
||||||
const terminalWidth = window.innerWidth - sidebarWidth - panelWidth
|
const terminalWidth = window.innerWidth - sidebarWidth - panelWidth
|
||||||
const maxWidth = terminalWidth - BLOCK_COLUMN_WIDTH
|
const maxWidth = terminalWidth - BLOCK_COLUMN_WIDTH
|
||||||
const clampedWidth = Math.max(MIN_WIDTH, Math.min(newWidth, maxWidth))
|
const clampedWidth = Math.max(OUTPUT_PANEL_WIDTH.MIN, Math.min(newWidth, maxWidth))
|
||||||
|
|
||||||
setOutputPanelWidth(clampedWidth)
|
setOutputPanelWidth(clampedWidth)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import {
|
|||||||
useTerminalResize,
|
useTerminalResize,
|
||||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/hooks'
|
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/hooks'
|
||||||
import { getBlock } from '@/blocks'
|
import { getBlock } from '@/blocks'
|
||||||
|
import { OUTPUT_PANEL_WIDTH, TERMINAL_HEIGHT } from '@/stores/constants'
|
||||||
import { useCopilotTrainingStore } from '@/stores/copilot-training/store'
|
import { useCopilotTrainingStore } from '@/stores/copilot-training/store'
|
||||||
import { useGeneralStore } from '@/stores/settings/general/store'
|
import { useGeneralStore } from '@/stores/settings/general/store'
|
||||||
import type { ConsoleEntry } from '@/stores/terminal'
|
import type { ConsoleEntry } from '@/stores/terminal'
|
||||||
@@ -53,15 +54,15 @@ import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
|||||||
/**
|
/**
|
||||||
* Terminal height configuration constants
|
* Terminal height configuration constants
|
||||||
*/
|
*/
|
||||||
const MIN_HEIGHT = 30
|
const MIN_HEIGHT = TERMINAL_HEIGHT.MIN
|
||||||
const NEAR_MIN_THRESHOLD = 40
|
const NEAR_MIN_THRESHOLD = 40
|
||||||
const DEFAULT_EXPANDED_HEIGHT = 155
|
const DEFAULT_EXPANDED_HEIGHT = TERMINAL_HEIGHT.DEFAULT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Column width constants - numeric values for calculations
|
* Column width constants - numeric values for calculations
|
||||||
*/
|
*/
|
||||||
const BLOCK_COLUMN_WIDTH_PX = 240
|
const BLOCK_COLUMN_WIDTH_PX = 240
|
||||||
const MIN_OUTPUT_PANEL_WIDTH_PX = 440
|
const MIN_OUTPUT_PANEL_WIDTH_PX = OUTPUT_PANEL_WIDTH.MIN
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Column width constants - Tailwind classes for styling
|
* Column width constants - Tailwind classes for styling
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
ModalContent,
|
ModalContent,
|
||||||
ModalFooter,
|
ModalFooter,
|
||||||
ModalHeader,
|
ModalHeader,
|
||||||
Slider,
|
|
||||||
Switch,
|
Switch,
|
||||||
} from '@/components/emcn'
|
} from '@/components/emcn'
|
||||||
import { Input, Skeleton } from '@/components/ui'
|
import { Input, Skeleton } from '@/components/ui'
|
||||||
@@ -70,7 +69,7 @@ function GeneralSkeleton() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Auto-connect row */}
|
{/* Auto-connect row */}
|
||||||
<div className='flex items-center justify-between pt-[12px]'>
|
<div className='flex items-center justify-between'>
|
||||||
<Skeleton className='h-4 w-36' />
|
<Skeleton className='h-4 w-36' />
|
||||||
<Skeleton className='h-[17px] w-[30px] rounded-full' />
|
<Skeleton className='h-[17px] w-[30px] rounded-full' />
|
||||||
</div>
|
</div>
|
||||||
@@ -82,7 +81,7 @@ function GeneralSkeleton() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Telemetry row */}
|
{/* Telemetry row */}
|
||||||
<div className='flex items-center justify-between border-t pt-[12px]'>
|
<div className='flex items-center justify-between border-t pt-[16px]'>
|
||||||
<Skeleton className='h-4 w-44' />
|
<Skeleton className='h-4 w-44' />
|
||||||
<Skeleton className='h-[17px] w-[30px] rounded-full' />
|
<Skeleton className='h-[17px] w-[30px] rounded-full' />
|
||||||
</div>
|
</div>
|
||||||
@@ -134,8 +133,7 @@ export function General({ onOpenChange }: GeneralProps) {
|
|||||||
|
|
||||||
const [uploadError, setUploadError] = useState<string | null>(null)
|
const [uploadError, setUploadError] = useState<string | null>(null)
|
||||||
|
|
||||||
const [localSnapValue, setLocalSnapValue] = useState<number | null>(null)
|
const snapToGridValue = settings?.snapToGridSize ?? 0
|
||||||
const snapToGridValue = localSnapValue ?? settings?.snapToGridSize ?? 0
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (profile?.name) {
|
if (profile?.name) {
|
||||||
@@ -295,16 +293,11 @@ export function General({ onOpenChange }: GeneralProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSnapToGridChange = (value: number[]) => {
|
const handleSnapToGridChange = async (value: string) => {
|
||||||
setLocalSnapValue(value[0])
|
const newValue = Number.parseInt(value, 10)
|
||||||
}
|
|
||||||
|
|
||||||
const handleSnapToGridCommit = async (value: number[]) => {
|
|
||||||
const newValue = value[0]
|
|
||||||
if (newValue !== settings?.snapToGridSize && !updateSetting.isPending) {
|
if (newValue !== settings?.snapToGridSize && !updateSetting.isPending) {
|
||||||
await updateSetting.mutateAsync({ key: 'snapToGridSize', value: newValue })
|
await updateSetting.mutateAsync({ key: 'snapToGridSize', value: newValue })
|
||||||
}
|
}
|
||||||
setLocalSnapValue(null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleTrainingControlsChange = async (checked: boolean) => {
|
const handleTrainingControlsChange = async (checked: boolean) => {
|
||||||
@@ -476,7 +469,7 @@ export function General({ onOpenChange }: GeneralProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex items-center justify-between pt-[12px]'>
|
<div className='flex items-center justify-between'>
|
||||||
<Label htmlFor='auto-connect'>Auto-connect on drop</Label>
|
<Label htmlFor='auto-connect'>Auto-connect on drop</Label>
|
||||||
<Switch
|
<Switch
|
||||||
id='auto-connect'
|
id='auto-connect'
|
||||||
@@ -486,26 +479,7 @@ export function General({ onOpenChange }: GeneralProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex items-center justify-between'>
|
<div className='flex items-center justify-between'>
|
||||||
<Label htmlFor='snap-to-grid'>Snap to grid</Label>
|
<Label htmlFor='error-notifications'>Workflow error notifications</Label>
|
||||||
<div className='flex items-center gap-[12px]'>
|
|
||||||
<span className='w-[32px] text-right text-[12px] text-[var(--text-tertiary)]'>
|
|
||||||
{snapToGridValue === 0 ? 'Off' : `${snapToGridValue}px`}
|
|
||||||
</span>
|
|
||||||
<Slider
|
|
||||||
id='snap-to-grid'
|
|
||||||
value={[snapToGridValue]}
|
|
||||||
onValueChange={handleSnapToGridChange}
|
|
||||||
onValueCommit={handleSnapToGridCommit}
|
|
||||||
min={0}
|
|
||||||
max={50}
|
|
||||||
step={1}
|
|
||||||
className='w-[100px]'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex items-center justify-between'>
|
|
||||||
<Label htmlFor='error-notifications'>Run error notifications</Label>
|
|
||||||
<Switch
|
<Switch
|
||||||
id='error-notifications'
|
id='error-notifications'
|
||||||
checked={settings?.errorNotificationsEnabled ?? true}
|
checked={settings?.errorNotificationsEnabled ?? true}
|
||||||
@@ -513,7 +487,29 @@ export function General({ onOpenChange }: GeneralProps) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex items-center justify-between border-t pt-[12px]'>
|
<div className='flex items-center justify-between'>
|
||||||
|
<Label htmlFor='snap-to-grid'>Snap to grid</Label>
|
||||||
|
<div className='w-[100px]'>
|
||||||
|
<Combobox
|
||||||
|
size='sm'
|
||||||
|
align='end'
|
||||||
|
dropdownWidth={140}
|
||||||
|
value={String(snapToGridValue)}
|
||||||
|
onChange={handleSnapToGridChange}
|
||||||
|
placeholder='Select size'
|
||||||
|
options={[
|
||||||
|
{ label: 'Off', value: '0' },
|
||||||
|
{ label: '10px', value: '10' },
|
||||||
|
{ label: '20px', value: '20' },
|
||||||
|
{ label: '30px', value: '30' },
|
||||||
|
{ label: '40px', value: '40' },
|
||||||
|
{ label: '50px', value: '50' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex items-center justify-between border-t pt-[16px]'>
|
||||||
<Label htmlFor='telemetry'>Allow anonymous telemetry</Label>
|
<Label htmlFor='telemetry'>Allow anonymous telemetry</Label>
|
||||||
<Switch
|
<Switch
|
||||||
id='telemetry'
|
id='telemetry'
|
||||||
@@ -522,9 +518,9 @@ export function General({ onOpenChange }: GeneralProps) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className='text-[12px] text-[var(--text-muted)]'>
|
<p className='-mt-[8px] text-[12px] text-[var(--text-muted)]'>
|
||||||
We use OpenTelemetry to collect anonymous usage data to improve Sim. All data is collected
|
We use OpenTelemetry to collect anonymous usage data to improve Sim. You can opt-out at any
|
||||||
in accordance with our privacy policy, and you can opt-out at any time.
|
time.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{isTrainingEnabled && (
|
{isTrainingEnabled && (
|
||||||
@@ -561,11 +557,11 @@ export function General({ onOpenChange }: GeneralProps) {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
onClick={() => window.open('/', '_blank', 'noopener,noreferrer')}
|
onClick={() => window.open('/?from=settings', '_blank', 'noopener,noreferrer')}
|
||||||
variant='active'
|
variant='active'
|
||||||
className='ml-auto'
|
className='ml-auto'
|
||||||
>
|
>
|
||||||
Landing
|
Home Page
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import {
|
|||||||
import { getBillingStatus, getSubscriptionStatus, getUsage } from '@/lib/billing/client/utils'
|
import { getBillingStatus, getSubscriptionStatus, getUsage } from '@/lib/billing/client/utils'
|
||||||
import { useSocket } from '@/app/workspace/providers/socket-provider'
|
import { useSocket } from '@/app/workspace/providers/socket-provider'
|
||||||
import { subscriptionKeys, useSubscriptionData } from '@/hooks/queries/subscription'
|
import { subscriptionKeys, useSubscriptionData } from '@/hooks/queries/subscription'
|
||||||
import { MIN_SIDEBAR_WIDTH, useSidebarStore } from '@/stores/sidebar/store'
|
import { SIDEBAR_WIDTH } from '@/stores/constants'
|
||||||
|
import { useSidebarStore } from '@/stores/sidebar/store'
|
||||||
|
|
||||||
const logger = createLogger('UsageIndicator')
|
const logger = createLogger('UsageIndicator')
|
||||||
|
|
||||||
@@ -172,7 +173,7 @@ function shouldShowPlanText(
|
|||||||
const countDigits = (value: number): number => value.toFixed(2).replace(/\D/g, '').length
|
const countDigits = (value: number): number => value.toFixed(2).replace(/\D/g, '').length
|
||||||
|
|
||||||
const usageDigits = countDigits(usage.current) + countDigits(usage.limit)
|
const usageDigits = countDigits(usage.current) + countDigits(usage.limit)
|
||||||
const extraWidth = Math.max(0, sidebarWidth - MIN_SIDEBAR_WIDTH)
|
const extraWidth = Math.max(0, sidebarWidth - SIDEBAR_WIDTH.MIN)
|
||||||
const bonusDigits = Math.floor(extraWidth / WIDTH_COSTS.WIDTH_PER_EXTRA_DIGIT)
|
const bonusDigits = Math.floor(extraWidth / WIDTH_COSTS.WIDTH_PER_EXTRA_DIGIT)
|
||||||
|
|
||||||
let totalCost = usageDigits
|
let totalCost = usageDigits
|
||||||
@@ -256,7 +257,7 @@ export function UsageIndicator({ onClick }: UsageIndicatorProps) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const pillCount = useMemo(() => {
|
const pillCount = useMemo(() => {
|
||||||
const widthDelta = sidebarWidth - MIN_SIDEBAR_WIDTH
|
const widthDelta = sidebarWidth - SIDEBAR_WIDTH.MIN
|
||||||
const additionalPills = Math.floor(widthDelta / PILL_CONFIG.WIDTH_PER_PILL)
|
const additionalPills = Math.floor(widthDelta / PILL_CONFIG.WIDTH_PER_PILL)
|
||||||
const calculatedCount = PILL_CONFIG.MIN_COUNT + additionalPills
|
const calculatedCount = PILL_CONFIG.MIN_COUNT + additionalPills
|
||||||
return Math.max(PILL_CONFIG.MIN_COUNT, Math.min(PILL_CONFIG.MAX_COUNT, calculatedCount))
|
return Math.max(PILL_CONFIG.MIN_COUNT, Math.min(PILL_CONFIG.MAX_COUNT, calculatedCount))
|
||||||
|
|||||||
@@ -718,7 +718,7 @@ export function InviteModal({ open, onOpenChange, workspaceName }: InviteModalPr
|
|||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
<div className='scrollbar-hide flex max-h-32 min-h-9 flex-wrap items-center gap-x-[8px] gap-y-[4px] overflow-y-auto rounded-[4px] border border-[var(--border-1)] bg-[var(--surface-5)] px-[6px] py-[4px] focus-within:outline-none dark:bg-[var(--surface-5)]'>
|
<div className='scrollbar-hide flex max-h-32 min-h-9 flex-wrap items-center gap-x-[8px] gap-y-[4px] overflow-y-auto rounded-[4px] border border-[var(--border-1)] bg-[var(--surface-4)] px-[6px] py-[4px] focus-within:outline-none'>
|
||||||
{invalidEmails.map((email, index) => (
|
{invalidEmails.map((email, index) => (
|
||||||
<EmailTag
|
<EmailTag
|
||||||
key={`invalid-${index}`}
|
key={`invalid-${index}`}
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { MIN_SIDEBAR_WIDTH, useSidebarStore } from '@/stores/sidebar/store'
|
import { SIDEBAR_WIDTH } from '@/stores/constants'
|
||||||
|
import { useSidebarStore } from '@/stores/sidebar/store'
|
||||||
/**
|
|
||||||
* Constants for sidebar sizing
|
|
||||||
*/
|
|
||||||
const MAX_WIDTH_PERCENTAGE = 0.3 // 30% of viewport width
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom hook to handle sidebar resize functionality.
|
* Custom hook to handle sidebar resize functionality.
|
||||||
@@ -33,9 +29,9 @@ export function useSidebarResize() {
|
|||||||
|
|
||||||
const handleMouseMove = (e: MouseEvent) => {
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
const newWidth = e.clientX
|
const newWidth = e.clientX
|
||||||
const maxWidth = window.innerWidth * MAX_WIDTH_PERCENTAGE
|
const maxWidth = window.innerWidth * SIDEBAR_WIDTH.MAX_PERCENTAGE
|
||||||
|
|
||||||
if (newWidth >= MIN_SIDEBAR_WIDTH && newWidth <= maxWidth) {
|
if (newWidth >= SIDEBAR_WIDTH.MIN && newWidth <= maxWidth) {
|
||||||
setSidebarWidth(newWidth)
|
setSidebarWidth(newWidth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,10 +30,11 @@ import {
|
|||||||
useExportWorkspace,
|
useExportWorkspace,
|
||||||
useImportWorkspace,
|
useImportWorkspace,
|
||||||
} from '@/app/workspace/[workspaceId]/w/hooks'
|
} from '@/app/workspace/[workspaceId]/w/hooks'
|
||||||
|
import { SIDEBAR_WIDTH } from '@/stores/constants'
|
||||||
import { useFolderStore } from '@/stores/folders/store'
|
import { useFolderStore } from '@/stores/folders/store'
|
||||||
import { useSearchModalStore } from '@/stores/search-modal/store'
|
import { useSearchModalStore } from '@/stores/search-modal/store'
|
||||||
import { useSettingsModalStore } from '@/stores/settings-modal/store'
|
import { useSettingsModalStore } from '@/stores/settings-modal/store'
|
||||||
import { MIN_SIDEBAR_WIDTH, useSidebarStore } from '@/stores/sidebar/store'
|
import { useSidebarStore } from '@/stores/sidebar/store'
|
||||||
|
|
||||||
const logger = createLogger('Sidebar')
|
const logger = createLogger('Sidebar')
|
||||||
|
|
||||||
@@ -250,7 +251,7 @@ export function Sidebar() {
|
|||||||
if (isCollapsed) {
|
if (isCollapsed) {
|
||||||
setIsCollapsed(false)
|
setIsCollapsed(false)
|
||||||
}
|
}
|
||||||
setSidebarWidth(MIN_SIDEBAR_WIDTH)
|
setSidebarWidth(SIDEBAR_WIDTH.MIN)
|
||||||
}
|
}
|
||||||
}, [isOnWorkflowPage, isCollapsed, setIsCollapsed, setSidebarWidth])
|
}, [isOnWorkflowPage, isCollapsed, setIsCollapsed, setSidebarWidth])
|
||||||
|
|
||||||
|
|||||||
@@ -59,3 +59,58 @@ export const COPILOT_TOOL_ERROR_NAMES: Record<string, string> = {
|
|||||||
} as const
|
} as const
|
||||||
|
|
||||||
export type CopilotToolId = keyof typeof COPILOT_TOOL_DISPLAY_NAMES
|
export type CopilotToolId = keyof typeof COPILOT_TOOL_DISPLAY_NAMES
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layout dimension constants.
|
||||||
|
*
|
||||||
|
* These values must stay in sync with:
|
||||||
|
* - `globals.css` (CSS variable defaults)
|
||||||
|
* - `layout.tsx` (blocking script validations)
|
||||||
|
*
|
||||||
|
* @see globals.css for CSS variable definitions
|
||||||
|
* @see layout.tsx for pre-hydration script that reads localStorage
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Sidebar width constraints */
|
||||||
|
export const SIDEBAR_WIDTH = {
|
||||||
|
DEFAULT: 232,
|
||||||
|
MIN: 232,
|
||||||
|
/** Maximum is 30% of viewport, enforced dynamically */
|
||||||
|
MAX_PERCENTAGE: 0.3,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
/** Right panel width constraints */
|
||||||
|
export const PANEL_WIDTH = {
|
||||||
|
DEFAULT: 290,
|
||||||
|
MIN: 290,
|
||||||
|
/** Maximum is 40% of viewport, enforced dynamically */
|
||||||
|
MAX_PERCENTAGE: 0.4,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
/** Terminal height constraints */
|
||||||
|
export const TERMINAL_HEIGHT = {
|
||||||
|
DEFAULT: 155,
|
||||||
|
MIN: 30,
|
||||||
|
/** Maximum is 70% of viewport, enforced dynamically */
|
||||||
|
MAX_PERCENTAGE: 0.7,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
/** Toolbar triggers section height constraints */
|
||||||
|
export const TOOLBAR_TRIGGERS_HEIGHT = {
|
||||||
|
DEFAULT: 300,
|
||||||
|
MIN: 30,
|
||||||
|
MAX: 800,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
/** Editor connections section height constraints */
|
||||||
|
export const EDITOR_CONNECTIONS_HEIGHT = {
|
||||||
|
DEFAULT: 172,
|
||||||
|
MIN: 30,
|
||||||
|
MAX: 300,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
/** Output panel (terminal execution results) width constraints */
|
||||||
|
export const OUTPUT_PANEL_WIDTH = {
|
||||||
|
DEFAULT: 440,
|
||||||
|
MIN: 440,
|
||||||
|
} as const
|
||||||
|
|||||||
@@ -2,15 +2,9 @@
|
|||||||
|
|
||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
import { persist } from 'zustand/middleware'
|
import { persist } from 'zustand/middleware'
|
||||||
|
import { EDITOR_CONNECTIONS_HEIGHT } from '@/stores/constants'
|
||||||
import { usePanelStore } from '../store'
|
import { usePanelStore } from '../store'
|
||||||
|
|
||||||
/**
|
|
||||||
* Connections height constraints
|
|
||||||
*/
|
|
||||||
const DEFAULT_CONNECTIONS_HEIGHT = 172
|
|
||||||
const MIN_CONNECTIONS_HEIGHT = 30
|
|
||||||
const MAX_CONNECTIONS_HEIGHT = 300
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* State for the Editor panel.
|
* State for the Editor panel.
|
||||||
* Tracks the currently selected block to edit its subblocks/values and connections panel height.
|
* Tracks the currently selected block to edit its subblocks/values and connections panel height.
|
||||||
@@ -38,7 +32,7 @@ export const usePanelEditorStore = create<PanelEditorState>()(
|
|||||||
persist(
|
persist(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
currentBlockId: null,
|
currentBlockId: null,
|
||||||
connectionsHeight: DEFAULT_CONNECTIONS_HEIGHT,
|
connectionsHeight: EDITOR_CONNECTIONS_HEIGHT.DEFAULT,
|
||||||
setCurrentBlockId: (blockId) => {
|
setCurrentBlockId: (blockId) => {
|
||||||
set({ currentBlockId: blockId })
|
set({ currentBlockId: blockId })
|
||||||
|
|
||||||
@@ -53,8 +47,8 @@ export const usePanelEditorStore = create<PanelEditorState>()(
|
|||||||
},
|
},
|
||||||
setConnectionsHeight: (height) => {
|
setConnectionsHeight: (height) => {
|
||||||
const clampedHeight = Math.max(
|
const clampedHeight = Math.max(
|
||||||
MIN_CONNECTIONS_HEIGHT,
|
EDITOR_CONNECTIONS_HEIGHT.MIN,
|
||||||
Math.min(MAX_CONNECTIONS_HEIGHT, height)
|
Math.min(EDITOR_CONNECTIONS_HEIGHT.MAX, height)
|
||||||
)
|
)
|
||||||
set({ connectionsHeight: clampedHeight })
|
set({ connectionsHeight: clampedHeight })
|
||||||
// Update CSS variable for immediate visual feedback
|
// Update CSS variable for immediate visual feedback
|
||||||
@@ -68,7 +62,9 @@ export const usePanelEditorStore = create<PanelEditorState>()(
|
|||||||
toggleConnectionsCollapsed: () => {
|
toggleConnectionsCollapsed: () => {
|
||||||
const currentState = get()
|
const currentState = get()
|
||||||
const isAtMinHeight = currentState.connectionsHeight <= 35
|
const isAtMinHeight = currentState.connectionsHeight <= 35
|
||||||
const newHeight = isAtMinHeight ? DEFAULT_CONNECTIONS_HEIGHT : MIN_CONNECTIONS_HEIGHT
|
const newHeight = isAtMinHeight
|
||||||
|
? EDITOR_CONNECTIONS_HEIGHT.DEFAULT
|
||||||
|
: EDITOR_CONNECTIONS_HEIGHT.MIN
|
||||||
|
|
||||||
set({ connectionsHeight: newHeight })
|
set({ connectionsHeight: newHeight })
|
||||||
|
|
||||||
@@ -88,7 +84,7 @@ export const usePanelEditorStore = create<PanelEditorState>()(
|
|||||||
if (state && typeof window !== 'undefined') {
|
if (state && typeof window !== 'undefined') {
|
||||||
document.documentElement.style.setProperty(
|
document.documentElement.style.setProperty(
|
||||||
'--editor-connections-height',
|
'--editor-connections-height',
|
||||||
`${state.connectionsHeight || DEFAULT_CONNECTIONS_HEIGHT}px`
|
`${state.connectionsHeight || EDITOR_CONNECTIONS_HEIGHT.DEFAULT}px`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
import { persist } from 'zustand/middleware'
|
import { persist } from 'zustand/middleware'
|
||||||
|
import { PANEL_WIDTH } from '@/stores/constants'
|
||||||
import type { PanelState, PanelTab } from '@/stores/panel/types'
|
import type { PanelState, PanelTab } from '@/stores/panel/types'
|
||||||
|
|
||||||
/**
|
|
||||||
* Panel width constraints
|
|
||||||
* Note: Maximum width is enforced dynamically at 40% of viewport width in the resize hook
|
|
||||||
*/
|
|
||||||
const MIN_PANEL_WIDTH = 260
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default panel tab
|
* Default panel tab
|
||||||
*/
|
*/
|
||||||
@@ -16,10 +11,10 @@ const DEFAULT_TAB: PanelTab = 'copilot'
|
|||||||
export const usePanelStore = create<PanelState>()(
|
export const usePanelStore = create<PanelState>()(
|
||||||
persist(
|
persist(
|
||||||
(set) => ({
|
(set) => ({
|
||||||
panelWidth: MIN_PANEL_WIDTH,
|
panelWidth: PANEL_WIDTH.DEFAULT,
|
||||||
setPanelWidth: (width) => {
|
setPanelWidth: (width) => {
|
||||||
// Only enforce minimum - maximum is enforced dynamically by the resize hook
|
// Only enforce minimum - maximum is enforced dynamically by the resize hook
|
||||||
const clampedWidth = Math.max(MIN_PANEL_WIDTH, width)
|
const clampedWidth = Math.max(PANEL_WIDTH.MIN, width)
|
||||||
set({ panelWidth: clampedWidth })
|
set({ panelWidth: clampedWidth })
|
||||||
// Update CSS variable for immediate visual feedback
|
// Update CSS variable for immediate visual feedback
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
import { persist } from 'zustand/middleware'
|
import { persist } from 'zustand/middleware'
|
||||||
|
import { TOOLBAR_TRIGGERS_HEIGHT } from '@/stores/constants'
|
||||||
/**
|
|
||||||
* Toolbar triggers height constraints
|
|
||||||
* Minimum is set low to allow collapsing to just the header height (~30-40px)
|
|
||||||
*/
|
|
||||||
const DEFAULT_TOOLBAR_TRIGGERS_HEIGHT = 300
|
|
||||||
const MIN_TOOLBAR_HEIGHT = 30
|
|
||||||
const MAX_TOOLBAR_HEIGHT = 800
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toolbar state interface
|
* Toolbar state interface
|
||||||
@@ -22,9 +15,12 @@ interface ToolbarState {
|
|||||||
export const useToolbarStore = create<ToolbarState>()(
|
export const useToolbarStore = create<ToolbarState>()(
|
||||||
persist(
|
persist(
|
||||||
(set) => ({
|
(set) => ({
|
||||||
toolbarTriggersHeight: DEFAULT_TOOLBAR_TRIGGERS_HEIGHT,
|
toolbarTriggersHeight: TOOLBAR_TRIGGERS_HEIGHT.DEFAULT,
|
||||||
setToolbarTriggersHeight: (height) => {
|
setToolbarTriggersHeight: (height) => {
|
||||||
const clampedHeight = Math.max(MIN_TOOLBAR_HEIGHT, Math.min(MAX_TOOLBAR_HEIGHT, height))
|
const clampedHeight = Math.max(
|
||||||
|
TOOLBAR_TRIGGERS_HEIGHT.MIN,
|
||||||
|
Math.min(TOOLBAR_TRIGGERS_HEIGHT.MAX, height)
|
||||||
|
)
|
||||||
set({ toolbarTriggersHeight: clampedHeight })
|
set({ toolbarTriggersHeight: clampedHeight })
|
||||||
// Update CSS variable for immediate visual feedback
|
// Update CSS variable for immediate visual feedback
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
@@ -44,7 +40,7 @@ export const useToolbarStore = create<ToolbarState>()(
|
|||||||
if (state && typeof window !== 'undefined') {
|
if (state && typeof window !== 'undefined') {
|
||||||
document.documentElement.style.setProperty(
|
document.documentElement.style.setProperty(
|
||||||
'--toolbar-triggers-height',
|
'--toolbar-triggers-height',
|
||||||
`${state.toolbarTriggersHeight || DEFAULT_TOOLBAR_TRIGGERS_HEIGHT}px`
|
`${state.toolbarTriggersHeight || TOOLBAR_TRIGGERS_HEIGHT.DEFAULT}px`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
import { persist } from 'zustand/middleware'
|
import { persist } from 'zustand/middleware'
|
||||||
|
import { SIDEBAR_WIDTH } from '@/stores/constants'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sidebar state interface
|
* Sidebar state interface
|
||||||
@@ -15,24 +16,17 @@ interface SidebarState {
|
|||||||
setHasHydrated: (hasHydrated: boolean) => void
|
setHasHydrated: (hasHydrated: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sidebar width constraints
|
|
||||||
* Note: Maximum width is enforced dynamically at 30% of viewport width in the resize hook
|
|
||||||
*/
|
|
||||||
export const DEFAULT_SIDEBAR_WIDTH = 232
|
|
||||||
export const MIN_SIDEBAR_WIDTH = 232
|
|
||||||
|
|
||||||
export const useSidebarStore = create<SidebarState>()(
|
export const useSidebarStore = create<SidebarState>()(
|
||||||
persist(
|
persist(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
workspaceDropdownOpen: false,
|
workspaceDropdownOpen: false,
|
||||||
sidebarWidth: DEFAULT_SIDEBAR_WIDTH,
|
sidebarWidth: SIDEBAR_WIDTH.DEFAULT,
|
||||||
isCollapsed: false,
|
isCollapsed: false,
|
||||||
_hasHydrated: false,
|
_hasHydrated: false,
|
||||||
setWorkspaceDropdownOpen: (isOpen) => set({ workspaceDropdownOpen: isOpen }),
|
setWorkspaceDropdownOpen: (isOpen) => set({ workspaceDropdownOpen: isOpen }),
|
||||||
setSidebarWidth: (width) => {
|
setSidebarWidth: (width) => {
|
||||||
// Only enforce minimum - maximum is enforced dynamically by the resize hook
|
// Only enforce minimum - maximum is enforced dynamically by the resize hook
|
||||||
const clampedWidth = Math.max(MIN_SIDEBAR_WIDTH, width)
|
const clampedWidth = Math.max(SIDEBAR_WIDTH.MIN, width)
|
||||||
set({ sidebarWidth: clampedWidth })
|
set({ sidebarWidth: clampedWidth })
|
||||||
// Update CSS variable for immediate visual feedback
|
// Update CSS variable for immediate visual feedback
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export type { ConsoleEntry, ConsoleStore, ConsoleUpdate } from './console'
|
export type { ConsoleEntry, ConsoleStore, ConsoleUpdate } from './console'
|
||||||
export { useTerminalConsoleStore } from './console'
|
export { useTerminalConsoleStore } from './console'
|
||||||
export { DEFAULT_TERMINAL_HEIGHT, MIN_TERMINAL_HEIGHT, useTerminalStore } from './store'
|
export { useTerminalStore } from './store'
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
import { persist } from 'zustand/middleware'
|
import { persist } from 'zustand/middleware'
|
||||||
|
import { OUTPUT_PANEL_WIDTH, TERMINAL_HEIGHT } from '@/stores/constants'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display mode type for terminal output.
|
* Display mode type for terminal output.
|
||||||
@@ -41,22 +42,6 @@ interface TerminalState {
|
|||||||
setHasHydrated: (hasHydrated: boolean) => void
|
setHasHydrated: (hasHydrated: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Terminal height constraints.
|
|
||||||
*
|
|
||||||
* @remarks
|
|
||||||
* The maximum height is enforced dynamically at 70% of the viewport height
|
|
||||||
* inside the resize hook to keep the workflow canvas visible.
|
|
||||||
*/
|
|
||||||
export const MIN_TERMINAL_HEIGHT = 30
|
|
||||||
export const DEFAULT_TERMINAL_HEIGHT = 155
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Output panel width constraints.
|
|
||||||
*/
|
|
||||||
const MIN_OUTPUT_PANEL_WIDTH = 440
|
|
||||||
const DEFAULT_OUTPUT_PANEL_WIDTH = 440
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default display mode for terminal output.
|
* Default display mode for terminal output.
|
||||||
*/
|
*/
|
||||||
@@ -65,8 +50,8 @@ const DEFAULT_OUTPUT_PANEL_WIDTH = 440
|
|||||||
export const useTerminalStore = create<TerminalState>()(
|
export const useTerminalStore = create<TerminalState>()(
|
||||||
persist(
|
persist(
|
||||||
(set) => ({
|
(set) => ({
|
||||||
terminalHeight: DEFAULT_TERMINAL_HEIGHT,
|
terminalHeight: TERMINAL_HEIGHT.DEFAULT,
|
||||||
lastExpandedHeight: DEFAULT_TERMINAL_HEIGHT,
|
lastExpandedHeight: TERMINAL_HEIGHT.DEFAULT,
|
||||||
isResizing: false,
|
isResizing: false,
|
||||||
/**
|
/**
|
||||||
* Updates the terminal height and synchronizes the CSS custom property.
|
* Updates the terminal height and synchronizes the CSS custom property.
|
||||||
@@ -79,12 +64,12 @@ export const useTerminalStore = create<TerminalState>()(
|
|||||||
* @param height - Desired terminal height in pixels.
|
* @param height - Desired terminal height in pixels.
|
||||||
*/
|
*/
|
||||||
setTerminalHeight: (height) => {
|
setTerminalHeight: (height) => {
|
||||||
const clampedHeight = Math.max(MIN_TERMINAL_HEIGHT, height)
|
const clampedHeight = Math.max(TERMINAL_HEIGHT.MIN, height)
|
||||||
|
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
terminalHeight: clampedHeight,
|
terminalHeight: clampedHeight,
|
||||||
lastExpandedHeight:
|
lastExpandedHeight:
|
||||||
clampedHeight > MIN_TERMINAL_HEIGHT ? clampedHeight : state.lastExpandedHeight,
|
clampedHeight > TERMINAL_HEIGHT.MIN ? clampedHeight : state.lastExpandedHeight,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Update CSS variable for immediate visual feedback
|
// Update CSS variable for immediate visual feedback
|
||||||
@@ -100,14 +85,14 @@ export const useTerminalStore = create<TerminalState>()(
|
|||||||
setIsResizing: (isResizing) => {
|
setIsResizing: (isResizing) => {
|
||||||
set({ isResizing })
|
set({ isResizing })
|
||||||
},
|
},
|
||||||
outputPanelWidth: DEFAULT_OUTPUT_PANEL_WIDTH,
|
outputPanelWidth: OUTPUT_PANEL_WIDTH.DEFAULT,
|
||||||
/**
|
/**
|
||||||
* Updates the output panel width, enforcing the minimum constraint.
|
* Updates the output panel width, enforcing the minimum constraint.
|
||||||
*
|
*
|
||||||
* @param width - Desired width in pixels for the output panel.
|
* @param width - Desired width in pixels for the output panel.
|
||||||
*/
|
*/
|
||||||
setOutputPanelWidth: (width) => {
|
setOutputPanelWidth: (width) => {
|
||||||
const clampedWidth = Math.max(MIN_OUTPUT_PANEL_WIDTH, width)
|
const clampedWidth = Math.max(OUTPUT_PANEL_WIDTH.MIN, width)
|
||||||
set({ outputPanelWidth: clampedWidth })
|
set({ outputPanelWidth: clampedWidth })
|
||||||
},
|
},
|
||||||
openOnRun: true,
|
openOnRun: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user