diff options
-rw-r--r-- | data-viewer/data-viewer.css | 58 | ||||
-rw-r--r-- | data-viewer/data-viewer.html | 24 | ||||
-rw-r--r-- | data-viewer/data-viewer.js | 154 |
3 files changed, 232 insertions, 4 deletions
diff --git a/data-viewer/data-viewer.css b/data-viewer/data-viewer.css index 508d34f..84f0131 100644 --- a/data-viewer/data-viewer.css +++ b/data-viewer/data-viewer.css @@ -502,10 +502,60 @@ body { margin-bottom: 20px; } -/* Responsive handling for mobile */ +/* Backup and Restore Styling */ +.backup-restore-container { + background-color: var(--bg-color); + border-radius: var(--radius-sm); + padding: 16px; + border-left: 3px solid var(--accent-color); +} + +.backup-description { + margin-top: 0; + margin-bottom: 16px; + color: var(--text-secondary); + font-size: 14px; + line-height: 1.5; +} + +.backup-actions { + display: flex; + gap: 16px; + margin-bottom: 16px; +} + +.import-container, #export-settings { + flex: 1; +} + +.import-label { + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; +} + +.import-status { + font-size: 14px; + margin-top: 8px; + padding: 8px 0; + border-radius: var(--radius-sm); +} + +.status-success { + color: var(--success-color); + font-weight: 500; +} + +.status-error { + color: var(--danger-color); + font-weight: 500; +} + +/* Add responsive adjustments for mobile */ @media (max-width: 768px) { - .tables-container { - grid-template-columns: 1fr; - gap: 16px; + .backup-actions { + flex-direction: column; + gap: 8px; } } diff --git a/data-viewer/data-viewer.html b/data-viewer/data-viewer.html index 716942d..5336bfe 100644 --- a/data-viewer/data-viewer.html +++ b/data-viewer/data-viewer.html @@ -46,6 +46,30 @@ </div> </div> + <!-- New section for backup/restore --> + <div class="data-section"> + <h2 class="section-title">Backup & Restore</h2> + <div class="backup-restore-container"> + <p class="backup-description"> + Export your settings to a JSON file or import settings from a previously exported file. + <br><strong>Note:</strong> This includes global settings, website lists, and website-specific + feature toggles, but not the actual style data which will be re-fetched. + </p> + <div class="backup-actions"> + <button id="export-settings" class="action-button primary"> + <i class="fas fa-upload"></i> Export Settings + </button> + <div class="import-container"> + <label for="import-file" class="action-button secondary import-label"> + <i class="fas fa-download"></i> Import Settings + </label> + <input type="file" id="import-file" accept=".json" hidden> + </div> + </div> + <div id="import-status" class="import-status"></div> + </div> + </div> + <div class="data-section"> <h2 class="section-title">Global Settings</h2> <div id="global-settings-data" class="data-container"></div> diff --git a/data-viewer/data-viewer.js b/data-viewer/data-viewer.js index c006f09..55ec808 100644 --- a/data-viewer/data-viewer.js +++ b/data-viewer/data-viewer.js @@ -15,6 +15,11 @@ document.addEventListener("DOMContentLoaded", function () { "disable-transparency" ); + // Backup & Restore Elements + const exportButton = document.getElementById("export-settings"); + const importFileInput = document.getElementById("import-file"); + const importStatusElement = document.getElementById("import-status"); + // Load and display the data loadAllData(); @@ -42,6 +47,10 @@ document.addEventListener("DOMContentLoaded", function () { } }); + // New event listeners for export and import functionality + exportButton.addEventListener("click", exportSettings); + importFileInput.addEventListener("change", importSettings); + async function deleteAllData() { try { // Clear all storage data @@ -83,6 +92,151 @@ document.addEventListener("DOMContentLoaded", function () { } } + // Export settings functionality + async function exportSettings() { + try { + // Retrieve all storage data to find site-specific settings + const allData = await browser.storage.local.get(null); + + // Extract only the settings we want to backup + const settingsToBackup = { + [BROWSER_STORAGE_KEY]: allData[BROWSER_STORAGE_KEY] || {}, + [SKIP_FORCE_THEMING_KEY]: allData[SKIP_FORCE_THEMING_KEY] || [], + [SKIP_THEMING_KEY]: allData[SKIP_THEMING_KEY] || [], + }; + + // Also extract site-specific settings (keys that start with BROWSER_STORAGE_KEY.) + const siteSpecificSettings = {}; + for (const [key, value] of Object.entries(allData)) { + if (key.startsWith(BROWSER_STORAGE_KEY + ".")) { + siteSpecificSettings[key] = value; + } + } + + // Add export timestamp and version + const manifest = browser.runtime.getManifest(); + const exportData = { + exportDate: new Date().toISOString(), + addonVersion: manifest.version, + settings: settingsToBackup, + siteSettings: siteSpecificSettings, + }; + + // Convert to JSON + const jsonData = JSON.stringify(exportData, null, 2); + + // Create a blob and download link + const blob = new Blob([jsonData], { type: "application/json" }); + const url = URL.createObjectURL(blob); + + // Create a temporary anchor and trigger download + const a = document.createElement("a"); + a.href = url; + a.download = `zen-internet-settings-${ + new Date().toISOString().split("T")[0] + }.json`; + document.body.appendChild(a); + a.click(); + + // Cleanup + setTimeout(() => { + document.body.removeChild(a); + URL.revokeObjectURL(url); + }, 0); + + // Show success message + showImportStatus("Settings exported successfully!", "success"); + } catch (error) { + console.error("Error exporting settings:", error); + showImportStatus(`Export failed: ${error.message}`, "error"); + } + } + + // Import settings functionality + async function importSettings(event) { + try { + const file = event.target.files[0]; + if (!file) return; + + const reader = new FileReader(); + + reader.onload = async (e) => { + try { + const importData = JSON.parse(e.target.result); + + // Validate the imported data structure + if ( + !importData.settings || + !importData.settings[BROWSER_STORAGE_KEY] + ) { + throw new Error("Invalid settings file format"); + } + + // Confirm the import + if ( + confirm( + `Are you sure you want to import settings from ${importData.exportDate}? This will overwrite your current settings.` + ) + ) { + // First store the global settings and lists + const importOperations = { + [BROWSER_STORAGE_KEY]: importData.settings[BROWSER_STORAGE_KEY], + [SKIP_FORCE_THEMING_KEY]: + importData.settings[SKIP_FORCE_THEMING_KEY] || [], + [SKIP_THEMING_KEY]: importData.settings[SKIP_THEMING_KEY] || [], + }; + + // Then add any site-specific settings if they exist + if (importData.siteSettings) { + for (const [key, value] of Object.entries( + importData.siteSettings + )) { + importOperations[key] = value; + } + } + + // Apply all settings at once + await browser.storage.local.set(importOperations); + + showImportStatus( + "Settings imported successfully! Reloading...", + "success" + ); + + // Reload the page after a short delay + setTimeout(() => { + window.location.reload(); + }, 1500); + } else { + // User cancelled + importFileInput.value = ""; + showImportStatus("Import cancelled", "error"); + } + } catch (parseError) { + console.error("Error parsing import file:", parseError); + showImportStatus(`Import failed: ${parseError.message}`, "error"); + } + }; + + reader.readAsText(file); + } catch (error) { + console.error("Error handling import:", error); + showImportStatus(`Import failed: ${error.message}`, "error"); + } + } + + // Helper function to show import status messages + function showImportStatus(message, type) { + importStatusElement.textContent = message; + importStatusElement.className = `import-status status-${type}`; + + // Clear the message after 5 seconds + setTimeout(() => { + importStatusElement.textContent = ""; + importStatusElement.className = "import-status"; + }, 5000); + } + async function displayAddonVersion() { const manifest = browser.runtime.getManifest(); versionElement.textContent = `Version: ${manifest.version}`; |