selfstore 1.8.3 → 1.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/advanced.js +1 -1
- package/dist/index.js +1 -1
- package/dist/widgets.d.ts +87 -5
- package/dist/widgets.js +274 -11
- package/dist/widgets.js.map +1 -1
- package/llms.txt +13 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -162,7 +162,7 @@ too, as subpaths of the same install:
|
|
|
162
162
|
|---|---|---|
|
|
163
163
|
| `selfstore` | the simple store + backup files | almost always: start here |
|
|
164
164
|
| `selfstore/flows` | the user journeys (connect a destination, share panel, join an invitation) as headless, tested state machines | you are building the connect/share/join UI and want the ordering and failure rules - one popup per gesture, password proven before attach, merge by default - already right |
|
|
165
|
-
| `selfstore/widgets` | the same journeys as drop-in web components (`<selfstore-connect>`, `<selfstore-gate>`, `<selfstore-share>`, `<selfstore-join>`) - framework-free, themable via CSS custom properties and `::part()`, reworded/localized via a `labels` map | you want the screens ready-made and your app's look on top |
|
|
165
|
+
| `selfstore/widgets` | the same journeys as drop-in web components (`<selfstore-connect>`, `<selfstore-gate>`, `<selfstore-destination>`, `<selfstore-share>`, `<selfstore-join>`) - framework-free, themable via CSS custom properties and `::part()`, reworded/localized via a `labels` map | you want the screens ready-made and your app's look on top |
|
|
166
166
|
| `selfstore/advanced` | the pull-model store (`createLocalStore`), custom `BackupTarget`s, caches, headless status derivation, functional codec | your state lives in its own reactive model (Svelte runes, Redux), or you are writing a destination (S3, your KV...) |
|
|
167
167
|
| `selfstore/groups` | passwordless group encryption: per-member identities, sealed envelopes, signed membership manifests | several PEOPLE share one encrypted store without a shared password ([PEERS.md](./PEERS.md)) |
|
|
168
168
|
| `selfstore/sync` | the bare merge engine (HLC + per-collection strategies, no CRDT runtime) | you only want the algorithm inside your own persistence ([SYNC docs in SPEC.md](./SPEC.md)) |
|
package/dist/advanced.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache } from './chunk-ELDSPUTS.js';
|
|
2
|
-
export { datedName, pickFromDisk, saveToDisk } from './chunk-DCEXWSM6.js';
|
|
3
2
|
import './chunk-SKWHKM25.js';
|
|
3
|
+
export { datedName, pickFromDisk, saveToDisk } from './chunk-DCEXWSM6.js';
|
|
4
4
|
import { openExisting, isOpenSupported, isSupported, fromSession as fromSession$1, connect as connect$1, fromSession3, connect3, peer, fromSession2, connect2 } from './chunk-TKWU52LK.js';
|
|
5
5
|
import { FILE_ID_KEY, account, renameBackup, deleteBackup, createBackup, listBackups, findOrCreateOwnFile, adopt, preview, fromSession, connect } from './chunk-J5MFXJU6.js';
|
|
6
6
|
export { RESERVED_COLLECTION_PREFIX, checkPasswordPolicy, exportSnapshot, importSnapshot, inspect, isEncrypted } from './chunk-MGDXR3RB.js';
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { indexedDbCache, memoryCache, isLockableCache, createLocalStore } from './chunk-ELDSPUTS.js';
|
|
2
2
|
export { gisDriveAuth } from './chunk-ELDSPUTS.js';
|
|
3
|
+
import './chunk-SKWHKM25.js';
|
|
3
4
|
import { saveToDisk } from './chunk-DCEXWSM6.js';
|
|
4
5
|
export { pickFromDisk, saveToDisk } from './chunk-DCEXWSM6.js';
|
|
5
|
-
import './chunk-SKWHKM25.js';
|
|
6
6
|
import { fromSession as fromSession$1, fromSession2, isSupported, connect3, connect2, connect } from './chunk-TKWU52LK.js';
|
|
7
7
|
export { hasDesktopFiles, useDesktopFiles } from './chunk-TKWU52LK.js';
|
|
8
8
|
import { fromSession, connect as connect$1 } from './chunk-J5MFXJU6.js';
|
package/dist/widgets.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as StoreLike, C as ConnectTargets, a as ConnectFlowOptions, b as ConnectKind, c as ConnectFlow, d as ShareEngine, e as ShareLevel, f as ShareFlow, J as JoinEngine, g as JoinFlow, R as ReplicaFlow } from './flows-1EntUZ91.js';
|
|
1
|
+
import { F as FlowHost, S as StoreLike, C as ConnectTargets, a as ConnectFlowOptions, b as ConnectKind, c as ConnectFlow, d as ShareEngine, e as ShareLevel, f as ShareFlow, J as JoinEngine, g as JoinFlow, R as ReplicaFlow } from './flows-1EntUZ91.js';
|
|
2
2
|
import { f as BackupsManager } from './manager-DZ1c_aSS.js';
|
|
3
3
|
import './store-BiQ-BuVl.js';
|
|
4
4
|
import './cache-ilsK3Tki.js';
|
|
@@ -22,6 +22,10 @@ declare abstract class FlowWidget extends HTMLElement {
|
|
|
22
22
|
protected packs(): Record<string, WidgetLabels>;
|
|
23
23
|
/** Build the current view into the given container. */
|
|
24
24
|
protected abstract view(into: HTMLElement): void;
|
|
25
|
+
/** Follow an engine: rerender on every notification, replacing any previous
|
|
26
|
+
* subscription. A no-op before connection and on a null host, so a host may
|
|
27
|
+
* assign the store from a reactive effect that runs more than once. */
|
|
28
|
+
protected follow(host: FlowHost | null): void;
|
|
25
29
|
constructor();
|
|
26
30
|
/** Partial override map, merged over the EN defaults (also the i18n hook). */
|
|
27
31
|
get labels(): WidgetLabels;
|
|
@@ -523,10 +527,88 @@ declare class SelfstoreGateElement extends FlowWidget {
|
|
|
523
527
|
protected view(into: HTMLElement): void;
|
|
524
528
|
}
|
|
525
529
|
|
|
530
|
+
/**
|
|
531
|
+
* <selfstore-destination>: where the data is kept, once it has a home.
|
|
532
|
+
*
|
|
533
|
+
* The counterpart of <selfstore-gate>. The gate asks the question before there
|
|
534
|
+
* is an answer; this panel states the answer and carries the gestures that act
|
|
535
|
+
* on it - export a copy, change destination, stop saving here.
|
|
536
|
+
*
|
|
537
|
+
* const el = document.querySelector('selfstore-destination');
|
|
538
|
+
* el.store = store;
|
|
539
|
+
* el.targets = { drive: driveAuth, file: true }; // enables "change"
|
|
540
|
+
*
|
|
541
|
+
* It DOES the work rather than asking the host to. Exporting a copy, detaching
|
|
542
|
+
* and re-connecting are all the engine's own operations, so a panel that only
|
|
543
|
+
* emitted intentions would leave the same two hundred lines in every app - the
|
|
544
|
+
* very lines this widget exists to delete.
|
|
545
|
+
*
|
|
546
|
+
* The one exception is structural, not a choice: **loading a copy back cannot
|
|
547
|
+
* be done from here.** The library holds no records; the app's `apply` does.
|
|
548
|
+
* The engine exposes `exportBlob()` and no import, so the panel asks for that
|
|
549
|
+
* one through `selfstore-destination-action` and the host runs its own import.
|
|
550
|
+
*
|
|
551
|
+
* Two things stay the host's, deliberately: the WORDS (every label is
|
|
552
|
+
* overridable, and a pack ships per language) and the VETO on what is
|
|
553
|
+
* destructive (`confirmAction`, which defaults to proceeding). Neither is
|
|
554
|
+
* customisation - they are the decisions a library must not take for an app.
|
|
555
|
+
*/
|
|
556
|
+
|
|
557
|
+
/** What the panel is about to do, for the host's veto. */
|
|
558
|
+
interface DestinationAction {
|
|
559
|
+
type: 'detach';
|
|
560
|
+
/** The destination's own name, when it has one. */
|
|
561
|
+
label: string | null;
|
|
562
|
+
}
|
|
563
|
+
declare class SelfstoreDestinationElement extends FlowWidget {
|
|
564
|
+
#private;
|
|
565
|
+
protected defaults(): WidgetLabels;
|
|
566
|
+
protected packs(): Record<string, WidgetLabels>;
|
|
567
|
+
constructor();
|
|
568
|
+
/** The simple store (anything exposing `flowHost`), or a hand-built FlowHost. */
|
|
569
|
+
get store(): StoreLike | null;
|
|
570
|
+
set store(v: StoreLike | null);
|
|
571
|
+
/** The destinations offered when the user changes their mind. Given them, the
|
|
572
|
+
* panel runs the connect journey in place; without them the button is absent
|
|
573
|
+
* rather than dead - a host that cannot offer a choice should not show one. */
|
|
574
|
+
get targets(): ConnectTargets | null;
|
|
575
|
+
set targets(v: ConnectTargets | null);
|
|
576
|
+
/** Who holds the destination ("someone@example.com"), when the host knows.
|
|
577
|
+
* For Drive that is `driveTarget.account()`: a brand name is not an address,
|
|
578
|
+
* and several accounts look alike. */
|
|
579
|
+
get account(): string | null;
|
|
580
|
+
set account(v: string | null);
|
|
581
|
+
/** Optional glyph per destination kind, same contract as the connect cards. */
|
|
582
|
+
get icons(): Partial<Record<ConnectKind, string>>;
|
|
583
|
+
set icons(v: Partial<Record<ConnectKind, string>> | null);
|
|
584
|
+
/** Veto on what is destructive. Returns false (or throws) to stop; absent
|
|
585
|
+
* means proceed. The panel never invents its own confirm dialog: an app has
|
|
586
|
+
* its own words and its own modal. */
|
|
587
|
+
get confirmAction(): ((a: DestinationAction) => boolean | Promise<boolean>) | null;
|
|
588
|
+
set confirmAction(fn: ((a: DestinationAction) => boolean | Promise<boolean>) | null);
|
|
589
|
+
connectedCallback(): void;
|
|
590
|
+
private host;
|
|
591
|
+
private wire;
|
|
592
|
+
/** Run a gesture, keeping the panel honest while it is in flight. */
|
|
593
|
+
private run;
|
|
594
|
+
/** A portable copy, on the user's own terms. A closed save dialog wrote
|
|
595
|
+
* nothing, so it must not clear the pending-export nudge nor claim success -
|
|
596
|
+
* that is the download that lies about itself. */
|
|
597
|
+
private exportCopy;
|
|
598
|
+
/** Stop saving to this destination. The local data stays, and so does the
|
|
599
|
+
* backup already written: this detaches, it never deletes. */
|
|
600
|
+
private detach;
|
|
601
|
+
/** The connect journey, in place. Built like the gate builds it, so the
|
|
602
|
+
* destinations, the password step and the cancel path are the library's
|
|
603
|
+
* tested ones rather than a second implementation. */
|
|
604
|
+
private connectView;
|
|
605
|
+
protected view(into: HTMLElement): void;
|
|
606
|
+
}
|
|
607
|
+
|
|
526
608
|
/** Register the elements as <PREFIX-connect>, <PREFIX-share>, <PREFIX-join>,
|
|
527
|
-
* <PREFIX-status>, <PREFIX-backups
|
|
528
|
-
* 'selfstore'). Safe to call twice;
|
|
529
|
-
* elements (browser code only). */
|
|
609
|
+
* <PREFIX-status>, <PREFIX-backups>, <PREFIX-gate> and
|
|
610
|
+
* <PREFIX-destination> (default prefix 'selfstore'). Safe to call twice;
|
|
611
|
+
* throws in environments without custom elements (browser code only). */
|
|
530
612
|
declare function defineSelfstoreWidgets(prefix?: string): void;
|
|
531
613
|
|
|
532
|
-
export { type BackupsAction, FlowWidget, SelfstoreBackupsElement, SelfstoreConnectElement, SelfstoreGateElement, SelfstoreJoinElement, SelfstoreShareElement, SelfstoreStatusElement, type ShareAction, type WebdavPreset, type WidgetLabels, defineSelfstoreWidgets };
|
|
614
|
+
export { type BackupsAction, type DestinationAction, FlowWidget, SelfstoreBackupsElement, SelfstoreConnectElement, SelfstoreDestinationElement, SelfstoreGateElement, SelfstoreJoinElement, SelfstoreShareElement, SelfstoreStatusElement, type ShareAction, type WebdavPreset, type WidgetLabels, defineSelfstoreWidgets };
|
package/dist/widgets.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { saveToDisk, datedName } from './chunk-DCEXWSM6.js';
|
|
1
2
|
import { connectFlow, shareFlow, joinFlow } from './chunk-PNX7AJYO.js';
|
|
2
3
|
import { isOpenSupported } from './chunk-TKWU52LK.js';
|
|
3
4
|
import './chunk-W22V5CRM.js';
|
|
@@ -26,6 +27,10 @@ var defaultIcons = {
|
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
// src/widgets/base.ts
|
|
30
|
+
function hostOf(store) {
|
|
31
|
+
if (!store) return null;
|
|
32
|
+
return "flowHost" in store ? store.flowHost : store;
|
|
33
|
+
}
|
|
29
34
|
var baseStyles = `
|
|
30
35
|
:host {
|
|
31
36
|
display: block;
|
|
@@ -376,6 +381,16 @@ var FlowWidget = class extends HTMLElement {
|
|
|
376
381
|
packs() {
|
|
377
382
|
return {};
|
|
378
383
|
}
|
|
384
|
+
/** Follow an engine: rerender on every notification, replacing any previous
|
|
385
|
+
* subscription. A no-op before connection and on a null host, so a host may
|
|
386
|
+
* assign the store from a reactive effect that runs more than once. */
|
|
387
|
+
follow(host) {
|
|
388
|
+
this.unsub?.();
|
|
389
|
+
this.unsub = null;
|
|
390
|
+
if (!this.isConnected || !host) return;
|
|
391
|
+
this.unsub = host.engine.subscribe(() => this.rerender());
|
|
392
|
+
this.rerender();
|
|
393
|
+
}
|
|
379
394
|
constructor() {
|
|
380
395
|
super();
|
|
381
396
|
this.root = this.attachShadow({ mode: "open" });
|
|
@@ -1949,18 +1964,10 @@ var SelfstoreStatusElement = class extends FlowWidget {
|
|
|
1949
1964
|
this.wire();
|
|
1950
1965
|
}
|
|
1951
1966
|
host() {
|
|
1952
|
-
|
|
1953
|
-
if (!s) return null;
|
|
1954
|
-
return "flowHost" in s ? s.flowHost : s;
|
|
1967
|
+
return hostOf(this.#store);
|
|
1955
1968
|
}
|
|
1956
1969
|
wire() {
|
|
1957
|
-
|
|
1958
|
-
this.unsub?.();
|
|
1959
|
-
this.unsub = null;
|
|
1960
|
-
const host = this.host();
|
|
1961
|
-
if (!host) return;
|
|
1962
|
-
this.unsub = host.engine.subscribe(() => this.rerender());
|
|
1963
|
-
this.rerender();
|
|
1970
|
+
this.follow(this.host());
|
|
1964
1971
|
}
|
|
1965
1972
|
view(into) {
|
|
1966
1973
|
const host = this.host();
|
|
@@ -3512,6 +3519,261 @@ var SelfstoreGateElement = class extends FlowWidget {
|
|
|
3512
3519
|
}
|
|
3513
3520
|
};
|
|
3514
3521
|
|
|
3522
|
+
// src/widgets/destination.ts
|
|
3523
|
+
var EN7 = {
|
|
3524
|
+
"destination.heading": "Where your data is kept",
|
|
3525
|
+
"destination.kind.drive": "Google Drive",
|
|
3526
|
+
"destination.kind.file": "A file on this device",
|
|
3527
|
+
"destination.kind.webdav": "Your own server",
|
|
3528
|
+
"destination.kind.s3": "Your own bucket",
|
|
3529
|
+
"destination.kind.device": "This device only",
|
|
3530
|
+
"destination.encrypted": "Password-protected",
|
|
3531
|
+
"destination.export": "Export a copy",
|
|
3532
|
+
"destination.exported": "The copy is written.",
|
|
3533
|
+
"destination.exportCancelled": "Nothing was written.",
|
|
3534
|
+
"destination.restore": "Load a copy",
|
|
3535
|
+
"destination.change": "Change destination",
|
|
3536
|
+
"destination.detach": "Stop saving here",
|
|
3537
|
+
"destination.detached": "Stopped. Your data stays on this device.",
|
|
3538
|
+
"destination.back": "Back",
|
|
3539
|
+
"error.generic": "Something went wrong."
|
|
3540
|
+
};
|
|
3541
|
+
var FR7 = {
|
|
3542
|
+
"destination.heading": "O\xF9 vos donn\xE9es sont enregistr\xE9es",
|
|
3543
|
+
"destination.kind.file": "Un fichier sur cet appareil",
|
|
3544
|
+
"destination.kind.webdav": "Votre serveur",
|
|
3545
|
+
"destination.kind.s3": "Votre bucket",
|
|
3546
|
+
"destination.kind.device": "Cet appareil seulement",
|
|
3547
|
+
"destination.encrypted": "Prot\xE9g\xE9e par un mot de passe",
|
|
3548
|
+
"destination.export": "Exporter une copie",
|
|
3549
|
+
"destination.exported": "La copie est \xE9crite.",
|
|
3550
|
+
"destination.exportCancelled": "Rien n'a \xE9t\xE9 \xE9crit.",
|
|
3551
|
+
"destination.restore": "Charger une copie",
|
|
3552
|
+
"destination.change": "Changer de destination",
|
|
3553
|
+
"destination.detach": "Ne plus enregistrer ici",
|
|
3554
|
+
"destination.detached": "Arr\xEAt\xE9. Vos donn\xE9es restent sur cet appareil.",
|
|
3555
|
+
"destination.back": "Retour",
|
|
3556
|
+
"error.generic": "Quelque chose n'a pas fonctionn\xE9."
|
|
3557
|
+
};
|
|
3558
|
+
var DESTINATION_STYLES = `
|
|
3559
|
+
[part~='destination-card'] { align-items: flex-start; }
|
|
3560
|
+
[part~='destination-actions'] {
|
|
3561
|
+
display: flex;
|
|
3562
|
+
flex-wrap: wrap;
|
|
3563
|
+
gap: var(--_gap);
|
|
3564
|
+
}
|
|
3565
|
+
[part~='destination-heading'] {
|
|
3566
|
+
margin: 0;
|
|
3567
|
+
font-size: 1rem;
|
|
3568
|
+
font-weight: 600;
|
|
3569
|
+
}
|
|
3570
|
+
`;
|
|
3571
|
+
function siblingTag(own, name) {
|
|
3572
|
+
const suffix = "-destination";
|
|
3573
|
+
return own.endsWith(suffix) ? `${own.slice(0, -suffix.length)}-${name}` : `selfstore-${name}`;
|
|
3574
|
+
}
|
|
3575
|
+
var SelfstoreDestinationElement = class extends FlowWidget {
|
|
3576
|
+
#store = null;
|
|
3577
|
+
#targets = null;
|
|
3578
|
+
#account = null;
|
|
3579
|
+
#icons = {};
|
|
3580
|
+
#confirm = null;
|
|
3581
|
+
#changing = false;
|
|
3582
|
+
#busy = false;
|
|
3583
|
+
#message = null;
|
|
3584
|
+
defaults() {
|
|
3585
|
+
return EN7;
|
|
3586
|
+
}
|
|
3587
|
+
packs() {
|
|
3588
|
+
return { fr: FR7 };
|
|
3589
|
+
}
|
|
3590
|
+
constructor() {
|
|
3591
|
+
super();
|
|
3592
|
+
this.root.append(h("style", {}, DESTINATION_STYLES));
|
|
3593
|
+
}
|
|
3594
|
+
/** The simple store (anything exposing `flowHost`), or a hand-built FlowHost. */
|
|
3595
|
+
get store() {
|
|
3596
|
+
return this.#store;
|
|
3597
|
+
}
|
|
3598
|
+
set store(v) {
|
|
3599
|
+
if (v === this.#store) return;
|
|
3600
|
+
this.#store = v;
|
|
3601
|
+
this.wire();
|
|
3602
|
+
}
|
|
3603
|
+
/** The destinations offered when the user changes their mind. Given them, the
|
|
3604
|
+
* panel runs the connect journey in place; without them the button is absent
|
|
3605
|
+
* rather than dead - a host that cannot offer a choice should not show one. */
|
|
3606
|
+
get targets() {
|
|
3607
|
+
return this.#targets;
|
|
3608
|
+
}
|
|
3609
|
+
set targets(v) {
|
|
3610
|
+
this.#targets = v;
|
|
3611
|
+
this.rerender();
|
|
3612
|
+
}
|
|
3613
|
+
/** Who holds the destination ("someone@example.com"), when the host knows.
|
|
3614
|
+
* For Drive that is `driveTarget.account()`: a brand name is not an address,
|
|
3615
|
+
* and several accounts look alike. */
|
|
3616
|
+
get account() {
|
|
3617
|
+
return this.#account;
|
|
3618
|
+
}
|
|
3619
|
+
set account(v) {
|
|
3620
|
+
this.#account = v || null;
|
|
3621
|
+
this.rerender();
|
|
3622
|
+
}
|
|
3623
|
+
/** Optional glyph per destination kind, same contract as the connect cards. */
|
|
3624
|
+
get icons() {
|
|
3625
|
+
return this.#icons;
|
|
3626
|
+
}
|
|
3627
|
+
set icons(v) {
|
|
3628
|
+
this.#icons = v ?? {};
|
|
3629
|
+
this.rerender();
|
|
3630
|
+
}
|
|
3631
|
+
/** Veto on what is destructive. Returns false (or throws) to stop; absent
|
|
3632
|
+
* means proceed. The panel never invents its own confirm dialog: an app has
|
|
3633
|
+
* its own words and its own modal. */
|
|
3634
|
+
get confirmAction() {
|
|
3635
|
+
return this.#confirm;
|
|
3636
|
+
}
|
|
3637
|
+
set confirmAction(fn) {
|
|
3638
|
+
this.#confirm = fn;
|
|
3639
|
+
}
|
|
3640
|
+
connectedCallback() {
|
|
3641
|
+
super.connectedCallback();
|
|
3642
|
+
this.wire();
|
|
3643
|
+
}
|
|
3644
|
+
host() {
|
|
3645
|
+
return hostOf(this.#store);
|
|
3646
|
+
}
|
|
3647
|
+
wire() {
|
|
3648
|
+
this.follow(this.host());
|
|
3649
|
+
}
|
|
3650
|
+
/** Run a gesture, keeping the panel honest while it is in flight. */
|
|
3651
|
+
async run(job) {
|
|
3652
|
+
this.#busy = true;
|
|
3653
|
+
this.#message = null;
|
|
3654
|
+
this.rerender();
|
|
3655
|
+
try {
|
|
3656
|
+
this.#message = await job();
|
|
3657
|
+
} catch {
|
|
3658
|
+
this.#message = this.t("error.generic");
|
|
3659
|
+
} finally {
|
|
3660
|
+
this.#busy = false;
|
|
3661
|
+
this.rerender();
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
/** A portable copy, on the user's own terms. A closed save dialog wrote
|
|
3665
|
+
* nothing, so it must not clear the pending-export nudge nor claim success -
|
|
3666
|
+
* that is the download that lies about itself. */
|
|
3667
|
+
exportCopy() {
|
|
3668
|
+
const host = this.host();
|
|
3669
|
+
if (!host) return Promise.resolve();
|
|
3670
|
+
return this.run(async () => {
|
|
3671
|
+
const blob = await host.engine.exportBlob();
|
|
3672
|
+
const written = await saveToDisk(blob, datedName(host.backupName));
|
|
3673
|
+
if (!written) return this.t("destination.exportCancelled");
|
|
3674
|
+
host.engine.markDownloaded();
|
|
3675
|
+
return this.t("destination.exported");
|
|
3676
|
+
});
|
|
3677
|
+
}
|
|
3678
|
+
/** Stop saving to this destination. The local data stays, and so does the
|
|
3679
|
+
* backup already written: this detaches, it never deletes. */
|
|
3680
|
+
detach(label) {
|
|
3681
|
+
const host = this.host();
|
|
3682
|
+
if (!host) return Promise.resolve();
|
|
3683
|
+
return this.run(async () => {
|
|
3684
|
+
try {
|
|
3685
|
+
if (this.#confirm && !await this.#confirm({ type: "detach", label })) return null;
|
|
3686
|
+
} catch {
|
|
3687
|
+
return null;
|
|
3688
|
+
}
|
|
3689
|
+
await host.engine.detachTarget();
|
|
3690
|
+
this.emit("selfstore-destination-detached");
|
|
3691
|
+
return this.t("destination.detached");
|
|
3692
|
+
});
|
|
3693
|
+
}
|
|
3694
|
+
/** The connect journey, in place. Built like the gate builds it, so the
|
|
3695
|
+
* destinations, the password step and the cancel path are the library's
|
|
3696
|
+
* tested ones rather than a second implementation. */
|
|
3697
|
+
connectView(into) {
|
|
3698
|
+
const el = document.createElement(
|
|
3699
|
+
siblingTag(this.localName, "connect")
|
|
3700
|
+
);
|
|
3701
|
+
el.store = this.#store;
|
|
3702
|
+
el.targets = this.#targets;
|
|
3703
|
+
el.icons = this.#icons;
|
|
3704
|
+
el.labels = this.labels;
|
|
3705
|
+
const done = () => {
|
|
3706
|
+
this.#changing = false;
|
|
3707
|
+
this.rerender();
|
|
3708
|
+
};
|
|
3709
|
+
el.addEventListener("selfstore-connected", done);
|
|
3710
|
+
el.addEventListener("selfstore-cancelled", done);
|
|
3711
|
+
put(
|
|
3712
|
+
into,
|
|
3713
|
+
h(
|
|
3714
|
+
"button",
|
|
3715
|
+
{ part: "link destination-back", type: "button", onclick: done },
|
|
3716
|
+
this.t("destination.back")
|
|
3717
|
+
),
|
|
3718
|
+
el
|
|
3719
|
+
);
|
|
3720
|
+
}
|
|
3721
|
+
view(into) {
|
|
3722
|
+
const host = this.host();
|
|
3723
|
+
if (!host) return;
|
|
3724
|
+
if (this.#changing && this.#targets) return this.connectView(into);
|
|
3725
|
+
const { targetKind, label, encrypted } = host.engine.state;
|
|
3726
|
+
const kind = this.t(`destination.kind.${targetKind}`);
|
|
3727
|
+
const title = kind === `destination.kind.${targetKind}` ? label ?? targetKind : kind;
|
|
3728
|
+
const sub = [this.#account, label && label !== title ? label : null].filter(Boolean).join(" \xB7 ");
|
|
3729
|
+
const icon = this.#icons[targetKind];
|
|
3730
|
+
const status = document.createElement(siblingTag(this.localName, "status"));
|
|
3731
|
+
status.setAttribute("variant", "row");
|
|
3732
|
+
status.labels = this.labels;
|
|
3733
|
+
status.icons = this.#icons;
|
|
3734
|
+
status.store = this.#store;
|
|
3735
|
+
const act = (key, onclick) => h(
|
|
3736
|
+
"button",
|
|
3737
|
+
{ part: "button", type: "button", disabled: this.#busy ? "" : null, onclick },
|
|
3738
|
+
this.t(key)
|
|
3739
|
+
);
|
|
3740
|
+
put(
|
|
3741
|
+
into,
|
|
3742
|
+
this.heading("destination-heading", "destination.heading"),
|
|
3743
|
+
h(
|
|
3744
|
+
"div",
|
|
3745
|
+
{ part: "card destination-card" },
|
|
3746
|
+
icon ? h("img", { part: "icon", src: icon, alt: "" }) : null,
|
|
3747
|
+
h(
|
|
3748
|
+
"div",
|
|
3749
|
+
{},
|
|
3750
|
+
h("div", { part: "title" }, title),
|
|
3751
|
+
sub ? h("div", { part: "sub" }, sub) : null,
|
|
3752
|
+
encrypted ? h("div", { part: "sub" }, this.t("destination.encrypted")) : null
|
|
3753
|
+
)
|
|
3754
|
+
),
|
|
3755
|
+
status,
|
|
3756
|
+
h(
|
|
3757
|
+
"div",
|
|
3758
|
+
{ part: "destination-actions" },
|
|
3759
|
+
act("destination.export", () => void this.exportCopy()),
|
|
3760
|
+
// The library holds no records, so loading a copy back is the host's
|
|
3761
|
+
// own gesture - see the note at the top of this file.
|
|
3762
|
+
act(
|
|
3763
|
+
"destination.restore",
|
|
3764
|
+
() => this.emit("selfstore-destination-action", { action: "restore" })
|
|
3765
|
+
),
|
|
3766
|
+
this.#targets ? act("destination.change", () => {
|
|
3767
|
+
this.#changing = true;
|
|
3768
|
+
this.rerender();
|
|
3769
|
+
}) : null,
|
|
3770
|
+
act("destination.detach", () => void this.detach(label))
|
|
3771
|
+
),
|
|
3772
|
+
this.#message ? h("div", { part: "sub destination-message", role: "status" }, this.#message) : null
|
|
3773
|
+
);
|
|
3774
|
+
}
|
|
3775
|
+
};
|
|
3776
|
+
|
|
3515
3777
|
// src/entries/widgets.ts
|
|
3516
3778
|
function defineSelfstoreWidgets(prefix = "selfstore") {
|
|
3517
3779
|
const define = (name, ctor) => {
|
|
@@ -3523,8 +3785,9 @@ function defineSelfstoreWidgets(prefix = "selfstore") {
|
|
|
3523
3785
|
define(`${prefix}-status`, SelfstoreStatusElement);
|
|
3524
3786
|
define(`${prefix}-backups`, SelfstoreBackupsElement);
|
|
3525
3787
|
define(`${prefix}-gate`, SelfstoreGateElement);
|
|
3788
|
+
define(`${prefix}-destination`, SelfstoreDestinationElement);
|
|
3526
3789
|
}
|
|
3527
3790
|
|
|
3528
|
-
export { FlowWidget, SelfstoreBackupsElement, SelfstoreConnectElement, SelfstoreGateElement, SelfstoreJoinElement, SelfstoreShareElement, SelfstoreStatusElement, defineSelfstoreWidgets };
|
|
3791
|
+
export { FlowWidget, SelfstoreBackupsElement, SelfstoreConnectElement, SelfstoreDestinationElement, SelfstoreGateElement, SelfstoreJoinElement, SelfstoreShareElement, SelfstoreStatusElement, defineSelfstoreWidgets };
|
|
3529
3792
|
//# sourceMappingURL=widgets.js.map
|
|
3530
3793
|
//# sourceMappingURL=widgets.js.map
|