strom-research 1.18.0-beta.6 → 1.18.0-beta.7
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/CHANGELOG.cs.md +4 -2
- package/CHANGELOG.de.md +5 -2
- package/CHANGELOG.md +5 -2
- package/assets/lang/cs.json +4 -1
- package/assets/lang/de.json +4 -1
- package/assets/phone/index.html +9 -7
- package/assets/phone/phone.css +11 -6
- package/assets/phone/phone.js +116 -24
- package/dist/cli/ui.js +4 -1
- package/dist/core/phone-send.js +21 -3
- package/dist/core/phone-watch.js +29 -10
- package/dist/core/tree.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.cs.md
CHANGED
|
@@ -7,7 +7,7 @@ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou n
|
|
|
7
7
|
|
|
8
8
|
## 1.18.0
|
|
9
9
|
|
|
10
|
-
*Připravuje se: bety do 1.18.0-beta.
|
|
10
|
+
*Připravuje se: bety do 1.18.0-beta.7. Oddíl bude úplný s vydáním.*
|
|
11
11
|
|
|
12
12
|
- **Fotky z telefonu nastojato.** Fotka otočená podle údaje EXIF se čte tak, jak se má dívat, nastojato; místa na ní
|
|
13
13
|
vyznačená (výřezy, poloviny dvoustrany, uložená část) jsou místa obrázku, jak se ukazuje. Fotka sama zůstává, jak
|
|
@@ -30,7 +30,9 @@ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou n
|
|
|
30
30
|
rozhodnutí, posledních 24 hodin a poslední změny, obnovené každých 10 sekund, dokud je stránka na očích — jen
|
|
31
31
|
k podívání: v telefonu se nic neodpovídá, nespouští ani nerozhoduje. Nedostupný počítač stránka řekne i s posledními
|
|
32
32
|
údaji a jejich časem. Otevře strom, jehož QR kód ji spároval, a jde přidat na plochu jako ikona, která ji otevře
|
|
33
|
-
bez nového párování.
|
|
33
|
+
bez nového párování. Co čeká a změny jdou na telefonu přečíst celé: text toho, co čeká, se neořezává, další položky
|
|
34
|
+
nad prvních 20 se donačtou klepnutím a změna s víc řádky se rozbalí celá; Co čeká je poslední částí stránky. Změna
|
|
35
|
+
starší než dnešek má u času i datum (rok jen u loňských a starších).
|
|
34
36
|
- **Souhlas v jednom jazyce.** Souhlas, který dává jen člověk (rozbalení výzkumu, odebrání stromu, odmítnuté okno),
|
|
35
37
|
zazní celý v jazyce výzkumu, nic z něj nezůstane anglicky.
|
|
36
38
|
- **Odmítnutá aktualizace.** Když se `strom update` odmítne, řekne, že se nic nenainstalovalo a která verze zůstává,
|
package/CHANGELOG.de.md
CHANGED
|
@@ -7,7 +7,7 @@ Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Ins
|
|
|
7
7
|
|
|
8
8
|
## 1.18.0
|
|
9
9
|
|
|
10
|
-
*In Vorbereitung: die Betas bis 1.18.0-beta.
|
|
10
|
+
*In Vorbereitung: die Betas bis 1.18.0-beta.7. Der Abschnitt ist mit dem Release vollständig.*
|
|
11
11
|
|
|
12
12
|
- **Handyfotos aufrecht.** Ein Foto, das nach seiner EXIF-Ausrichtung gedreht ist, wird gelesen, wie es gesehen
|
|
13
13
|
werden soll, aufrecht; die darauf markierten Stellen (Ausschnitte, die Hälften einer Doppelseite, ein gespeicherter
|
|
@@ -34,7 +34,10 @@ Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Ins
|
|
|
34
34
|
Sekunden aufgefrischt, solange die Seite angesehen wird — nur zum Ansehen: auf dem Handy wird nichts beantwortet,
|
|
35
35
|
gestartet oder entschieden. Ein unerreichbarer Computer wird gesagt, mit den letzten Daten und ihrer Zeit. Die Seite
|
|
36
36
|
öffnet den Stammbaum, dessen QR-Code sie gekoppelt hat, und lässt sich als Symbol auf den Home-Bildschirm legen,
|
|
37
|
-
das sie ohne neues Koppeln öffnet.
|
|
37
|
+
das sie ohne neues Koppeln öffnet. Was wartet und die Änderungen lassen sich auf dem Handy ganz lesen: kein Text
|
|
38
|
+
dessen, was wartet, wird gekürzt, mehr als die ersten 20 kommen auf Antippen, eine Änderung mit vielen Zeilen klappt
|
|
39
|
+
ganz auf; Was wartet ist der letzte Teil der Seite. Eine Änderung vor heute hat bei der Uhrzeit auch ihr Datum (das
|
|
40
|
+
Jahr nur, wenn es nicht das laufende ist).
|
|
38
41
|
- **Eine Zustimmung in einer Sprache.** Eine Zustimmung, die nur ein Mensch gibt (eine Forschung entpacken, einen
|
|
39
42
|
Stammbaum entfernen, ein abgelehntes Fenster), steht ganz in der Sprache der Forschung, nichts davon bleibt
|
|
40
43
|
Englisch.
|
package/CHANGELOG.md
CHANGED
|
@@ -7,7 +7,7 @@ The changes of each version in short. The full notes and the install lines are o
|
|
|
7
7
|
|
|
8
8
|
## 1.18.0
|
|
9
9
|
|
|
10
|
-
*In preparation: the betas up to 1.18.0-beta.
|
|
10
|
+
*In preparation: the betas up to 1.18.0-beta.7. The section is complete with the release.*
|
|
11
11
|
|
|
12
12
|
- **Phone photos upright.** A photo turned by its EXIF orientation is read as it is meant to be seen, upright; the
|
|
13
13
|
places marked on it (excerpts, the halves of a spread, a cut-out part) are of the picture as it is shown. The
|
|
@@ -32,7 +32,10 @@ The changes of each version in short. The full notes and the install lines are o
|
|
|
32
32
|
what waits for a decision, the last 24 hours and the latest changes, refreshed every 10 seconds while the page is
|
|
33
33
|
looked at — only to look at: nothing on the phone answers, starts or decides anything. A computer out of reach is
|
|
34
34
|
said, with the last data and their time. The page opens the tree whose QR code paired it, and it can be added to
|
|
35
|
-
the home screen as an icon that opens it without pairing again.
|
|
35
|
+
the home screen as an icon that opens it without pairing again. What waits and the changes read whole on the phone:
|
|
36
|
+
no text of what waits is cut, more of it than the first 20 comes on a tap, a change of many lines unfolds whole;
|
|
37
|
+
What waits is the last part of the page. A change older than today has its date beside the time (the year only when
|
|
38
|
+
it is not this one).
|
|
36
39
|
- **A consent in one language.** A consent only a person gives (unpacking a research, removing a tree, a window said
|
|
37
40
|
no to) is said whole in the research's language, nothing of it left in English.
|
|
38
41
|
- **An update said no to.** When `strom update` is declined, it says nothing was installed and which version stays,
|
package/assets/lang/cs.json
CHANGED
|
@@ -1403,7 +1403,9 @@
|
|
|
1403
1403
|
"ui.phone.page.waiting.title": "Co čeká",
|
|
1404
1404
|
"ui.phone.page.waiting.none": "Nic nečeká.",
|
|
1405
1405
|
"ui.phone.page.waiting.where": "Rozhodnout jde na počítači v Přehledu výzkumu.",
|
|
1406
|
-
"ui.phone.page.waiting.more": "{n, plural, one {
|
|
1406
|
+
"ui.phone.page.waiting.more": "{n, plural, one {Ukázat další · zbývá #} few {Ukázat další · zbývají #} other {Ukázat další · zbývá #}}",
|
|
1407
|
+
"ui.phone.page.waiting.loading": "Načítá se…",
|
|
1408
|
+
"ui.phone.page.waiting.rest": "{n, plural, one {a # další v Přehledu výzkumu na počítači} few {a # další v Přehledu výzkumu na počítači} other {a # dalších v Přehledu výzkumu na počítači}}",
|
|
1407
1409
|
"ui.phone.page.day": "Posledních 24 h",
|
|
1408
1410
|
"ui.phone.page.day.people": "{n, plural, one {osoba} few {osoby} other {osob}}",
|
|
1409
1411
|
"ui.phone.page.day.sources": "{n, plural, one {pramen} few {prameny} other {pramenů}}",
|
|
@@ -1412,6 +1414,7 @@
|
|
|
1412
1414
|
"ui.phone.page.log.more": "Starší změny jsou v Přehledu výzkumu na počítači.",
|
|
1413
1415
|
"ui.phone.page.log.new": "{n, plural, one {# nová změna ↑} few {# nové změny ↑} other {# nových změn ↑}}",
|
|
1414
1416
|
"ui.phone.page.log.lines": "{n, plural, one {a # další} few {a # další} other {a # dalších}}",
|
|
1417
|
+
"ui.phone.page.log.fold": "Sbalit",
|
|
1415
1418
|
"ui.phone.page.log.none": "Zatím se nic neuložilo.",
|
|
1416
1419
|
"ui.settings.phone": "Propojení s telefonem (fotky z telefonu do výzkumu v místní síti): {state}",
|
|
1417
1420
|
"ui.settings.phone.on": "zapnuto, spárované telefony: {n}",
|
package/assets/lang/de.json
CHANGED
|
@@ -1403,7 +1403,9 @@
|
|
|
1403
1403
|
"ui.phone.page.waiting.title": "Wartet",
|
|
1404
1404
|
"ui.phone.page.waiting.none": "Nichts wartet.",
|
|
1405
1405
|
"ui.phone.page.waiting.where": "Entscheiden lässt sich am Computer in der Forschungsübersicht.",
|
|
1406
|
-
"ui.phone.page.waiting.more": "{n, plural, one {
|
|
1406
|
+
"ui.phone.page.waiting.more": "{n, plural, one {Weitere zeigen · noch #} other {Weitere zeigen · noch #}}",
|
|
1407
|
+
"ui.phone.page.waiting.loading": "Wird geladen…",
|
|
1408
|
+
"ui.phone.page.waiting.rest": "{n, plural, one {und # weiteres in der Forschungsübersicht am Computer} other {und # weitere in der Forschungsübersicht am Computer}}",
|
|
1407
1409
|
"ui.phone.page.day": "Letzte 24 Std.",
|
|
1408
1410
|
"ui.phone.page.day.people": "{n, plural, one {Person} other {Personen}}",
|
|
1409
1411
|
"ui.phone.page.day.sources": "{n, plural, one {Quelle} other {Quellen}}",
|
|
@@ -1412,6 +1414,7 @@
|
|
|
1412
1414
|
"ui.phone.page.log.more": "Ältere Änderungen stehen in der Forschungsübersicht am Computer.",
|
|
1413
1415
|
"ui.phone.page.log.new": "{n, plural, one {# neue Änderung ↑} other {# neue Änderungen ↑}}",
|
|
1414
1416
|
"ui.phone.page.log.lines": "{n, plural, one {und # weitere} other {und # weitere}}",
|
|
1417
|
+
"ui.phone.page.log.fold": "Weniger zeigen",
|
|
1415
1418
|
"ui.phone.page.log.none": "Noch nichts gespeichert.",
|
|
1416
1419
|
"ui.settings.phone": "Verbindung mit dem Telefon (Fotos vom Telefon in die Forschung im lokalen Netz): {state}",
|
|
1417
1420
|
"ui.settings.phone.on": "an, gekoppelte Telefone: {n}",
|
package/assets/phone/index.html
CHANGED
|
@@ -58,13 +58,6 @@
|
|
|
58
58
|
<ul id="nowLines" class="lines"></ul>
|
|
59
59
|
<div id="nowIdle" hidden><p class="idle-row"><span class="ring" aria-hidden="true"></span><span id="nowIdleText"></span></p><p id="nowIdleLast" class="quiet small"></p></div>
|
|
60
60
|
</section>
|
|
61
|
-
<section id="waitCard" class="card">
|
|
62
|
-
<h2 id="waitTitle" class="cap"></h2>
|
|
63
|
-
<p id="waitNone" class="quiet" hidden></p>
|
|
64
|
-
<ul id="waitList" class="waits"></ul>
|
|
65
|
-
<p id="waitMore" class="quiet small" hidden></p>
|
|
66
|
-
<p id="waitWhere" class="quiet small"></p>
|
|
67
|
-
</section>
|
|
68
61
|
<section id="dayCard" class="card" hidden>
|
|
69
62
|
<h2 id="dayTitle" class="cap"></h2>
|
|
70
63
|
<div class="day">
|
|
@@ -79,6 +72,15 @@
|
|
|
79
72
|
<ul id="logList" class="log"></ul>
|
|
80
73
|
<p id="logMore" class="quiet small" hidden></p>
|
|
81
74
|
</section>
|
|
75
|
+
<!-- the last: each text whole, more of them asked for on a tap -->
|
|
76
|
+
<section id="waitCard" class="card">
|
|
77
|
+
<h2 id="waitTitle" class="cap"></h2>
|
|
78
|
+
<p id="waitNone" class="quiet" hidden></p>
|
|
79
|
+
<ul id="waitList" class="waits"></ul>
|
|
80
|
+
<p class="morerow"><button id="waitMore" class="linkbtn" type="button" hidden></button></p>
|
|
81
|
+
<p id="waitRest" class="quiet small" hidden></p>
|
|
82
|
+
<p id="waitWhere" class="quiet small"></p>
|
|
83
|
+
</section>
|
|
82
84
|
</div>
|
|
83
85
|
<p class="homerow"><button id="homeBtn" class="linkbtn" type="button" hidden></button></p>
|
|
84
86
|
<section id="homeCard" class="card" hidden>
|
package/assets/phone/phone.css
CHANGED
|
@@ -74,18 +74,23 @@ button{font:inherit;color:inherit}
|
|
|
74
74
|
.waits li{display:grid;grid-template-columns:8px minmax(0,1fr);gap:10px;padding:8px 0;border-top:1px solid var(--divider)}
|
|
75
75
|
.waits li:first-child{border-top:0;padding-top:0}
|
|
76
76
|
.waits .wdot{width:8px;height:8px;border-radius:50%;background:var(--waiting-dot);margin-top:7px}
|
|
77
|
-
.waits .what{font-size:14.5px;font-weight:500}
|
|
78
|
-
.waits .on{font-size:13px;color:var(--text-light);
|
|
77
|
+
.waits .what{font-size:14.5px;font-weight:500;overflow-wrap:anywhere}
|
|
78
|
+
.waits .on{font-size:13px;color:var(--text-light);overflow-wrap:anywhere;margin-top:2px}
|
|
79
79
|
#waitCard.none{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap}
|
|
80
80
|
#waitCard.none .cap,#waitCard.none p{margin:0}
|
|
81
|
-
#waitWhere,#
|
|
81
|
+
#waitWhere,#waitRest,#logMore{margin:10px 0 0}
|
|
82
|
+
.morerow{margin:6px 0 0}
|
|
83
|
+
.morerow .linkbtn{padding:10px 0;min-height:44px;text-align:left}
|
|
84
|
+
.morerow .linkbtn:disabled{color:var(--text-faint);text-decoration:none;cursor:default}
|
|
82
85
|
.day{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
|
|
83
86
|
.day b{display:block;font-size:20px;font-weight:600;font-variant-numeric:tabular-nums}
|
|
84
87
|
.day span{font-size:12.5px;color:var(--text-light)}
|
|
85
|
-
.log
|
|
86
|
-
.log
|
|
87
|
-
.log
|
|
88
|
+
.log{display:grid;grid-template-columns:fit-content(88px) minmax(0,1fr)}
|
|
89
|
+
.log li{grid-column:1/-1;display:grid;grid-template-columns:fit-content(88px) minmax(0,1fr);grid-template-columns:subgrid;gap:8px;padding:7px 6px;margin:0 -6px;border-radius:8px}
|
|
90
|
+
.log time{min-width:42px;overflow-wrap:normal;font-size:13px;color:var(--text-faint);font-variant-numeric:tabular-nums;padding-top:1px}
|
|
91
|
+
.log p{margin:0;font-size:14px;overflow-wrap:anywhere}
|
|
88
92
|
.log .more{font-size:13px;color:var(--text-faint)}
|
|
93
|
+
.log .unfold{display:block;background:none;border:0;padding:6px 0;margin:0;min-height:32px;font:inherit;font-size:13px;color:var(--primary);text-align:left;cursor:pointer}
|
|
89
94
|
.log li.fresh{background:var(--primary-soft)}
|
|
90
95
|
.newpill{position:fixed;top:calc(72px + env(safe-area-inset-top));left:50%;transform:translateX(-50%);z-index:7;border:0;border-radius:999px;background:var(--primary);color:var(--on-primary);padding:0 16px;min-height:36px;font-size:13px;font-weight:600;cursor:pointer;box-shadow:0 2px 8px rgba(20,16,8,.25)}
|
|
91
96
|
|
package/assets/phone/phone.js
CHANGED
|
@@ -68,7 +68,9 @@
|
|
|
68
68
|
const t = (key, v = {}) => fill(T[key] ?? key, v);
|
|
69
69
|
// a small photo is never "0.0 MB"
|
|
70
70
|
const mb = (bytes) => new Intl.NumberFormat(LANG, { minimumFractionDigits: 1, maximumFractionDigits: 1 }).format(bytes > 0 ? Math.max(0.1, bytes / 1024 / 1024) : 0);
|
|
71
|
-
|
|
71
|
+
// a 12-hour clock without a leading zero ("2:05 PM"), a 24-hour one with it ("09:07")
|
|
72
|
+
const HOUR = /^h1[12]$/u.test(new Intl.DateTimeFormat(LANG, { hour: "numeric" }).resolvedOptions().hourCycle ?? "") ? "numeric" : "2-digit";
|
|
73
|
+
const hhmm = (ms) => new Intl.DateTimeFormat(LANG, { hour: HOUR, minute: "2-digit" }).format(new Date(ms));
|
|
72
74
|
const fold = (s) => String(s).normalize("NFD").replace(/\p{M}/gu, "").toLowerCase();
|
|
73
75
|
const rid = () => Array.from(crypto.getRandomValues(new Uint8Array(16)), (b) => b.toString(16).padStart(2, "0")).join("").replace(/^(.{8})(.{4})(.{4})(.{4})/, "$1-$2-$3-$4-");
|
|
74
76
|
const el = (tag, cls, text) => {
|
|
@@ -164,6 +166,8 @@
|
|
|
164
166
|
shown: null, // what the tree shown does: now, lastWorked, waiting, day, latest
|
|
165
167
|
logTree: "", // the tree and its newest change the progress shows
|
|
166
168
|
logLatest: "",
|
|
169
|
+
waitLoaded: null, // more of what waits, asked for on a tap: {tree, of (what the overview gave then), n, items}
|
|
170
|
+
waitBusy: false,
|
|
167
171
|
newCount: 0, // changes added above while scrolled down
|
|
168
172
|
returning: false, // the page looked at again: the first answer after it awaited
|
|
169
173
|
};
|
|
@@ -259,6 +263,7 @@
|
|
|
259
263
|
if (asked === S.tree) {
|
|
260
264
|
S.shown = r.body.shown || null;
|
|
261
265
|
if (S.shown && S.shown.latest && (S.logTree !== S.tree || S.logLatest !== S.shown.latest)) void loadLog(S.tree, S.shown.latest);
|
|
266
|
+
keepWait();
|
|
262
267
|
} else {
|
|
263
268
|
S.shown = null;
|
|
264
269
|
setTimeout(() => void refresh(), 0);
|
|
@@ -287,6 +292,34 @@
|
|
|
287
292
|
if (S.shown && S.shown.latest && (S.logTree !== S.tree || S.logLatest !== S.shown.latest)) void loadLog(S.tree, S.shown.latest);
|
|
288
293
|
})());
|
|
289
294
|
|
|
295
|
+
// more of what waits than the overview gives, the first ones of the same list again with them
|
|
296
|
+
const WAIT_PAGE = 50;
|
|
297
|
+
const WAIT_MAX = 200;
|
|
298
|
+
let waitAsking = null;
|
|
299
|
+
const loadWait = (count, quiet = false) =>
|
|
300
|
+
(waitAsking ??= (async () => {
|
|
301
|
+
const tree = S.tree;
|
|
302
|
+
const of = JSON.stringify(S.shown && S.shown.waiting);
|
|
303
|
+
// asked on a tap: the button says so (asked again after an answer: nothing changes till it comes)
|
|
304
|
+
if (!quiet) {
|
|
305
|
+
S.waitBusy = true;
|
|
306
|
+
drawWait();
|
|
307
|
+
}
|
|
308
|
+
const r = await ask(`/phone/waiting?tree=${encodeURIComponent(tree)}&from=0&limit=${Math.min(WAIT_MAX, count)}`);
|
|
309
|
+
waitAsking = null;
|
|
310
|
+
S.waitBusy = false;
|
|
311
|
+
if (tree === S.tree && r.status === 200 && Array.isArray(r.body.items)) S.waitLoaded = { tree, of, n: Number(r.body.n) || 0, items: r.body.items };
|
|
312
|
+
drawWait();
|
|
313
|
+
})());
|
|
314
|
+
// more of what waits asked for, kept over each answer: what waits another, asked again as many (all of it in the
|
|
315
|
+
// overview: none)
|
|
316
|
+
const keepWait = () => {
|
|
317
|
+
const w = S.shown && S.shown.waiting;
|
|
318
|
+
if (!S.waitLoaded || JSON.stringify(w) === S.waitLoaded.of) return;
|
|
319
|
+
if (!w || !Array.isArray(w.items) || w.n <= w.items.length) S.waitLoaded = null;
|
|
320
|
+
else void loadWait(S.waitLoaded.items.length, true);
|
|
321
|
+
};
|
|
322
|
+
|
|
290
323
|
// ── each photo: its SHA-256, in the research already?, sent ──────────────────────────────────────────────────────
|
|
291
324
|
let worker = null;
|
|
292
325
|
const waiting = new Map();
|
|
@@ -641,7 +674,16 @@
|
|
|
641
674
|
// a time of today, else its day too
|
|
642
675
|
const atTime = (iso) => {
|
|
643
676
|
const d = new Date(iso);
|
|
644
|
-
return sameDay(d, new Date()) ? hhmm(d) : new Intl.DateTimeFormat(LANG, { day: "numeric", month: "numeric", hour:
|
|
677
|
+
return sameDay(d, new Date()) ? hhmm(d) : new Intl.DateTimeFormat(LANG, { day: "numeric", month: "numeric", hour: HOUR, minute: "2-digit" }).format(d);
|
|
678
|
+
};
|
|
679
|
+
// a change's time: of today the time alone, else its day before it (the year when not this one), broken only
|
|
680
|
+
// between the two
|
|
681
|
+
const logTime = (ms, now = new Date()) => {
|
|
682
|
+
const d = new Date(ms);
|
|
683
|
+
if (sameDay(d, now)) return hhmm(d);
|
|
684
|
+
const day = new Intl.DateTimeFormat(LANG, { day: "numeric", month: "numeric", ...(d.getFullYear() !== now.getFullYear() ? { year: "numeric" } : {}) }).format(d);
|
|
685
|
+
const whole = (text) => text.replace(/\s/gu, "\u00a0");
|
|
686
|
+
return `${whole(day)} ${whole(hhmm(d))}`;
|
|
645
687
|
};
|
|
646
688
|
const whenOf = (iso) => {
|
|
647
689
|
const d = new Date(iso);
|
|
@@ -689,26 +731,7 @@
|
|
|
689
731
|
$("nowIdleLast").hidden = !o.lastWorked;
|
|
690
732
|
if (o.lastWorked) $("nowIdleLast").textContent = t("now.idleLast", { when: whenOf(o.lastWorked) });
|
|
691
733
|
}
|
|
692
|
-
|
|
693
|
-
if (!archive && changed("wait", w)) {
|
|
694
|
-
$("waitCard").classList.toggle("none", !w.n);
|
|
695
|
-
$("waitTitle").textContent = w.n ? t("waiting", { n: w.n }) : t("waiting.title");
|
|
696
|
-
$("waitNone").hidden = w.n > 0;
|
|
697
|
-
$("waitList").replaceChildren(
|
|
698
|
-
...w.items.map((x) => {
|
|
699
|
-
const li = el("li");
|
|
700
|
-
const box = el("div");
|
|
701
|
-
box.append(el("div", "what", x.what));
|
|
702
|
-
if (x.on) box.append(el("div", "on", x.on));
|
|
703
|
-
li.append(el("span", "wdot"), box);
|
|
704
|
-
return li;
|
|
705
|
-
}),
|
|
706
|
-
);
|
|
707
|
-
$("waitList").hidden = !w.n;
|
|
708
|
-
$("waitMore").hidden = w.n <= w.items.length;
|
|
709
|
-
if (w.n > w.items.length) $("waitMore").textContent = t("waiting.more", { n: w.n - w.items.length });
|
|
710
|
-
$("waitWhere").hidden = !w.n;
|
|
711
|
-
}
|
|
734
|
+
if (!archive) drawWait();
|
|
712
735
|
$("dayCard").hidden = !o.day;
|
|
713
736
|
if (o.day && changed("day", o.day)) {
|
|
714
737
|
const plus = (n) => (n > 0 ? "+" : "") + count(n);
|
|
@@ -722,16 +745,68 @@
|
|
|
722
745
|
$("logCard").hidden = S.logTree !== S.tree;
|
|
723
746
|
};
|
|
724
747
|
|
|
748
|
+
// what waits, each text whole: the overview's first ones, or more of them asked for (kept till what waits is another)
|
|
749
|
+
const drawWait = () => {
|
|
750
|
+
const tree = treeById(S.tree);
|
|
751
|
+
const o = S.shown;
|
|
752
|
+
if (!tree || !o || tree.archive) return;
|
|
753
|
+
const first = o.waiting && Array.isArray(o.waiting.items) ? o.waiting : { n: 0, items: [] };
|
|
754
|
+
const more = S.waitLoaded && S.waitLoaded.tree === S.tree && S.waitLoaded.items.length > first.items.length ? S.waitLoaded : null;
|
|
755
|
+
const w = more ? { n: more.n, items: more.items } : first;
|
|
756
|
+
if (!changed("wait", [w, S.waitBusy])) return;
|
|
757
|
+
$("waitCard").classList.toggle("none", !w.n);
|
|
758
|
+
$("waitTitle").textContent = w.n ? t("waiting", { n: w.n }) : t("waiting.title");
|
|
759
|
+
$("waitNone").hidden = w.n > 0;
|
|
760
|
+
$("waitList").replaceChildren(
|
|
761
|
+
...w.items.map((x) => {
|
|
762
|
+
const li = el("li");
|
|
763
|
+
const box = el("div");
|
|
764
|
+
box.append(el("div", "what", x.what));
|
|
765
|
+
if (x.on) box.append(el("div", "on", x.on));
|
|
766
|
+
li.append(el("span", "wdot"), box);
|
|
767
|
+
return li;
|
|
768
|
+
}),
|
|
769
|
+
);
|
|
770
|
+
$("waitList").hidden = !w.n;
|
|
771
|
+
const left = Math.max(0, w.n - w.items.length);
|
|
772
|
+
const can = left > 0 && w.items.length < WAIT_MAX;
|
|
773
|
+
$("waitMore").hidden = !can;
|
|
774
|
+
$("waitMore").disabled = S.waitBusy;
|
|
775
|
+
if (can) $("waitMore").textContent = S.waitBusy ? t("waiting.loading") : t("waiting.more", { n: left });
|
|
776
|
+
$("waitRest").hidden = !left || can;
|
|
777
|
+
if (left && !can) $("waitRest").textContent = t("waiting.rest", { n: left });
|
|
778
|
+
$("waitWhere").hidden = !w.n;
|
|
779
|
+
};
|
|
780
|
+
|
|
725
781
|
// the progress: the changes drawn kept by their id, a new one put on top (lit for 6 s); scrolled down, what is under
|
|
726
782
|
// the finger stays where it is and a pill says how many came above
|
|
727
783
|
const logItems = new Map();
|
|
728
784
|
const drawChange = (c) => {
|
|
729
785
|
const li = el("li");
|
|
730
|
-
const time = el("time", "",
|
|
786
|
+
const time = el("time", "", logTime(Date.parse(c.at)));
|
|
731
787
|
time.dateTime = c.at;
|
|
732
788
|
const box = el("div");
|
|
733
789
|
for (const line of c.lines || []) box.append(el("p", "", line));
|
|
734
|
-
|
|
790
|
+
// its other lines unfolded on a tap (the change drawn is kept: a new answer folds nothing)
|
|
791
|
+
const rest = Array.isArray(c.rest) ? c.rest : [];
|
|
792
|
+
if (c.more && rest.length) {
|
|
793
|
+
const hid = el("div", "rest");
|
|
794
|
+
hid.hidden = true;
|
|
795
|
+
hid.id = `rest-${c.id}`;
|
|
796
|
+
for (const line of rest) hid.append(el("p", "", line));
|
|
797
|
+
if (c.more > rest.length) hid.append(el("p", "more", t("log.lines", { n: c.more - rest.length })));
|
|
798
|
+
const unfold = el("button", "unfold", t("log.lines", { n: c.more }));
|
|
799
|
+
unfold.type = "button";
|
|
800
|
+
unfold.setAttribute("aria-expanded", "false");
|
|
801
|
+
unfold.setAttribute("aria-controls", hid.id);
|
|
802
|
+
unfold.onclick = () => {
|
|
803
|
+
const open = hid.hidden;
|
|
804
|
+
hid.hidden = !open;
|
|
805
|
+
unfold.setAttribute("aria-expanded", String(open));
|
|
806
|
+
unfold.textContent = open ? t("log.fold") : t("log.lines", { n: c.more });
|
|
807
|
+
};
|
|
808
|
+
box.append(hid, unfold);
|
|
809
|
+
} else if (c.more) box.append(el("p", "more", t("log.lines", { n: c.more })));
|
|
735
810
|
li.append(time, box);
|
|
736
811
|
return li;
|
|
737
812
|
};
|
|
@@ -775,6 +850,17 @@
|
|
|
775
850
|
}
|
|
776
851
|
renderPill();
|
|
777
852
|
};
|
|
853
|
+
// past midnight the changes of the day before get their day (asked every second, drawn again once a day)
|
|
854
|
+
let logDay = "";
|
|
855
|
+
const restampLog = (now = new Date()) => {
|
|
856
|
+
const day = now.toDateString();
|
|
857
|
+
if (day === logDay) return;
|
|
858
|
+
logDay = day;
|
|
859
|
+
for (const li of logItems.values()) {
|
|
860
|
+
const time = li.children[0];
|
|
861
|
+
time.textContent = logTime(Date.parse(time.dateTime), now);
|
|
862
|
+
}
|
|
863
|
+
};
|
|
778
864
|
const renderPill = () => {
|
|
779
865
|
if (window.scrollY <= 4) S.newCount = 0;
|
|
780
866
|
$("newPill").hidden = !S.newCount || S.view !== "main";
|
|
@@ -980,6 +1066,7 @@
|
|
|
980
1066
|
closeTrees();
|
|
981
1067
|
if (S.tree !== id) {
|
|
982
1068
|
S.shown = null;
|
|
1069
|
+
S.waitLoaded = null;
|
|
983
1070
|
S.logTree = "";
|
|
984
1071
|
for (const li of logItems.values()) li.remove();
|
|
985
1072
|
logItems.clear();
|
|
@@ -1078,6 +1165,10 @@
|
|
|
1078
1165
|
$("personClear").setAttribute("aria-label", t("remove"));
|
|
1079
1166
|
|
|
1080
1167
|
$("plainRetry").onclick = () => start("");
|
|
1168
|
+
$("waitMore").onclick = () => {
|
|
1169
|
+
const w = S.waitLoaded && S.waitLoaded.tree === S.tree ? S.waitLoaded : S.shown && S.shown.waiting;
|
|
1170
|
+
if (w && Array.isArray(w.items)) void loadWait(w.items.length + WAIT_PAGE);
|
|
1171
|
+
};
|
|
1081
1172
|
$("photoBtn").onclick = $("morePhotoBtn").onclick = () => $("photoInput").click();
|
|
1082
1173
|
$("galleryBtn").onclick = $("moreGalleryBtn").onclick = () => $("galleryInput").click();
|
|
1083
1174
|
$("photoInput").onchange = (e) => {
|
|
@@ -1210,6 +1301,7 @@
|
|
|
1210
1301
|
ticks++;
|
|
1211
1302
|
if (S.view === "main") {
|
|
1212
1303
|
renderHeader();
|
|
1304
|
+
restampLog();
|
|
1213
1305
|
if (ticks % 10 === 0 && document.visibilityState !== "hidden") void refresh();
|
|
1214
1306
|
} else if (S.view === "unpaired" && ticks % 10 === 0) void whoami().then((w) => w === "paired" && void refresh());
|
|
1215
1307
|
}, 1000);
|
package/dist/cli/ui.js
CHANGED
|
@@ -1304,7 +1304,9 @@ export const UI = {
|
|
|
1304
1304
|
"ui.phone.page.waiting.title": "Waiting",
|
|
1305
1305
|
"ui.phone.page.waiting.none": "Nothing is waiting.",
|
|
1306
1306
|
"ui.phone.page.waiting.where": "Decisions are made on the computer in the Research overview.",
|
|
1307
|
-
"ui.phone.page.waiting.more": "{n, plural, one {
|
|
1307
|
+
"ui.phone.page.waiting.more": "{n, plural, one {Show more · # left} other {Show more · # left}}",
|
|
1308
|
+
"ui.phone.page.waiting.loading": "Loading…",
|
|
1309
|
+
"ui.phone.page.waiting.rest": "{n, plural, one {and # more in the Research overview on the computer} other {and # more in the Research overview on the computer}}",
|
|
1308
1310
|
"ui.phone.page.day": "Last 24 h",
|
|
1309
1311
|
"ui.phone.page.day.people": "{n, plural, one {person} other {people}}",
|
|
1310
1312
|
"ui.phone.page.day.sources": "{n, plural, one {source} other {sources}}",
|
|
@@ -1313,6 +1315,7 @@ export const UI = {
|
|
|
1313
1315
|
"ui.phone.page.log.more": "Older changes are in the Research overview on the computer.",
|
|
1314
1316
|
"ui.phone.page.log.new": "{n, plural, one {# new change ↑} other {# new changes ↑}}",
|
|
1315
1317
|
"ui.phone.page.log.lines": "{n, plural, one {and # more} other {and # more}}",
|
|
1318
|
+
"ui.phone.page.log.fold": "Show less",
|
|
1316
1319
|
"ui.phone.page.log.none": "Nothing saved yet.",
|
|
1317
1320
|
"ui.settings.phone": "Phone connection (photos from a phone into the research on the local network): {state}",
|
|
1318
1321
|
"ui.settings.phone.on": "on, paired phones: {n}",
|
package/dist/core/phone-send.js
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
// (this computer converts HEIC: else the page asks the phone for JPEG), max (the largest
|
|
6
6
|
// file, MB); of the tree asked, what it shows (phone-watch.ts): now, lastWorked, waiting,
|
|
7
7
|
// day, latest (a tree asked that is gone: 404 phone.tree-gone, as /phone/log)
|
|
8
|
-
// GET /phone/log?tree=&limit= the last changes the research saved, newest first ({changes: [{id, at, lines, more
|
|
9
|
-
// more}), at most 20
|
|
8
|
+
// GET /phone/log?tree=&limit= the last changes the research saved, newest first ({changes: [{id, at, lines, more?,
|
|
9
|
+
// rest?}], more}), at most 20; rest: the lines of a change past the 3 shown
|
|
10
|
+
// GET /phone/waiting?tree=&from=&limit= more of what waits than the state gives ({n, items: [{what, on?}]}), of the
|
|
11
|
+
// same list, at most 200 at once
|
|
10
12
|
// GET /phone/people?tree=&q= up to 8 people of the tree matching q (2 letters at least; case, accents and NFD
|
|
11
13
|
// aside): {id, name, birth, death, place}
|
|
12
14
|
// GET /phone/media/<sha256>?tree= is that content in the research already ({known: false} or {known, kind})
|
|
@@ -30,7 +32,7 @@ import { waysHere } from "./readable.js";
|
|
|
30
32
|
import { birthEvent, deathEvent, displayName, findPersons } from "./people.js";
|
|
31
33
|
import { yearLabel } from "./gdate.js";
|
|
32
34
|
import { foldText } from "./text.js";
|
|
33
|
-
import { LOG_PHONE, treeLog, treeMarks, treeOverview } from "./phone-watch.js";
|
|
35
|
+
import { LOG_PHONE, WAITING_ASKED, WAITING_SHOWN, treeLog, treeMarks, treeOverview, treeWaiting } from "./phone-watch.js";
|
|
34
36
|
/** How often the batches of a phone are looked at, and the parts of uploads cut short. */
|
|
35
37
|
const INCOMING_EVERY_MS = 60 * 60_000;
|
|
36
38
|
export function phoneSend(env, note) {
|
|
@@ -128,6 +130,22 @@ export function phoneSend(env, note) {
|
|
|
128
130
|
const limit = Math.min(LOG_PHONE, Math.max(1, Number.parseInt(url.searchParams.get("limit") ?? "", 10) || LOG_PHONE));
|
|
129
131
|
reply(200, treeLog(t.root, env, limit));
|
|
130
132
|
};
|
|
133
|
+
if (req.method === "GET" && p === "/phone/waiting")
|
|
134
|
+
return () => {
|
|
135
|
+
if (foreign())
|
|
136
|
+
return refuse(403, refusal("only the phone's page asks", "phone.origin"));
|
|
137
|
+
req.resume();
|
|
138
|
+
const t = treeOf();
|
|
139
|
+
if (!t)
|
|
140
|
+
return gone();
|
|
141
|
+
const number = (name, or) => {
|
|
142
|
+
const n = Number.parseInt(url.searchParams.get(name) ?? "", 10);
|
|
143
|
+
return Number.isFinite(n) ? n : or;
|
|
144
|
+
};
|
|
145
|
+
const from = Math.max(0, number("from", WAITING_SHOWN));
|
|
146
|
+
const limit = Math.min(WAITING_ASKED, Math.max(1, number("limit", WAITING_ASKED)));
|
|
147
|
+
reply(200, treeWaiting(t.root, env, from, limit));
|
|
148
|
+
};
|
|
131
149
|
if (req.method === "GET" && p === "/phone/people")
|
|
132
150
|
return () => {
|
|
133
151
|
if (foreign())
|
package/dist/core/phone-watch.js
CHANGED
|
@@ -21,10 +21,13 @@ import { ALL_PREFIXES } from "./model.js";
|
|
|
21
21
|
/** How long the overview of the tree shown is kept (the page asks every 10 s); the marks of the other trees in the switcher. */
|
|
22
22
|
export const OVERVIEW_MS = 15_000;
|
|
23
23
|
const MARKS_MS = 60_000;
|
|
24
|
-
/** At most so many changes the page shows; so many lines of one change, and
|
|
24
|
+
/** At most so many changes the page shows; so many lines of one change shown, and kept to be unfolded on the phone. */
|
|
25
25
|
export const LOG_PHONE = 20;
|
|
26
26
|
const LINES_PER_CHANGE = 3;
|
|
27
|
-
const
|
|
27
|
+
const LINES_KEPT = 200;
|
|
28
|
+
/** So many of what waits the overview gives; more asked by the page, at most so many at once. */
|
|
29
|
+
export const WAITING_SHOWN = 20;
|
|
30
|
+
export const WAITING_ASKED = 200;
|
|
28
31
|
// A record's ID standing alone (every prefix of the model, a document read's D + its time + 4 hex); a list of them
|
|
29
32
|
// (", ", a dash, a short word between two: "H0005, H0010, …", "T0400 und T0401") goes as one.
|
|
30
33
|
const ALONE = String.raw `(?<![\p{L}\p{M}\p{N}])`;
|
|
@@ -125,6 +128,7 @@ function waitingOf(tree, env, all) {
|
|
|
125
128
|
});
|
|
126
129
|
}
|
|
127
130
|
const marksKept = new Map();
|
|
131
|
+
// what waits is kept whole beside the overview: the page asks for more of the same list the overview began
|
|
128
132
|
const overviewKept = new Map();
|
|
129
133
|
const logKept = new Map();
|
|
130
134
|
/** An agent at work on a tree, and how much waits for the person (none of either in an archive). */
|
|
@@ -154,6 +158,7 @@ export function treeOverview(root, env) {
|
|
|
154
158
|
return { ...kept.value, ...day, ...latest };
|
|
155
159
|
const tree = Tree.open(root, env);
|
|
156
160
|
let value;
|
|
161
|
+
let waits = [];
|
|
157
162
|
if (actsAsArchive(tree))
|
|
158
163
|
value = { now: [], waiting: { n: 0, items: [] } };
|
|
159
164
|
else {
|
|
@@ -171,18 +176,32 @@ export function treeOverview(root, env) {
|
|
|
171
176
|
});
|
|
172
177
|
const sessions = tree.list("session");
|
|
173
178
|
const last = sessions.map((s) => s.ended ?? s.started).sort().at(-1);
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const items = waits.slice(0, WAITING_SHOWN).map(({ item }) => {
|
|
177
|
-
const on = item.on ? forPhone(humanTask(tree, item.on, tree.lang)) : "";
|
|
178
|
-
return { what: forPhone(humanTask(tree, item.what, tree.lang)) || ui(tree.lang, "ui.phone.waiting.item"), ...(on ? { on } : {}) };
|
|
179
|
-
});
|
|
179
|
+
waits = waitingOf(tree, env, all);
|
|
180
|
+
const items = waits.slice(0, WAITING_SHOWN).map((w) => waitingLine(tree, w));
|
|
180
181
|
value = { now, ...(!now.length && last ? { lastWorked: last } : {}), waiting: { n: waits.length, items } };
|
|
181
182
|
marksKept.set(root, { at: Date.now(), value: { working: now.some((l) => !l.paused), waiting: waits.length } });
|
|
182
183
|
}
|
|
183
|
-
overviewKept.set(root, { at: Date.now(), key, value });
|
|
184
|
+
overviewKept.set(root, { at: Date.now(), key, value, tree, waits });
|
|
184
185
|
return { ...value, ...day, ...latest };
|
|
185
186
|
}
|
|
187
|
+
/** One of what waits as the phone shows it, whole; a text of nothing but IDs, paths and links: said in general. */
|
|
188
|
+
function waitingLine(tree, { item }) {
|
|
189
|
+
const on = item.on ? forPhone(humanTask(tree, item.on, tree.lang)) : "";
|
|
190
|
+
return { what: forPhone(humanTask(tree, item.what, tree.lang)) || ui(tree.lang, "ui.phone.waiting.item"), ...(on ? { on } : {}) };
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* More of what waits than the overview gives: `limit` of them (at most WAITING_ASKED) from `from`, of the same list the
|
|
194
|
+
* overview of the tree began (kept as long as it is), each text as cleaned for the phone; n: how many wait in all.
|
|
195
|
+
*/
|
|
196
|
+
export function treeWaiting(root, env, from, limit) {
|
|
197
|
+
treeOverview(root, env);
|
|
198
|
+
const kept = overviewKept.get(root);
|
|
199
|
+
if (!kept?.tree)
|
|
200
|
+
return { n: 0, items: [] };
|
|
201
|
+
const start = Math.max(0, Math.floor(from));
|
|
202
|
+
const tree = kept.tree;
|
|
203
|
+
return { n: kept.waits.length, items: kept.waits.slice(start, start + Math.min(WAITING_ASKED, Math.max(0, Math.floor(limit)))).map((w) => waitingLine(tree, w)) };
|
|
204
|
+
}
|
|
186
205
|
/** The last changes the research saved, newest first, as the bridge's /log gives them (each commit, its lines). */
|
|
187
206
|
export function treeLog(root, env, limit = LOG_PHONE) {
|
|
188
207
|
const h = head(root);
|
|
@@ -199,7 +218,7 @@ export function treeLog(root, env, limit = LOG_PHONE) {
|
|
|
199
218
|
id: e.head.slice(0, 12),
|
|
200
219
|
at: e.at,
|
|
201
220
|
lines: lines.slice(0, LINES_PER_CHANGE),
|
|
202
|
-
...(lines.length > LINES_PER_CHANGE ? { more: lines.length - LINES_PER_CHANGE } : {}),
|
|
221
|
+
...(lines.length > LINES_PER_CHANGE ? { more: lines.length - LINES_PER_CHANGE, rest: lines.slice(LINES_PER_CHANGE, LINES_PER_CHANGE + LINES_KEPT) } : {}),
|
|
203
222
|
}));
|
|
204
223
|
const value = { changes, more: all.length > limit };
|
|
205
224
|
logKept.set(root, { head: h, limit, value });
|
package/dist/core/tree.js
CHANGED
|
@@ -27,7 +27,7 @@ import { lastBackup } from "./backup.js";
|
|
|
27
27
|
export const TREE_FILE = "strom.json";
|
|
28
28
|
// between releases the candidate being made (a bridge run from the sources says what it is: the Strom app goes by it);
|
|
29
29
|
// at a release the release's own (package.json)
|
|
30
|
-
export const VERSION = "1.18.0-beta.
|
|
30
|
+
export const VERSION = "1.18.0-beta.7";
|
|
31
31
|
const GITIGNORE = `# Strom Research — local state and large files are not versioned
|
|
32
32
|
.strom/
|
|
33
33
|
/media/
|