1
1
import { append , clone , collect , h } from 'stage1' ;
2
2
import { compile } from 'stage1/macro' with { type : 'macro' } ;
3
3
import { reconcile } from 'stage1/reconcile/non-keyed' ;
4
- import type { SectionOrderItem , ThemesData } from './types' ;
4
+ import type {
5
+ SectionOrderItem ,
6
+ SyncStorageData ,
7
+ ThemesData ,
8
+ UserStorageData ,
9
+ } from './types' ;
5
10
import { DEFAULT_SECTION_ORDER , storage } from './utils' ;
6
11
7
12
// TODO: Show errors in the UI.
@@ -10,6 +15,7 @@ import { DEFAULT_SECTION_ORDER, storage } from './utils';
10
15
11
16
interface SettingsState {
12
17
order : [ SectionOrderItem [ ] , SectionOrderItem [ ] ] ;
18
+ pushSyncData ?( forceUpdate ?: boolean ) : Promise < void > ;
13
19
}
14
20
15
21
type ItemIndex = [ listIndex : 0 | 1 , itemIndex : number ] ;
@@ -28,6 +34,19 @@ const themesData = fetch('themes.json').then(
28
34
( response ) => response . json ( ) as Promise < ThemesData > ,
29
35
) ;
30
36
37
+ const supportsSync = async ( ) : Promise < boolean > => {
38
+ try {
39
+ await chrome . storage . sync . set ( { _ : 1 } ) ;
40
+ await chrome . storage . sync . remove ( '_' ) ;
41
+ if ( chrome . runtime . lastError ) {
42
+ return false ;
43
+ }
44
+ return true ;
45
+ } catch {
46
+ return false ;
47
+ }
48
+ } ;
49
+
31
50
type SectionComponent = HTMLLIElement ;
32
51
33
52
interface SectionRefs {
@@ -102,17 +121,23 @@ const SectionItem = (
102
121
} ;
103
122
104
123
interface Refs {
105
- feedback : HTMLDivElement ;
124
+ feedback : Text ;
106
125
theme : HTMLSelectElement ;
107
126
b : HTMLInputElement ;
108
127
se : HTMLUListElement ;
109
128
sd : HTMLUListElement ;
110
129
reset : HTMLButtonElement ;
130
+
131
+ feedback2 : Text ;
132
+ sync : HTMLInputElement ;
133
+ pull : HTMLButtonElement ;
134
+ push : HTMLButtonElement ;
135
+ clear : HTMLButtonElement ;
111
136
}
112
137
113
138
const meta = compile ( `
114
139
<div>
115
- <div @feedback> </div>
140
+ <div> @feedback</div>
116
141
117
142
<div class=row>
118
143
<label>Theme</label>
@@ -151,6 +176,29 @@ const meta = compile(`
151
176
<label>Reset</label>
152
177
<button @reset>Reset all settings</button>
153
178
</div>
179
+
180
+ <hr>
181
+
182
+ <h2>Experimental</h2>
183
+
184
+ <h3>Sync Settings</h3>
185
+
186
+ <div class=row>
187
+ @feedback2
188
+ </div>
189
+
190
+ <div class=row>
191
+ <label>
192
+ <input @sync type=checkbox class=box disabled> Automatically sync settings
193
+ </label>
194
+ <small class=muted>Sync on profile startup (requires sign-in)</small>
195
+ </div>
196
+
197
+ <div class=row>
198
+ <button @pull disabled>Pull now (local ⟸ sync)</button>
199
+ <button @push disabled>Push now (local ⟹ sync)</button>
200
+ <button @clear disabled>Reset sync data</button>
201
+ </div>
154
202
</div>
155
203
` ) ;
156
204
const view = h < HTMLDivElement > ( meta . html ) ;
@@ -192,8 +240,10 @@ const Settings = () => {
192
240
} ) ;
193
241
194
242
if ( themeName === DEFAULT_THEME ) {
195
- void chrome . storage . local . remove ( 'tn' ) ;
243
+ await chrome . storage . local . remove ( 'tn' ) ;
196
244
}
245
+
246
+ void state . pushSyncData ?.( ) ;
197
247
} ;
198
248
199
249
const updateOrder = ( order : SettingsState [ 'order' ] , skipSave ?: boolean ) => {
@@ -214,6 +264,8 @@ const Settings = () => {
214
264
o : order [ 0 ] ,
215
265
} ) ;
216
266
}
267
+
268
+ void state . pushSyncData ?.( ) ;
217
269
}
218
270
} ;
219
271
@@ -234,15 +286,18 @@ const Settings = () => {
234
286
235
287
refs . theme . onchange = ( ) => updateTheme ( refs . theme . value ) ;
236
288
237
- refs . b . onchange = ( ) => {
289
+ refs . b . onchange = async ( ) => {
290
+ // eslint-disable-next-line unicorn/prefer-ternary
238
291
if ( refs . b . checked ) {
239
292
// When value is same as default, we don't need to store it
240
- void chrome . storage . local . remove ( 'b' ) ;
293
+ await chrome . storage . local . remove ( 'b' ) ;
241
294
} else {
242
- void chrome . storage . local . set ( {
295
+ await chrome . storage . local . set ( {
243
296
b : true ,
244
297
} ) ;
245
298
}
299
+
300
+ void state . pushSyncData ?.( ) ;
246
301
} ;
247
302
248
303
// eslint-disable-next-line no-multi-assign
@@ -266,10 +321,84 @@ const Settings = () => {
266
321
( item ) => ! orderEnabled . includes ( item ) ,
267
322
) ;
268
323
269
- void updateTheme ( themeName ) ;
324
+ refs . theme . value = themeName ;
270
325
refs . b . checked = ! storage . b ;
271
326
updateOrder ( [ orderEnabled , orderDisabled ] , true ) ;
272
327
328
+ /* ********************************** */
329
+ // Experimental sync settings feature //
330
+ /* ********************************** */
331
+
332
+ refs . sync . checked = ! ! storage . s ;
333
+
334
+ const updateSync = ( syncData : SyncStorageData ) => {
335
+ if ( syncData . ts ) {
336
+ refs . feedback2 . nodeValue = `Sync data found (last updated: ${ new Date (
337
+ syncData . ts ,
338
+ ) . toLocaleString ( ) } )`;
339
+ refs . pull . disabled = false ;
340
+ refs . clear . disabled = false ;
341
+ } else {
342
+ refs . feedback2 . nodeValue = 'No sync data found' ;
343
+ refs . pull . disabled = true ;
344
+ refs . clear . disabled = true ;
345
+ }
346
+
347
+ refs . push . disabled = false ;
348
+ refs . sync . disabled = false ;
349
+
350
+ refs . sync . onchange = ( ) => {
351
+ if ( refs . sync . checked ) {
352
+ void chrome . storage . local . set ( {
353
+ s : true ,
354
+ } ) ;
355
+ // @ts -expect-error - doesn't need event argument
356
+ refs . pull . onclick ?.( ) ;
357
+ } else {
358
+ void chrome . storage . local . remove ( 's' ) ;
359
+ }
360
+ } ;
361
+
362
+ refs . pull . onclick = ( ) => {
363
+ if ( syncData . data ) {
364
+ void chrome . storage . local . set ( syncData . data ) ;
365
+ void updateTheme ( syncData . data . tn ?? DEFAULT_THEME ) ;
366
+ updateOrder ( [ syncData . data . o ?? [ ...DEFAULT_SECTION_ORDER ] , [ ] ] , true ) ;
367
+ }
368
+ } ;
369
+
370
+ state . pushSyncData = async ( forceUpdate ?: boolean ) => {
371
+ const { t, s, ...rest } =
372
+ await chrome . storage . local . get < UserStorageData > ( ) ;
373
+
374
+ if ( forceUpdate || s ) {
375
+ const newSyncData : SyncStorageData = {
376
+ data : rest ,
377
+ ts : Date . now ( ) ,
378
+ } ;
379
+ void chrome . storage . sync . set ( newSyncData ) ;
380
+ updateSync ( newSyncData ) ;
381
+ }
382
+ } ;
383
+
384
+ refs . push . onclick = ( ) => state . pushSyncData ! ( true ) ;
385
+
386
+ refs . clear . onclick = ( ) => {
387
+ void chrome . storage . sync . clear ( ) ;
388
+ updateSync ( { } ) ;
389
+ } ;
390
+ } ;
391
+
392
+ void supportsSync ( ) . then ( ( canSync ) => {
393
+ if ( canSync ) {
394
+ void chrome . storage . sync . get < SyncStorageData > ( ) . then ( updateSync ) ;
395
+ // TODO: Listen for sync data changes?
396
+ // chrome.storage.sync.onChanged.addListener((changes) => {});
397
+ } else {
398
+ refs . feedback2 . nodeValue = 'Not signed in or sync not supported' ;
399
+ }
400
+ } ) ;
401
+
273
402
return root ;
274
403
} ;
275
404
0 commit comments