selfstore 1.8.11 → 1.8.12
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/dist/index.js +29 -6
- package/dist/index.js.map +1 -1
- package/dist/widgets.d.ts +28 -29
- package/dist/widgets.js +182 -148
- package/dist/widgets.js.map +1 -1
- package/package.json +1 -1
package/dist/widgets.js
CHANGED
|
@@ -130,7 +130,23 @@ img[part~='icon'] {
|
|
|
130
130
|
background: color-mix(in srgb, var(--_accent) 10%, transparent);
|
|
131
131
|
}
|
|
132
132
|
[part~='status-ok'] { background: color-mix(in srgb, var(--_ok) 12%, transparent); }
|
|
133
|
+
[part~='status-warn'] { background: color-mix(in srgb, var(--_warn) 12%, transparent); }
|
|
133
134
|
[part~='status-error'] { background: color-mix(in srgb, var(--_danger) 12%, transparent); }
|
|
135
|
+
/* The state, said in one glyph before the sentence is read: a saved backup is a
|
|
136
|
+
tick, anything asking for a gesture is a mark. It carries the severity color
|
|
137
|
+
the dot would have carried, and reads at a glance from across the page. */
|
|
138
|
+
span[part~='status-glyph'] {
|
|
139
|
+
display: inline-flex;
|
|
140
|
+
align-items: center;
|
|
141
|
+
justify-content: center;
|
|
142
|
+
width: 1.5em;
|
|
143
|
+
height: 1.5em;
|
|
144
|
+
border-radius: 50%;
|
|
145
|
+
flex-shrink: 0;
|
|
146
|
+
font-size: 0.85em;
|
|
147
|
+
font-weight: 700;
|
|
148
|
+
background: color-mix(in srgb, currentColor 15%, transparent);
|
|
149
|
+
}
|
|
134
150
|
[part='spinner'] {
|
|
135
151
|
width: 0.7em; height: 0.7em; flex-shrink: 0;
|
|
136
152
|
border-radius: 50%;
|
|
@@ -406,6 +422,42 @@ var FlowWidget = class extends HTMLElement {
|
|
|
406
422
|
packs() {
|
|
407
423
|
return {};
|
|
408
424
|
}
|
|
425
|
+
#store = null;
|
|
426
|
+
#icons = {};
|
|
427
|
+
/**
|
|
428
|
+
* The store this widget reads: the simple facade (anything exposing
|
|
429
|
+
* `flowHost`) or a hand-built FlowHost. Idempotent, so a host may assign it
|
|
430
|
+
* from a reactive effect that runs more than once.
|
|
431
|
+
*
|
|
432
|
+
* Every widget over a store had written this same pair, plus the host lookup
|
|
433
|
+
* and the glyph map beside it - the same fifteen lines in six files, each one
|
|
434
|
+
* a place where a fix could land in five.
|
|
435
|
+
*/
|
|
436
|
+
get store() {
|
|
437
|
+
return this.#store;
|
|
438
|
+
}
|
|
439
|
+
set store(v) {
|
|
440
|
+
if (v === this.#store) return;
|
|
441
|
+
this.#store = v;
|
|
442
|
+
this.storeChanged();
|
|
443
|
+
this.follow(this.hostOf());
|
|
444
|
+
}
|
|
445
|
+
/** Called before the widget follows a newly assigned store, for one that has
|
|
446
|
+
* its own view state to drop (a journey in progress belongs to the old one). */
|
|
447
|
+
storeChanged() {
|
|
448
|
+
}
|
|
449
|
+
/** The ports behind the current store, or null while it is unwired. */
|
|
450
|
+
hostOf() {
|
|
451
|
+
return hostOf(this.#store);
|
|
452
|
+
}
|
|
453
|
+
/** Optional glyph per destination kind, same contract in every widget. */
|
|
454
|
+
get icons() {
|
|
455
|
+
return this.#icons;
|
|
456
|
+
}
|
|
457
|
+
set icons(v) {
|
|
458
|
+
this.#icons = v ?? {};
|
|
459
|
+
this.rerender();
|
|
460
|
+
}
|
|
409
461
|
/** Follow an engine: rerender on every notification, replacing any previous
|
|
410
462
|
* subscription. A no-op before connection and on a null host, so a host may
|
|
411
463
|
* assign the store from a reactive effect that runs more than once. */
|
|
@@ -460,6 +512,21 @@ var FlowWidget = class extends HTMLElement {
|
|
|
460
512
|
if (!vars) return copy;
|
|
461
513
|
return copy.replace(/\{(\w+)\}/g, (whole, name) => vars[name] ?? whole);
|
|
462
514
|
}
|
|
515
|
+
/**
|
|
516
|
+
* "just now", "3 minutes ago", "yesterday" - in the widget's language, from
|
|
517
|
+
* Intl rather than from five label keys per language. The word for the first
|
|
518
|
+
* minute is passed in: it is the one part Intl says badly ("this minute"),
|
|
519
|
+
* and each widget words it in its own register.
|
|
520
|
+
*/
|
|
521
|
+
since(ts, justNow) {
|
|
522
|
+
const mins = Math.floor((Date.now() - ts) / 6e4);
|
|
523
|
+
if (mins < 1) return justNow;
|
|
524
|
+
const rtf = new Intl.RelativeTimeFormat(this.langTag(), { numeric: "auto" });
|
|
525
|
+
if (mins < 60) return rtf.format(-mins, "minute");
|
|
526
|
+
const hours = Math.floor(mins / 60);
|
|
527
|
+
if (hours < 24) return rtf.format(-hours, "hour");
|
|
528
|
+
return new Date(ts).toLocaleDateString(this.langTag());
|
|
529
|
+
}
|
|
463
530
|
/** A heading an empty-string label removes: labels = { 'share.title': '' }
|
|
464
531
|
* hides that heading (the host page already says it) without a fork.
|
|
465
532
|
* Append through put(), which skips the null. */
|
|
@@ -1993,8 +2060,25 @@ var SelfstoreJoinElement = class extends FlowWidget {
|
|
|
1993
2060
|
}
|
|
1994
2061
|
};
|
|
1995
2062
|
|
|
1996
|
-
// src/widgets/
|
|
2063
|
+
// src/widgets/kinds.ts
|
|
1997
2064
|
var EN4 = {
|
|
2065
|
+
"destination.kind.drive": "Google Drive",
|
|
2066
|
+
"destination.kind.file": "A file on this device",
|
|
2067
|
+
"destination.kind.webdav": "Your own server",
|
|
2068
|
+
"destination.kind.s3": "Your own bucket",
|
|
2069
|
+
"destination.kind.device": "This device only"
|
|
2070
|
+
};
|
|
2071
|
+
var FR4 = {
|
|
2072
|
+
"destination.kind.file": "Un fichier sur cet appareil",
|
|
2073
|
+
"destination.kind.webdav": "Votre serveur",
|
|
2074
|
+
"destination.kind.s3": "Votre bucket",
|
|
2075
|
+
"destination.kind.device": "Cet appareil seulement"
|
|
2076
|
+
};
|
|
2077
|
+
|
|
2078
|
+
// src/widgets/status.ts
|
|
2079
|
+
var EN5 = {
|
|
2080
|
+
...EN4,
|
|
2081
|
+
"status.justNow": "just now",
|
|
1998
2082
|
"status.ephemeral": "Nothing is saved",
|
|
1999
2083
|
"status.cacheOnly": "Never saved anywhere yet",
|
|
2000
2084
|
"status.saving": "Saving...",
|
|
@@ -2008,7 +2092,9 @@ var EN4 = {
|
|
|
2008
2092
|
"status.action.reconnect": "Reconnect",
|
|
2009
2093
|
"status.action.unlock": "Unlock"
|
|
2010
2094
|
};
|
|
2011
|
-
var
|
|
2095
|
+
var FR5 = {
|
|
2096
|
+
...FR4,
|
|
2097
|
+
"status.justNow": "a l'instant",
|
|
2012
2098
|
"status.ephemeral": "Rien n'est enregistr\xE9",
|
|
2013
2099
|
"status.cacheOnly": "Vos donn\xE9es n'ont jamais \xE9t\xE9 enregistr\xE9es ailleurs",
|
|
2014
2100
|
"status.saving": "Enregistrement...",
|
|
@@ -2026,23 +2112,12 @@ var SelfstoreStatusElement = class extends FlowWidget {
|
|
|
2026
2112
|
static get observedAttributes() {
|
|
2027
2113
|
return ["variant"];
|
|
2028
2114
|
}
|
|
2029
|
-
#store = null;
|
|
2030
2115
|
#variant = "row";
|
|
2031
|
-
#icons = {};
|
|
2032
2116
|
defaults() {
|
|
2033
|
-
return
|
|
2117
|
+
return EN5;
|
|
2034
2118
|
}
|
|
2035
2119
|
packs() {
|
|
2036
|
-
return { fr:
|
|
2037
|
-
}
|
|
2038
|
-
/** The simple store (anything exposing `flowHost`), or a hand-built FlowHost. */
|
|
2039
|
-
get store() {
|
|
2040
|
-
return this.#store;
|
|
2041
|
-
}
|
|
2042
|
-
set store(v) {
|
|
2043
|
-
if (v === this.#store) return;
|
|
2044
|
-
this.#store = v;
|
|
2045
|
-
this.wire();
|
|
2120
|
+
return { fr: FR5 };
|
|
2046
2121
|
}
|
|
2047
2122
|
/** 'row' (dot + text + action) or 'dot' (the dot alone). Also the attribute. */
|
|
2048
2123
|
get variant() {
|
|
@@ -2052,31 +2127,16 @@ var SelfstoreStatusElement = class extends FlowWidget {
|
|
|
2052
2127
|
this.#variant = v === "dot" ? "dot" : "row";
|
|
2053
2128
|
this.rerender();
|
|
2054
2129
|
}
|
|
2055
|
-
/** Optional icon per target kind (e.g. { drive: url, file: url }), shown
|
|
2056
|
-
* before the text in the row variant. Same contract as the connect cards. */
|
|
2057
|
-
get icons() {
|
|
2058
|
-
return this.#icons;
|
|
2059
|
-
}
|
|
2060
|
-
set icons(v) {
|
|
2061
|
-
this.#icons = v ?? {};
|
|
2062
|
-
this.rerender();
|
|
2063
|
-
}
|
|
2064
2130
|
attributeChangedCallback(name, _old, value) {
|
|
2065
2131
|
if (name === "variant") this.variant = value;
|
|
2066
2132
|
}
|
|
2067
2133
|
connectedCallback() {
|
|
2068
2134
|
super.connectedCallback();
|
|
2069
2135
|
if (this.hasAttribute("variant")) this.variant = this.getAttribute("variant");
|
|
2070
|
-
this.
|
|
2071
|
-
}
|
|
2072
|
-
host() {
|
|
2073
|
-
return hostOf(this.#store);
|
|
2074
|
-
}
|
|
2075
|
-
wire() {
|
|
2076
|
-
this.follow(this.host());
|
|
2136
|
+
this.follow(this.hostOf());
|
|
2077
2137
|
}
|
|
2078
2138
|
view(into) {
|
|
2079
|
-
const host = this.
|
|
2139
|
+
const host = this.hostOf();
|
|
2080
2140
|
if (!host) return;
|
|
2081
2141
|
const { status, targetKind, label } = host.engine.state;
|
|
2082
2142
|
const sansLieu = (cle) => {
|
|
@@ -2084,6 +2144,7 @@ var SelfstoreStatusElement = class extends FlowWidget {
|
|
|
2084
2144
|
const copie = this.t(jumelle);
|
|
2085
2145
|
return copie === jumelle ? this.t(cle) : copie;
|
|
2086
2146
|
};
|
|
2147
|
+
const { lastSavedAt } = host.engine.state;
|
|
2087
2148
|
const text = label ? this.t(status.labelKey, { label }) : sansLieu(status.labelKey);
|
|
2088
2149
|
const dot = h("span", {
|
|
2089
2150
|
part: `status-dot sev-${status.severity}`,
|
|
@@ -2104,20 +2165,36 @@ var SelfstoreStatusElement = class extends FlowWidget {
|
|
|
2104
2165
|
);
|
|
2105
2166
|
return;
|
|
2106
2167
|
}
|
|
2107
|
-
const
|
|
2168
|
+
const TINT = {
|
|
2169
|
+
ok: "status-ok",
|
|
2170
|
+
warn: "status-warn",
|
|
2171
|
+
danger: "status-error",
|
|
2172
|
+
info: ""
|
|
2173
|
+
};
|
|
2174
|
+
const GLYPH = { ok: "\u2713", warn: "!", danger: "!", info: "" };
|
|
2175
|
+
const glyph = GLYPH[status.severity];
|
|
2176
|
+
const icon = this.icons[targetKind];
|
|
2108
2177
|
const where = this.t(`status.where.${targetKind}`);
|
|
2109
|
-
const
|
|
2178
|
+
const kindName = this.t(`destination.kind.${targetKind}`);
|
|
2179
|
+
const named = kindName === `destination.kind.${targetKind}` ? null : kindName;
|
|
2180
|
+
const own = targetKind === "drive" ? null : label;
|
|
2181
|
+
const place = where !== `status.where.${targetKind}` ? where : own ?? named ?? label ?? "";
|
|
2182
|
+
const when = lastSavedAt ? this.since(lastSavedAt, this.t("status.justNow")) : "";
|
|
2183
|
+
const sub = [place, status.state === "saved" ? when : ""].filter(Boolean).join(", ");
|
|
2184
|
+
const title = sub ? sansLieu(status.labelKey) : text;
|
|
2110
2185
|
put(
|
|
2111
2186
|
into,
|
|
2112
2187
|
h(
|
|
2113
2188
|
"div",
|
|
2114
|
-
{ part:
|
|
2115
|
-
|
|
2189
|
+
{ part: `status row status-row ${TINT[status.severity] ?? ""}` },
|
|
2190
|
+
// The severity colors the glyph, not the box: tinting the whole line
|
|
2191
|
+
// turns a title into a warning about itself.
|
|
2192
|
+
glyph ? h("span", { part: `status-glyph sev-${status.severity}`, "aria-hidden": "true" }, glyph) : dot,
|
|
2116
2193
|
icon ? h("img", { part: "icon", src: icon, alt: "" }) : null,
|
|
2117
2194
|
h(
|
|
2118
2195
|
"div",
|
|
2119
2196
|
{},
|
|
2120
|
-
h("div", { part: "title" },
|
|
2197
|
+
h("div", { part: "title" }, title),
|
|
2121
2198
|
sub ? h("div", { part: "sub" }, sub) : null
|
|
2122
2199
|
),
|
|
2123
2200
|
status.actionable && status.action ? h(
|
|
@@ -2134,7 +2211,7 @@ var SelfstoreStatusElement = class extends FlowWidget {
|
|
|
2134
2211
|
};
|
|
2135
2212
|
|
|
2136
2213
|
// src/widgets/backups.ts
|
|
2137
|
-
var
|
|
2214
|
+
var EN6 = {
|
|
2138
2215
|
"backups.mine.heading": "My backups",
|
|
2139
2216
|
"backups.mine.personal": "Main backup",
|
|
2140
2217
|
"backups.active": "Active",
|
|
@@ -2191,7 +2268,7 @@ var EN5 = {
|
|
|
2191
2268
|
"backups.replica.s3.secret": "Secret access key",
|
|
2192
2269
|
"error.generic": "That did not work. Check the connection and try again."
|
|
2193
2270
|
};
|
|
2194
|
-
var
|
|
2271
|
+
var FR6 = {
|
|
2195
2272
|
"backups.mine.heading": "Sauvegardes",
|
|
2196
2273
|
"backups.mine.personal": "Sauvegarde principale",
|
|
2197
2274
|
"backups.active": "Active",
|
|
@@ -2300,10 +2377,10 @@ var SelfstoreBackupsElement = class extends FlowWidget {
|
|
|
2300
2377
|
#withShare = true;
|
|
2301
2378
|
#withShared = true;
|
|
2302
2379
|
defaults() {
|
|
2303
|
-
return
|
|
2380
|
+
return EN6;
|
|
2304
2381
|
}
|
|
2305
2382
|
packs() {
|
|
2306
|
-
return { fr:
|
|
2383
|
+
return { fr: FR6 };
|
|
2307
2384
|
}
|
|
2308
2385
|
/** The headless manager this panel drives (see 'selfstore/backups'). */
|
|
2309
2386
|
get manager() {
|
|
@@ -3318,14 +3395,14 @@ var SelfstoreBackupsElement = class extends FlowWidget {
|
|
|
3318
3395
|
var pillStyle = "display:inline-block;font-size:0.72em;font-weight:600;line-height:1.6;padding:0 0.55em;border-radius:999px;border:1px solid var(--_border)";
|
|
3319
3396
|
|
|
3320
3397
|
// src/widgets/gate.ts
|
|
3321
|
-
var
|
|
3398
|
+
var EN7 = {
|
|
3322
3399
|
"gate.title": "Where should your data live?",
|
|
3323
3400
|
"gate.hint": "It is encrypted on this device. Choose where to keep it and it gets saved there for you from now on.",
|
|
3324
3401
|
"gate.fine": "",
|
|
3325
3402
|
"gate.defer": "Later - keep it in this browser only",
|
|
3326
3403
|
"gate.defer.note": "Data kept only in this browser is lost if its storage is cleared."
|
|
3327
3404
|
};
|
|
3328
|
-
var
|
|
3405
|
+
var FR7 = {
|
|
3329
3406
|
"gate.title": "O\xF9 enregistrer vos donn\xE9es ?",
|
|
3330
3407
|
"gate.hint": "Elles sont chiffr\xE9es sur cet appareil. Choisissez une sauvegarde : tout y sera enregistr\xE9 automatiquement.",
|
|
3331
3408
|
"gate.fine": "",
|
|
@@ -3389,10 +3466,10 @@ var SelfstoreGateElement = class extends FlowWidget {
|
|
|
3389
3466
|
#open = false;
|
|
3390
3467
|
#connect = null;
|
|
3391
3468
|
defaults() {
|
|
3392
|
-
return
|
|
3469
|
+
return EN7;
|
|
3393
3470
|
}
|
|
3394
3471
|
packs() {
|
|
3395
|
-
return { fr:
|
|
3472
|
+
return { fr: FR7 };
|
|
3396
3473
|
}
|
|
3397
3474
|
/** The simple store (anything exposing `flowHost`), or a hand-built FlowHost. */
|
|
3398
3475
|
get store() {
|
|
@@ -3628,26 +3705,14 @@ var SelfstoreGateElement = class extends FlowWidget {
|
|
|
3628
3705
|
}
|
|
3629
3706
|
};
|
|
3630
3707
|
|
|
3631
|
-
// src/widgets/kinds.ts
|
|
3632
|
-
var EN7 = {
|
|
3633
|
-
"destination.kind.drive": "Google Drive",
|
|
3634
|
-
"destination.kind.file": "A file on this device",
|
|
3635
|
-
"destination.kind.webdav": "Your own server",
|
|
3636
|
-
"destination.kind.s3": "Your own bucket",
|
|
3637
|
-
"destination.kind.device": "This device only"
|
|
3638
|
-
};
|
|
3639
|
-
var FR7 = {
|
|
3640
|
-
"destination.kind.file": "Un fichier sur cet appareil",
|
|
3641
|
-
"destination.kind.webdav": "Votre serveur",
|
|
3642
|
-
"destination.kind.s3": "Votre bucket",
|
|
3643
|
-
"destination.kind.device": "Cet appareil seulement"
|
|
3644
|
-
};
|
|
3645
|
-
|
|
3646
3708
|
// src/widgets/destination.ts
|
|
3647
3709
|
var EN8 = {
|
|
3648
|
-
...
|
|
3710
|
+
...EN4,
|
|
3649
3711
|
"destination.heading": "Where your data is kept",
|
|
3650
3712
|
"destination.encrypted": "Password-protected",
|
|
3713
|
+
"destination.active": "Active",
|
|
3714
|
+
"destination.reach.drive": "On all your devices",
|
|
3715
|
+
"destination.reach.file": "",
|
|
3651
3716
|
"destination.export": "Export a copy",
|
|
3652
3717
|
"destination.exported": "The copy is written.",
|
|
3653
3718
|
"destination.exportCancelled": "Nothing was written.",
|
|
@@ -3659,9 +3724,12 @@ var EN8 = {
|
|
|
3659
3724
|
"error.generic": "Something went wrong."
|
|
3660
3725
|
};
|
|
3661
3726
|
var FR8 = {
|
|
3662
|
-
...
|
|
3727
|
+
...FR4,
|
|
3663
3728
|
"destination.heading": "O\xF9 vos donn\xE9es sont enregistr\xE9es",
|
|
3664
3729
|
"destination.encrypted": "Prot\xE9g\xE9e par un mot de passe",
|
|
3730
|
+
"destination.active": "Active",
|
|
3731
|
+
"destination.reach.drive": "Sur tous vos appareils",
|
|
3732
|
+
"destination.reach.file": "",
|
|
3665
3733
|
"destination.export": "Exporter une copie",
|
|
3666
3734
|
"destination.exported": "La copie est \xE9crite.",
|
|
3667
3735
|
"destination.exportCancelled": "Rien n'a \xE9t\xE9 \xE9crit.",
|
|
@@ -3689,10 +3757,8 @@ var DESTINATION_STYLES = `
|
|
|
3689
3757
|
}
|
|
3690
3758
|
`;
|
|
3691
3759
|
var SelfstoreDestinationElement = class extends FlowWidget {
|
|
3692
|
-
#store = null;
|
|
3693
3760
|
#targets = null;
|
|
3694
3761
|
#account = null;
|
|
3695
|
-
#icons = {};
|
|
3696
3762
|
#confirm = null;
|
|
3697
3763
|
#changing = false;
|
|
3698
3764
|
#busy = false;
|
|
@@ -3707,15 +3773,6 @@ var SelfstoreDestinationElement = class extends FlowWidget {
|
|
|
3707
3773
|
super();
|
|
3708
3774
|
this.root.append(h("style", {}, DESTINATION_STYLES));
|
|
3709
3775
|
}
|
|
3710
|
-
/** The simple store (anything exposing `flowHost`), or a hand-built FlowHost. */
|
|
3711
|
-
get store() {
|
|
3712
|
-
return this.#store;
|
|
3713
|
-
}
|
|
3714
|
-
set store(v) {
|
|
3715
|
-
if (v === this.#store) return;
|
|
3716
|
-
this.#store = v;
|
|
3717
|
-
this.wire();
|
|
3718
|
-
}
|
|
3719
3776
|
/** The destinations offered when the user changes their mind. Given them, the
|
|
3720
3777
|
* panel runs the connect journey in place; without them the button is absent
|
|
3721
3778
|
* rather than dead - a host that cannot offer a choice should not show one. */
|
|
@@ -3736,14 +3793,6 @@ var SelfstoreDestinationElement = class extends FlowWidget {
|
|
|
3736
3793
|
this.#account = v || null;
|
|
3737
3794
|
this.rerender();
|
|
3738
3795
|
}
|
|
3739
|
-
/** Optional glyph per destination kind, same contract as the connect cards. */
|
|
3740
|
-
get icons() {
|
|
3741
|
-
return this.#icons;
|
|
3742
|
-
}
|
|
3743
|
-
set icons(v) {
|
|
3744
|
-
this.#icons = v ?? {};
|
|
3745
|
-
this.rerender();
|
|
3746
|
-
}
|
|
3747
3796
|
/** Veto on what is destructive. Returns false (or throws) to stop; absent
|
|
3748
3797
|
* means proceed. The panel never invents its own confirm dialog: an app has
|
|
3749
3798
|
* its own words and its own modal. */
|
|
@@ -3755,13 +3804,7 @@ var SelfstoreDestinationElement = class extends FlowWidget {
|
|
|
3755
3804
|
}
|
|
3756
3805
|
connectedCallback() {
|
|
3757
3806
|
super.connectedCallback();
|
|
3758
|
-
this.
|
|
3759
|
-
}
|
|
3760
|
-
host() {
|
|
3761
|
-
return hostOf(this.#store);
|
|
3762
|
-
}
|
|
3763
|
-
wire() {
|
|
3764
|
-
this.follow(this.host());
|
|
3807
|
+
this.follow(this.hostOf());
|
|
3765
3808
|
}
|
|
3766
3809
|
/** Run a gesture, keeping the panel honest while it is in flight. */
|
|
3767
3810
|
async run(job) {
|
|
@@ -3781,7 +3824,7 @@ var SelfstoreDestinationElement = class extends FlowWidget {
|
|
|
3781
3824
|
* nothing, so it must not clear the pending-export nudge nor claim success -
|
|
3782
3825
|
* that is the download that lies about itself. */
|
|
3783
3826
|
exportCopy() {
|
|
3784
|
-
const host = this.
|
|
3827
|
+
const host = this.hostOf();
|
|
3785
3828
|
if (!host) return Promise.resolve();
|
|
3786
3829
|
return this.run(async () => {
|
|
3787
3830
|
const blob = await host.engine.exportBlob();
|
|
@@ -3794,7 +3837,7 @@ var SelfstoreDestinationElement = class extends FlowWidget {
|
|
|
3794
3837
|
/** Stop saving to this destination. The local data stays, and so does the
|
|
3795
3838
|
* backup already written: this detaches, it never deletes. */
|
|
3796
3839
|
detach(label) {
|
|
3797
|
-
const host = this.
|
|
3840
|
+
const host = this.hostOf();
|
|
3798
3841
|
if (!host) return Promise.resolve();
|
|
3799
3842
|
return this.run(async () => {
|
|
3800
3843
|
try {
|
|
@@ -3814,9 +3857,9 @@ var SelfstoreDestinationElement = class extends FlowWidget {
|
|
|
3814
3857
|
const el = document.createElement(
|
|
3815
3858
|
siblingTag(this.localName, "destination", "connect")
|
|
3816
3859
|
);
|
|
3817
|
-
el.store = this
|
|
3860
|
+
el.store = this.store;
|
|
3818
3861
|
el.targets = this.#targets;
|
|
3819
|
-
el.icons = this
|
|
3862
|
+
el.icons = this.icons;
|
|
3820
3863
|
el.labels = this.labels;
|
|
3821
3864
|
const done = () => {
|
|
3822
3865
|
this.#changing = false;
|
|
@@ -3835,21 +3878,21 @@ var SelfstoreDestinationElement = class extends FlowWidget {
|
|
|
3835
3878
|
);
|
|
3836
3879
|
}
|
|
3837
3880
|
view(into) {
|
|
3838
|
-
const host = this.
|
|
3881
|
+
const host = this.hostOf();
|
|
3839
3882
|
if (!host) return;
|
|
3840
3883
|
if (this.#changing && this.#targets) return this.connectView(into);
|
|
3841
3884
|
const { targetKind, label, encrypted } = host.engine.state;
|
|
3842
3885
|
const kind = this.t(`destination.kind.${targetKind}`);
|
|
3843
3886
|
const title = kind === `destination.kind.${targetKind}` ? label ?? targetKind : kind;
|
|
3844
|
-
const sub =
|
|
3845
|
-
const icon = this
|
|
3887
|
+
const sub = this.#account || (label && label !== title ? label : null);
|
|
3888
|
+
const icon = this.icons[targetKind];
|
|
3846
3889
|
const status = document.createElement(
|
|
3847
3890
|
siblingTag(this.localName, "destination", "status")
|
|
3848
3891
|
);
|
|
3849
3892
|
status.setAttribute("variant", "row");
|
|
3850
3893
|
status.labels = this.labels;
|
|
3851
|
-
status.icons = this
|
|
3852
|
-
status.store = this
|
|
3894
|
+
status.icons = this.icons;
|
|
3895
|
+
status.store = this.store;
|
|
3853
3896
|
const act = (key, part, onclick) => {
|
|
3854
3897
|
const label2 = this.t(key);
|
|
3855
3898
|
if (!label2) return null;
|
|
@@ -3859,8 +3902,14 @@ var SelfstoreDestinationElement = class extends FlowWidget {
|
|
|
3859
3902
|
label2
|
|
3860
3903
|
);
|
|
3861
3904
|
};
|
|
3905
|
+
const durable = targetKind !== "device" && targetKind !== "ephemeral";
|
|
3906
|
+
const reach = this.t(`destination.reach.${targetKind}`);
|
|
3907
|
+
const line = [sub, reach === `destination.reach.${targetKind}` ? null : reach].filter(Boolean).join(" \xB7 ");
|
|
3862
3908
|
put(
|
|
3863
3909
|
into,
|
|
3910
|
+
// The situation first, then what holds it: a panel that opens on a card
|
|
3911
|
+
// makes the reader work out from the name whether anything is wrong.
|
|
3912
|
+
status,
|
|
3864
3913
|
this.heading("destination-heading", "destination.heading"),
|
|
3865
3914
|
h(
|
|
3866
3915
|
"div",
|
|
@@ -3869,12 +3918,18 @@ var SelfstoreDestinationElement = class extends FlowWidget {
|
|
|
3869
3918
|
h(
|
|
3870
3919
|
"div",
|
|
3871
3920
|
{},
|
|
3872
|
-
h(
|
|
3873
|
-
|
|
3874
|
-
|
|
3921
|
+
h(
|
|
3922
|
+
"div",
|
|
3923
|
+
{ part: "title" },
|
|
3924
|
+
title,
|
|
3925
|
+
// Only where there IS a backup: "Active" over the device-only
|
|
3926
|
+
// state would badge the absence of one as a live destination.
|
|
3927
|
+
durable ? h("span", { part: "tag destination-active" }, this.t("destination.active")) : null,
|
|
3928
|
+
encrypted ? h("span", { part: "tag destination-encrypted" }, this.t("destination.encrypted")) : null
|
|
3929
|
+
),
|
|
3930
|
+
line ? h("div", { part: "sub" }, line) : null
|
|
3875
3931
|
)
|
|
3876
3932
|
),
|
|
3877
|
-
status,
|
|
3878
3933
|
h(
|
|
3879
3934
|
"div",
|
|
3880
3935
|
{ part: "destination-actions" },
|
|
@@ -4036,7 +4091,7 @@ var SelfstoreStorageElement = class extends FlowWidget {
|
|
|
4036
4091
|
|
|
4037
4092
|
// src/widgets/account.ts
|
|
4038
4093
|
var EN9 = {
|
|
4039
|
-
...
|
|
4094
|
+
...EN4,
|
|
4040
4095
|
"account.open": "Storage",
|
|
4041
4096
|
"account.settings": "Settings",
|
|
4042
4097
|
"account.change": "Change backup",
|
|
@@ -4044,7 +4099,7 @@ var EN9 = {
|
|
|
4044
4099
|
"account.justNow": "just now"
|
|
4045
4100
|
};
|
|
4046
4101
|
var FR9 = {
|
|
4047
|
-
...
|
|
4102
|
+
...FR4,
|
|
4048
4103
|
"account.open": "Sauvegarde",
|
|
4049
4104
|
"account.settings": "Param\xE8tres",
|
|
4050
4105
|
"account.change": "Changer de sauvegarde",
|
|
@@ -4088,7 +4143,8 @@ var ACCOUNT_STYLES = `
|
|
|
4088
4143
|
right: 0;
|
|
4089
4144
|
top: calc(100% + 0.4rem);
|
|
4090
4145
|
z-index: 20;
|
|
4091
|
-
min-width: min(
|
|
4146
|
+
min-width: min(17rem, calc(100vw - 2rem));
|
|
4147
|
+
max-width: min(24rem, calc(100vw - 2rem));
|
|
4092
4148
|
display: flex;
|
|
4093
4149
|
flex-direction: column;
|
|
4094
4150
|
align-items: stretch;
|
|
@@ -4113,7 +4169,7 @@ var ACCOUNT_STYLES = `
|
|
|
4113
4169
|
cursor: pointer;
|
|
4114
4170
|
}
|
|
4115
4171
|
[part~='account-card']:hover { background: color-mix(in srgb, currentColor 7%, transparent); }
|
|
4116
|
-
[part~='account-logo'] { width: 1.
|
|
4172
|
+
[part~='account-logo'] { width: 1.4rem; height: 1.4rem; object-fit: contain; flex: none; }
|
|
4117
4173
|
[part~='account-text'] {
|
|
4118
4174
|
display: flex;
|
|
4119
4175
|
flex-direction: column;
|
|
@@ -4121,11 +4177,21 @@ var ACCOUNT_STYLES = `
|
|
|
4121
4177
|
min-width: 0;
|
|
4122
4178
|
line-height: 1.35;
|
|
4123
4179
|
}
|
|
4124
|
-
[part~='account-title'] {
|
|
4180
|
+
[part~='account-title'] {
|
|
4181
|
+
font-weight: 600;
|
|
4182
|
+
overflow: hidden;
|
|
4183
|
+
text-overflow: ellipsis;
|
|
4184
|
+
white-space: nowrap;
|
|
4185
|
+
}
|
|
4186
|
+
/* An address is long and its END is the part that identifies it, so it gets a
|
|
4187
|
+
line of its own and an ellipsis rather than a wrap that pushes the status
|
|
4188
|
+
line out of the card. */
|
|
4125
4189
|
[part~='account-mail'], [part~='account-line'] {
|
|
4126
|
-
font-size: 0.
|
|
4190
|
+
font-size: 0.8125rem;
|
|
4127
4191
|
color: var(--_ink-dim);
|
|
4128
|
-
overflow
|
|
4192
|
+
overflow: hidden;
|
|
4193
|
+
text-overflow: ellipsis;
|
|
4194
|
+
white-space: nowrap;
|
|
4129
4195
|
}
|
|
4130
4196
|
[part~='account-item'] {
|
|
4131
4197
|
text-align: start;
|
|
@@ -4139,8 +4205,6 @@ var ACCOUNT_STYLES = `
|
|
|
4139
4205
|
`;
|
|
4140
4206
|
var TICK_MS = 3e4;
|
|
4141
4207
|
var SelfstoreAccountElement = class extends FlowWidget {
|
|
4142
|
-
#store = null;
|
|
4143
|
-
#icons = {};
|
|
4144
4208
|
#confirm = null;
|
|
4145
4209
|
#open = false;
|
|
4146
4210
|
#timer = null;
|
|
@@ -4155,35 +4219,16 @@ var SelfstoreAccountElement = class extends FlowWidget {
|
|
|
4155
4219
|
super();
|
|
4156
4220
|
this.root.append(h("style", {}, ACCOUNT_STYLES));
|
|
4157
4221
|
}
|
|
4158
|
-
/** The simple store (anything exposing `flowHost`), or a hand-built FlowHost.
|
|
4159
|
-
* A simple store also answers `account` on its own, so nothing else is
|
|
4160
|
-
* needed to name who holds the backup. */
|
|
4161
|
-
get store() {
|
|
4162
|
-
return this.#store;
|
|
4163
|
-
}
|
|
4164
|
-
set store(v) {
|
|
4165
|
-
if (v === this.#store) return;
|
|
4166
|
-
this.#store = v;
|
|
4167
|
-
this.follow(hostOf(v));
|
|
4168
|
-
}
|
|
4169
4222
|
/** Who holds the backup ("someone@example.com"). Absent: whatever the store
|
|
4170
4223
|
* knows. A brand name is not an address, and several accounts look alike. */
|
|
4171
4224
|
#account = null;
|
|
4172
4225
|
get account() {
|
|
4173
|
-
return this.#account ?? this
|
|
4226
|
+
return this.#account ?? this.store?.account ?? null;
|
|
4174
4227
|
}
|
|
4175
4228
|
set account(v) {
|
|
4176
4229
|
this.#account = v || null;
|
|
4177
4230
|
this.rerender();
|
|
4178
4231
|
}
|
|
4179
|
-
/** Optional glyph per destination kind, same contract as the connect cards. */
|
|
4180
|
-
get icons() {
|
|
4181
|
-
return this.#icons;
|
|
4182
|
-
}
|
|
4183
|
-
set icons(v) {
|
|
4184
|
-
this.#icons = v ?? {};
|
|
4185
|
-
this.rerender();
|
|
4186
|
-
}
|
|
4187
4232
|
/** Veto on changing backup, which leaves the app with no destination until
|
|
4188
4233
|
* the user picks one. Returns false (or throws) to stop. */
|
|
4189
4234
|
get confirmAction() {
|
|
@@ -4205,7 +4250,7 @@ var SelfstoreAccountElement = class extends FlowWidget {
|
|
|
4205
4250
|
}
|
|
4206
4251
|
connectedCallback() {
|
|
4207
4252
|
super.connectedCallback();
|
|
4208
|
-
this.follow(hostOf(
|
|
4253
|
+
this.follow(this.hostOf());
|
|
4209
4254
|
}
|
|
4210
4255
|
disconnectedCallback() {
|
|
4211
4256
|
this.unsub?.();
|
|
@@ -4245,17 +4290,6 @@ var SelfstoreAccountElement = class extends FlowWidget {
|
|
|
4245
4290
|
const kind = this.t(`destination.kind.${targetKind}`);
|
|
4246
4291
|
return kind === `destination.kind.${targetKind}` ? label ?? targetKind : kind;
|
|
4247
4292
|
}
|
|
4248
|
-
/** "just now", "3 minutes ago", "yesterday" - in the page's language, from
|
|
4249
|
-
* Intl rather than from five label keys per language. */
|
|
4250
|
-
when(ts) {
|
|
4251
|
-
const mins = Math.floor((Date.now() - ts) / 6e4);
|
|
4252
|
-
if (mins < 1) return this.t("account.justNow");
|
|
4253
|
-
const rtf = new Intl.RelativeTimeFormat(this.langTag(), { numeric: "auto" });
|
|
4254
|
-
if (mins < 60) return rtf.format(-mins, "minute");
|
|
4255
|
-
const hours = Math.floor(mins / 60);
|
|
4256
|
-
if (hours < 24) return rtf.format(-hours, "hour");
|
|
4257
|
-
return new Date(ts).toLocaleDateString(this.langTag());
|
|
4258
|
-
}
|
|
4259
4293
|
/**
|
|
4260
4294
|
* The one line under the account. A plain "saved" is the only state this
|
|
4261
4295
|
* element words itself, because it is the only one with nothing to do about
|
|
@@ -4269,15 +4303,15 @@ var SelfstoreAccountElement = class extends FlowWidget {
|
|
|
4269
4303
|
return h(
|
|
4270
4304
|
"span",
|
|
4271
4305
|
{ part: "account-line" },
|
|
4272
|
-
this.t("account.saved", { when: this.
|
|
4306
|
+
this.t("account.saved", { when: this.since(lastSavedAt, this.t("account.justNow")) })
|
|
4273
4307
|
);
|
|
4274
4308
|
const el = document.createElement(
|
|
4275
4309
|
siblingTag(this.localName, "account", "status")
|
|
4276
4310
|
);
|
|
4277
4311
|
el.setAttribute("variant", "row");
|
|
4278
4312
|
el.labels = this.labels;
|
|
4279
|
-
el.icons = this
|
|
4280
|
-
el.store = this
|
|
4313
|
+
el.icons = this.icons;
|
|
4314
|
+
el.store = this.store;
|
|
4281
4315
|
return el;
|
|
4282
4316
|
}
|
|
4283
4317
|
/** Ask for the settings. The host owns the route, so this only says that it
|
|
@@ -4290,7 +4324,7 @@ var SelfstoreAccountElement = class extends FlowWidget {
|
|
|
4290
4324
|
/** Change backup: detach, and let the first-run screen take it from there.
|
|
4291
4325
|
* The data stays; only the destination is let go. */
|
|
4292
4326
|
async change() {
|
|
4293
|
-
const host = hostOf(
|
|
4327
|
+
const host = this.hostOf();
|
|
4294
4328
|
if (!host) return;
|
|
4295
4329
|
this.open = false;
|
|
4296
4330
|
const label = host.engine.state.label;
|
|
@@ -4303,7 +4337,7 @@ var SelfstoreAccountElement = class extends FlowWidget {
|
|
|
4303
4337
|
}
|
|
4304
4338
|
card(host) {
|
|
4305
4339
|
const { targetKind } = host.engine.state;
|
|
4306
|
-
const icon = this
|
|
4340
|
+
const icon = this.icons[targetKind];
|
|
4307
4341
|
const mail = this.account;
|
|
4308
4342
|
return h(
|
|
4309
4343
|
"button",
|
|
@@ -4325,7 +4359,7 @@ var SelfstoreAccountElement = class extends FlowWidget {
|
|
|
4325
4359
|
);
|
|
4326
4360
|
}
|
|
4327
4361
|
view(into) {
|
|
4328
|
-
const host = hostOf(
|
|
4362
|
+
const host = this.hostOf();
|
|
4329
4363
|
if (!host) return;
|
|
4330
4364
|
const { status } = host.engine.state;
|
|
4331
4365
|
const trigger = h(
|