PageSourceSearch

https://progressiveplants.com/2026projects/updategardenplan/js/pro…?v=20250813_firebase_fix

js progressiveplants.com collected 2026-09-30 17:23:21 UTC 12,096 bytes, 276 lines download raw bytes

1/**
2 * Project Export Generator
3 * Generates standalone HTML exports for client presentations
4 */
5
6const ProjectExportGenerator = {
7    
8    /**
9     * Generate export HTML with embedded project data
10     */
11    async generateExport(projectData) {
12        console.log('[ExportGenerator] Generating export for project:', projectData.jobName);
13        
14        try {
15            // Load the template HTML
16            const templateResponse = await fetch('project-export.html');
17            let templateHTML = await templateResponse.text();
18            
19            // Prepare project data for embedding
20            const exportData = {
21                jobName: projectData.jobName,
22                client: projectData.client || '',
23                notes: projectData.notes || '',
24                categories: projectData.categories || [],
25                items: Array.from(projectData.items.entries()).map(([instanceId, item]) => ({
26                    plantId: item.plantId || instanceId,  // Use the actual plantId from item, fallback to instanceId for old data
27                    instanceId: instanceId,
28                    quantity: item.quantity,
29                    category: item.category,
30                    notes: item.notes,
31                    addedDate: item.addedDate,
32                    plantData: item.plantData || ProjectFavoritesManager.plantsData.get(item.plantId || instanceId)
33                })),
34                createdDate: projectData.createdDate,
35                exportDate: new Date().toISOString()
36            };
37            
38            // Embed the project data into the HTML
39            const dataScript = `
40                <script id="projectData">
41                    window.PROJECT_DATA = ${JSON.stringify(exportData, null, 2)};
42                </script>
43            `;
44            
45            // Replace the placeholder script with actual data
46            templateHTML = templateHTML.replace(
47                /<script id="projectData">[\s\S]*?<\/script>/,
48                dataScript
49            );
50            
51            return templateHTML;
52        } catch (error) {
53            console.error('[ExportGenerator] Failed to generate export:', error);
54            throw error;
55        }
56    },
57    
58    /**
59     * Open export in new window/tab
60     */
61    async openExport(projectData) {
62        try {
63            const html = await this.generateExport(projectData);
64            
65            // Open in new window
66            const exportWindow = window.open('', '_blank');
67            exportWindow.document.write(html);
68            exportWindow.document.close();
69            
70            return exportWindow;
71        } catch (error) {
72            console.error('[ExportGenerator] Failed to open export:', error);
73            alert('Failed to generate export. Please try again.');
74        }
75    },
76    
77    /**
78     * Download export as HTML file
79     */
80    async downloadExport(projectData) {
81        try {
82            const html = await this.generateExport(projectData);
83            
84            // Create blob and download
85            const blob = new Blob([html], { type: 'text/html' });
86            const url = URL.createObjectURL(blob);
87            
88            const a = document.createElement('a');
89            a.href = url;
90            a.download = `${projectData.jobName.replace(/[^a-z0-9]/gi, '_')}_plant_proposal.html`;
91            document.body.appendChild(a);
92            a.click();
93            document.body.removeChild(a);
94            
95            URL.revokeObjectURL(url);
96            
97            console.log('[ExportGenerator] Export downloaded successfully');
98        } catch (error) {
99            console.error('[ExportGenerator] Failed to download export:', error);
100            alert('Failed to download export. Please try again.');
101        }
102    },
103    
104    /**
105     * Generate shareable URL using Firebase storage for short URLs
106     */
107    async generateShareableURL(projectData) {
108        try {
109            // Check Firebase availability
110            if (!window.FirebaseStorage || !FirebaseStorage.isInitialized) {
111                alert('Please wait for the system to initialize and try again.');
112                return null;
113            }
114            
115            console.log('[ExportGenerator] Generating Firebase short URL...');
116            
117            // Helper function to clean undefined values for Firebase
118            const cleanObject = (obj) => {
119                const cleaned = {};
120                for (const [key, value] of Object.entries(obj)) {
121                    if (value !== undefined && value !== null) {
122                        if (typeof value === 'object' && !Array.isArray(value)) {
123                            const cleanedSubObj = cleanObject(value);
124                            if (Object.keys(cleanedSubObj).length > 0) {
125                                cleaned[key] = cleanedSubObj;
126                            }
127                        } else {
128                            cleaned[key] = value;
129                        }
130                    }
131                }
132                return cleaned;
133            };
134            
135            // Prepare export data
136            const exportData = {
137                jobName: projectData.jobName || '',
138                client: projectData.client || '',
139                notes: projectData.notes || '',
140                categories: projectData.categories || [],
141                items: Array.from(projectData.items.entries()).map(([instanceId, item]) => {
142                    const plantId = item.plantId || instanceId;  // Use the actual plantId from item
143                    const cleanedItem = {
144                        plantId: plantId,
145                        instanceId: instanceId,
146                        quantity: item.quantity || 1,
147                        category: item.category || '',
148                        notes: item.notes || '',
149                        addedDate: item.addedDate || Date.now(),
150                        // Include all plant data for display
151                        plantData: item.plantData || ProjectFavoritesManager.plantsData.get(plantId) || {
152                            'Botanical Name': plantId
153                        }
154                    };
155                    return cleanObject(cleanedItem);
156                }),
157                createdDate: projectData.createdDate || Date.now(),
158                exportDate: new Date().toISOString()
159            };
160            
161            // Final clean of the entire export data
162            const cleanedExportData = cleanObject(exportData);
163            
164            // Save to Firebase and get short ID
165            const exportId = await FirebaseStorage.saveExport(cleanedExportData, projectData.jobName);
166            if (exportId) {
167                const baseURL = window.location.origin + '/2026projects/updategardenplan/project-export.html';
168                const shortURL = `${baseURL}
168?id=${exportId}`;
169                console.log(`[ExportGenerator] ✓ Generated short URL: ${shortURL}`);
170                return shortURL;
171            } else {
172                throw new Error('Failed to generate export ID');
173            }
174            
175        } catch (error) {
176            console.error('[ExportGenerator] Failed to generate shareable URL:', error);
177            alert('Failed to generate shareable link. Please check your internet connection and try again.');
178            return null;
179        }
180    },
181    
182    /**
183     * Copy shareable URL to clipboard
184     */
185    async copyShareableURL(projectData) {
186        try {
187            // Show loading indicator
188            const loadingMessage = 'Generating shareable link...';
189            console.log('[ExportGenerator]', loadingMessage);
190            
191            const url = await this.generateShareableURL(projectData);
192            
193            if (!url) {
194                return false;
195            }
196            
197            try {
198                await navigator.clipboard.writeText(url);
199                alert('Shareable link copied to clipboard!\n\nLink length: ' + url.length + ' characters');
200                return true;
201            } catch (error) {
202                console.error('[ExportGenerator] Failed to copy URL:', error);
203                
204                // Fallback for older browsers
205                const textArea = document.createElement('textarea');
206                textArea.value = url;
207                textArea.style.position = 'fixed';
208                textArea.style.left = '-999999px';
209                document.body.appendChild(textArea);
210                textArea.select();
211                
212                try {
213                    document.execCommand('copy');
214                    alert('Shareable link copied to clipboard!\n\nLink length: ' + url.length + ' characters');
215                    return true;
216                } catch (e) {
217                    alert('Failed to copy link. Please copy manually:\n\n' + url);
218                    return false;
219                } finally {
220                    document.body.removeChild(textArea);
221                }
222            }
223        } catch (error) {
224            console.error('[ExportGenerator] Error generating shareable URL:', error);
225            alert('Failed to generate shareable link. Please try again.');
226            return false;
227        }
228    },
229    
230    /**
231     * Show export options dialog
232     */
233    showExportDialog(projectData) {
234        // Create modal HTML
235        const modalHTML = `
236            <div id="exportModal" class="modal" style="display: block;">
237                <div class="modal-content" style="max-width: 500px;">
238                    <span class="close-modal" onclick="document.getElementById('exportModal').remove()">&times;</span>
239                    <h2>Export Project for Client</h2>
240                    <p>Choose how to share "${projectData.jobName}" with your client:</p>
241                    
242                    <div style="display: flex; flex-direction: column; gap: 15px; margin-top: 20px;">
243                        <button class="primary-btn" onclick="ProjectExportGenerator.openExport(ProjectFavoritesManager.projects.get('${projectData.jobName}')); document.getElementById('exportModal').remove();">
244                            📄 Open in New Tab
245                            <small style="display: block; font-weight: normal; margin-top: 5px;">View and print the presentation</small>
246                        </button>
247                        
248                        <!--<button class="primary-btn" onclick="ProjectExportGenerator.downloadExport(ProjectFavoritesManager.projects.get('${projectData.jobName}')); document.getElementById('exportModal').remove();">
249                            💾 Download HTML File
250                            <small style="display: block; font-weight: normal; margin-top: 5px;">Save for email attachment</small>
251                        </button>-->
252                        
253                        <button class="primary-btn" onclick="ProjectExportGenerator.copyShareableURL(ProjectFavoritesManager.projects.get('${projectData.jobName}')); document.getElementById('exportModal').remove();">
254                            🔗 Copy Shareable Link
255                            <small style="display: block; font-weight: normal; margin-top: 5px;">Generate a short link to share</small>
256                        </button>
257                    </div>
258                    
259                    <div style="margin-top: 20px; padding-top: 20px; border-top: 1px solid #eee;">
260                        <p style="font-size: 12px; color: #666;">
261                            <strong>Note:</strong> The export will include all plants in the current project. Links are automatically shortened for easy sharing.
262                        </p>
263                    </div>
264                </div>
265            </div>
266        `;
267        
268        // Add modal to page
269        const modalDiv = document.createElement('div');
270        modalDiv.innerHTML = modalHTML;
271        document.body.appendChild(modalDiv.firstElementChild);
272    }
273};
274
275// Make available globally
276window.ProjectExportGenerator = ProjectExportGenerator;

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.