## Me > Sharing: > - 👀 popup/popup.html > - 👀 popup/popup.js > - files @files this is the html and js code of a firefox browser extension popup window, implemented with alpine.js (using the CSP-friendly mode). add a button at the bottom of the window that will export the playlists and the playback history and download it as a json file. ## CodeCompanion (Anthropic) ### Reasoning I need to add a button to the popup HTML and implement JavaScript functionality to export playlists and playback history as a JSON file. Let's break down the task: 1. Add an export button to the HTML at the bottom of the popup window 2. Implement a JavaScript function to: - Gather playlist data (already in the `this.playlists` object) - Gather playback history (doesn't seem to be implemented yet, so I'll add it) - Combine them into a JSON object - Create and trigger a download for this JSON data Let's start by modifying the HTML file to add the export button: For the HTML (`popup.html`), I'll add a button at the bottom, after the playlists container but still within the app div: ```html
``` Now for the JavaScript (`popup.js`), I need to add: 1. An `exportButton` binding similar to the existing button bindings 2. An `exportPlaylists` function that does the actual export work Here's what I'll add to the Alpine data object: ```javascript exportButton: { ["@click"]() { this.exportPlaylists(); }, }, async exportPlaylists() { try { // Get current playlists from our data const playlists = this.playlists; // Get playback history if it exists (or use empty object) const result = await browser.storage.local.get("playbackHistory"); const playbackHistory = result.playbackHistory || {}; // Combine into a single export object const exportData = { playlists: playlists, playbackHistory: playbackHistory, exportDate: new Date().toISOString() }; // Convert to JSON string const jsonString = JSON.stringify(exportData, null, 2); // Create a download link const blob = new Blob([jsonString], { type: 'application/json' }); const url = URL.createObjectURL(blob); // Create a temporary link element and trigger download const a = document.createElement('a'); a.href = url; a.download = `playlists-export-${new Date().toISOString().split('T')[0]}.json`; document.body.appendChild(a); a.click(); // Clean up setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 100); } catch (error) { console.error("Error exporting playlists:", error); } } ``` Now I'll put this all together to create the changes needed. For `popup.html`, I'll add the export button at the bottom, after line 59 (the closing of the playlists-container) but before line 60 (the closing of the app div). For `popup.js`, I'll add the new methods to the Alpine data object before the final closing braces. Let me create the XML response for the tools: ### Response Looking at your files, I'll add an export button at the bottom of the popup window to export playlists and playback history as a JSON file. First, let's add the button to the HTML and then implement the JavaScript functionality: ```xml /home/brwong/projects/experiments/playlist/popup/popup.html ]]>
]]>
``` Now let's add the JavaScript functionality to handle the export: ```xml /home/brwong/projects/experiments/playlist/popup/popup.js { document.body.removeChild(a); URL.revokeObjectURL(url); }, 100); } catch (error) { console.error("Error exporting playlists:", error); } },]]> ``` Now we should also add some basic styling for the export button. Would you like me to create or modify a CSS file for this as well? ## Me > Sharing: > - 👀 popup/popup.html > - 👀 popup/popup.js > - files add another button next to the first one that does a playlist import. the button will select a json file from the filesystem (in the same format as the export), do some rudimentary structural validation of the data (such as making sure that the value under "playlists" is an object with playlist names as keys and an array as the value, and each element in the arrays are objects with at least "url" and "title"), and replace the storage and the alpine.js state with the imported playlists. ignore "history" or any other key that may be present in the json file. ## CodeCompanion (Anthropic) ### Reasoning I need to add functionality for importing playlists. This will involve: 1. Adding an "Import Playlists" button next to the export button 2. Implementing file selection and reading 3. Adding validation for the imported JSON data 4. Updating both the browser storage and Alpine.js state with the imported playlists Let's start by adding the button to the HTML: ### Response I'll add an import button next to the export button and implement the import functionality with validation: ```xml /home/brwong/projects/experiments/playlist/popup/popup.html
]]>
]]>
``` Now, let's add the JavaScript functions for importing: ```xml /home/brwong/projects/experiments/playlist/popup/popup.js { try { const importedData = JSON.parse(e.target.result); this.validateAndImportPlaylists(importedData); } catch (error) { console.error("Error parsing JSON file:", error); alert("Invalid JSON file. Please select a valid playlist export file."); } finally { // Reset the file input so the same file can be selected again event.target.value = ''; } }; reader.readAsText(file); }, validateAndImportPlaylists(data) { // Validate the playlists structure if (!data.playlists || typeof data.playlists !== 'object') { alert("Invalid file format: Missing or invalid 'playlists' property"); return; } // Validate each playlist const validPlaylists = {}; let hasErrors = false; for (const [playlistName, videos] of Object.entries(data.playlists)) { // Check if videos is an array if (!Array.isArray(videos)) { console.error(`Playlist '${playlistName}' does not contain a valid array of videos`); hasErrors = true; continue; } // Validate each video in the playlist const validVideos = videos.filter(video => { if (!video || typeof video !== 'object') { console.error(`Invalid video object in '${playlistName}'`); return false; } if (!video.url || typeof video.url !== 'string' || !video.title || typeof video.title !== 'string') { console.error(`Video in '${playlistName}' missing required properties (url, title)`); return false; } return true; }); // Add the validated playlist if it has valid videos if (validVideos.length > 0) { validPlaylists[playlistName] = validVideos; } } if (Object.keys(validPlaylists).length === 0) { alert("No valid playlists found in the import file"); return; } if (hasErrors) { const confirmImport = confirm("Some playlists or videos were invalid and will be skipped. Do you want to continue with the import?"); if (!confirmImport) return; } // Update storage and state this.updatePlaylists(validPlaylists); }, async updatePlaylists(playlists) { try { await browser.storage.local.set({ playlists }); this.playlists = playlists; alert("Playlists imported successfully!"); } catch (error) { console.error("Error updating playlists:", error); alert("Error importing playlists: " + error.message); } },]]> ``` This implementation: 1. Adds an import button next to the export button 2. Includes a hidden file input that accepts only JSON files 3. Provides a function to validate the JSON structure, checking that: - The "playlists" property exists and is an object - Each playlist contains an array of videos - Each video has at least "url" and "title" properties 4. Filters out invalid playlists and videos 5. Alerts the user if there are validation errors but allows them to continue with valid playlists 6. Updates both the browser storage and Alpine.js state with the imported playlists Would you like to add any specific styles for the import button to match the export button? ## Me > Sharing: > - 👀 popup/popup.html > - 👀 popup/popup.js > - files