Improve canvas import and pane interactions
This commit is contained in:
@@ -56,6 +56,8 @@
|
||||
:root {
|
||||
--background: oklch(0.985 0.01 85);
|
||||
--foreground: oklch(0.18 0.02 60);
|
||||
--canvas-dot-color: rgb(57 48 35 / 0.18);
|
||||
--canvas-dot-accent-color: rgb(196 126 30 / 0.26);
|
||||
--card: oklch(0.995 0.005 85);
|
||||
--card-foreground: oklch(0.18 0.02 60);
|
||||
--popover: oklch(0.995 0.005 85);
|
||||
@@ -91,6 +93,8 @@
|
||||
.dark {
|
||||
--background: oklch(0.16 0.015 260);
|
||||
--foreground: oklch(0.98 0.01 80);
|
||||
--canvas-dot-color: rgb(255 255 255 / 0.34);
|
||||
--canvas-dot-accent-color: rgb(255 255 255 / 0.2);
|
||||
--card: oklch(0.21 0.025 260);
|
||||
--card-foreground: oklch(0.98 0.01 80);
|
||||
--popover: oklch(0.25 0.025 260);
|
||||
|
||||
@@ -1064,6 +1064,25 @@ export function InfiniteCanvasWorkspace() {
|
||||
void importProjectDirectory(files);
|
||||
}, [importProjectDirectory]);
|
||||
|
||||
const setImportDirectoryInputRef = useCallback((element: HTMLInputElement | null) => {
|
||||
const directoryInput = element as FileInputWithDirectory | null;
|
||||
importDirectoryInputRef.current = directoryInput;
|
||||
if (!directoryInput) return;
|
||||
directoryInput.webkitdirectory = true;
|
||||
directoryInput.directory = true;
|
||||
}, []);
|
||||
|
||||
const openImportDirectoryPicker = useCallback(() => {
|
||||
if (!project || importingProject) return;
|
||||
const input = importDirectoryInputRef.current;
|
||||
if (!input) {
|
||||
toast.error('目录导入控件尚未准备好');
|
||||
return;
|
||||
}
|
||||
toast.info('正在打开目录选择器');
|
||||
input.click();
|
||||
}, [importingProject, project]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!dirty || !project || !accessToken) return;
|
||||
const timer = window.setTimeout(() => {
|
||||
@@ -2038,10 +2057,7 @@ export function InfiniteCanvasWorkspace() {
|
||||
setSelectedNodeIds([]);
|
||||
setAddMenu(null);
|
||||
setContextMenu(null);
|
||||
if (event.detail !== 1) return;
|
||||
const menuPosition = getCanvasMenuPosition(event.clientX, event.clientY);
|
||||
setAddMenu({ x: point.x, y: point.y, ...menuPosition });
|
||||
}, [getCanvasMenuPosition]);
|
||||
}, []);
|
||||
|
||||
const handleCanvasContextMenu = useCallback((event: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (!project) return;
|
||||
@@ -2057,9 +2073,14 @@ export function InfiniteCanvasWorkspace() {
|
||||
}
|
||||
}, [getCanvasMenuPosition, getNodeIdFromEvent, project, screenToCanvasPoint, selectedNodeIds]);
|
||||
|
||||
const handlePaneDoubleClick = useCallback((point: { x: number; y: number }) => {
|
||||
addNode('text2img', point.x, point.y);
|
||||
}, [addNode]);
|
||||
const handlePaneDoubleClick = useCallback((point: { x: number; y: number }, event: MouseEvent | React.MouseEvent) => {
|
||||
setConnectingFromId(null);
|
||||
setSelectedNodeId(null);
|
||||
setSelectedNodeIds([]);
|
||||
setContextMenu(null);
|
||||
const menuPosition = getCanvasMenuPosition(event.clientX, event.clientY);
|
||||
setAddMenu({ x: point.x, y: point.y, ...menuPosition });
|
||||
}, [getCanvasMenuPosition]);
|
||||
|
||||
const handleCanvasDragOver = useCallback((event: React.DragEvent<HTMLDivElement>) => {
|
||||
if (!Array.from(event.dataTransfer?.items || []).some(item => item.kind === 'file' && item.type.startsWith('image/'))) return;
|
||||
@@ -2254,12 +2275,11 @@ export function InfiniteCanvasWorkspace() {
|
||||
<input ref={fileInputRef} type="file" accept="image/*" className="hidden" onChange={handleFileChange} />
|
||||
<input ref={importInputRef} type="file" accept="application/json,.json" className="hidden" onChange={handleImportFileChange} />
|
||||
<input
|
||||
ref={importDirectoryInputRef}
|
||||
ref={setImportDirectoryInputRef}
|
||||
type="file"
|
||||
className="hidden"
|
||||
multiple
|
||||
onChange={handleImportDirectoryChange}
|
||||
{...({ webkitdirectory: '', directory: '' } as Record<string, string>)}
|
||||
/>
|
||||
|
||||
<aside className="hidden w-14 shrink-0 flex-col items-center gap-2 border-r bg-background/95 px-2 py-3 text-foreground shadow-sm lg:flex">
|
||||
@@ -2648,7 +2668,7 @@ export function InfiniteCanvasWorkspace() {
|
||||
<Button variant="outline" size="sm" className="gap-2" onClick={() => importInputRef.current?.click()} disabled={!project || importingProject} title="导入 JSON">
|
||||
{importingProject ? <Loader2 className="h-4 w-4 animate-spin" /> : <Upload className="h-4 w-4" />} JSON
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" className="gap-2" onClick={() => importDirectoryInputRef.current?.click()} disabled={!project || importingProject} title="导入工作流目录">
|
||||
<Button variant="outline" size="sm" className="gap-2" onClick={openImportDirectoryPicker} disabled={!project || importingProject} title="导入工作流目录">
|
||||
{importingProject ? <Loader2 className="h-4 w-4 animate-spin" /> : <FolderOpen className="h-4 w-4" />} 目录
|
||||
</Button>
|
||||
<Button variant="outline" size="icon" className="text-destructive" onClick={() => void deleteCurrentProject()} disabled={!project} title="删除画布">
|
||||
@@ -2704,7 +2724,7 @@ export function InfiniteCanvasWorkspace() {
|
||||
</div>
|
||||
) : null}
|
||||
<div className="pointer-events-none absolute left-6 top-6 z-20 rounded-lg border bg-background/70 px-3 py-2 text-xs text-muted-foreground shadow-sm backdrop-blur">
|
||||
左侧工具添加节点 · 空白点击打开添加菜单 · Ctrl+Z 撤销 · Delete 删除
|
||||
左侧工具添加节点 · 空白双击打开添加菜单 · Ctrl+Z 撤销 · Delete 删除
|
||||
</div>
|
||||
{!project ? (
|
||||
<div className="absolute inset-0 z-30 flex items-center justify-center bg-background/40 backdrop-blur-[1px]">
|
||||
|
||||
@@ -80,7 +80,7 @@ type ReactFlowCanvasProps = {
|
||||
onViewportCommit: (viewport: CanvasViewport) => void;
|
||||
onReady?: (controls: CanvasFlowControls | null) => void;
|
||||
onPaneClick: (point: { x: number; y: number }, event: MouseEvent | React.MouseEvent) => void;
|
||||
onPaneDoubleClick: (point: { x: number; y: number }) => void;
|
||||
onPaneDoubleClick: (point: { x: number; y: number }, event: MouseEvent | React.MouseEvent) => void;
|
||||
};
|
||||
|
||||
export type CanvasFlowControls = {
|
||||
@@ -905,7 +905,7 @@ function FlowCanvasInner({
|
||||
window.clearTimeout(paneClickTimerRef.current);
|
||||
paneClickTimerRef.current = null;
|
||||
}
|
||||
onPaneDoubleClick(reactFlow.screenToFlowPosition({ x: event.clientX, y: event.clientY }, { snapToGrid: true, snapGrid: SNAP_GRID }));
|
||||
onPaneDoubleClick(reactFlow.screenToFlowPosition({ x: event.clientX, y: event.clientY }, { snapToGrid: true, snapGrid: SNAP_GRID }), event);
|
||||
return;
|
||||
}
|
||||
if (paneClickTimerRef.current !== null) {
|
||||
@@ -970,8 +970,8 @@ function FlowCanvasInner({
|
||||
onSelectionChange={handleSelectionChange}
|
||||
onInit={handleInit}
|
||||
>
|
||||
<Background gap={20} size={1} color="hsl(var(--foreground) / 0.16)" />
|
||||
<Background gap={100} size={1.5} color="hsl(var(--primary) / 0.28)" />
|
||||
<Background gap={20} size={1} color="var(--canvas-dot-color)" />
|
||||
<Background gap={100} size={1.5} color="var(--canvas-dot-accent-color)" />
|
||||
<Controls showInteractive={false} position="bottom-left" />
|
||||
<MiniMap
|
||||
position="bottom-right"
|
||||
|
||||
Reference in New Issue
Block a user