File size: 9,556 Bytes
2798672
 
8ab98be
 
9f072e4
88e8d49
2798672
 
 
8ab98be
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d81f838
 
 
 
2798672
d81f838
 
 
 
 
 
 
 
 
2798672
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d81f838
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2798672
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d81f838
 
 
2798672
 
 
 
 
 
 
 
 
d81f838
 
 
 
 
 
 
 
 
 
 
 
 
 
2798672
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d81f838
 
 
2798672
d81f838
 
 
 
 
2798672
 
 
d81f838
 
 
 
2798672
 
 
 
 
 
 
d81f838
 
9f072e4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88e8d49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
import { useState, useRef, useEffect, ChangeEvent } from 'react'
import { useEditor, TldrawUiMenuItem, uniqueId } from 'tldraw'
import { useAuth } from '../hooks/useAuth'
import { useBackups } from '../hooks/useBackups'
import { exportToImage } from '../utils/exportUtils'
import { triggerSvgImport } from '../utils/svgImport'
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem'
import { Share } from '@capacitor/share'
import { Capacitor } from '@capacitor/core'

export function BackupMenuItem({ roomId }: { roomId: string }) {
    const editor = useEditor()
    const { isAuthenticated } = useAuth()
    const { createBackup } = useBackups()
    const [isBackingUp, setIsBackingUp] = useState(false)

    if (!isAuthenticated) return null

    const handleBackup = async () => {
        if (isBackingUp) return

        const confirmBackup = window.confirm("Save a backup of this board to the cloud?")
        if (!confirmBackup) return

        setIsBackingUp(true)
        try {
            const snapshot = editor.getSnapshot()
            // Get room name if available (from local storage or metadata?)
            // We can try to get it from the store if we stored it there,
            // otherwise just use the ID or ask the user.
            // For now let's check localStorage for the name we saved in Lobby/RoomPage

            // Try to find name in local storage first
            let roomName = 'Untitled'
            try {
                const storedRooms = localStorage.getItem('tldraw_saved_rooms')
                if (storedRooms) {
                    const parsed = JSON.parse(storedRooms)
                    const room = parsed.find((r: any) => r.id === roomId)
                    if (room) roomName = room.name
                }
            } catch (e) {}

            await createBackup(snapshot, roomName, roomId, 'tldraw')
            alert('Backup saved successfully!')
        } catch (e: any) {
            console.error(e)
            alert('Failed to save backup: ' + e.message)
        } finally {
            setIsBackingUp(false)
        }
    }

    return (
        <TldrawUiMenuItem
            id="cloud-backup"
            label={isBackingUp ? "Backing up..." : "Save to Cloud"}
            icon="cloud"
            readonlyOk
            onSelect={handleBackup}
            disabled={isBackingUp}
        />
    )
}

export function DownloadMenuItem({ roomId }: { roomId: string }) {
    const editor = useEditor()

    const handleDownload = async () => {
        try {
            const snapshot = editor.getSnapshot()
            const jsonStr = JSON.stringify({
                snapshot,
                roomId,
                timestamp: Date.now(),
                source: 'tldraw-multiplayer'
            }, null, 2)

            const fileName = `tldraw-room-${roomId}-${new Date().toISOString().slice(0,10)}.json`

            if (Capacitor.isNativePlatform()) {
                // Native (Android/iOS): Use Filesystem + Share
                try {
                    const savedFile = await Filesystem.writeFile({
                        path: fileName,
                        data: jsonStr,
                        directory: Directory.Cache,
                        encoding: Encoding.UTF8
                    })

                    await Share.share({
                        title: 'Backup Board JSON',
                        files: [savedFile.uri],
                    })
                } catch (err: any) {
                    console.error('Native save failed', err)
                    alert('Failed to save file: ' + err.message)
                }
            } else {
                // Web: Use anchor tag download
                const blob = new Blob([jsonStr], { type: 'application/json' })
                const url = URL.createObjectURL(blob)
                const link = document.createElement('a')
                link.href = url
                link.download = fileName
                document.body.appendChild(link)
                link.click()
                document.body.removeChild(link)
                URL.revokeObjectURL(url)
            }
        } catch (e: any) {
            console.error('Failed to download backup', e)
            alert('Failed to download backup: ' + e.message)
        }
    }

    return (
        <TldrawUiMenuItem
            id="download-json"
            label="Download File"
            icon="download"
            readonlyOk
            onSelect={handleDownload}
        />
    )
}

export function RestoreMenuItem() {
    const handleRestoreClick = () => {
        window.dispatchEvent(new CustomEvent('tldraw-trigger-file-restore'))
    }

    return (
        <TldrawUiMenuItem
            id="restore-json"
            label="Restore from File"
            icon="external-link"
            readonlyOk
            onSelect={handleRestoreClick}
        />
    )
}

export function RestoreFileHandler({ roomId: _roomId }: { roomId: string }) {
    const editor = useEditor()
    const inputRef = useRef<HTMLInputElement>(null)

    useEffect(() => {
        const handleTrigger = () => {
            if (inputRef.current) {
                inputRef.current.click()
            }
        }
        window.addEventListener('tldraw-trigger-file-restore', handleTrigger)
        return () => window.removeEventListener('tldraw-trigger-file-restore', handleTrigger)
    }, [])

    const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
        const file = e.target.files?.[0]
        if (!file) return

        const reader = new FileReader()
        reader.onload = (event) => {
            try {
                const jsonStr = event.target?.result as string
                const data = JSON.parse(jsonStr)

                // Handle both raw snapshot or our wrapped format
                const snapshot = data.snapshot || data

                const mode = window.confirm('Restore as a NEW board? (Click Cancel to OVERWRITE the current board)')

                if (mode) {
                    // Restore as New
                    const newId = uniqueId()
                    const name = data.roomName || 'Restored Board'

                    // Store for the new room to pick up
                    localStorage.setItem(`restore_data_${newId}`, JSON.stringify({
                        snapshot,
                        roomName: name
                    }))

                    // Save to recents
                    const storedRooms = localStorage.getItem('tldraw_saved_rooms')
                    let recentRooms = []
                    try {
                        if (storedRooms) recentRooms = JSON.parse(storedRooms)
                    } catch (e) {}

                    recentRooms.push({
                        id: newId,
                        name: name,
                        lastVisited: Date.now()
                    })
                    localStorage.setItem('tldraw_saved_rooms', JSON.stringify(recentRooms))

                    // Open in new tab or navigate?
                    // Let's navigate to keep it simple, or open in new tab if requested.
                    // Implementation plan said "Creates new Room -> Loads snapshot -> Navigates to room"
                    window.location.assign(`/#/${newId}`)
                } else {
                    // Overwrite Current
                    if (window.confirm('WARNING: This will permanently overwrite the current board content for everyone. Are you sure?')) {
                        editor.loadSnapshot(snapshot)
                    }
                }
            } catch (e: any) {
                console.error('Failed to parse backup file', e)
                alert('Failed to restore backup: Invalid file format (' + e.message + ')')
            } finally {
                // Reset input
                if (inputRef.current) inputRef.current.value = ''
            }
        }
        reader.onerror = (err) => {
             alert('FileReader Error: ' + err)
        }
        reader.readAsText(file)
    }

    return (
        <input
            ref={inputRef}
            type="file"
            accept="application/json,.json"
            style={{ position: 'fixed', top: '-10000px', left: '-10000px', opacity: 0, pointerEvents: 'none' }}
            onChange={handleFileChange}
        />
    )
}

export function PdfExportMenuItem({ roomId }: { roomId: string }) {
    const editor = useEditor()
    const [isExporting, setIsExporting] = useState(false)

    const handleExport = async () => {
        if (isExporting) return
        setIsExporting(true)
        try {
            await exportToImage(editor, roomId, 'pdf')
        } catch (e) {
            console.error(e)
        } finally {
            setIsExporting(false)
        }
    }

    return (
        <TldrawUiMenuItem
            id="export-as-pdf"
            label={isExporting ? "Exporting PDF..." : "Export as PDF"}
            icon="file" // Using generic file icon as 'pdf' might not be standard in Tldraw icon set yet, or we could check. 'file' is safe.
            readonlyOk
            onSelect={handleExport}
            disabled={isExporting}
        />
    )
}

export function ImportSvgMenuItem() {
    const editor = useEditor()

    const handleImport = () => {
        triggerSvgImport(editor)
    }

    return (
        <TldrawUiMenuItem
            id="import-svg"
            label="Import from SVG"
            icon="image"
            readonlyOk
            onSelect={handleImport}
        />
    )
}