Skip to content

Commit a64298a

Browse files
authored
fix(UI): render Backup controls as <form> elements (#2158)
1 parent 09ba0d3 commit a64298a

File tree

5 files changed

+99
-128
lines changed

5 files changed

+99
-128
lines changed

src/action/backup.css

Lines changed: 32 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,25 @@
1-
#backup-panel details {
2-
padding: 1ch;
1+
#backup-panel section {
2+
padding: 8px;
33
}
44

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

9+
#backup-panel form {
810
display: flex;
911
flex-direction: column;
10-
gap: 0.5em;
11-
}
12-
13-
#backup-panel details[open] ~ details summary ~ * {
14-
display: none;
15-
}
16-
17-
#backup-panel h4 {
18-
display: inline-block;
19-
margin: 0;
20-
}
21-
22-
#backup-panel label {
23-
margin: 0;
12+
justify-content: flex-start;
13+
align-items: stretch;
14+
row-gap: 8px;
15+
margin-block-start: 0;
16+
margin-block-end: 8px;
2417
}
2518

26-
#backup-panel pre {
27-
width: 0;
28-
min-width: 100%;
29-
30-
-webkit-user-select: all;
31-
user-select: all;
32-
}
33-
34-
#backup-panel pre,
3519
#backup-panel textarea {
36-
max-height: calc(10em + 2ch);
3720
overflow: auto;
3821
overflow-wrap: break-word;
39-
padding: 1ch;
22+
padding: 8px;
4023
border: none;
4124
margin: 0;
4225

@@ -47,23 +30,16 @@
4730
white-space: pre;
4831
}
4932

50-
#overwrite-warning[data-force-hidden="false"],
51-
#overwrite-warning:where([data-hidden="false"]) {
52-
display: flex;
53-
justify-content: center;
54-
}
55-
#overwrite-warning[data-force-hidden="true"],
56-
#overwrite-warning:where([data-hidden="true"]) {
57-
display: none;
58-
}
59-
60-
.buttons-container {
33+
#backup-panel fieldset {
6134
display: flex;
6235
flex-direction: row;
6336
justify-content: space-between;
37+
padding: 0;
38+
border: none;
39+
margin: 0;
6440
}
6541

66-
.buttons-container button {
42+
#backup-panel fieldset button {
6743
padding: 0;
6844
border: none;
6945
margin: 0;
@@ -74,7 +50,7 @@
7450
font-weight: bold;
7551
}
7652

77-
#copy-local.copied::after {
53+
#export-copy.copied::after {
7854
margin-left: 1ch;
7955

8056
content: "Copied!";
@@ -85,37 +61,32 @@
8561
opacity: 1;
8662
}
8763

88-
#copy-local.copied.fading::after {
64+
#export-copy.copied.fading::after {
8965
opacity: 0;
9066
}
9167

92-
#import {
93-
border-top: 1px dotted rgb(var(--active-grey));
94-
}
95-
96-
#restore-local {
68+
#import-submit {
9769
display: block;
9870
width: 100%;
9971

10072
text-align: center;
10173
}
10274

103-
#restore-local:empty::before {
104-
content: "Restore";
105-
}
106-
107-
#restore-local.success {
108-
border: none;
109-
75+
#import-submit:is(.success, .failure) {
11076
background-color: transparent;
111-
color: #00cf35;
77+
border-color: transparent;
11278
font-weight: bold;
11379
}
11480

115-
#restore-local.failure {
116-
border: none;
81+
#import-submit.success { color: #00cf35; }
82+
#import-submit.failure { color: #cf0000; }
11783

118-
background-color: transparent;
119-
color: #cf0000;
120-
font-weight: bold;
84+
#import-warning[data-force-hidden="false"],
85+
#import-warning:where([data-hidden="false"]) {
86+
display: flex;
87+
justify-content: center;
88+
}
89+
#import-warning[data-force-hidden="true"],
90+
#import-warning:where([data-hidden="true"]) {
91+
display: none;
12192
}

src/action/links.css

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@
22
padding: 8px;
33
}
44

5-
#links-panel h4 {
6-
margin-block: 8px;
7-
}
8-
95
#links-panel ul {
106
padding-inline: 16px;
117
margin-block-start: 0;

src/action/popup.css

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,13 +152,14 @@ select:focus {
152152
background-color: rgb(var(--active-grey));
153153
}
154154

155+
h4 {
156+
margin-block: 8px;
157+
}
158+
155159
textarea {
156160
resize: none;
157161
width: 100%;
158-
}
159162

160-
textarea,
161-
pre {
162163
scrollbar-color: rgba(var(--black), 0.5) rgb(var(--passive-grey));
163164
scrollbar-width: thin;
164165
}

src/action/popup.html

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -51,25 +51,25 @@
5151
<sponsor-progress></sponsor-progress>
5252
</section>
5353
<section id="backup-panel" role="tabpanel" aria-labelledby="backup-tab" tabindex="0" hidden>
54-
<details id="export" open>
55-
<summary><h4>Export</h4></summary>
56-
<div class="content">
57-
<pre id="local-storage-export"></pre>
58-
<div class="buttons-container">
59-
<button id="copy-local">Copy All</button>
60-
<button id="download-local">Download</button>
61-
</div>
62-
</div>
63-
</details>
64-
<details id="import">
65-
<summary><h4>Import</h4></summary>
66-
<div class="content">
67-
<label for="local-storage-import">Paste the contents of your saved backup here.</label>
68-
<textarea id="local-storage-import" rows="10" spellcheck="false"></textarea>
69-
<button id="restore-local"></button>
70-
<div id="overwrite-warning">⚠️ Warning: Existing data will be overwritten!</div>
71-
</div>
72-
</details>
54+
<section aria-labelledby="export-heading">
55+
<h4 id="export-heading">Export</h4>
56+
<form id="export" aria-labelledby="export-heading">
57+
<textarea id="export-value" rows="10" spellcheck="false" readonly></textarea>
58+
<fieldset>
59+
<button id="export-copy" type="submit">Copy All</button>
60+
<button id="export-download" type="submit">Download</button>
61+
</fieldset>
62+
</form>
63+
</section>
64+
<section aria-labelledby="import-heading">
65+
<h4 id="import-heading">Import</h4>
66+
<form id="import" aria-labelledby="import-heading">
67+
<label for="import-value">Paste the contents of your saved backup here.</label>
68+
<textarea id="import-value" rows="10" spellcheck="false"></textarea>
69+
<button id="import-submit" type="submit">Restore</button>
70+
<div id="import-warning">⚠️ Warning: Existing data will be overwritten!</div>
71+
</form>
72+
</section>
7373
</section>
7474
<section id="links-panel" role="tabpanel" aria-labelledby="links-tab" tabindex="0" hidden>
7575
<h4>Help & information</h4>

src/action/render_backup.js

Lines changed: 44 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,32 @@
1-
const localExportDisplayElement = document.getElementById('local-storage-export');
2-
const localCopyButton = document.getElementById('copy-local');
3-
const localDownloadButton = document.getElementById('download-local');
1+
const exportForm = document.getElementById('export');
2+
const exportValueTextarea = document.getElementById('export-value');
3+
const exportCopyButton = document.getElementById('export-copy');
4+
const exportDownloadButton = document.getElementById('export-download');
45

5-
const localImportTextarea = document.getElementById('local-storage-import');
6-
const localOverwriteWarning = document.getElementById('overwrite-warning');
7-
const localRestoreButton = document.getElementById('restore-local');
6+
const importForm = document.getElementById('import');
7+
const importValueTextarea = document.getElementById('import-value');
8+
const importSubmitButton = document.getElementById('import-submit');
9+
const importWarningElement = document.getElementById('import-warning');
810

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

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

15-
localExportDisplayElement.textContent = stringifiedStorage;
16-
localOverwriteWarning.dataset.hidden = Object.keys(storageLocal).length === 0;
17+
exportValueTextarea.value = stringifiedStorage;
18+
importWarningElement.dataset.hidden = Object.keys(storageLocal).length === 0;
1719
};
1820

19-
const localCopy = async function () {
20-
if (localCopyButton.classList.contains('copied')) { return; }
21+
const localCopy = () => {
22+
if (exportCopyButton.classList.contains('copied')) { return; }
2123

22-
navigator.clipboard.writeText(localExportDisplayElement.textContent).then(async () => {
23-
localCopyButton.classList.add('copied');
24+
navigator.clipboard.writeText(exportValueTextarea.value).then(async () => {
25+
exportCopyButton.classList.add('copied');
2426
await sleep(2000);
25-
localCopyButton.classList.add('fading');
27+
exportCopyButton.classList.add('fading');
2628
await sleep(1000);
27-
localCopyButton.classList.remove('copied', 'fading');
29+
exportCopyButton.classList.remove('copied', 'fading');
2830
});
2931
};
3032

@@ -53,53 +55,54 @@ const localExport = async function () {
5355
URL.revokeObjectURL(blobUrl);
5456
};
5557

56-
const localRestore = async function () {
57-
const importText = localImportTextarea.value;
58+
/** @type {(event: SubmitEvent) => void} */
59+
function onExportSubmit (event) {
60+
event.preventDefault();
61+
if (event.submitter === exportCopyButton) localCopy();
62+
if (event.submitter === exportDownloadButton) localExport();
63+
}
64+
65+
/** @type {(event: SubmitEvent) => Promise<void>} */
66+
async function onImportSubmit (event) {
67+
event.preventDefault();
68+
69+
const importText = importValueTextarea.value;
5870

5971
try {
60-
localRestoreButton.disabled = true;
72+
importSubmitButton.disabled = true;
6173

6274
const parsedStorage = JSON.parse(importText);
6375

64-
localOverwriteWarning.dataset.forceHidden = localOverwriteWarning.dataset.hidden;
76+
importWarningElement.dataset.forceHidden = importWarningElement.dataset.hidden;
6577

6678
await browser.storage.local.clear();
6779
await browser.storage.local.set(parsedStorage);
6880

69-
localRestoreButton.classList.add('success');
70-
localRestoreButton.textContent = 'Successfully restored!';
71-
localImportTextarea.value = '';
81+
importSubmitButton.classList.add('success');
82+
importSubmitButton.textContent = 'Successfully restored!';
83+
importValueTextarea.value = '';
7284
document.getElementById('configuration-tab').classList.add('outdated');
7385
} catch (exception) {
74-
localRestoreButton.classList.add('failure');
75-
localRestoreButton.textContent =
76-
exception instanceof SyntaxError ? 'Failed to parse backup contents!' : 'Failed to restore!';
86+
importSubmitButton.classList.add('failure');
87+
importSubmitButton.textContent = exception instanceof SyntaxError
88+
? 'Failed to parse backup contents!'
89+
: 'Failed to restore!';
7790
console.error(exception);
7891
} finally {
7992
await sleep(3000);
80-
localRestoreButton.disabled = false;
81-
localRestoreButton.classList.remove('success', 'failure');
82-
localRestoreButton.textContent = '';
83-
delete localOverwriteWarning.dataset.forceHidden;
93+
importSubmitButton.disabled = false;
94+
importSubmitButton.classList.remove('success', 'failure');
95+
importSubmitButton.textContent = 'Restore';
96+
delete importWarningElement.dataset.forceHidden;
8497
}
85-
};
98+
}
8699

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

91-
localCopyButton.addEventListener('click', localCopy);
92-
localDownloadButton.addEventListener('click', localExport);
93-
94-
localRestoreButton.addEventListener('click', localRestore);
104+
exportForm.addEventListener('submit', onExportSubmit);
105+
importForm.addEventListener('submit', onImportSubmit);
95106
};
96107

97108
renderLocalBackup();
98-
99-
document.querySelectorAll('#backup-panel details').forEach(details => details.addEventListener('toggle', ({ currentTarget }) => {
100-
if (currentTarget.open) {
101-
[...currentTarget.parentNode.children]
102-
.filter(element => element !== currentTarget)
103-
.forEach(sibling => { sibling.open = false; });
104-
}
105-
}));

0 commit comments

Comments
 (0)