mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-09-01 17:10:24 +08:00
Compare commits
3 Commits
v0.4.16
...
fix/electr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7525253499 | ||
|
|
384221c498 | ||
|
|
7877b39fa1 |
@@ -11,10 +11,16 @@ import {
|
|||||||
} from "@/components/ui/resizable"
|
} from "@/components/ui/resizable"
|
||||||
import { useDiagram } from "@/contexts/diagram-context"
|
import { useDiagram } from "@/contexts/diagram-context"
|
||||||
import { i18n, type Locale } from "@/lib/i18n/config"
|
import { i18n, type Locale } from "@/lib/i18n/config"
|
||||||
|
import { isIndexedDBUsable } from "@/lib/session-storage"
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const { drawioRef, handleDiagramExport, onDrawioLoad, resetDrawioReady } =
|
const {
|
||||||
useDiagram()
|
drawioRef,
|
||||||
|
handleDiagramExport,
|
||||||
|
handleDiagramAutoSave,
|
||||||
|
onDrawioLoad,
|
||||||
|
resetDrawioReady,
|
||||||
|
} = useDiagram()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
// Extract current language from pathname (e.g., "/zh/about" → "zh")
|
// Extract current language from pathname (e.g., "/zh/about" → "zh")
|
||||||
@@ -26,6 +32,7 @@ export default function Home() {
|
|||||||
const [isLoaded, setIsLoaded] = useState(false)
|
const [isLoaded, setIsLoaded] = useState(false)
|
||||||
const [isDrawioReady, setIsDrawioReady] = useState(false)
|
const [isDrawioReady, setIsDrawioReady] = useState(false)
|
||||||
const [isElectron, setIsElectron] = useState(false)
|
const [isElectron, setIsElectron] = useState(false)
|
||||||
|
const [canPersist, setCanPersist] = useState(false)
|
||||||
const [drawioBaseUrl, setDrawioBaseUrl] = useState(
|
const [drawioBaseUrl, setDrawioBaseUrl] = useState(
|
||||||
process.env.NEXT_PUBLIC_DRAWIO_BASE_URL || "https://embed.diagrams.net",
|
process.env.NEXT_PUBLIC_DRAWIO_BASE_URL || "https://embed.diagrams.net",
|
||||||
)
|
)
|
||||||
@@ -76,6 +83,10 @@ export default function Home() {
|
|||||||
setDrawioBaseUrl(`${window.location.origin}/drawio/index.html`)
|
setDrawioBaseUrl(`${window.location.origin}/drawio/index.html`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void (async () => {
|
||||||
|
const usable = await isIndexedDBUsable()
|
||||||
|
setCanPersist(usable)
|
||||||
|
})()
|
||||||
setIsLoaded(true)
|
setIsLoaded(true)
|
||||||
}, [pathname, router])
|
}, [pathname, router])
|
||||||
|
|
||||||
@@ -84,6 +95,17 @@ export default function Home() {
|
|||||||
onDrawioLoad()
|
onDrawioLoad()
|
||||||
}, [onDrawioLoad])
|
}, [onDrawioLoad])
|
||||||
|
|
||||||
|
const handleDrawioAutoSave = useCallback(
|
||||||
|
(data: { xml?: string }) => {
|
||||||
|
handleDiagramAutoSave(data)
|
||||||
|
// Only suppress modified state when persistence is available
|
||||||
|
if (canPersist) {
|
||||||
|
drawioRef.current?.status({ message: "", modified: false })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[canPersist, drawioRef, handleDiagramAutoSave],
|
||||||
|
)
|
||||||
|
|
||||||
const handleDarkModeChange = () => {
|
const handleDarkModeChange = () => {
|
||||||
const newValue = !darkMode
|
const newValue = !darkMode
|
||||||
setDarkMode(newValue)
|
setDarkMode(newValue)
|
||||||
@@ -174,13 +196,25 @@ export default function Home() {
|
|||||||
<DrawIoEmbed
|
<DrawIoEmbed
|
||||||
key={`${drawioUi}-${darkMode}-${currentLang}-${isElectron}`}
|
key={`${drawioUi}-${darkMode}-${currentLang}-${isElectron}`}
|
||||||
ref={drawioRef}
|
ref={drawioRef}
|
||||||
|
autosave
|
||||||
|
onAutoSave={handleDrawioAutoSave}
|
||||||
onExport={handleDiagramExport}
|
onExport={handleDiagramExport}
|
||||||
onLoad={handleDrawioLoad}
|
onLoad={handleDrawioLoad}
|
||||||
baseUrl={drawioBaseUrl}
|
baseUrl={drawioBaseUrl}
|
||||||
|
configuration={
|
||||||
|
canPersist
|
||||||
|
? { confirmExit: false }
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
urlParameters={{
|
urlParameters={{
|
||||||
ui: drawioUi,
|
ui: drawioUi,
|
||||||
spin: false,
|
spin: false,
|
||||||
libraries: false,
|
libraries: false,
|
||||||
|
// Disable modified tracking only when persistence is available
|
||||||
|
...(canPersist && {
|
||||||
|
modified: false,
|
||||||
|
keepmodified: false,
|
||||||
|
}),
|
||||||
saveAndExit: false,
|
saveAndExit: false,
|
||||||
noSaveBtn: true,
|
noSaveBtn: true,
|
||||||
noExitBtn: true,
|
noExitBtn: true,
|
||||||
|
|||||||
@@ -137,26 +137,22 @@ export default function ChatPanel({
|
|||||||
const onFetchChart = (saveToHistory = true) => {
|
const onFetchChart = (saveToHistory = true) => {
|
||||||
return Promise.race([
|
return Promise.race([
|
||||||
new Promise<string>((resolve) => {
|
new Promise<string>((resolve) => {
|
||||||
if (resolverRef && "current" in resolverRef) {
|
resolverRef.current = resolve
|
||||||
resolverRef.current = resolve
|
|
||||||
}
|
|
||||||
if (saveToHistory) {
|
if (saveToHistory) {
|
||||||
onExport()
|
onExport()
|
||||||
} else {
|
} else {
|
||||||
handleExportWithoutHistory()
|
handleExportWithoutHistory()
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
new Promise<string>((_, reject) =>
|
new Promise<string>((_, reject) => {
|
||||||
setTimeout(
|
const currentResolver = resolverRef.current
|
||||||
() =>
|
setTimeout(() => {
|
||||||
reject(
|
if (resolverRef.current === currentResolver) {
|
||||||
new Error(
|
resolverRef.current = null
|
||||||
"Chart export timed out after 10 seconds",
|
}
|
||||||
),
|
reject(new Error("Chart export timed out after 10 seconds"))
|
||||||
),
|
}, 10000)
|
||||||
10000,
|
}),
|
||||||
),
|
|
||||||
),
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -677,7 +673,7 @@ export default function ChatPanel({
|
|||||||
// Debounce: save after 1 second of no changes
|
// Debounce: save after 1 second of no changes
|
||||||
localStorageDebounceRef.current = setTimeout(async () => {
|
localStorageDebounceRef.current = setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
if (messages.length > 0) {
|
if (messages.length > 0 || hasDiagramNow) {
|
||||||
const sessionData = await buildSessionData({
|
const sessionData = await buildSessionData({
|
||||||
// Only capture thumbnail if there was a diagram AND this isn't a no-diagram session
|
// Only capture thumbnail if there was a diagram AND this isn't a no-diagram session
|
||||||
withThumbnail: hasDiagramNow && !isNodiagramSession,
|
withThumbnail: hasDiagramNow && !isNodiagramSession,
|
||||||
@@ -699,6 +695,7 @@ export default function ChatPanel({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
|
chartXML,
|
||||||
messages,
|
messages,
|
||||||
status,
|
status,
|
||||||
sessionIsAvailable,
|
sessionIsAvailable,
|
||||||
@@ -729,7 +726,8 @@ export default function ChatPanel({
|
|||||||
const handleVisibilityChange = async () => {
|
const handleVisibilityChange = async () => {
|
||||||
if (
|
if (
|
||||||
document.visibilityState === "hidden" &&
|
document.visibilityState === "hidden" &&
|
||||||
messagesRef.current.length > 0
|
(messagesRef.current.length > 0 ||
|
||||||
|
isRealDiagram(chartXMLRef.current))
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
// Attempt to save session - browser may not wait for completion
|
// Attempt to save session - browser may not wait for completion
|
||||||
|
|||||||
@@ -20,9 +20,10 @@ interface DiagramContextType {
|
|||||||
loadDiagram: (chart: string, skipValidation?: boolean) => string | null
|
loadDiagram: (chart: string, skipValidation?: boolean) => string | null
|
||||||
handleExport: () => void
|
handleExport: () => void
|
||||||
handleExportWithoutHistory: () => void
|
handleExportWithoutHistory: () => void
|
||||||
resolverRef: React.Ref<((value: string) => void) | null>
|
resolverRef: React.MutableRefObject<((value: string) => void) | null>
|
||||||
drawioRef: React.Ref<DrawIoEmbedRef | null>
|
drawioRef: React.MutableRefObject<DrawIoEmbedRef | null>
|
||||||
handleDiagramExport: (data: any) => void
|
handleDiagramExport: (data: any) => void
|
||||||
|
handleDiagramAutoSave: (data: { xml?: string }) => void
|
||||||
clearDiagram: () => void
|
clearDiagram: () => void
|
||||||
saveDiagramToFile: (
|
saveDiagramToFile: (
|
||||||
filename: string,
|
filename: string,
|
||||||
@@ -267,6 +268,11 @@ export function DiagramProvider({ children }: { children: React.ReactNode }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDiagramAutoSave = (data: { xml?: string }) => {
|
||||||
|
if (!data?.xml) return
|
||||||
|
setChartXML(data.xml)
|
||||||
|
}
|
||||||
|
|
||||||
const clearDiagram = () => {
|
const clearDiagram = () => {
|
||||||
const emptyDiagram = `<mxfile><diagram name="Page-1" id="page-1"><mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/></root></mxGraphModel></diagram></mxfile>`
|
const emptyDiagram = `<mxfile><diagram name="Page-1" id="page-1"><mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/></root></mxGraphModel></diagram></mxfile>`
|
||||||
// Skip validation for trusted internal template (loadDiagram also sets chartXML)
|
// Skip validation for trusted internal template (loadDiagram also sets chartXML)
|
||||||
@@ -391,6 +397,7 @@ export function DiagramProvider({ children }: { children: React.ReactNode }) {
|
|||||||
resolverRef,
|
resolverRef,
|
||||||
drawioRef,
|
drawioRef,
|
||||||
handleDiagramExport,
|
handleDiagramExport,
|
||||||
|
handleDiagramAutoSave,
|
||||||
clearDiagram,
|
clearDiagram,
|
||||||
saveDiagramToFile,
|
saveDiagramToFile,
|
||||||
getThumbnailSvg,
|
getThumbnailSvg,
|
||||||
|
|||||||
@@ -75,6 +75,18 @@ export function isIndexedDBAvailable(): boolean {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if IndexedDB is actually usable (not just present).
|
||||||
|
export async function isIndexedDBUsable(): Promise<boolean> {
|
||||||
|
if (!isIndexedDBAvailable()) return false
|
||||||
|
try {
|
||||||
|
const db = await getDB()
|
||||||
|
db.close()
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// CRUD Operations
|
// CRUD Operations
|
||||||
export async function getAllSessionMetadata(): Promise<SessionMetadata[]> {
|
export async function getAllSessionMetadata(): Promise<SessionMetadata[]> {
|
||||||
if (!isIndexedDBAvailable()) return []
|
if (!isIndexedDBAvailable()) return []
|
||||||
|
|||||||
Reference in New Issue
Block a user