Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 32 additions & 61 deletions src/action/backup.css
Original file line number Diff line number Diff line change
@@ -1,42 +1,25 @@
#backup-panel details {
padding: 1ch;
#backup-panel section {
padding: 8px;
}

#backup-panel details > .content {
margin-top: 0.5em;
#backup-panel section:not(:last-child) {
border-bottom: 1px dotted rgb(var(--active-grey));
}

#backup-panel form {
display: flex;
flex-direction: column;
gap: 0.5em;
}

#backup-panel details[open] ~ details summary ~ * {
display: none;
}

#backup-panel h4 {
display: inline-block;
margin: 0;
}

#backup-panel label {
margin: 0;
justify-content: flex-start;
align-items: stretch;
row-gap: 8px;
margin-block-start: 0;
margin-block-end: 8px;
}

#backup-panel pre {
width: 0;
min-width: 100%;

-webkit-user-select: all;
user-select: all;
}

#backup-panel pre,
#backup-panel textarea {
max-height: calc(10em + 2ch);
overflow: auto;
overflow-wrap: break-word;
padding: 1ch;
padding: 8px;
border: none;
margin: 0;

Expand All @@ -47,23 +30,16 @@
white-space: pre;
}

#overwrite-warning[data-force-hidden="false"],
#overwrite-warning:where([data-hidden="false"]) {
display: flex;
justify-content: center;
}
#overwrite-warning[data-force-hidden="true"],
#overwrite-warning:where([data-hidden="true"]) {
display: none;
}

.buttons-container {
#backup-panel fieldset {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 0;
border: none;
margin: 0;
}

.buttons-container button {
#backup-panel fieldset button {
padding: 0;
border: none;
margin: 0;
Expand All @@ -74,7 +50,7 @@
font-weight: bold;
}

#copy-local.copied::after {
#export-copy.copied::after {
margin-left: 1ch;

content: "Copied!";
Expand All @@ -85,37 +61,32 @@
opacity: 1;
}

#copy-local.copied.fading::after {
#export-copy.copied.fading::after {
opacity: 0;
}

#import {
border-top: 1px dotted rgb(var(--active-grey));
}

#restore-local {
#import-submit {
display: block;
width: 100%;

text-align: center;
}

#restore-local:empty::before {
content: "Restore";
}

#restore-local.success {
border: none;

#import-submit:is(.success, .failure) {
background-color: transparent;
color: #00cf35;
border-color: transparent;
font-weight: bold;
}

#restore-local.failure {
border: none;
#import-submit.success { color: #00cf35; }
#import-submit.failure { color: #cf0000; }

background-color: transparent;
color: #cf0000;
font-weight: bold;
#import-warning[data-force-hidden="false"],
#import-warning:where([data-hidden="false"]) {
display: flex;
justify-content: center;
}
#import-warning[data-force-hidden="true"],
#import-warning:where([data-hidden="true"]) {
display: none;
}
4 changes: 0 additions & 4 deletions src/action/links.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@
padding: 8px;
}

#links-panel h4 {
margin-block: 8px;
}

#links-panel ul {
padding-inline: 16px;
margin-block-start: 0;
Expand Down
7 changes: 4 additions & 3 deletions src/action/popup.css
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,14 @@ select:focus {
background-color: rgb(var(--active-grey));
}

h4 {
margin-block: 8px;
}

textarea {
resize: none;
width: 100%;
}

textarea,
pre {
scrollbar-color: rgba(var(--black), 0.5) rgb(var(--passive-grey));
scrollbar-width: thin;
}
38 changes: 19 additions & 19 deletions src/action/popup.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,25 +51,25 @@
<sponsor-progress></sponsor-progress>
</section>
<section id="backup-panel" role="tabpanel" aria-labelledby="backup-tab" tabindex="0" hidden>
<details id="export" open>
<summary><h4>Export</h4></summary>
<div class="content">
<pre id="local-storage-export"></pre>
<div class="buttons-container">
<button id="copy-local">Copy All</button>
<button id="download-local">Download</button>
</div>
</div>
</details>
<details id="import">
<summary><h4>Import</h4></summary>
<div class="content">
<label for="local-storage-import">Paste the contents of your saved backup here.</label>
<textarea id="local-storage-import" rows="10" spellcheck="false"></textarea>
<button id="restore-local"></button>
<div id="overwrite-warning">⚠️ Warning: Existing data will be overwritten!</div>
</div>
</details>
<section aria-labelledby="export-heading">
<h4 id="export-heading">Export</h4>
<form id="export" aria-labelledby="export-heading">
<textarea id="export-value" rows="10" spellcheck="false" readonly></textarea>
<fieldset>
<button id="export-copy" type="submit">Copy All</button>
<button id="export-download" type="submit">Download</button>
</fieldset>
</form>
</section>
<section aria-labelledby="import-heading">
<h4 id="import-heading">Import</h4>
<form id="import" aria-labelledby="import-heading">
<label for="import-value">Paste the contents of your saved backup here.</label>
<textarea id="import-value" rows="10" spellcheck="false"></textarea>
<button id="import-submit" type="submit">Restore</button>
<div id="import-warning">⚠️ Warning: Existing data will be overwritten!</div>
</form>
</section>
</section>
<section id="links-panel" role="tabpanel" aria-labelledby="links-tab" tabindex="0" hidden>
<h4>Help & information</h4>
Expand Down
85 changes: 44 additions & 41 deletions src/action/render_backup.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
const localExportDisplayElement = document.getElementById('local-storage-export');
const localCopyButton = document.getElementById('copy-local');
const localDownloadButton = document.getElementById('download-local');
const exportForm = document.getElementById('export');
const exportValueTextarea = document.getElementById('export-value');
const exportCopyButton = document.getElementById('export-copy');
const exportDownloadButton = document.getElementById('export-download');

const localImportTextarea = document.getElementById('local-storage-import');
const localOverwriteWarning = document.getElementById('overwrite-warning');
const localRestoreButton = document.getElementById('restore-local');
const importForm = document.getElementById('import');
const importValueTextarea = document.getElementById('import-value');
const importSubmitButton = document.getElementById('import-submit');
const importWarningElement = document.getElementById('import-warning');

const sleep = ms => new Promise(resolve => setTimeout(() => resolve(), ms));

const onStorageChanged = async function () {
const storageLocal = await browser.storage.local.get();
const stringifiedStorage = JSON.stringify(storageLocal, null, 2);

localExportDisplayElement.textContent = stringifiedStorage;
localOverwriteWarning.dataset.hidden = Object.keys(storageLocal).length === 0;
exportValueTextarea.value = stringifiedStorage;
importWarningElement.dataset.hidden = Object.keys(storageLocal).length === 0;
};

const localCopy = async function () {
if (localCopyButton.classList.contains('copied')) { return; }
const localCopy = () => {
if (exportCopyButton.classList.contains('copied')) { return; }

navigator.clipboard.writeText(localExportDisplayElement.textContent).then(async () => {
localCopyButton.classList.add('copied');
navigator.clipboard.writeText(exportValueTextarea.value).then(async () => {
exportCopyButton.classList.add('copied');
await sleep(2000);
localCopyButton.classList.add('fading');
exportCopyButton.classList.add('fading');
await sleep(1000);
localCopyButton.classList.remove('copied', 'fading');
exportCopyButton.classList.remove('copied', 'fading');
});
};

Expand Down Expand Up @@ -53,53 +55,54 @@ const localExport = async function () {
URL.revokeObjectURL(blobUrl);
};

const localRestore = async function () {
const importText = localImportTextarea.value;
/** @type {(event: SubmitEvent) => void} */
function onExportSubmit (event) {
event.preventDefault();
if (event.submitter === exportCopyButton) localCopy();
if (event.submitter === exportDownloadButton) localExport();
}

/** @type {(event: SubmitEvent) => Promise<void>} */
async function onImportSubmit (event) {
event.preventDefault();

const importText = importValueTextarea.value;

try {
localRestoreButton.disabled = true;
importSubmitButton.disabled = true;

const parsedStorage = JSON.parse(importText);

localOverwriteWarning.dataset.forceHidden = localOverwriteWarning.dataset.hidden;
importWarningElement.dataset.forceHidden = importWarningElement.dataset.hidden;

await browser.storage.local.clear();
await browser.storage.local.set(parsedStorage);

localRestoreButton.classList.add('success');
localRestoreButton.textContent = 'Successfully restored!';
localImportTextarea.value = '';
importSubmitButton.classList.add('success');
importSubmitButton.textContent = 'Successfully restored!';
importValueTextarea.value = '';
document.getElementById('configuration-tab').classList.add('outdated');
} catch (exception) {
localRestoreButton.classList.add('failure');
localRestoreButton.textContent =
exception instanceof SyntaxError ? 'Failed to parse backup contents!' : 'Failed to restore!';
importSubmitButton.classList.add('failure');
importSubmitButton.textContent = exception instanceof SyntaxError
? 'Failed to parse backup contents!'
: 'Failed to restore!';
console.error(exception);
} finally {
await sleep(3000);
localRestoreButton.disabled = false;
localRestoreButton.classList.remove('success', 'failure');
localRestoreButton.textContent = '';
delete localOverwriteWarning.dataset.forceHidden;
importSubmitButton.disabled = false;
importSubmitButton.classList.remove('success', 'failure');
importSubmitButton.textContent = 'Restore';
delete importWarningElement.dataset.forceHidden;
}
};
}

const renderLocalBackup = async function () {
onStorageChanged();
browser.storage.local.onChanged.addListener(onStorageChanged);

localCopyButton.addEventListener('click', localCopy);
localDownloadButton.addEventListener('click', localExport);

localRestoreButton.addEventListener('click', localRestore);
exportForm.addEventListener('submit', onExportSubmit);
importForm.addEventListener('submit', onImportSubmit);
};

renderLocalBackup();

document.querySelectorAll('#backup-panel details').forEach(details => details.addEventListener('toggle', ({ currentTarget }) => {
if (currentTarget.open) {
[...currentTarget.parentNode.children]
.filter(element => element !== currentTarget)
.forEach(sibling => { sibling.open = false; });
}
}));
Loading