From f394cd738588a588e6b863031b85230b7e316b44 Mon Sep 17 00:00:00 2001 From: ddCat <2695092045@qq.com> Date: Wed, 16 Sep 2026 22:59:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(canvas):=20=E7=94=BB=E5=B8=83=E4=B8=BB?= =?UTF-8?q?=E9=A2=98=20-=20=E4=B8=8E=E5=B7=A5=E4=BD=9C=E5=8F=B0=E5=A4=96?= =?UTF-8?q?=E8=A7=82=E9=9A=94=E7=A6=BB=E5=B9=B6=E6=8F=90=E9=AB=98=E8=BF=9E?= =?UTF-8?q?=E7=BA=BF=E5=AF=B9=E6=AF=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- web/src/components/brand/brand-logo.tsx | 5 +-- .../art-critique/ai-art-critique-modal.tsx | 4 +-- .../canvas/canvas-active-task-panel.tsx | 4 +-- .../components/canvas/canvas-asset-tray.tsx | 6 ++-- .../canvas/canvas-audio-settings-popover.tsx | 4 +-- .../canvas/canvas-config-composer.tsx | 4 +-- .../canvas/canvas-config-node-panel.tsx | 4 +-- .../components/canvas/canvas-connections.tsx | 8 ++--- .../components/canvas/canvas-context-menu.tsx | 12 +++---- .../components/canvas/canvas-create-menu.tsx | 6 ++-- .../canvas/canvas-drawing-editor-modal.tsx | 4 +-- .../canvas/canvas-emotion-workspace.tsx | 6 ++-- .../canvas/canvas-focus-mode-bar.tsx | 4 +-- .../components/canvas/canvas-frame-node.tsx | 4 +-- .../canvas/canvas-image-settings-popover.tsx | 4 +-- .../canvas/canvas-leafer-graphics-layer.tsx | 4 +-- web/src/components/canvas/canvas-mini-map.tsx | 4 +-- .../canvas/canvas-node-angle-dialog.tsx | 4 +-- .../canvas/canvas-node-camera-dialog.tsx | 6 ++-- .../canvas/canvas-node-emotion-panel.tsx | 8 ++--- .../canvas/canvas-node-lighting-dialog.tsx | 4 +-- .../canvas/canvas-node-mask-edit-dialog.tsx | 4 +-- .../canvas/canvas-node-prompt-panel.tsx | 4 +-- .../components/canvas/canvas-node-toolbar.tsx | 6 ++-- web/src/components/canvas/canvas-node.tsx | 4 +-- .../canvas-portrait-texture-popover.tsx | 4 +-- .../canvas/canvas-preset-picker.tsx | 4 +-- .../canvas-resource-mention-textarea.tsx | 4 +-- .../components/canvas/canvas-script-node.tsx | 4 +-- .../components/canvas/canvas-share-modal.tsx | 4 +-- .../canvas/canvas-short-drama-entry.tsx | 14 ++++---- .../canvas/canvas-style-picker-modal.tsx | 6 ++-- .../canvas/canvas-subtitle-dialog.tsx | 4 +-- .../canvas/canvas-text-editor-modal.tsx | 8 ++--- .../canvas/canvas-timeline-dialog.tsx | 4 +-- web/src/components/canvas/canvas-toolbar.tsx | 4 +-- .../canvas/canvas-version-compare-modal.tsx | 4 +-- .../canvas/canvas-video-frame-dialog.tsx | 4 +-- .../canvas/canvas-video-prompt-tools.tsx | 4 +-- .../canvas/canvas-video-segment-dialog.tsx | 4 +-- .../canvas/canvas-video-settings-popover.tsx | 4 +-- .../canvas/canvas-workspace-overlays.tsx | 8 ++--- .../canvas/canvas-zoom-controls.tsx | 4 +-- .../director/canvas-director-node-panel.tsx | 4 +-- .../director/canvas-director-workbench.tsx | 4 +-- web/src/components/canvas/infinite-canvas.tsx | 6 ++-- .../canvas/nodes/ai-art-critique-node.tsx | 4 +-- .../toolbars/toolbar-settings-modal.tsx | 4 +-- .../conversation/voice-recording-button.tsx | 14 ++++---- .../conversation/voice-recording-inline.tsx | 4 +-- web/src/components/layout/app-providers.tsx | 8 ++--- .../components/prompt/prompt-code-editor.tsx | 4 +-- .../stores/canvas/use-canvas-theme-store.ts | 34 ++++++++++++++++++ web/test/canvas-theme-isolation.test.ts | 36 +++++++++++++++++++ 54 files changed, 207 insertions(+), 134 deletions(-) create mode 100644 web/src/stores/canvas/use-canvas-theme-store.ts create mode 100644 web/test/canvas-theme-isolation.test.ts diff --git a/web/src/components/brand/brand-logo.tsx b/web/src/components/brand/brand-logo.tsx index b21d48b7d..6e92afe6a 100644 --- a/web/src/components/brand/brand-logo.tsx +++ b/web/src/components/brand/brand-logo.tsx @@ -2,7 +2,8 @@ import { useState, type CSSProperties, type ReactNode } from "react"; import { cn } from "@/lib/utils"; import { appearanceLogoURL, useAppearanceStore } from "@/stores/use-appearance-store"; -import { useThemeStore, type ThemeName } from "@/stores/use-theme-store"; +import type { ThemeName } from "@/stores/use-theme-store"; +import { useActiveTheme } from "@/stores/canvas/use-canvas-theme-store"; type BrandLogoProps = { className?: string; @@ -13,7 +14,7 @@ type BrandLogoProps = { export function BrandLogo({ className, fallback, alt = "", theme = "auto" }: BrandLogoProps) { const appearance = useAppearanceStore((state) => state.appearance); - const currentTheme = useThemeStore((state) => state.theme); + const currentTheme = useActiveTheme(); const source = appearanceLogoURL(appearance, theme === "auto" ? currentTheme : theme); const [failedSource, setFailedSource] = useState(null); if (!appearance.logoConfigured) return <>{fallback}; diff --git a/web/src/components/canvas/art-critique/ai-art-critique-modal.tsx b/web/src/components/canvas/art-critique/ai-art-critique-modal.tsx index 201320fe7..90d46c74c 100644 --- a/web/src/components/canvas/art-critique/ai-art-critique-modal.tsx +++ b/web/src/components/canvas/art-critique/ai-art-critique-modal.tsx @@ -30,7 +30,7 @@ import { useCopyText } from "@/hooks/use-copy-text"; import { resolveImageUrl } from "@/services/image-storage"; import { modelOptionLabel, useEffectiveConfig } from "@/stores/use-config-store"; import { usePluginStore } from "@/stores/use-plugin-store"; -import { useThemeStore } from "@/stores/use-theme-store"; +import { useActiveTheme } from "@/stores/canvas/use-canvas-theme-store"; import type { CanvasNodeData } from "@/types/canvas"; import { IconButton } from "@/components/ui/base/buttons"; import { Callout } from "@/components/ui/product/callout"; @@ -63,7 +63,7 @@ type AiArtCritiqueView = "overview" | "detail"; type ArtCritiquePromptStatus = "ready" | "pending" | "unavailable"; export function AiArtCritiqueModal({ node, upstreamNodes, open, onClose, onUpdateState, startRequestId, restartRequested, onRunningChange }: AiArtCritiqueModalProps) { - const theme = canvasThemes[useThemeStore((state) => state.theme)]; + const theme = canvasThemes[useActiveTheme()]; const effectiveConfig = useEffectiveConfig(); const selectedCritiqueModel = effectiveConfig.textModel.trim(); const configuredCritiqueModelLabel = selectedCritiqueModel ? modelOptionLabel(effectiveConfig, selectedCritiqueModel) : "未配置文本/视觉理解模型"; diff --git a/web/src/components/canvas/canvas-active-task-panel.tsx b/web/src/components/canvas/canvas-active-task-panel.tsx index 6e8868ff6..4d89d3cb4 100644 --- a/web/src/components/canvas/canvas-active-task-panel.tsx +++ b/web/src/components/canvas/canvas-active-task-panel.tsx @@ -7,12 +7,12 @@ import { aceternityMotion } from "@/lib/aceternity-motion"; import { formatTaskKind, generationTaskShowsProgress, generationTaskStageLabel, generationTaskStatusLabel } from "@/lib/generation-task-display"; import { canvasThemes } from "@/lib/canvas-theme"; import type { GenerationTask } from "@/services/api/task-center"; -import { useThemeStore } from "@/stores/use-theme-store"; +import { useActiveTheme } from "@/stores/canvas/use-canvas-theme-store"; import { useUserStore } from "@/stores/use-user-store"; // 顶栏是绝对定位浮层,面板必须按调用方传入的 topInset 避让;专注模式隐藏顶栏时传小间距。 export function CanvasActiveTaskPanel({ tasks, align = "right", topInset = "var(--canvas-topbar-offset)", onCancelTask }: { tasks: GenerationTask[]; align?: "left" | "right"; topInset?: string; onCancelTask?: (task: GenerationTask) => void }) { - const theme = canvasThemes[useThemeStore((state) => state.theme)]; + const theme = canvasThemes[useActiveTheme()]; const creditsEnabled = useUserStore((state) => state.features.creditsEnabled); const reducedMotion = useReducedMotion(); const [now, setNow] = useState(() => Date.now()); diff --git a/web/src/components/canvas/canvas-asset-tray.tsx b/web/src/components/canvas/canvas-asset-tray.tsx index 503316d9c..845d68351 100644 --- a/web/src/components/canvas/canvas-asset-tray.tsx +++ b/web/src/components/canvas/canvas-asset-tray.tsx @@ -10,7 +10,7 @@ import { canvasDockStyle } from "@/lib/canvas/canvas-aceternity-style"; import { canvasThemes } from "@/lib/canvas-theme"; import { resourceStorageLabel, resourceStorageLocation, resourceStorageTitle } from "@/lib/canvas/resource-storage-status"; import { cn } from "@/lib/utils"; -import { useThemeStore } from "@/stores/use-theme-store"; +import { useActiveTheme } from "@/stores/canvas/use-canvas-theme-store"; import type { ImageAsset } from "@/stores/use-asset-store"; import { CanvasNodeType, type CanvasNodeData } from "@/types/canvas"; @@ -51,7 +51,7 @@ type CanvasAssetTrayProps = { }; export function CanvasAssetTray({ assetImages, canvasImages, showLibrary = true, activeNodeId, onInsertAssetImage, onFocusCanvasImage }: CanvasAssetTrayProps) { - const theme = canvasThemes[useThemeStore((state) => state.theme)]; + const theme = canvasThemes[useActiveTheme()]; const reducedMotion = useReducedMotion(); const { bringToFront, zIndex } = useCanvasOverlayLayer("asset-tray", "var(--z-panel)"); const rootRef = useRef(null); @@ -265,7 +265,7 @@ function TrayTabButton({ active, label, theme, onClick }: { active: boolean; lab } function AssetTrayRow({ title, imageUrl, storageKey, icon, active = false, draggable = false, motionEnabled, onClick, onDragStart }: { title: string; imageUrl: string; storageKey?: string; icon: ReactNode; active?: boolean; draggable?: boolean; motionEnabled: boolean; onClick: () => void; onDragStart?: (event: DragEvent) => void }) { - const theme = canvasThemes[useThemeStore((state) => state.theme)]; + const theme = canvasThemes[useActiveTheme()]; const location = resourceStorageLocation(storageKey); return ( state.theme)]; + const theme = canvasThemes[useActiveTheme()]; const buttonRef = useRef(null); const panelRef = useRef(null); const [open, setOpen] = useState(false); diff --git a/web/src/components/canvas/canvas-config-composer.tsx b/web/src/components/canvas/canvas-config-composer.tsx index fda57e78f..ddd6d6c88 100644 --- a/web/src/components/canvas/canvas-config-composer.tsx +++ b/web/src/components/canvas/canvas-config-composer.tsx @@ -5,7 +5,7 @@ import { FileText, Image as ImageIcon, Music2, Pencil, Sparkles, Video, X } from import { canvasThemes } from "@/lib/canvas-theme"; import { isCanvasWorkflowProvider } from "@/lib/canvas/canvas-workflow"; -import { useThemeStore } from "@/stores/use-theme-store"; +import { useActiveTheme } from "@/stores/canvas/use-canvas-theme-store"; import type { CanvasResourceReference } from "@/lib/canvas/canvas-resource-references"; import { generationInputMentionLabel, normalizeGenerationNodeMentionTokens, type NodeGenerationInput } from "./canvas-node-generation"; import { CanvasVideoPromptTools } from "./canvas-video-prompt-tools"; @@ -45,7 +45,7 @@ type ComposerCandidate = export const CONFIG_REFERENCE_PATTERN = /@\[node:([^\]]+)\]|@(图片|视频|音频|文本|角色|绘图)(\d+)/g; export function CanvasConfigComposer({ value, inputs, skillReferences = [], generationMode, metadata, onChange, onMetadataChange, onClose, workspaceMode = "professional" }: CanvasConfigComposerProps) { - const theme = canvasThemes[useThemeStore((state) => state.theme)]; + const theme = canvasThemes[useActiveTheme()]; const editorRef = useRef(null); const composingRef = useRef(false); const [mention, setMention] = useState(null); diff --git a/web/src/components/canvas/canvas-config-node-panel.tsx b/web/src/components/canvas/canvas-config-node-panel.tsx index ee234bd53..5c3c6c972 100644 --- a/web/src/components/canvas/canvas-config-node-panel.tsx +++ b/web/src/components/canvas/canvas-config-node-panel.tsx @@ -12,7 +12,7 @@ import { defaultModelCapabilityConfig, modelCapabilityConfigFor, normalizeImageV import { defaultImageParamsForModel, modelCompatibilityError, modelRequestOptions, resolveCompatibleModel, resolveModelGenerationDefaults, type ModelRequirements } from "@/lib/model-selection"; import { resolveCanvasWorkflowProvider } from "@/lib/canvas/canvas-workflow"; import type { CanvasAudioSettingKey } from "./canvas-audio-settings-popover"; -import { useThemeStore } from "@/stores/use-theme-store"; +import { useActiveTheme } from "@/stores/canvas/use-canvas-theme-store"; import { workflowProviderPluginEnabled } from "@/lib/plugins/builtin/workflows"; import { usePluginStore } from "@/stores/use-plugin-store"; import type { CanvasGenerationMode, CanvasNodeData, CanvasNodeMetadata, CanvasVideoEditOperation, CanvasWorkspaceMode } from "@/types/canvas"; @@ -64,7 +64,7 @@ function runningHubWorkflowEntryKey(workflow: RunningHubWorkflow): string { export function CanvasConfigNodePanel({ node, isRunning, inputSummary, onConfigChange, onGenerate, onComposerToggle, workspaceMode = "professional" }: CanvasConfigNodePanelProps) { const globalConfig = useEffectiveConfig(); const runtimeStatuses = usePluginStore((state) => state.runtimeStatuses); - const theme = canvasThemes[useThemeStore((state) => state.theme)]; + const theme = canvasThemes[useActiveTheme()]; const mode = node.metadata?.generationMode === "video" || node.metadata?.generationMode === "audio" ? node.metadata.generationMode : "image"; const simpleMode = workspaceMode === "simple"; const resolvedProvider = resolveCanvasWorkflowProvider(node.metadata); diff --git a/web/src/components/canvas/canvas-connections.tsx b/web/src/components/canvas/canvas-connections.tsx index c1dd59cf6..baf73774a 100644 --- a/web/src/components/canvas/canvas-connections.tsx +++ b/web/src/components/canvas/canvas-connections.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; import type { MouseEvent as ReactMouseEvent } from "react"; import { canvasThemes } from "@/lib/canvas-theme"; -import { useThemeStore } from "@/stores/use-theme-store"; +import { useActiveTheme } from "@/stores/canvas/use-canvas-theme-store"; import { STORYBOARD_HEADER_HEIGHT, STORYBOARD_ROW_HEIGHT, storyboardTableHeight } from "@/lib/canvas/canvas-storyboard-layout"; import { batchReferenceHandleY } from "@/lib/canvas/canvas-batch-table"; import type { CanvasConnection, CanvasNodeData, ConnectionHandle, Position } from "@/types/canvas"; @@ -30,7 +30,7 @@ export const ConnectionPath = React.memo(function ConnectionPath({ onSelect: () => void; onContextMenu?: (event: ReactMouseEvent) => void; }) { - const theme = canvasThemes[useThemeStore((state) => state.theme)]; + const theme = canvasThemes[useActiveTheme()]; const [hovered, setHovered] = useState(false); const { pathD, startX, startY, endX, endY } = canvasConnectionPath(connection, from, to, fromScrollTop, toScrollTop); const emphasized = active || hovered; @@ -100,9 +100,9 @@ export const ConnectionPath = React.memo(function ConnectionPath({ {showVisual ? state.theme)]; + const theme = canvasThemes[useActiveTheme()]; const [addOpen, setAddOpen] = useState(false); const [categoryOpen, setCategoryOpen] = useState(false); @@ -279,7 +279,7 @@ export function CanvasNodeContextMenu({ } function AddNodeContextMenu({ parentPosition, workspaceMode, isProjectLinked, onAddNode, onAddFolder, onChooseStyle, onOpenDirector, onUpload, onOpenAssets, onOpenProjectCharacters }: { parentPosition: { left: number; top: number }; workspaceMode: CanvasWorkspaceMode; isProjectLinked: boolean; onAddNode: (type: CanvasNodeTypeId) => void; onAddFolder: () => void; onChooseStyle: () => void; onOpenDirector: () => void; onUpload: () => void; onOpenAssets: () => void; onOpenProjectCharacters: () => void }) { - const theme = canvasThemes[useThemeStore((state) => state.theme)]; + const theme = canvasThemes[useActiveTheme()]; const installations = usePluginStore((state) => state.installations); const pluginStates = usePluginStore((state) => state.pluginStates); const left = getSubmenuLeft(parentPosition.left); @@ -332,7 +332,7 @@ function AddNodeContextMenu({ parentPosition, workspaceMode, isProjectLinked, on } function MenuHeader({ title, description, onBack }: { title: string; description?: string; onBack?: () => void }) { - const theme = canvasThemes[useThemeStore((state) => state.theme)]; + const theme = canvasThemes[useActiveTheme()]; return (
{onBack ? : null} @@ -346,7 +346,7 @@ function MenuSection({ label }: { label: string }) { } function MenuButton({ icon, label, detail, shortcut, badge, chevron = false, active = false, disabled = false, danger = false, onClick }: { icon: ReactNode; label: string; detail?: string; shortcut?: string; badge?: string; chevron?: boolean; active?: boolean; disabled?: boolean; danger?: boolean; onClick?: () => void }) { - const theme = canvasThemes[useThemeStore((state) => state.theme)]; + const theme = canvasThemes[useActiveTheme()]; const color = danger ? theme.accent.danger : theme.node.text; return ( diff --git a/web/src/components/canvas/canvas-timeline-dialog.tsx b/web/src/components/canvas/canvas-timeline-dialog.tsx index ea134a5b4..4e44b890b 100644 --- a/web/src/components/canvas/canvas-timeline-dialog.tsx +++ b/web/src/components/canvas/canvas-timeline-dialog.tsx @@ -13,7 +13,7 @@ import { saveAs } from "file-saver"; import { CanvasTimelineRuler } from "./canvas-timeline-ruler"; import { CanvasTimelinePreview } from "./canvas-timeline-preview"; import { canvasThemes } from "@/lib/canvas-theme"; -import { useThemeStore } from "@/stores/use-theme-store"; +import { useActiveTheme } from "@/stores/canvas/use-canvas-theme-store"; import { buildTimelineFromNodes, isNodeInTimeline, syncTimelineSubtitleClips } from "@/lib/timeline/timeline-build"; import { canPlaceAt, clampClipDurationByNeighbors, findNearestAvailablePlacement } from "@/lib/timeline/timeline-placement"; import { computeSnap } from "@/lib/timeline/timeline-snap"; @@ -77,7 +77,7 @@ export function CanvasTimelineDialog({ onCreateAssembledNode, }: CanvasTimelineDialogProps) { const { message } = App.useApp(); - const theme = canvasThemes[useThemeStore((state) => state.theme)]; + const theme = canvasThemes[useActiveTheme()]; const [draft, setDraft] = useState(() => buildTimelineFromNodes([])); const [playheadMs, setPlayheadMs] = useState(0); const [zoomLevel, setZoomLevel] = useState(1); diff --git a/web/src/components/canvas/canvas-toolbar.tsx b/web/src/components/canvas/canvas-toolbar.tsx index 6cd2fc569..65fe0e5d3 100644 --- a/web/src/components/canvas/canvas-toolbar.tsx +++ b/web/src/components/canvas/canvas-toolbar.tsx @@ -15,7 +15,7 @@ import { canvasDockStyle } from "@/lib/canvas/canvas-aceternity-style"; import type { CanvasAppearance } from "@/lib/canvas/canvas-appearance"; import { canvasThemes, type CanvasBackgroundMode, type CanvasTheme } from "@/lib/canvas-theme"; import { defaultToolbarPrefs, readToolbarPrefs, resolveToolbarEntries, type ToolContext, type ToolbarHandlers, type ToolbarPrefs } from "@/lib/canvas/tool-registry"; -import { useThemeStore } from "@/stores/use-theme-store"; +import { useActiveTheme } from "@/stores/canvas/use-canvas-theme-store"; import type { CanvasNodeTypeId, CanvasToolMode, CanvasWorkspaceMode } from "@/types/canvas"; export function CanvasToolbar({ @@ -92,7 +92,7 @@ export function CanvasToolbar({ const rootRef = useRef(null); const { bringToFront, zIndex } = useCanvasOverlayLayer("main-toolbar", "var(--z-toolbar)"); const dockRef = useRef(null); - const colorTheme = useThemeStore((state) => state.theme); + const colorTheme = useActiveTheme(); const theme = canvasThemes[colorTheme]; const [addOpen, setAddOpen] = useState(false); const [appearanceOpen, setAppearanceOpen] = useState(false); diff --git a/web/src/components/canvas/canvas-version-compare-modal.tsx b/web/src/components/canvas/canvas-version-compare-modal.tsx index 173d55ffc..a10a34f21 100644 --- a/web/src/components/canvas/canvas-version-compare-modal.tsx +++ b/web/src/components/canvas/canvas-version-compare-modal.tsx @@ -3,11 +3,11 @@ import { Check, Star } from "lucide-react"; import { canvasThemes } from "@/lib/canvas-theme"; import { canvasNodeVideoPreviewUrl } from "@/lib/canvas/canvas-media-preview"; -import { useThemeStore } from "@/stores/use-theme-store"; +import { useActiveTheme } from "@/stores/canvas/use-canvas-theme-store"; import { CanvasNodeType, type CanvasNodeData } from "@/types/canvas"; export function CanvasVersionCompareModal({ open, versions, onClose, onSetPrimary, onFocus }: { open: boolean; versions: CanvasNodeData[]; onClose: () => void; onSetPrimary: (nodeId: string) => void; onFocus: (nodeId: string) => void }) { - const theme = canvasThemes[useThemeStore((state) => state.theme)]; + const theme = canvasThemes[useActiveTheme()]; const modalWidth = Math.min(1180, Math.max(440, 112 + versions.length * 340)); return ( diff --git a/web/src/components/canvas/canvas-video-frame-dialog.tsx b/web/src/components/canvas/canvas-video-frame-dialog.tsx index 2f9021268..c6bb1f6eb 100644 --- a/web/src/components/canvas/canvas-video-frame-dialog.tsx +++ b/web/src/components/canvas/canvas-video-frame-dialog.tsx @@ -8,7 +8,7 @@ import { formatVideoFrameTime, normalizeVideoFrameTimes } from "@/lib/canvas/can import { resourceIdFromStorageKey } from "@/services/api/resources"; import { resolveMediaUrl } from "@/services/file-storage"; import { cacheResourceObjectUrl } from "@/services/resource-blob-cache"; -import { useThemeStore } from "@/stores/use-theme-store"; +import { useActiveTheme } from "@/stores/canvas/use-canvas-theme-store"; import type { CanvasNodeData } from "@/types/canvas"; type SelectedVideoFrame = { @@ -31,7 +31,7 @@ const MAX_SELECTED_FRAMES = 30; export function CanvasVideoFrameDialog({ node, open, onClose, onConfirm }: CanvasVideoFrameDialogProps) { const { message } = App.useApp(); - const theme = canvasThemes[useThemeStore((state) => state.theme)]; + const theme = canvasThemes[useActiveTheme()]; const videoRef = useRef(null); const [videoUrl, setVideoUrl] = useState(""); const [videoError, setVideoError] = useState(false); diff --git a/web/src/components/canvas/canvas-video-prompt-tools.tsx b/web/src/components/canvas/canvas-video-prompt-tools.tsx index 26563cbea..d73c59ab4 100644 --- a/web/src/components/canvas/canvas-video-prompt-tools.tsx +++ b/web/src/components/canvas/canvas-video-prompt-tools.tsx @@ -3,7 +3,7 @@ import { createPortal } from "react-dom"; import { Check, ChevronDown, Image as ImageIcon } from "lucide-react"; import { canvasThemes, type CanvasTheme } from "@/lib/canvas-theme"; -import { useThemeStore } from "@/stores/use-theme-store"; +import { useActiveTheme } from "@/stores/canvas/use-canvas-theme-store"; import type { CanvasNodeMetadata } from "@/types/canvas"; type VideoFrameOption = { @@ -34,7 +34,7 @@ const MENU_ITEM_HEIGHT = 28; const CONTROL_TEXT_STYLE: CSSProperties = { fontFamily: "inherit", fontSize: 11, fontWeight: 400, letterSpacing: 0, lineHeight: 1 }; export function CanvasVideoPromptTools({ metadata, frameOptions, onMetadataChange, referenceMode = "frames", referenceSummary }: CanvasVideoPromptToolsProps) { - const theme = canvasThemes[useThemeStore((state) => state.theme)]; + const theme = canvasThemes[useActiveTheme()]; const startFrame = metadata?.videoStartFrameNodeId || EMPTY_FRAME_VALUE; const endFrame = metadata?.videoEndFrameNodeId || EMPTY_FRAME_VALUE; diff --git a/web/src/components/canvas/canvas-video-segment-dialog.tsx b/web/src/components/canvas/canvas-video-segment-dialog.tsx index ad5b9b73e..eeae10690 100644 --- a/web/src/components/canvas/canvas-video-segment-dialog.tsx +++ b/web/src/components/canvas/canvas-video-segment-dialog.tsx @@ -10,7 +10,7 @@ import { listVideoReferenceModels } from "@/lib/canvas/canvas-video-regeneration import { modelCapabilityConfigFor } from "@/lib/model-capabilities"; import { modelRequestOptions, resolveCompatibleModel, type ModelRequirements } from "@/lib/model-selection"; import { navigateToSettings } from "@/lib/settings-navigation"; -import { useThemeStore } from "@/stores/use-theme-store"; +import { useActiveTheme } from "@/stores/canvas/use-canvas-theme-store"; import { resolveMediaUrl } from "@/services/file-storage"; import { cacheResourceObjectUrl } from "@/services/resource-blob-cache"; import { resourceIdFromStorageKey } from "@/services/api/resources"; @@ -47,7 +47,7 @@ const MIN_SEGMENT_MS = 100; export function CanvasVideoSegmentDialog({ node, nodes, connections, open, mode, config, timeline, onClose, onConfirm }: CanvasVideoSegmentDialogProps) { const { message } = App.useApp(); - const theme = canvasThemes[useThemeStore((state) => state.theme)]; + const theme = canvasThemes[useActiveTheme()]; const videoRef = useRef(null); const segmentsSeededRef = useRef(false); const [videoUrl, setVideoUrl] = useState(""); diff --git a/web/src/components/canvas/canvas-video-settings-popover.tsx b/web/src/components/canvas/canvas-video-settings-popover.tsx index f013fd7c3..bf9322d27 100644 --- a/web/src/components/canvas/canvas-video-settings-popover.tsx +++ b/web/src/components/canvas/canvas-video-settings-popover.tsx @@ -6,7 +6,7 @@ import { Button } from "antd"; import { VideoSettingsPanel, videoResolutionLabel, videoSecondsLabel, videoSizeLabel } from "@/components/video-settings-panel"; import { canvasThemes } from "@/lib/canvas-theme"; import { modelCapabilityConfigFor, resolveVideoRatioValue, resolveVideoResolutionValue } from "@/lib/model-capabilities"; -import { useThemeStore } from "@/stores/use-theme-store"; +import { useActiveTheme } from "@/stores/canvas/use-canvas-theme-store"; import type { AiConfig } from "@/stores/use-config-store"; type CanvasVideoSettingsPopoverProps = { @@ -17,7 +17,7 @@ type CanvasVideoSettingsPopoverProps = { }; export function CanvasVideoSettingsPopover({ config, onConfigChange, buttonClassName, placement = "topLeft" }: CanvasVideoSettingsPopoverProps) { - const theme = canvasThemes[useThemeStore((state) => state.theme)]; + const theme = canvasThemes[useActiveTheme()]; const buttonRef = useRef(null); const panelRef = useRef(null); const [open, setOpen] = useState(false); diff --git a/web/src/components/canvas/canvas-workspace-overlays.tsx b/web/src/components/canvas/canvas-workspace-overlays.tsx index b3a2c2b0d..f7b558ce5 100644 --- a/web/src/components/canvas/canvas-workspace-overlays.tsx +++ b/web/src/components/canvas/canvas-workspace-overlays.tsx @@ -6,7 +6,7 @@ import { useCanvasOverlayLayer } from "@/components/canvas/canvas-overlay-layer" import { canvasThemes } from "@/lib/canvas-theme"; import { aceternityMotion } from "@/lib/aceternity-motion"; import { subscribeCanvasGraphicsViewportPreview, subscribeCanvasNodeDragPreview, subscribeCanvasViewportPreview } from "@/lib/canvas/canvas-live-viewport"; -import { useThemeStore } from "@/stores/use-theme-store"; +import { useActiveTheme } from "@/stores/canvas/use-canvas-theme-store"; import { CanvasNodeType, type CanvasNodeData, type ConnectionHandle, type Position, type ViewportTransform } from "@/types/canvas"; export type PendingConnectionCreate = { @@ -17,7 +17,7 @@ export type PendingConnectionCreate = { }; export function CanvasSelectionToolbar({ anchorRef, containerRef, count, children }: { anchorRef: RefObject; containerRef: RefObject; count: number; children: ReactNode }) { - const theme = canvasThemes[useThemeStore((state) => state.theme)]; + const theme = canvasThemes[useActiveTheme()]; const reducedMotion = useReducedMotion(); const toolbarRef = useRef(null); const [anchor, setAnchor] = useState<{ left: number; top: number; placement: "above" | "below" } | null>(null); @@ -155,7 +155,7 @@ function resolveNodePanelWidth(node: CanvasNodeData, viewport: ViewportTransform } export function CanvasConnectionCreateMenu({ pending, viewport, viewportSize, containerRef, canCreateDrawing, getDisabledReason, onCreate, onClose }: { pending: PendingConnectionCreate; viewport: ViewportTransform; viewportSize: { width: number; height: number }; containerRef: RefObject; canCreateDrawing: boolean; getDisabledReason: (type: CanvasNodeType.Image | CanvasNodeType.Text | CanvasNodeType.Script | CanvasNodeType.BatchTable | CanvasNodeType.Video | CanvasNodeType.Audio | CanvasNodeType.Drawing | CanvasNodeType.Config | CanvasNodeType.MediaConversion, provider?: "runninghub") => string; onCreate: (type: CanvasNodeType.Image | CanvasNodeType.Text | CanvasNodeType.Script | CanvasNodeType.BatchTable | CanvasNodeType.Video | CanvasNodeType.Audio | CanvasNodeType.Drawing | CanvasNodeType.Config | CanvasNodeType.MediaConversion, provider?: "runninghub") => void; onClose: () => void }) { - const theme = canvasThemes[useThemeStore((state) => state.theme)]; + const theme = canvasThemes[useActiveTheme()]; const reducedMotion = useReducedMotion(); const menuRef = useRef(null); const [activeOption, setActiveOption] = useState(null); @@ -241,7 +241,7 @@ export function CanvasConnectionCreateMenu({ pending, viewport, viewportSize, co } function ConnectionCreateOption({ expanded, motionEnabled, icon, title, description, disabledReason, onClick }: { expanded: boolean; motionEnabled: boolean; icon: ReactNode; title: string; description: string; disabledReason?: string; onClick: () => void }) { - const theme = canvasThemes[useThemeStore((state) => state.theme)]; + const theme = canvasThemes[useActiveTheme()]; return (