@volter/world-console 2.0.6 → 2.0.8
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/client/console.tsx +51 -9
- package/dist/client/console.bundle.js +41 -41
- package/dist/client/console.tsx +51 -9
- package/package.json +3 -3
package/dist/client/console.tsx
CHANGED
|
@@ -427,6 +427,9 @@ type Placed = { twin: string; position: number; entry: Entry };
|
|
|
427
427
|
type Timeline = { entries: Placed[]; next: string | null };
|
|
428
428
|
type Diff = { base: { id: string; at: string | null }; changes: Array<{ twin: string; entry: Entry }> };
|
|
429
429
|
type Branch = { name: string; at?: { instant?: string }; createdAt: string; expiresAt: string | null };
|
|
430
|
+
/** The branches door: the host's snapshots of this World, and the World's own named branches (`volter world branch`),
|
|
431
|
+
* one of which, `checkedOut`, is the one this serve holds. */
|
|
432
|
+
type BranchesDoor = { branches: Branch[]; checkedOut?: string; named?: Array<{ name: string; running: boolean }> };
|
|
430
433
|
/** What the World's session answered: its scope; `unknown` when the host has no session door (a write then says). */
|
|
431
434
|
type Scope = 'pending' | 'read' | 'write' | 'unknown';
|
|
432
435
|
|
|
@@ -502,7 +505,7 @@ function WorldView({ name, token, twin, tab, arg, admin, snapshot, onReturn, ope
|
|
|
502
505
|
const [clockVersion, setClockVersion] = useState(0);
|
|
503
506
|
const clock = useDoor<Clock>(token, `/-/${name}/clock`, tick + clockVersion);
|
|
504
507
|
const twins = useDoor<{ twins: Twin[] }>(token, `/-/${name}/twins`);
|
|
505
|
-
const branches = useDoor<
|
|
508
|
+
const branches = useDoor<BranchesDoor>(token, `/-/${name}/branches`, clockVersion);
|
|
506
509
|
const map = useDoor<WorldMap>(token, `/-/${name}/map`, Math.floor(tick / 2));
|
|
507
510
|
// branches are the host's: a host that does not answer the door has no "as of", and the console shows none
|
|
508
511
|
const hasBranches = !(branches.state === 'error' && (branches.status === 404 || branches.status === 405));
|
|
@@ -613,13 +616,13 @@ function Connect({ name }: { name: string }): React.JSX.Element {
|
|
|
613
616
|
|
|
614
617
|
/** The branch picker, between the World's pages and the branch's (where Neon's sits): this World live, or one of the
|
|
615
618
|
* host's branches of it; a snapshot names its parent as the way back. */
|
|
616
|
-
function BranchPicker({ name, snapshot, branches, opens }: { name: string; snapshot: Snapshot | null; branches: Load<
|
|
619
|
+
function BranchPicker({ name, snapshot, branches, opens }: { name: string; snapshot: Snapshot | null; branches: Load<BranchesDoor> | null; opens: (name: string) => boolean }): React.JSX.Element {
|
|
617
620
|
const short = (n: string): string => n.split('/').pop() ?? n;
|
|
618
621
|
const live = snapshot?.parent ?? name;
|
|
619
622
|
const others = branches?.state === 'ok' ? branches.data.branches.filter((b) => b.name !== name) : [];
|
|
620
623
|
return (
|
|
621
624
|
<details className="menu branch-picker">
|
|
622
|
-
<summary><svg className="branch-glyph" viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><path fill="currentColor" d="M5 3.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Zm0 2.122a2.25 2.25 0 1 0-1.5 0v5.256a2.25 2.25 0 1 0 1.5 0V9.5h4.25A2.25 2.25 0 0 0 11.5 7.25v-.878a2.25 2.25 0 1 0-1.5 0v.878a.75.75 0 0 1-.75.75H5V5.372ZM4.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm6.5-9.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z" /></svg><span className="branch-name">{snapshot ? short(name) : short(live)}</span><span className="badge">{snapshot ? 'snapshot' : 'live'}</span></summary>
|
|
625
|
+
<summary><svg className="branch-glyph" viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><path fill="currentColor" d="M5 3.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Zm0 2.122a2.25 2.25 0 1 0-1.5 0v5.256a2.25 2.25 0 1 0 1.5 0V9.5h4.25A2.25 2.25 0 0 0 11.5 7.25v-.878a2.25 2.25 0 1 0-1.5 0v.878a.75.75 0 0 1-.75.75H5V5.372ZM4.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm6.5-9.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z" /></svg><span className="branch-name">{snapshot ? short(name) : (branches?.state === 'ok' && branches.data.checkedOut) || short(live)}</span><span className="badge">{snapshot ? 'snapshot' : 'live'}</span></summary>
|
|
623
626
|
<div className="popover">
|
|
624
627
|
<p className="mode">Branch</p>
|
|
625
628
|
<ul className="branch-list">
|
|
@@ -631,7 +634,17 @@ function BranchPicker({ name, snapshot, branches, opens }: { name: string; snaps
|
|
|
631
634
|
: <span className="mode" title="Its token was handed to whoever made it">{short(b.name)}</span>}</li>
|
|
632
635
|
))}
|
|
633
636
|
</ul>
|
|
634
|
-
{branches?.state === 'ok' &&
|
|
637
|
+
{branches?.state === 'ok' && (branches.data.named?.length ?? 0) > 1 ? (
|
|
638
|
+
<>
|
|
639
|
+
<p className="mode">This World's branches</p>
|
|
640
|
+
<ul className="branch-list">
|
|
641
|
+
{branches.data.named!.map((b) => (
|
|
642
|
+
<li key={b.name}><span aria-current={b.name === branches.data.checkedOut ? 'true' : undefined}>{b.name} <span className="mode">{b.name === branches.data.checkedOut ? 'served here' : <code>volter world checkout {b.name}</code>}</span></span></li>
|
|
643
|
+
))}
|
|
644
|
+
</ul>
|
|
645
|
+
</>
|
|
646
|
+
) : null}
|
|
647
|
+
{branches?.state === 'ok' && others.length === 0 && !snapshot && (branches.data.named?.length ?? 0) <= 1 ? <p className="mode">No branches yet. “View an earlier moment…” opens one.</p> : null}
|
|
635
648
|
</div>
|
|
636
649
|
</details>
|
|
637
650
|
);
|
|
@@ -700,7 +713,7 @@ function BranchOverview({ name, token, map, vendors, tick }: { name: string; tok
|
|
|
700
713
|
* Neon with how to connect and the branches. A World's live preview is its vendors' own screens; beside them, how an
|
|
701
714
|
* app connects and the branches; under them, what just happened. Counts sit with what they count, not in a row. */
|
|
702
715
|
const THUMB_SCALE = 0.2;
|
|
703
|
-
function WorldOverview({ name, token, map, vendors, twins, session, branches, canWrite, tick, snapshotOf }: { name: string; token: string; map: Load<WorldMap>; vendors: string[]; twins: Load<{ twins: Twin[] }>; session: MirrorSession; branches: Load<
|
|
716
|
+
function WorldOverview({ name, token, map, vendors, twins, session, branches, canWrite, tick, snapshotOf }: { name: string; token: string; map: Load<WorldMap>; vendors: string[]; twins: Load<{ twins: Twin[] }>; session: MirrorSession; branches: Load<BranchesDoor> | null; canWrite: boolean; tick: number; /** the World this one is a snapshot of, when it is one */ snapshotOf?: string }): React.JSX.Element {
|
|
704
717
|
const diff = useDoor<Diff>(token, `/-/${name}/diff`, Math.floor(tick / 2));
|
|
705
718
|
const recent = useDoor<Timeline>(token, `/-/${name}/timeline?limit=8`, tick);
|
|
706
719
|
const rows = map.state === 'ok' ? map.data.twins : [];
|
|
@@ -867,7 +880,7 @@ function AsOf({ name, token, clock, refused }: { name: string; token: string; cl
|
|
|
867
880
|
}
|
|
868
881
|
|
|
869
882
|
/** Overview: what the World holds, one card per twin (a resource explorer), and the branches cut from it. */
|
|
870
|
-
function Overview({ name, token, canWrite, branches, opens, onBranchesChanged, refused }: { name: string; token: string; canWrite: boolean; branches: Load<
|
|
883
|
+
function Overview({ name, token, canWrite, branches, opens, onBranchesChanged, refused }: { name: string; token: string; canWrite: boolean; branches: Load<BranchesDoor> | null; opens: (name: string) => boolean; onBranchesChanged: () => void; refused: (r: Response) => void }): React.JSX.Element {
|
|
871
884
|
const map = useDoor<WorldMap>(token, `/-/${name}/map`);
|
|
872
885
|
const [note, setNote] = useState<string | null>(null);
|
|
873
886
|
const remove = async (b: string): Promise<void> => {
|
|
@@ -918,11 +931,20 @@ function Overview({ name, token, canWrite, branches, opens, onBranchesChanged, r
|
|
|
918
931
|
/** A World's branches, as a database's are (Neon's Branches page): each a copy of it as of a moment, made here (as of now
|
|
919
932
|
* or a moment you pick, for as long as you say), opened read-only in this tab, compared with its parent (what it changed
|
|
920
933
|
* since it branched), reset to its parent as it is now, or removed. Each is the World's own branches door. */
|
|
921
|
-
function BranchList({ name, token, canWrite, branches, opens, note, remove, onChanged, refused }: { name: string; token: string; canWrite: boolean; branches: Load<
|
|
934
|
+
function BranchList({ name, token, canWrite, branches, opens, note, remove, onChanged, refused }: { name: string; token: string; canWrite: boolean; branches: Load<BranchesDoor>; opens: (name: string) => boolean; note: string | null; remove: (b: string) => Promise<void>; onChanged: () => void; refused: (r: Response) => void }): React.JSX.Element {
|
|
922
935
|
const short = (n: string): string => n.split('/').pop() ?? n;
|
|
923
936
|
const list = branches.state === 'ok' ? branches.data.branches : [];
|
|
924
937
|
const [making, setMaking] = useState(false); const [busy, setBusy] = useState(false); const [error, setError] = useState<string | null>(null);
|
|
925
938
|
const [label, setLabel] = useState(''); const [when, setWhen] = useState<'now' | 'at'>('now'); const [at, setAt] = useState(''); const [hours, setHours] = useState('24');
|
|
939
|
+
// SERVE ANOTHER of the World's own branches here: the World switches under this page, which then reads it afresh
|
|
940
|
+
const [serving, setServing] = useState<string | null>(null); const [serveError, setServeError] = useState<string | null>(null);
|
|
941
|
+
const serveHere = async (branch: string): Promise<void> => {
|
|
942
|
+
setServing(branch); setServeError(null);
|
|
943
|
+
const r = await door(token, `/-/${name}/branches/${encodeURIComponent(branch)}/serve`, { method: 'POST' }).catch(() => null);
|
|
944
|
+
if (r?.ok) { location.reload(); return; }
|
|
945
|
+
setServing(null);
|
|
946
|
+
setServeError(r ? ((await r.json().catch(() => null)) as { error?: string } | null)?.error ?? `${r.status}` : `${ORIGIN} did not answer`);
|
|
947
|
+
};
|
|
926
948
|
// made here: kept with its tokens so it opens in this tab; not `made` (a snapshot's "Return to live" never removes it: it lives as long as it was given)
|
|
927
949
|
const make = async (body: Record<string, unknown>, opened: string): Promise<{ name: string } | string> => {
|
|
928
950
|
const r = await door(token, `/-/${name}/branches`, { method: 'POST', body: JSON.stringify(body) }).catch(() => null);
|
|
@@ -973,7 +995,7 @@ function BranchList({ name, token, canWrite, branches, opens, note, remove, onCh
|
|
|
973
995
|
<table className="dense" aria-label="Branches">
|
|
974
996
|
<thead><tr><th id="branches">Branch</th><th>Parent</th><th>As of</th><th>Made</th><th>Goes away</th><th /></tr></thead>
|
|
975
997
|
<tbody>
|
|
976
|
-
<tr><td><b>{short(name)}</b> <span className="badge">default</span></td><td className="mode">—</td><td className="mode">live</td><td className="mode">—</td><td className="mode">kept</td><td /></tr>
|
|
998
|
+
<tr><td><b>{branches.data.checkedOut ?? short(name)}</b> <span className="badge">{branches.data.checkedOut ? 'served here' : 'default'}</span></td><td className="mode">—</td><td className="mode">live</td><td className="mode">—</td><td className="mode">kept</td><td /></tr>
|
|
977
999
|
{list.map((b) => {
|
|
978
1000
|
const items = menu(b);
|
|
979
1001
|
return (
|
|
@@ -992,8 +1014,28 @@ function BranchList({ name, token, canWrite, branches, opens, note, remove, onCh
|
|
|
992
1014
|
})}
|
|
993
1015
|
</tbody>
|
|
994
1016
|
</table>
|
|
995
|
-
{list.length === 0 ? <p className="mode pad">No
|
|
1017
|
+
{list.length === 0 ? <p className="mode pad">No snapshots yet.</p> : null}
|
|
1018
|
+
</div>
|
|
1019
|
+
) : null}
|
|
1020
|
+
{branches.state === 'ok' && (branches.data.named?.length ?? 0) > 0 ? (
|
|
1021
|
+
<>
|
|
1022
|
+
<p className="lede">This World's own branches (<code>volter world branch</code>). One runs at a time; “Serve here” stops this one's twins and serves that one in its place.</p>
|
|
1023
|
+
{serveError ? <p role="alert">{serveError}</p> : null}
|
|
1024
|
+
<div className="block">
|
|
1025
|
+
<table className="dense" aria-label="World branches">
|
|
1026
|
+
<thead><tr><th>Branch</th><th>State</th><th /></tr></thead>
|
|
1027
|
+
<tbody>
|
|
1028
|
+
{branches.data.named!.map((b) => (
|
|
1029
|
+
<tr key={b.name}>
|
|
1030
|
+
<td><b>{b.name}</b>{b.name === branches.data.checkedOut ? <> <span className="badge">served here</span></> : null}</td>
|
|
1031
|
+
<td className="mode">{b.running ? 'running' : 'stopped'}</td>
|
|
1032
|
+
<td className="num row-actions">{canWrite && b.name !== branches.data.checkedOut ? <button type="button" disabled={serving !== null} onClick={() => void serveHere(b.name)}>{serving === b.name ? 'Switching…' : 'Serve here'}</button> : null}</td>
|
|
1033
|
+
</tr>
|
|
1034
|
+
))}
|
|
1035
|
+
</tbody>
|
|
1036
|
+
</table>
|
|
996
1037
|
</div>
|
|
1038
|
+
</>
|
|
997
1039
|
) : null}
|
|
998
1040
|
<Dialog open={making} onClose={() => { if (!busy) setMaking(false); }} title={`New branch of ${short(name)}`} footer={<><button type="button" onClick={() => setMaking(false)} disabled={busy}>Cancel</button><button type="submit" form="new-branch" className="primary" disabled={busy}>{busy ? 'Making…' : 'Make branch'}</button></>}>
|
|
999
1041
|
<form id="new-branch" onSubmit={(e) => void create(e)} className="stack">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/world-console",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.8",
|
|
4
4
|
"description": "The dashboard of Volter World: a World's pages, the same on a laptop, a self-hosted host and the hosted cloud — mounted by the host at /-/console/. A pure frontend over the World's own doors; its kit (./kit) is the pieces the platform's pages share.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"react-dom": "^19.2.7"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@volter/world-host": "2.0.
|
|
27
|
-
"@volter/world-runtime": "2.0.
|
|
26
|
+
"@volter/world-host": "2.0.13",
|
|
27
|
+
"@volter/world-runtime": "2.0.14",
|
|
28
28
|
"@volter/world-tooling": "0.1.0",
|
|
29
29
|
"@types/bun": "^1.2.20",
|
|
30
30
|
"@types/node": "^24.0.0",
|