From b4bbda1ccf8f2921faac0ba5d4c33493058dd5d6 Mon Sep 17 00:00:00 2001 From: "dayuan.jiang" Date: Wed, 17 Dec 2025 19:12:23 +0900 Subject: [PATCH] fix: make draw.io built-in save button work - Lift showSaveDialog state to DiagramContext for sharing between components - Add onSave handler to DrawIoEmbed that opens the save dialog - Add guard (isSavingRef) with 1s delay to prevent repeated save events from draw.io - Add deprecation notice to custom download button tooltip Closes #93, Closes #290 --- app/page.tsx | 23 ++++++++++++++++++++++- components/chat-input.tsx | 10 +++++++--- contexts/diagram-context.tsx | 5 +++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 09ce7a1..01584be 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,5 +1,5 @@ "use client" -import { useEffect, useRef, useState } from "react" +import { useCallback, useEffect, useRef, useState } from "react" import { DrawIoEmbed } from "react-drawio" import type { ImperativePanelHandle } from "react-resizable-panels" import ChatPanel from "@/components/chat-panel" @@ -21,6 +21,8 @@ export default function Home() { onDrawioLoad, resetDrawioReady, saveDiagramToStorage, + showSaveDialog, + setShowSaveDialog, } = useDiagram() const [isMobile, setIsMobile] = useState(false) const [isChatVisible, setIsChatVisible] = useState(true) @@ -30,6 +32,24 @@ export default function Home() { const [closeProtection, setCloseProtection] = useState(false) const chatPanelRef = useRef(null) + const isSavingRef = useRef(false) + + // Reset saving flag when dialog closes (with delay to ignore lingering save events from draw.io) + useEffect(() => { + if (!showSaveDialog && isSavingRef.current) { + const timeout = setTimeout(() => { + isSavingRef.current = false + }, 1000) + return () => clearTimeout(timeout) + } + }, [showSaveDialog]) + + // Handle save from draw.io's built-in save button + const handleDrawioSave = useCallback(() => { + if (isSavingRef.current) return + isSavingRef.current = true + setShowSaveDialog(true) + }, [setShowSaveDialog]) // Load preferences from localStorage after mount useEffect(() => { @@ -155,6 +175,7 @@ export default function Home() { ref={drawioRef} onExport={handleDiagramExport} onLoad={onDrawioLoad} + onSave={handleDrawioSave} baseUrl={drawioBaseUrl} urlParameters={{ ui: drawioUi, diff --git a/components/chat-input.tsx b/components/chat-input.tsx index 9843484..702561c 100644 --- a/components/chat-input.tsx +++ b/components/chat-input.tsx @@ -155,12 +155,16 @@ export function ChatInput({ minimalStyle = false, onMinimalStyleChange = () => {}, }: ChatInputProps) { - const { diagramHistory, saveDiagramToFile } = useDiagram() + const { + diagramHistory, + saveDiagramToFile, + showSaveDialog, + setShowSaveDialog, + } = useDiagram() const textareaRef = useRef(null) const fileInputRef = useRef(null) const [isDragging, setIsDragging] = useState(false) const [showClearDialog, setShowClearDialog] = useState(false) - const [showSaveDialog, setShowSaveDialog] = useState(false) // Allow retry when there's an error (even if status is still "streaming" or "submitted") const isDisabled = @@ -401,7 +405,7 @@ export function ChatInput({ size="sm" onClick={() => setShowSaveDialog(true)} disabled={isDisabled} - tooltipContent="Save diagram" + tooltipContent="Save diagram (deprecated: use Save button in upper right corner of draw.io)" className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground" > diff --git a/contexts/diagram-context.tsx b/contexts/diagram-context.tsx index 69abdc1..a7cd4ab 100644 --- a/contexts/diagram-context.tsx +++ b/contexts/diagram-context.tsx @@ -27,6 +27,8 @@ interface DiagramContextType { isDrawioReady: boolean onDrawioLoad: () => void resetDrawioReady: () => void + showSaveDialog: boolean + setShowSaveDialog: (show: boolean) => void } const DiagramContext = createContext(undefined) @@ -38,6 +40,7 @@ export function DiagramProvider({ children }: { children: React.ReactNode }) { { svg: string; xml: string }[] >([]) const [isDrawioReady, setIsDrawioReady] = useState(false) + const [showSaveDialog, setShowSaveDialog] = useState(false) const hasCalledOnLoadRef = useRef(false) const drawioRef = useRef(null) const resolverRef = useRef<((value: string) => void) | null>(null) @@ -309,6 +312,8 @@ export function DiagramProvider({ children }: { children: React.ReactNode }) { isDrawioReady, onDrawioLoad, resetDrawioReady, + showSaveDialog, + setShowSaveDialog, }} > {children}