@volter/world-console 3.0.17 → 3.0.19
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.css +2 -0
- package/client/console.tsx +34 -8
- package/dist/client/console.bundle.js +11 -11
- package/dist/client/console.css +2 -0
- package/dist/client/console.tsx +34 -8
- package/dist/client/main.bundle.js +73 -73
- package/package.json +3 -3
package/dist/client/console.css
CHANGED
|
@@ -624,6 +624,8 @@ main.app.home { max-width: none; }
|
|
|
624
624
|
.section-tag button { display: inline-flex; align-items: center; gap: 6px; padding: 2px 8px; border: 0; border-radius: 4px; background: var(--surface-2); color: var(--ink); font: 600 12px/1.4 var(--sans); }
|
|
625
625
|
.section-tag button.on { background: var(--accent); color: var(--surface); }
|
|
626
626
|
.section-tag .mark { width: 14px; height: 14px; }
|
|
627
|
+
.frame-name { display: inline-flex; align-items: center; gap: 7px; cursor: grab; touch-action: none; }
|
|
628
|
+
.section-tag button { cursor: grab; touch-action: none; }
|
|
627
629
|
.name-field { font: 600 12px/1.4 var(--sans); padding: 2px 8px; border: 1px solid var(--accent); border-radius: 4px; background: var(--surface); color: var(--ink); outline: none; }
|
|
628
630
|
.board-tabs .name-field { border-radius: 999px; font-weight: 400; font-size: 12.5px; }
|
|
629
631
|
/* a section's edges are grabbed as wide on screen at every zoom (xyflow scales only its corner handles); the selected
|
package/dist/client/console.tsx
CHANGED
|
@@ -1706,9 +1706,9 @@ const frameUrl = (name: string, f: BoardFrame): string | null => (f.at && 'site'
|
|
|
1706
1706
|
const shownOn = (b: SavedBoard, all: BoardFrame[]): BoardFrame[] => b.include
|
|
1707
1707
|
? all.filter((f) => b.include!.includes(frameKey(f)) || sectionKeys(f).some((k) => b.include!.includes(k)))
|
|
1708
1708
|
: all.filter((f) => !(b.hidden ?? []).includes(frameKey(f)));
|
|
1709
|
-
type Screen = { world: string; frame: BoardFrame; url: string | null; active: boolean; picked: boolean; boards: SavedBoard[]; addTo: (board: string, key: string) => void; remove: { label: string; act: (key: string) => void } | null; compare: (key: string) => void };
|
|
1709
|
+
type Screen = { world: string; frame: BoardFrame; url: string | null; active: boolean; picked: boolean; boards: SavedBoard[]; addTo: (board: string, key: string) => void; remove: { label: string; act: (key: string) => void } | null; compare: (key: string) => void; /** its name moves it, as a canvas app's frame name does */ grab: (e: React.PointerEvent) => void };
|
|
1710
1710
|
function ScreenFrame({ data }: NodeProps<Node<Screen>>): React.JSX.Element {
|
|
1711
|
-
const { frame, url, active, picked, boards, addTo, remove, compare } = data;
|
|
1711
|
+
const { frame, url, active, picked, boards, addTo, remove, compare, grab } = data;
|
|
1712
1712
|
const key = frameKey(frame);
|
|
1713
1713
|
const others = boards.filter((b) => b.include && !b.include.includes(key) && !sectionKeys(frame).some((k) => b.include!.includes(k)));
|
|
1714
1714
|
// its name is drawn the same size at every zoom while the head above it shrinks: zoomed out past the point the name fits
|
|
@@ -1718,7 +1718,7 @@ function ScreenFrame({ data }: NodeProps<Node<Screen>>): React.JSX.Element {
|
|
|
1718
1718
|
<>
|
|
1719
1719
|
{/* its name, as a canvas app labels its frames; what can be done to it once it is selected */}
|
|
1720
1720
|
<NodeToolbar isVisible={roomy || picked || active} position={Position.Top} align="start" className="frame-label">
|
|
1721
|
-
<Mark vendor={frame.vendor} /><span>{frame.title}</span>
|
|
1721
|
+
<span className="frame-name" onPointerDown={grab}><Mark vendor={frame.vendor} /><span>{frame.title}</span></span>
|
|
1722
1722
|
{frame.changes ? <button type="button" className="frame-changed nodrag" onClick={() => compare(key)} title={`${counted(frame.changes, 'change')} here, not in a changeset: compare before and now`}><Icon name="square-dot" size={14} />{frame.changes}</button>
|
|
1723
1723
|
: picked ? <button type="button" className="ghost nodrag" onClick={() => compare(key)} title="Compare with a branch of this World" aria-label={`Compare ${frame.title} with a branch`}><Icon name="file-diff" size={14} /></button> : null}
|
|
1724
1724
|
{picked && others.length ? <select className="nodrag frame-add" value="" aria-label={`Add ${frame.title} to a board`} onChange={(e) => { if (e.target.value) addTo(e.target.value, key); }}><option value="">Add to board…</option>{others.map((b) => <option key={b.id} value={b.id}>{b.name}</option>)}</select> : null}
|
|
@@ -1749,7 +1749,7 @@ function NameField({ name, label, done }: { name: string; label: string; done: (
|
|
|
1749
1749
|
}
|
|
1750
1750
|
/** A section, as a canvas app's: a region of the board with a name, holding whatever frames sit in it. Its name stays the
|
|
1751
1751
|
* same size at every zoom; selected, it can be resized from its edges, renamed (double-click the name) or removed. */
|
|
1752
|
-
type SectionData = { key: string; name: string; vendor: string | null; /** renamed by someone, so its name is kept */ named: boolean; picked: boolean; editable: boolean; renaming: boolean; pick: (key: string) => void; rename: (key: string) => void; renamed: (key: string, next: string | null) => void; remove: (key: string) => void; resized: () => void };
|
|
1752
|
+
type SectionData = { key: string; name: string; vendor: string | null; /** renamed by someone, so its name is kept */ named: boolean; picked: boolean; editable: boolean; renaming: boolean; pick: (key: string) => void; rename: (key: string) => void; renamed: (key: string, next: string | null) => void; remove: (key: string) => void; resized: () => void; /** its name moves it, with what it holds */ grab: (e: React.PointerEvent) => void };
|
|
1753
1753
|
function SectionBox({ data }: NodeProps<Node<SectionData>>): React.JSX.Element {
|
|
1754
1754
|
return (
|
|
1755
1755
|
<>
|
|
@@ -1757,7 +1757,7 @@ function SectionBox({ data }: NodeProps<Node<SectionData>>): React.JSX.Element {
|
|
|
1757
1757
|
{/* inside its top-left corner, as a canvas app names a section: it never reaches a neighbouring section at any zoom */}
|
|
1758
1758
|
<NodeToolbar isVisible position={Position.Top} align="start" offset={-30} className="section-tag">
|
|
1759
1759
|
{data.renaming ? <NameField name={data.name} label="Name this section" done={(next) => data.renamed(data.key, next)} />
|
|
1760
|
-
: <button type="button" className={data.picked ? 'nodrag on' : 'nodrag'} onClick={() => data.pick(data.key)} onDoubleClick={() => { if (data.editable) data.rename(data.key); }} title={data.editable ? 'Double-click to rename' : undefined}>
|
|
1760
|
+
: <button type="button" className={data.picked ? 'nodrag on' : 'nodrag'} onPointerDown={data.grab} onClick={() => data.pick(data.key)} onDoubleClick={() => { if (data.editable) data.rename(data.key); }} title={data.editable ? 'Double-click to rename' : undefined}>
|
|
1761
1761
|
{data.vendor ? <Mark vendor={data.vendor} /> : null}<span>{data.name}</span>
|
|
1762
1762
|
</button>}
|
|
1763
1763
|
{data.picked && data.editable && !data.renaming ? <button type="button" className="ghost nodrag" onClick={() => data.remove(data.key)} aria-label={`Remove the section ${data.name}`} title="Remove the section (its frames stay)">✕</button> : null}
|
|
@@ -2130,10 +2130,34 @@ function CanvasSurface({ name, token, session, retry, snapshot, back, canWrite }
|
|
|
2130
2130
|
setPicked(`sec:${key}`);
|
|
2131
2131
|
change(board.id, (b) => ({ ...b, sections: { ...(b.sections ?? {}), [key]: { x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.w), h: Math.round(r.h), name: `Section ${count}` } } }));
|
|
2132
2132
|
};
|
|
2133
|
+
/** A node moved by its name, which sits outside it (the canvas takes no drag there): the same drag as the node's own,
|
|
2134
|
+
* carrying what a section holds and kept when it ends; a press that does not move is a click. */
|
|
2135
|
+
const grab = (id: string) => (e: React.PointerEvent): void => {
|
|
2136
|
+
if (!editable || e.button !== 0) return;
|
|
2137
|
+
const node = flow.getNode(id); if (!node) return;
|
|
2138
|
+
const el = e.currentTarget as HTMLElement; el.setPointerCapture(e.pointerId);
|
|
2139
|
+
const sx = e.clientX; const sy = e.clientY; const from = { ...node.position };
|
|
2140
|
+
let moved = false;
|
|
2141
|
+
const at = (ev: PointerEvent): Node => { const z = flow.getZoom(); return { ...node, position: { x: from.x + (ev.clientX - sx) / z, y: from.y + (ev.clientY - sy) / z } }; };
|
|
2142
|
+
const move = (ev: PointerEvent): void => {
|
|
2143
|
+
if (!moved && Math.hypot(ev.clientX - sx, ev.clientY - sy) < 4) return;
|
|
2144
|
+
if (!moved) { moved = true; onDragStart(null, node); }
|
|
2145
|
+
const n = at(ev);
|
|
2146
|
+
setNodes((ns) => ns.map((x) => (x.id === id ? { ...x, position: n.position } : x)));
|
|
2147
|
+
onDrag(null, n);
|
|
2148
|
+
};
|
|
2149
|
+
const up = (ev: PointerEvent): void => {
|
|
2150
|
+
el.removeEventListener('pointermove', move); el.removeEventListener('pointerup', up); el.removeEventListener('pointercancel', up);
|
|
2151
|
+
// a drag is not also a click on the name (which would select or open what it names)
|
|
2152
|
+
if (moved) { onDragStop(null, at(ev)); el.addEventListener('click', (c) => { c.stopPropagation(); c.preventDefault(); }, { capture: true, once: true }); }
|
|
2153
|
+
else if (node.type === 'screen') setPicked(id);
|
|
2154
|
+
};
|
|
2155
|
+
el.addEventListener('pointermove', move); el.addEventListener('pointerup', up); el.addEventListener('pointercancel', up);
|
|
2156
|
+
};
|
|
2133
2157
|
// what each node shows: in use, selected, and what can be done to it
|
|
2134
2158
|
const shownNodes = nodes.map((n) => (n.type === 'section'
|
|
2135
|
-
? { ...n, data: { ...n.data, picked: picked === n.id, editable, renaming: editable && renaming === (n.data as { key: string }).key, pick: (k: string) => setPicked(`sec:${k}`), rename: renameSection, renamed: renamedSection, remove: removeSection, resized: () => requestAnimationFrame(() => { const k = (n.data as { key: string }).key; keep(groupFrames(k), [k]); }) } }
|
|
2136
|
-
: { ...n, data: { ...n.data, active: active === n.id, picked: picked === n.id, boards: editable ? others : [], addTo, remove: !editable || (board.include && !board.include.includes(n.id)) ? null : removeFrame, compare: (k: string) => { setPicked(k); setComparing(k); } } }));
|
|
2159
|
+
? { ...n, data: { ...n.data, grab: grab(n.id), picked: picked === n.id, editable, renaming: editable && renaming === (n.data as { key: string }).key, pick: (k: string) => setPicked(`sec:${k}`), rename: renameSection, renamed: renamedSection, remove: removeSection, resized: () => requestAnimationFrame(() => { const k = (n.data as { key: string }).key; keep(groupFrames(k), [k]); }) } }
|
|
2160
|
+
: { ...n, data: { ...n.data, grab: grab(n.id), active: active === n.id, picked: picked === n.id, boards: editable ? others : [], addTo, remove: !editable || (board.include && !board.include.includes(n.id)) ? null : removeFrame, compare: (k: string) => { setPicked(k); setComparing(k); } } }));
|
|
2137
2161
|
// the keys a canvas app answers: Esc leaves a frame, then the selection; Delete removes what is selected; ⇧S draws a section
|
|
2138
2162
|
useEffect(() => {
|
|
2139
2163
|
const on = (e: KeyboardEvent): void => {
|
|
@@ -2179,7 +2203,9 @@ function CanvasSurface({ name, token, session, retry, snapshot, back, canWrite }
|
|
|
2179
2203
|
onPaneClick={() => { setActive(null); setPicked(null); }}
|
|
2180
2204
|
minZoom={0.03} maxZoom={1.5} nodesConnectable={false} elementsSelectable={false}
|
|
2181
2205
|
panOnScroll zoomOnPinch zoomOnScroll={false} zoomOnDoubleClick={false} colorMode={dark ? 'dark' : 'light'} proOptions={{ hideAttribution: true }}>
|
|
2182
|
-
|
|
2206
|
+
{/* dots the same size on screen at every zoom, their spacing doubling as the board zooms out (as a canvas app's
|
|
2207
|
+
grid steps), so the board shows where it is and how far it moved at any zoom */}
|
|
2208
|
+
<Background variant={BackgroundVariant.Dots} gap={28 * 2 ** Math.max(0, Math.ceil(Math.log2(16 / (28 * zoom))))} size={2 / zoom} />
|
|
2183
2209
|
<Panel position="top-left" className="float layers">
|
|
2184
2210
|
<div className="float-head">
|
|
2185
2211
|
<button type="button" className="back" onClick={back} aria-label={`Back to ${name}`}>←</button>
|