File size: 4,997 Bytes
e1753d8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { Share } from '@capacitor/share'
import { Filesystem, Directory } from '@capacitor/filesystem'
import { SERVER_URL } from '../config'
import { Editor } from 'tldraw'
import { customAlert, customPrompt } from './uiUtils'

// Helper: Convert Blob to Base64
const blobToBase64 = (blob: Blob): Promise<string> => {
    return new Promise((resolve, reject) => {
        const reader = new FileReader()
        reader.onerror = reject
        reader.onload = () => resolve(reader.result as string)
        reader.readAsDataURL(blob)
    })
}

export const shareLink = async (roomId: string) => {
    const url = `${SERVER_URL}/#/${roomId}`
    try {
        // Try native share or Web Share API first
        await Share.share({
            title: 'Join my Whiteboard',
            text: 'Collaborate with me on this board:',
            url: url,
            dialogTitle: 'Share Board Link',
        })
    } catch (error) {
        // Fallback to clipboard if Share API fails (common on desktop)
        console.log('Share API not available, falling back to clipboard', error)
        try {
            await navigator.clipboard.writeText(url)
            await customAlert('Link copied to clipboard!')
        } catch (clipboardError) {
            console.error('Failed to copy to clipboard:', clipboardError)
            await customPrompt('Copy this link:', url)
        }
    }
}

export const exportToImage = async (editor: Editor, roomId: string) => {
    try {
        // 1. Get all shape IDs from the current page
        const shapeIds = Array.from(editor.getCurrentPageShapeIds())
        if (shapeIds.length === 0) {
            await customAlert('Board is empty')
            return
        }

        // 2. Calculate Bounds to prevent OOM
        let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity
        shapeIds.forEach(id => {
            const bounds = editor.getShapePageBounds(id)
            if (bounds) {
                minX = Math.min(minX, bounds.x)
                minY = Math.min(minY, bounds.y)
                maxX = Math.max(maxX, bounds.x + bounds.w)
                maxY = Math.max(maxY, bounds.y + bounds.h)
            }
        })

        const width = maxX - minX
        const height = maxY - minY
        
        // Limit max dimension to ~3000px to avoid Android texture/memory limits
        const MAX_DIMENSION = 3000
        let scale = 2
        if (width * scale > MAX_DIMENSION || height * scale > MAX_DIMENSION) {
            scale = Math.min(MAX_DIMENSION / width, MAX_DIMENSION / height)
            console.log(`Large board detected. Reducing scale to ${scale.toFixed(2)} to prevent crash.`)
        }

        // 3. Generate Image with safe scale
        let result
        try {
            result = await editor.toImage(shapeIds, {
                format: 'png',
                background: true,
                scale: scale,
                padding: 32,
            })
        } catch (e) {
            throw new Error('Image generation failed: ' + (e as any).message)
        }

        if (!result || !result.blob) {
            throw new Error('Failed to generate image blob (empty result)')
        }

        // 4. Check file size. If huge (>2MB), convert to JPEG to ensure shareability
        if (result.blob.size > 2 * 1024 * 1024) {
            console.log('Image too large (>2MB), switching to JPEG for compression')
            try {
                result = await editor.toImage(shapeIds, {
                    format: 'jpeg',
                    quality: 0.8,
                    background: true,
                    scale: scale,
                    padding: 32,
                })
            } catch (e) {
                console.warn('JPEG fallback failed, using original PNG')
            }
        }

        // 5. Convert Blob to Base64 for Capacitor
        let pureBase64
        try {
            const base64Data = await blobToBase64(result.blob!)
            pureBase64 = base64Data.split(',')[1]
        } catch (e) {
            throw new Error('Base64 conversion failed')
        }
        
        const ext = result.blob!.type === 'image/jpeg' ? 'jpg' : 'png'
        const fileName = `board-${roomId}-${Date.now()}.${ext}`
        
        // 6. Save to Capacitor Filesystem
        let savedFile
        try {
            savedFile = await Filesystem.writeFile({
                path: fileName,
                data: pureBase64,
                directory: Directory.Cache
            })
        } catch (e) {
            throw new Error('File save failed: ' + (e as any).message)
        }

        // 7. Share
        try {
            await Share.share({
                title: 'Export Board',
                files: [savedFile.uri],
            })
        } catch (e) {
            throw new Error('Share API failed: ' + (e as any).message)
        }

    } catch (error) {
        console.error('Export failed:', error)
        await customAlert((error as any).message)
    }
}