From 9f77199272fc2f7514dd89260fef1c3bf379b03a Mon Sep 17 00:00:00 2001 From: Dayuan Jiang <34411969+DayuanJiang@users.noreply.github.com> Date: Sat, 6 Dec 2025 21:42:28 +0900 Subject: [PATCH] feat: add configurable close protection setting (#123) - Add Close Protection toggle to Settings dialog - Save setting to localStorage (default: enabled) - Make beforeunload confirmation conditional - Settings button now always visible in header - Add shadcn Switch and Label components --- app/page.tsx | 13 +- components/chat-panel.tsx | 27 ++-- components/settings-dialog.tsx | 37 ++++- components/ui/label.tsx | 24 ++++ components/ui/switch.tsx | 31 +++++ package-lock.json | 239 +++++++++++++++++++++++++++++++++ package.json | 2 + 7 files changed, 358 insertions(+), 15 deletions(-) create mode 100644 components/ui/label.tsx create mode 100644 components/ui/switch.tsx diff --git a/app/page.tsx b/app/page.tsx index 186de86..2fca1b9 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -3,6 +3,7 @@ import { useEffect, useRef, useState } from "react" import { DrawIoEmbed } from "react-drawio" import type { ImperativePanelHandle } from "react-resizable-panels" import ChatPanel from "@/components/chat-panel" +import { STORAGE_CLOSE_PROTECTION_KEY } from "@/components/settings-dialog" import { ResizableHandle, ResizablePanel, @@ -21,6 +22,13 @@ export default function Home() { } return "min" }) + const [closeProtection, setCloseProtection] = useState(() => { + if (typeof window !== "undefined") { + const saved = localStorage.getItem(STORAGE_CLOSE_PROTECTION_KEY) + return saved !== "false" // Default to true + } + return true + }) const chatPanelRef = useRef(null) useEffect(() => { @@ -61,6 +69,8 @@ export default function Home() { // Show confirmation dialog when user tries to leave the page // This helps prevent accidental navigation from browser back gestures useEffect(() => { + if (!closeProtection) return + const handleBeforeUnload = (event: BeforeUnloadEvent) => { event.preventDefault() return "" @@ -69,7 +79,7 @@ export default function Home() { window.addEventListener("beforeunload", handleBeforeUnload) return () => window.removeEventListener("beforeunload", handleBeforeUnload) - }, []) + }, [closeProtection]) return (
@@ -127,6 +137,7 @@ export default function Home() { setDrawioUi(newTheme) }} isMobile={isMobile} + onCloseProtectionChange={setCloseProtection} />
diff --git a/components/chat-panel.tsx b/components/chat-panel.tsx index 6281648..f24c5e4 100644 --- a/components/chat-panel.tsx +++ b/components/chat-panel.tsx @@ -31,6 +31,7 @@ interface ChatPanelProps { drawioUi: "min" | "sketch" onToggleDrawioUi: () => void isMobile?: boolean + onCloseProtectionChange?: (enabled: boolean) => void } export default function ChatPanel({ @@ -39,6 +40,7 @@ export default function ChatPanel({ drawioUi, onToggleDrawioUi, isMobile = false, + onCloseProtectionChange, }: ChatPanelProps) { const { loadDiagram: onDisplayChart, @@ -497,19 +499,17 @@ Please retry with an adjusted search pattern or use display_diagram if retries a className={`${isMobile ? "w-4 h-4" : "w-5 h-5"}`} /> - {accessCodeRequired && ( - setShowSettingsDialog(true)} - className="hover:bg-accent" - > - - - )} + setShowSettingsDialog(true)} + className="hover:bg-accent" + > + + {!isMobile && ( ) diff --git a/components/settings-dialog.tsx b/components/settings-dialog.tsx index 79fff7a..298aef7 100644 --- a/components/settings-dialog.tsx +++ b/components/settings-dialog.tsx @@ -11,27 +11,47 @@ import { DialogTitle, } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { Switch } from "@/components/ui/switch" interface SettingsDialogProps { open: boolean onOpenChange: (open: boolean) => void + onCloseProtectionChange?: (enabled: boolean) => void } export const STORAGE_ACCESS_CODE_KEY = "next-ai-draw-io-access-code" +export const STORAGE_CLOSE_PROTECTION_KEY = "next-ai-draw-io-close-protection" -export function SettingsDialog({ open, onOpenChange }: SettingsDialogProps) { +export function SettingsDialog({ + open, + onOpenChange, + onCloseProtectionChange, +}: SettingsDialogProps) { const [accessCode, setAccessCode] = useState("") + const [closeProtection, setCloseProtection] = useState(true) useEffect(() => { if (open) { const storedCode = localStorage.getItem(STORAGE_ACCESS_CODE_KEY) || "" setAccessCode(storedCode) + + const storedCloseProtection = localStorage.getItem( + STORAGE_CLOSE_PROTECTION_KEY, + ) + // Default to true if not set + setCloseProtection(storedCloseProtection !== "false") } }, [open]) const handleSave = () => { localStorage.setItem(STORAGE_ACCESS_CODE_KEY, accessCode.trim()) + localStorage.setItem( + STORAGE_CLOSE_PROTECTION_KEY, + closeProtection.toString(), + ) + onCloseProtectionChange?.(closeProtection) onOpenChange(false) } @@ -68,6 +88,21 @@ export function SettingsDialog({ open, onOpenChange }: SettingsDialogProps) { Required if the server has enabled access control.

+
+
+ +

+ Show confirmation when leaving the page. +

+
+ +