按视觉层重设计 z-index(12→14 档),新增节点层/激活节点层/节点工具栏层等 语义 token,并下沉节点内部 stacking context(--node-z-content/overlay/handle) 与 dock 子面板(--dock-z-popover),消除硬编码 z 值。 新增专注模式(避让分屏可切换):use-focus-mode 管理 localStorage 偏好与小屏 (<1024px)强制避让;助手面板从 flex 改为 absolute + 垂直分槽(顶部 288px 让位活动任务面板),画布区按面板宽度动态 padding;顶栏新增 PanelRight 切换按钮并带强制态反馈。 节点工具栏由 hover+select 触发放为 click-only(选中即显),组件由 canvas-node-hover-toolbar 重命名为 canvas-node-toolbar;移除 project 与 render-model 中冗余的 toolbarNodeId 状态。 活动任务面板 z-index 由 --z-modal-overlay(110) 降至 --z-panel-floating(80), 归位浮动面板层;AGENTS.md Elevation 同步至 14 档。 Co-authored-by: sikongyue <3155157671@qq.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
||||
import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from "react";
|
||||
import { Link } from "react-router";
|
||||
import { Bot, Check, ChevronDown, Coins, FolderKanban, Gauge, Home, LayoutGrid, LoaderCircle, Menu, Pencil, Plus, Redo2, Search, Settings2, Share2, Sparkles, Trash2, Undo2, Upload } from "lucide-react";
|
||||
import { Bot, Check, ChevronDown, Coins, FolderKanban, Gauge, Home, LayoutGrid, LoaderCircle, Menu, Pencil, PanelRightClose, PanelRightOpen, Plus, Redo2, Search, Settings2, Share2, Sparkles, Trash2, Undo2, Upload } from "lucide-react";
|
||||
import { Button, Dropdown, Modal, Tooltip } from "antd";
|
||||
|
||||
import { useWalletBalance } from "@/hooks/use-wallet-balance";
|
||||
@@ -38,6 +38,9 @@ type CanvasTopBarProps = {
|
||||
onMediaPerformanceModeChange: (mode: CanvasMediaPerformanceMode) => void;
|
||||
onOpenSearch: () => void;
|
||||
projectContext?: CanvasContextSummary & { projectId: string; projectName: string };
|
||||
focusMode: boolean;
|
||||
focusModeForced: boolean;
|
||||
onToggleFocusMode: () => void;
|
||||
};
|
||||
|
||||
export function CanvasTopBar({
|
||||
@@ -66,6 +69,9 @@ export function CanvasTopBar({
|
||||
onMediaPerformanceModeChange,
|
||||
onOpenSearch,
|
||||
projectContext,
|
||||
focusMode,
|
||||
focusModeForced,
|
||||
onToggleFocusMode,
|
||||
}: CanvasTopBarProps) {
|
||||
const theme = canvasThemes[useThemeStore((state) => state.theme)];
|
||||
const user = useUserStore((state) => state.user);
|
||||
@@ -89,7 +95,7 @@ export function CanvasTopBar({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="pointer-events-none absolute left-0 right-0 top-0 z-50 flex h-16 items-center justify-between px-4">
|
||||
<div className="pointer-events-none absolute left-0 right-0 top-0 z-[var(--z-toolbar)] flex h-16 items-center justify-between px-4">
|
||||
<div className="pointer-events-auto flex min-w-0 items-center gap-3">
|
||||
<Dropdown
|
||||
trigger={["click"]}
|
||||
@@ -200,6 +206,22 @@ export function CanvasTopBar({
|
||||
<span>{availableMicrocredits === null ? "--" : (availableMicrocredits / 1_000_000).toLocaleString("zh-CN", { maximumFractionDigits: 3 })}</span>
|
||||
</Link>
|
||||
) : null}
|
||||
<Tooltip title={focusModeForced ? "小屏强制避让模式" : focusMode ? "专注模式:已避让(点击切换浮层)" : "专注模式:已浮层(点击切换避让)"}>
|
||||
<Button
|
||||
type="text"
|
||||
className="!h-10 !w-10 !min-w-10 !rounded-xl !p-0"
|
||||
style={{
|
||||
color: theme.node.text,
|
||||
opacity: focusModeForced ? 0.45 : 1,
|
||||
background: focusMode ? theme.toolbar.panel : undefined,
|
||||
}}
|
||||
icon={focusMode ? <PanelRightClose className="size-4" /> : <PanelRightOpen className="size-4" />}
|
||||
onClick={onToggleFocusMode}
|
||||
disabled={focusModeForced}
|
||||
aria-pressed={focusMode}
|
||||
aria-label="专注模式:画布与侧面板避让/浮层切换"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Button type="text" className="!h-10 !w-10 !min-w-10 !rounded-xl !p-0" style={{ color: theme.node.text }} icon={<Share2 className="size-4" />} onClick={onShare} aria-label="分享画布" title="分享画布" />
|
||||
<span className="h-6 w-px" style={{ background: theme.toolbar.border }} />
|
||||
<Button
|
||||
@@ -269,7 +291,7 @@ function CanvasWorkspaceModeSwitch({ mode, onChange }: { mode: CanvasWorkspaceMo
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={rootRef} className="aceternity-mode-switch pointer-events-auto absolute left-1/2 top-2 z-30 -translate-x-1/2">
|
||||
<div ref={rootRef} className="aceternity-mode-switch pointer-events-auto absolute left-1/2 top-2 z-[var(--dock-z-popover)] -translate-x-1/2">
|
||||
<motion.button
|
||||
type="button"
|
||||
whileHover={reducedMotion ? undefined : { y: -1, scale: 1.015 }}
|
||||
|
||||
@@ -161,7 +161,7 @@ export function CanvasProjectWorldLayers(props: CanvasProjectWorldLayersProps) {
|
||||
{props.selectedNodeBounds && !props.selectionBox && !props.isNodeDragging ? (
|
||||
<div
|
||||
ref={props.selectionBoundsElementRef}
|
||||
className="pointer-events-none absolute z-[65] rounded-xl"
|
||||
className="pointer-events-none absolute z-[var(--z-panel-floating)] rounded-xl"
|
||||
style={{
|
||||
left: props.selectedNodeBounds.left - 12 / viewportScale,
|
||||
top: props.selectedNodeBounds.top - 12 / viewportScale,
|
||||
|
||||
@@ -27,7 +27,7 @@ import { CanvasCharacterReferenceNodeContent } from "@/components/canvas/canvas-
|
||||
import { CanvasCharacterReferenceModal } from "@/components/canvas/canvas-character-reference-modal";
|
||||
import { WorkspaceState } from "@/components/layout/workspace-state";
|
||||
import { resolveCanvasStylePreset } from "@/components/canvas/canvas-style-picker-modal";
|
||||
import { CanvasNodeHoverToolbar, CanvasNodeInfoModal } from "@/components/canvas/canvas-node-hover-toolbar";
|
||||
import { CanvasNodeToolbar, CanvasNodeInfoModal } from "@/components/canvas/canvas-node-toolbar";
|
||||
import { CanvasNodeAnglePanel } from "@/components/canvas/canvas-node-angle-dialog";
|
||||
import { CanvasTextEditorModal } from "@/components/canvas/canvas-text-editor-modal";
|
||||
import { CanvasNodeSearchModal } from "@/components/canvas/canvas-node-search-modal";
|
||||
@@ -45,6 +45,7 @@ import { CanvasScriptEditor, CanvasScriptNodeContent, STORYBOARD_HEADER_HEIGHT,
|
||||
import { CanvasDirectorNodePanel } from "@/components/canvas/director/canvas-director-node-panel";
|
||||
import { CanvasVersionCompareModal } from "@/components/canvas/canvas-version-compare-modal";
|
||||
import { CanvasLocalAgentPanel } from "@/components/canvas/canvas-local-agent-panel";
|
||||
import { useFocusMode } from "@/hooks/use-focus-mode";
|
||||
import { useCanvasAgentStore } from "@/stores/canvas/use-canvas-agent-store";
|
||||
import type { CanvasResourceReference } from "@/lib/canvas/canvas-resource-references";
|
||||
import { CanvasConnectionCreateMenu, CanvasNodePanelOverlay } from "@/components/canvas/canvas-workspace-overlays";
|
||||
@@ -151,20 +152,20 @@ function CanvasRefreshShell() {
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="absolute bottom-5 left-1/2 z-50 flex h-14 -translate-x-1/2 items-center gap-1 rounded-xl border px-2 shadow-lg backdrop-blur" style={{ background: "var(--background)", borderColor: "var(--border)" }} aria-hidden="true">
|
||||
<div className="absolute bottom-5 left-1/2 z-[var(--z-toolbar)] flex h-14 -translate-x-1/2 items-center gap-1 rounded-xl border px-2 shadow-lg backdrop-blur" style={{ background: "var(--background)", borderColor: "var(--border)" }} aria-hidden="true">
|
||||
{Array.from({ length: 7 }).map((_, index) => (
|
||||
<div key={index} className="size-8 rounded-md bg-current opacity-10" />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-24 left-6 z-50 h-40 w-[240px] rounded-lg border shadow-2xl backdrop-blur-sm" style={{ background: "var(--background)", borderColor: "var(--border)" }} aria-hidden="true">
|
||||
<div className="absolute bottom-24 left-6 z-[var(--z-toolbar)] h-40 w-[240px] rounded-lg border shadow-2xl backdrop-blur-sm" style={{ background: "var(--background)", borderColor: "var(--border)" }} aria-hidden="true">
|
||||
<div className="absolute left-7 top-7 h-5 w-12 rounded-sm bg-current opacity-10" />
|
||||
<div className="absolute left-28 top-16 h-6 w-16 rounded-sm bg-current opacity-10" />
|
||||
<div className="absolute bottom-7 left-16 h-8 w-20 rounded-sm bg-current opacity-10" />
|
||||
<div className="absolute inset-5 rounded border border-current opacity-15" />
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-5 left-5 z-50 flex h-14 w-[260px] items-center gap-2 rounded-xl border px-2 shadow-lg backdrop-blur" style={{ background: "var(--background)", borderColor: "var(--border)" }} aria-hidden="true">
|
||||
<div className="absolute bottom-5 left-5 z-[var(--z-toolbar)] flex h-14 w-[260px] items-center gap-2 rounded-xl border px-2 shadow-lg backdrop-blur" style={{ background: "var(--background)", borderColor: "var(--border)" }} aria-hidden="true">
|
||||
<div className="size-8 rounded-md bg-current opacity-10" />
|
||||
<div className="size-8 rounded-md bg-current opacity-10" />
|
||||
<div className="h-1 flex-1 rounded-full bg-current opacity-10" />
|
||||
@@ -238,8 +239,12 @@ function InfiniteCanvasPage() {
|
||||
const [titleDraft, setTitleDraft] = useState("");
|
||||
const [shortcutRequestNonce, setShortcutRequestNonce] = useState(0);
|
||||
const [cinematicAgentEntry, setCinematicAgentEntry] = useState(false);
|
||||
const [assistantWidth, setAssistantWidth] = useState(520);
|
||||
const { agentMode, assistantClosing, assistantMounted, assistantOpen, closeAgent, openAgent, setAgentMode } = useCanvasAssistantVisibility();
|
||||
const { tasks: activeTasks } = useCanvasActiveTasks(projectId, projectLoaded);
|
||||
const { focusMode, forcedOn, toggleFocusMode } = useFocusMode();
|
||||
const anyPanelOpen = assistantMounted && !assistantClosing;
|
||||
const panelColumnWidth = anyPanelOpen ? Math.max(assistantWidth, 332) : 0;
|
||||
|
||||
useEffect(() => {
|
||||
persistCanvasWorkspaceMode(workspaceMode);
|
||||
@@ -805,7 +810,6 @@ function InfiniteCanvasPage() {
|
||||
collapsingBatchIds,
|
||||
addedSkills,
|
||||
directorScenes: currentProject?.directorScenes,
|
||||
toolbarNodeId,
|
||||
infoNodeId,
|
||||
cropNodeId,
|
||||
maskEditNodeId,
|
||||
@@ -1378,7 +1382,12 @@ function InfiniteCanvasPage() {
|
||||
<a href="#canvas-main" className="sr-only focus:not-sr-only focus:absolute focus:left-4 focus:top-4 focus:z-[var(--z-toast)] focus:rounded-md focus:border focus:bg-background focus:px-4 focus:py-2 focus:text-sm focus:font-medium focus:shadow-lg">跳转到画布主内容</a>
|
||||
<main id="canvas-main" tabIndex={-1} className="flex h-full min-h-0 overflow-hidden outline-none" style={{ background: theme.canvas.background, color: theme.node.text }}>
|
||||
{shortDramaEnabled && currentProject?.projectId ? <CanvasProjectSidebar projectId={currentProject.projectId} detail={linkedProjectQuery.data} onAddChapter={handleProjectChapterInsert} onLocateStyle={locateProjectStyleNode} onOpenAssets={() => openProjectAssets()} /> : null}
|
||||
<section className="relative min-w-0 flex-1 overflow-hidden">
|
||||
<section
|
||||
className="relative min-w-0 flex-1 overflow-hidden transition-[padding]"
|
||||
style={{
|
||||
paddingRight: focusMode && anyPanelOpen ? panelColumnWidth : undefined,
|
||||
}}
|
||||
>
|
||||
<CanvasTopBar
|
||||
title={currentProject?.title || "未命名画布"}
|
||||
workspaceMode={workspaceMode}
|
||||
@@ -1409,6 +1418,9 @@ function InfiniteCanvasPage() {
|
||||
projectId: currentProject.projectId,
|
||||
projectName: linkedProjectQuery.data?.project.name || currentProject.title,
|
||||
} : undefined}
|
||||
focusMode={focusMode}
|
||||
focusModeForced={forcedOn}
|
||||
onToggleFocusMode={toggleFocusMode}
|
||||
/>
|
||||
|
||||
<CanvasNodeSearchModal
|
||||
@@ -1564,7 +1576,7 @@ function InfiniteCanvasPage() {
|
||||
{mergeVideoProgress ? <CanvasMergeStatusToast progress={mergeVideoProgress} theme={theme} /> : null}
|
||||
{lastAgentChange ? <CanvasAgentChangeToast change={lastAgentChange} theme={theme} onView={viewLastAgentChange} onUndo={() => { undoAgentOps(); }} onClose={dismissLastAgentChange} /> : null}
|
||||
|
||||
<CanvasNodeHoverToolbar
|
||||
<CanvasNodeToolbar
|
||||
node={isNodeDragging || nodeImageSettingsOpen || emotionNodeId ? null : toolbarNode}
|
||||
workspaceMode={workspaceMode}
|
||||
viewport={viewport}
|
||||
@@ -1777,31 +1789,34 @@ function InfiniteCanvasPage() {
|
||||
/>
|
||||
<CanvasProjectAssetModal open={projectAssetOpen} detail={linkedProjectQuery.data} initialCategory={projectAssetInitialCategory} onClose={closeProjectAssets} onInsert={(payloads) => handleProjectAssetsInsert(payloads, projectAssetInsertPosition)} />
|
||||
{codexCompactAgent && !assistantMounted ? <CanvasLocalAgentPanel headless snapshot={agentSnapshot} canUndoOps={canUndoAgentOps} undoOpsCount={agentUndoCount} onApplyOps={applyAgentOps} onUndoOps={undoAgentOps} autoConnect={codexAutoConnect} /> : null}
|
||||
{assistantMounted ? (
|
||||
<CanvasAssistantPanel
|
||||
nodes={nodes}
|
||||
selectedNodeIds={selectedNodeIds}
|
||||
snapshot={agentSnapshot}
|
||||
projectId={projectId}
|
||||
sessions={chatSessions}
|
||||
activeSessionId={activeChatId}
|
||||
onSelectNodeIds={setSelectedNodeIds}
|
||||
onSessionsChange={handleAssistantSessionsChange}
|
||||
onApplyOps={applyAgentOps}
|
||||
canUndoOps={canUndoAgentOps}
|
||||
undoOpsCount={agentUndoCount}
|
||||
onUndoOps={undoAgentOps}
|
||||
onPasteImage={pasteAssistantImage}
|
||||
agentMode={agentMode}
|
||||
onAgentModeChange={setAgentMode}
|
||||
autoConnectLocal={codexAutoConnect}
|
||||
closing={assistantClosing}
|
||||
onCollapse={closeAgent}
|
||||
cinematicEntry={cinematicAgentEntry}
|
||||
onCinematicEntryConsumed={() => setCinematicAgentEntry(false)}
|
||||
width={assistantWidth}
|
||||
onWidthChange={setAssistantWidth}
|
||||
focusMode={focusMode}
|
||||
/>
|
||||
) : null}
|
||||
</section>
|
||||
{assistantMounted ? (
|
||||
<CanvasAssistantPanel
|
||||
nodes={nodes}
|
||||
selectedNodeIds={selectedNodeIds}
|
||||
snapshot={agentSnapshot}
|
||||
projectId={projectId}
|
||||
sessions={chatSessions}
|
||||
activeSessionId={activeChatId}
|
||||
onSelectNodeIds={setSelectedNodeIds}
|
||||
onSessionsChange={handleAssistantSessionsChange}
|
||||
onApplyOps={applyAgentOps}
|
||||
canUndoOps={canUndoAgentOps}
|
||||
undoOpsCount={agentUndoCount}
|
||||
onUndoOps={undoAgentOps}
|
||||
onPasteImage={pasteAssistantImage}
|
||||
agentMode={agentMode}
|
||||
onAgentModeChange={setAgentMode}
|
||||
autoConnectLocal={codexAutoConnect}
|
||||
closing={assistantClosing}
|
||||
onCollapse={closeAgent}
|
||||
cinematicEntry={cinematicAgentEntry}
|
||||
onCinematicEntryConsumed={() => setCinematicAgentEntry(false)}
|
||||
/>
|
||||
) : null}
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Link, useParams } from "react-router";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
import { ConnectionPath } from "@/components/canvas/canvas-connections";
|
||||
import { CanvasNodeHoverToolbar, CanvasNodeInfoModal } from "@/components/canvas/canvas-node-hover-toolbar";
|
||||
import { CanvasNodeToolbar, CanvasNodeInfoModal } from "@/components/canvas/canvas-node-toolbar";
|
||||
import { CanvasFrameNode } from "@/components/canvas/canvas-frame-node";
|
||||
import { CanvasNode } from "@/components/canvas/canvas-node";
|
||||
import { CanvasZoomControls } from "@/components/canvas/canvas-zoom-controls";
|
||||
@@ -215,7 +215,7 @@ export default function SharedCanvasPage() {
|
||||
};
|
||||
}));
|
||||
const renderSharedNode = useCallback((node: CanvasNodeData): ReactNode => node.type === CanvasNodeType.Script ? <SharedScriptNode node={node} onUnauthorized={unauthorized} /> : <SharedConfigNode node={node} onUnauthorized={unauthorized} />, [unauthorized]);
|
||||
const toolbarNodeKey = toolbarNodeId || selectedNodeId;
|
||||
const toolbarNodeKey = selectedNodeId;
|
||||
const toolbarNode = toolbarNodeKey ? nodeById.get(toolbarNodeKey) || null : null;
|
||||
|
||||
if (loading) return <FullScreenLoader label="正在打开共享画布" detail="读取节点、连线和视图状态" />;
|
||||
@@ -256,7 +256,7 @@ export default function SharedCanvasPage() {
|
||||
}} onHoverStart={keepToolbar} onHoverEnd={hideToolbar} onConnectStart={unauthorized} onResize={() => undefined} onContentChange={unauthorized} onRetry={unauthorized} onCancelTask={unauthorized} onOpenTaskDetails={unauthorized} onViewImage={(target) => setInfoNodeId(target.id)} onContextMenu={(event, nodeId) => openContextMenu(event, nodeId)} />)}
|
||||
</InfiniteCanvas>
|
||||
|
||||
<CanvasNodeHoverToolbar node={dragRef.current ? null : toolbarNode} viewport={viewport} containerRef={containerRef} onKeep={keepToolbar} onLeave={hideToolbar} onInfo={(node) => setInfoNodeId(node.id)} onEditText={unauthorized} onDecreaseFont={unauthorized} onIncreaseFont={unauthorized} onToggleDialog={unauthorized} onAnnotate={unauthorized} onGenerateImage={unauthorized} onUpload={unauthorized} onDownload={unauthorized} onSaveAsset={unauthorized} onMaskEdit={unauthorized} onEmotion={unauthorized} onPortraitTexture={unauthorized} onCrop={unauthorized} onSplit={unauthorized} onUpscale={unauthorized} onSuperResolve={unauthorized} onAngle={unauthorized} onViewImage={unauthorized} onExtractVideoLastFrame={unauthorized} extractingVideoFrame={false} onReversePrompt={unauthorized} onRetry={unauthorized} onToggleFreeResize={unauthorized} onToggleLocked={unauthorized} onDelete={unauthorized} />
|
||||
<CanvasNodeToolbar node={dragRef.current ? null : toolbarNode} viewport={viewport} containerRef={containerRef} onKeep={keepToolbar} onLeave={hideToolbar} onInfo={(node) => setInfoNodeId(node.id)} onEditText={unauthorized} onDecreaseFont={unauthorized} onIncreaseFont={unauthorized} onToggleDialog={unauthorized} onAnnotate={unauthorized} onGenerateImage={unauthorized} onUpload={unauthorized} onDownload={unauthorized} onSaveAsset={unauthorized} onMaskEdit={unauthorized} onEmotion={unauthorized} onPortraitTexture={unauthorized} onCrop={unauthorized} onSplit={unauthorized} onUpscale={unauthorized} onSuperResolve={unauthorized} onAngle={unauthorized} onViewImage={unauthorized} onExtractVideoLastFrame={unauthorized} extractingVideoFrame={false} onReversePrompt={unauthorized} onRetry={unauthorized} onToggleFreeResize={unauthorized} onToggleLocked={unauthorized} onDelete={unauthorized} />
|
||||
|
||||
<div className="absolute bottom-5 left-5 z-[var(--z-panel-floating)]"><CanvasZoomControls scale={viewport.k} containerRef={containerRef} onScaleChange={setZoom} onReset={resetViewport} isMiniMapOpen={false} onToggleMiniMap={unauthorized} onOpenShortcuts={unauthorized} /></div>
|
||||
<div className="pointer-events-none absolute bottom-5 right-5 z-[var(--z-panel-floating)] max-w-[340px] text-right text-xs leading-5" style={{ color: theme.node.muted }}>访客操作仅在当前页面临时生效</div>
|
||||
|
||||
@@ -26,7 +26,6 @@ type UseCanvasRenderModelOptions = {
|
||||
collapsingBatchIds: Set<string>;
|
||||
addedSkills: Skill[];
|
||||
directorScenes?: DirectorScene[];
|
||||
toolbarNodeId: string | null;
|
||||
infoNodeId: string | null;
|
||||
cropNodeId: string | null;
|
||||
maskEditNodeId: string | null;
|
||||
@@ -57,7 +56,6 @@ export function useCanvasRenderModel({
|
||||
collapsingBatchIds,
|
||||
addedSkills,
|
||||
directorScenes,
|
||||
toolbarNodeId,
|
||||
infoNodeId,
|
||||
cropNodeId,
|
||||
maskEditNodeId,
|
||||
@@ -140,7 +138,8 @@ export function useCanvasRenderModel({
|
||||
return nodes.filter((node) => (node.metadata?.versionOfNodeId || node.id) === versionCompareRootId).sort((a, b) => (a.metadata?.versionLabel || "").localeCompare(b.metadata?.versionLabel || ""));
|
||||
}, [nodes, versionCompareRootId]);
|
||||
|
||||
const toolbarCandidate = toolbarNodeId ? nodeById.get(toolbarNodeId) || null : null;
|
||||
const selectedNodeIdForToolbar = selectedNodeIds.size === 1 ? [...selectedNodeIds][0] : null;
|
||||
const toolbarCandidate = selectedNodeIdForToolbar ? nodeById.get(selectedNodeIdForToolbar) || null : null;
|
||||
const toolbarNode = isFrameNode(toolbarCandidate) ? null : toolbarCandidate;
|
||||
const infoNode = infoNodeId ? nodeById.get(infoNodeId) || null : null;
|
||||
const cropNode = cropNodeId ? nodeById.get(cropNodeId) || null : null;
|
||||
|
||||
Reference in New Issue
Block a user