@graview/react 0.1.28 → 0.1.30
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/affordances.d.ts +2 -1
- package/dist/affordances.d.ts.map +1 -1
- package/dist/affordances.js +2 -1
- package/dist/affordances.js.map +1 -1
- package/dist/beside-the-map.d.ts +13 -0
- package/dist/beside-the-map.d.ts.map +1 -0
- package/dist/beside-the-map.js +14 -0
- package/dist/beside-the-map.js.map +1 -0
- package/dist/channels.d.ts +2 -1
- package/dist/channels.d.ts.map +1 -1
- package/dist/channels.js +121 -22
- package/dist/channels.js.map +1 -1
- package/dist/connectors.d.ts +27 -2
- package/dist/connectors.d.ts.map +1 -1
- package/dist/connectors.js +144 -23
- package/dist/connectors.js.map +1 -1
- package/dist/context.d.ts +91 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +98 -16
- package/dist/context.js.map +1 -1
- package/dist/drawing.d.ts +2 -0
- package/dist/drawing.d.ts.map +1 -1
- package/dist/drawing.js +2 -0
- package/dist/drawing.js.map +1 -1
- package/dist/drawn.d.ts +1 -1
- package/dist/drawn.d.ts.map +1 -1
- package/dist/drawn.js +1 -1
- package/dist/drawn.js.map +1 -1
- package/dist/edge-signs.d.ts +12 -2
- package/dist/edge-signs.d.ts.map +1 -1
- package/dist/edge-signs.js +95 -9
- package/dist/edge-signs.js.map +1 -1
- package/dist/go.d.ts +28 -10
- package/dist/go.d.ts.map +1 -1
- package/dist/go.js +14 -5
- package/dist/go.js.map +1 -1
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/last-visit.d.ts +18 -0
- package/dist/last-visit.d.ts.map +1 -0
- package/dist/last-visit.js +79 -0
- package/dist/last-visit.js.map +1 -0
- package/dist/linked-reads.d.ts +3 -0
- package/dist/linked-reads.d.ts.map +1 -0
- package/dist/linked-reads.js +50 -0
- package/dist/linked-reads.js.map +1 -0
- package/dist/map-choice.d.ts +55 -0
- package/dist/map-choice.d.ts.map +1 -0
- package/dist/map-choice.js +73 -0
- package/dist/map-choice.js.map +1 -0
- package/dist/menu-sections.d.ts +67 -0
- package/dist/menu-sections.d.ts.map +1 -0
- package/dist/menu-sections.js +35 -0
- package/dist/menu-sections.js.map +1 -0
- package/dist/past.d.ts +58 -0
- package/dist/past.d.ts.map +1 -0
- package/dist/past.js +70 -0
- package/dist/past.js.map +1 -0
- package/dist/plots.d.ts +30 -24
- package/dist/plots.d.ts.map +1 -1
- package/dist/plots.js +168 -149
- package/dist/plots.js.map +1 -1
- package/dist/popover.d.ts +9 -1
- package/dist/popover.d.ts.map +1 -1
- package/dist/popover.js +3 -1
- package/dist/popover.js.map +1 -1
- package/dist/provider.d.ts +6 -2
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +5 -1
- package/dist/provider.js.map +1 -1
- package/dist/record-links-layer.d.ts +80 -0
- package/dist/record-links-layer.d.ts.map +1 -0
- package/dist/record-links-layer.js +127 -0
- package/dist/record-links-layer.js.map +1 -0
- package/dist/record-links.d.ts +58 -0
- package/dist/record-links.d.ts.map +1 -0
- package/dist/record-links.js +72 -0
- package/dist/record-links.js.map +1 -0
- package/dist/resolved-view.d.ts.map +1 -1
- package/dist/resolved-view.js +4 -0
- package/dist/resolved-view.js.map +1 -1
- package/dist/ribbon-width.d.ts +8 -0
- package/dist/ribbon-width.d.ts.map +1 -0
- package/dist/ribbon-width.js +15 -0
- package/dist/ribbon-width.js.map +1 -0
- package/dist/ribbons-layer.d.ts +56 -0
- package/dist/ribbons-layer.d.ts.map +1 -0
- package/dist/ribbons-layer.js +167 -0
- package/dist/ribbons-layer.js.map +1 -0
- package/dist/ribbons.d.ts +70 -0
- package/dist/ribbons.d.ts.map +1 -0
- package/dist/ribbons.js +400 -0
- package/dist/ribbons.js.map +1 -0
- package/dist/routes.d.ts +6 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +8 -0
- package/dist/routes.js.map +1 -1
- package/dist/scene-camera.d.ts.map +1 -1
- package/dist/scene-camera.js +10 -4
- package/dist/scene-camera.js.map +1 -1
- package/dist/scene-covered.d.ts +8 -0
- package/dist/scene-covered.d.ts.map +1 -0
- package/dist/scene-covered.js +118 -0
- package/dist/scene-covered.js.map +1 -0
- package/dist/scene-hand.d.ts +15 -0
- package/dist/scene-hand.d.ts.map +1 -1
- package/dist/scene-hand.js +45 -1
- package/dist/scene-hand.js.map +1 -1
- package/dist/scene-lines.d.ts +15 -2
- package/dist/scene-lines.d.ts.map +1 -1
- package/dist/scene-lines.js +305 -86
- package/dist/scene-lines.js.map +1 -1
- package/dist/scene-options.d.ts +10 -3
- package/dist/scene-options.d.ts.map +1 -1
- package/dist/scene-options.js +26 -9
- package/dist/scene-options.js.map +1 -1
- package/dist/scene-root.d.ts +7 -0
- package/dist/scene-root.d.ts.map +1 -1
- package/dist/scene-root.js +224 -158
- package/dist/scene-root.js.map +1 -1
- package/dist/tie-labels.d.ts +83 -0
- package/dist/tie-labels.d.ts.map +1 -0
- package/dist/tie-labels.js +98 -0
- package/dist/tie-labels.js.map +1 -0
- package/dist/view-host.d.ts.map +1 -1
- package/dist/view-host.js +7 -2
- package/dist/view-host.js.map +1 -1
- package/dist/view-registry.d.ts +5 -0
- package/dist/view-registry.d.ts.map +1 -1
- package/dist/view-registry.js +2 -6
- package/dist/view-registry.js.map +1 -1
- package/dist/where-drawn.d.ts +3 -1
- package/dist/where-drawn.d.ts.map +1 -1
- package/dist/where-drawn.js +10 -16
- package/dist/where-drawn.js.map +1 -1
- package/package.json +5 -5
package/dist/scene-root.js
CHANGED
|
@@ -1,22 +1,30 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { SCENE_LAYERS } from "@graview/core";
|
|
3
|
-
import { toIso } from "@graview/core/
|
|
4
|
-
import { aggregateId, cameraLimit, panForZoom, kindOfCard, kindsOf, layout,
|
|
5
|
-
import {
|
|
3
|
+
import { toIso, TOP_DOWN } from "@graview/core/city";
|
|
4
|
+
import { aggregateId, cameraLimit, panForZoom, kindOfCard, kindsOf, layout, withFocus, withOverview, panLayout, withRelation, withJackIn, holdLayout, isBandAggregate, toggleExpanded, withWithin, } from "@graview/layout";
|
|
5
|
+
import { withPan, withPin } from "@graview/layout/view-moves";
|
|
6
|
+
import { Suspense, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
|
|
7
|
+
import { retryingImport } from "@graview/core/retry";
|
|
8
|
+
import { lazyModule } from "./lazy-part.js";
|
|
9
|
+
import { BesideTheMap } from "./beside-the-map.js";
|
|
10
|
+
import { mapChoice } from "./map-choice.js";
|
|
6
11
|
import { useActivity } from "./attention.js";
|
|
7
12
|
import { useAnimatedLayout, useSeatWork, useTouched } from "./animation.js";
|
|
8
13
|
import { SeatMarks } from "./seat-marks.js";
|
|
9
|
-
import { useFound, useGraph, useGraview } from "./context.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
14
|
+
import { matchWith, useFound, useGraph, useGraview } from "./context.js";
|
|
15
|
+
import { search, touchWeights } from "@graview/core/search";
|
|
16
|
+
import { kitConnector, useKit } from "./kit.js";
|
|
17
|
+
// The scene searches: it hands the provider the matcher as it is fetched (FR-179).
|
|
18
|
+
matchWith({ search, touchWeights });
|
|
12
19
|
import { Occupants } from "./occupants.js";
|
|
13
20
|
import { EdgeSigns } from "./edge-signs.js";
|
|
14
21
|
import { useCameraFlights } from "./scene-camera.js";
|
|
15
|
-
import {
|
|
22
|
+
import { useKeyboardKeepsToWhatIsSeen } from "./scene-covered.js";
|
|
23
|
+
import { useDock, useHeldDistrict, useSceneDrag, useWheelAndPinch, useWorldShift } from "./scene-hand.js";
|
|
16
24
|
import { whereIsIn } from "./where-drawn.js";
|
|
17
25
|
import { BandCard, BeyondCard, SettledView } from "./resolved-view.js";
|
|
18
26
|
import { selectionFor, useElementSize, useRootUnit } from "./scene-helpers.js";
|
|
19
|
-
import { Lines, RelationCaptions } from "./scene-lines.js";
|
|
27
|
+
import { Lines, provisionalLines, RelationCaptions } from "./scene-lines.js";
|
|
20
28
|
import { railInset } from "./rails.js";
|
|
21
29
|
import { useSeatDrawn } from "./seat-talk.js";
|
|
22
30
|
import { useLayoutOptions } from "./scene-options.js";
|
|
@@ -66,25 +74,6 @@ function useHandZoom(overview) {
|
|
|
66
74
|
}, [overview]);
|
|
67
75
|
return [zoom, zoomLive, setZoom];
|
|
68
76
|
}
|
|
69
|
-
/**
|
|
70
|
-
* THE BILLBOARD IS CUT TO ITS PICTURE. The lens draws in a box as tall as
|
|
71
|
-
* the window; the screen's host reports how much of it the lens actually
|
|
72
|
-
* used, and the layout sizes the billboard to that — so the picture's foot
|
|
73
|
-
* is on the curb instead of a village's height above it. Whole pixels,
|
|
74
|
-
* and only a change re-lays the city.
|
|
75
|
-
*
|
|
76
|
-
* Reported by the layout's CURRENT screen only. A billboard on its way
|
|
77
|
-
* into its village is still drawn as one, but for the length of the tween
|
|
78
|
-
* two hosts would report, the layout would flip between their two heights,
|
|
79
|
-
* and every flip restarted the tween from where it was — a crawl of a
|
|
80
|
-
* pixel a frame until the old picture had faded.
|
|
81
|
-
*/
|
|
82
|
-
function useScreenHeight() {
|
|
83
|
-
const [screenHeight, setScreenHeight] = useState(undefined);
|
|
84
|
-
// Whole pixels: React keeps the height it has when the new one is the same, so only a change re-lays the city.
|
|
85
|
-
const noteScreenHeight = useCallback((height) => setScreenHeight(height && Math.round(height)), []);
|
|
86
|
-
return [screenHeight, noteScreenHeight];
|
|
87
|
-
}
|
|
88
77
|
/**
|
|
89
78
|
* THE CAMERA IS NOT A MOVE. A drive-in on the far side of a large city
|
|
90
79
|
* lights up off-screen unless the camera goes to it, and the camera's
|
|
@@ -299,7 +288,7 @@ function usePointerReport(pointer, stage) {
|
|
|
299
288
|
* A group is stale when any of its members changed, since its own view
|
|
300
289
|
* is a summary of them.
|
|
301
290
|
*/
|
|
302
|
-
function useAttachedRenderer(renderer, attachRenderer, canvasRef, frame, touched) {
|
|
291
|
+
function useAttachedRenderer(renderer, attachRenderer, canvasRef, frame, touched, drawOnDom) {
|
|
303
292
|
useEffect(() => {
|
|
304
293
|
if (renderer === "dom")
|
|
305
294
|
return;
|
|
@@ -317,12 +306,25 @@ function useAttachedRenderer(renderer, attachRenderer, canvasRef, frame, touched
|
|
|
317
306
|
canvas,
|
|
318
307
|
layout: frame,
|
|
319
308
|
dirty,
|
|
320
|
-
|
|
309
|
+
// In the canvas, or on the DOM path beside it.
|
|
310
|
+
hostOf: (id) => (canvas.parentElement ?? canvas).querySelector(`[data-graview-view="${CSS.escape(id)}"]`),
|
|
311
|
+
drawOnDom,
|
|
321
312
|
});
|
|
322
313
|
return () => {
|
|
323
314
|
detach?.();
|
|
324
315
|
};
|
|
325
|
-
}, [renderer, attachRenderer, frame, touched]);
|
|
316
|
+
}, [renderer, attachRenderer, frame, touched, drawOnDom]);
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* The views a renderer handed to the DOM path, as state. A set equal to the
|
|
320
|
+
* last one is not a new render: only a change of hands should cost one.
|
|
321
|
+
*/
|
|
322
|
+
function useDrawnOnDom() {
|
|
323
|
+
const [onDom, setOnDom] = useState(() => new Set());
|
|
324
|
+
const drawOnDom = useCallback((ids) => {
|
|
325
|
+
setOnDom((current) => (current.size === ids.size && [...ids].every((id) => current.has(id)) ? current : new Set(ids)));
|
|
326
|
+
}, []);
|
|
327
|
+
return [onDom, drawOnDom];
|
|
326
328
|
}
|
|
327
329
|
/**
|
|
328
330
|
* What just happened, resolved onto whatever is DRAWN.
|
|
@@ -373,6 +375,8 @@ const toggled = (current, id) => current.includes(id) ? current.filter((other) =
|
|
|
373
375
|
function useCardActs() {
|
|
374
376
|
const { views, view, setView, setSelection } = useGraview();
|
|
375
377
|
const found = useFound();
|
|
378
|
+
// Whether a panel beside the map lists a district opened from altitude.
|
|
379
|
+
const besideTheMap = useContext(BesideTheMap);
|
|
376
380
|
/*
|
|
377
381
|
* Whether this card stands for a kind the app gave a picture of its own —
|
|
378
382
|
* the same question `ResolvedView` asks to draw the ◆, asked here so the
|
|
@@ -481,6 +485,20 @@ function useCardActs() {
|
|
|
481
485
|
// A district the search lit opens narrowed by the same words.
|
|
482
486
|
const card = kindOfCard(node.id);
|
|
483
487
|
const lit = card !== null && (found?.byKind[card] ?? 0) > 0;
|
|
488
|
+
/*
|
|
489
|
+
* A DISTRICT WITH NO PICTURE OF ITS OWN, OPENED FROM ALTITUDE, OPENS
|
|
490
|
+
* TO ITS STOP BESIDE THE MAP, in the panel a lens opens in: its own
|
|
491
|
+
* focus, at altitude, so it takes the place of a lens or another
|
|
492
|
+
* kind's stop open there (one panel at a time), the address says so,
|
|
493
|
+
* and Back returns to what was open before. It used to be chosen,
|
|
494
|
+
* which listed it beside the map only when nothing was open — with a
|
|
495
|
+
* lens open, the lens stayed and the district opened behind it.
|
|
496
|
+
*/
|
|
497
|
+
if (besideTheMap && card !== null && view.overview && !ownPictureOf(node) && !(lit && view.q)) {
|
|
498
|
+
setView((current) => withWithin({ ...withFocus(current, `aggregate:${card}`), relation: null }, "view", null));
|
|
499
|
+
setSelection([]);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
484
502
|
setView((current) => withJackIn(current, node.id, { ownPicture: ownPictureOf(node), ...(lit && current.q ? { carry: current.q } : {}) }));
|
|
485
503
|
/*
|
|
486
504
|
* A zoomed RECORD is selected — reading closely is when you act.
|
|
@@ -492,37 +510,47 @@ function useCardActs() {
|
|
|
492
510
|
},
|
|
493
511
|
});
|
|
494
512
|
}
|
|
495
|
-
|
|
496
|
-
* THE
|
|
497
|
-
*
|
|
498
|
-
*
|
|
499
|
-
* doing so: the billboard is the biggest thing on screen and the one
|
|
500
|
-
* most likely to be under the hand, and taking panning away from it
|
|
501
|
-
* was how the far side of a city became unreachable once. So the
|
|
502
|
-
* board gets a rail along its top edge, the way a window has a title
|
|
503
|
-
* bar — the picture pans, the rail moves the board, and neither
|
|
504
|
-
* gesture has to be discovered from the other.
|
|
505
|
-
*
|
|
506
|
-
* How far it may go is not this rail's business: `layout` leashes
|
|
507
|
-
* the pin to its own plot, so a hand and a pasted link are held to
|
|
508
|
-
* the same distance.
|
|
509
|
-
*
|
|
510
|
-
* A TITLE BAR, not a gray strip with a pill floating over it: the
|
|
511
|
-
* picture's name on the left, the one way down on the right, and
|
|
512
|
-
* the whole bar the handle that moves the board.
|
|
513
|
+
/*
|
|
514
|
+
* THE RELATIONS FROM ALTITUDE, one ribbon each (`ribbons-layer.tsx`),
|
|
515
|
+
* fetched the first time the city is drawn: a page that stays on the
|
|
516
|
+
* ground carries none of them.
|
|
513
517
|
*/
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
518
|
+
const ribbonsModule = /* @__PURE__ */ lazyModule(retryingImport(() => import("./ribbons-layer.js")));
|
|
519
|
+
const Ribbons = /* @__PURE__ */ ribbonsModule.part((module, props) => _jsx(module.Ribbons, { ...props }), { what: "The relations", quiet: true });
|
|
520
|
+
const ABOVE_KEY = "graview:map-from-above";
|
|
521
|
+
/** Whether this person looks at the city from straight above: their own way of looking, kept in this browser when it can be. */
|
|
522
|
+
function useFromAbove() {
|
|
523
|
+
const [above, setAbove] = useState(() => {
|
|
524
|
+
try {
|
|
525
|
+
return globalThis.localStorage?.getItem(ABOVE_KEY) === "1";
|
|
526
|
+
}
|
|
527
|
+
catch {
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
const choose = useCallback((next) => {
|
|
532
|
+
setAbove(next);
|
|
533
|
+
try {
|
|
534
|
+
globalThis.localStorage?.setItem(ABOVE_KEY, next ? "1" : "0");
|
|
535
|
+
}
|
|
536
|
+
catch {
|
|
537
|
+
// Not kept: it holds for this visit.
|
|
538
|
+
}
|
|
539
|
+
}, []);
|
|
540
|
+
return [above, choose];
|
|
520
541
|
}
|
|
542
|
+
/*
|
|
543
|
+
* THE GROUND OF THE CITY — its plots, their blocks, the roads and their
|
|
544
|
+
* rules — fetched the first time the scene rises: a page that never looks
|
|
545
|
+
* from altitude never carries it (`plots.tsx`).
|
|
546
|
+
*/
|
|
547
|
+
export const cityGround = lazyModule(retryingImport(() => import("./plots.js")));
|
|
548
|
+
const Plots = cityGround.part((module, props) => _jsx(module.Plots, { ...props }), { quiet: true });
|
|
521
549
|
/** SCENE FURNITURE in the ground's other corner: the way a map carries its own zoom. */
|
|
522
|
-
function ZoomControls({ zoom, zoomBy }) {
|
|
550
|
+
function ZoomControls({ zoom, zoomBy, above, setAbove }) {
|
|
523
551
|
/** One way to zoom: its sign, the step it takes, and whether the zoom has gone as far as it goes that way. */
|
|
524
552
|
const button = (way, sign, factor, disabled) => (_jsx("button", { type: "button", className: "graview-zoom-button", "data-testid": `zoom-${way}`, "aria-label": `Zoom ${way}`, title: `Zoom ${way} — or pinch, or ctrl+wheel`, disabled: disabled, onClick: () => zoomBy(factor), children: sign }));
|
|
525
|
-
return (_jsxs("div", { className: "graview-zoom", "data-graview-foot": "", role: "group", "aria-label": "Zoom", onPointerDown: stop, onClick: stop, children: [button("out", "−", 1 / 1.25, zoom <= ZOOM_MIN + 1e-6), _jsxs("span", { className: "graview-zoom-level", "data-testid": "zoom-level", "aria-live": "polite", children: [Math.round(zoom * 100), "%"] }), button("in", "+", 1.25, zoom >= ZOOM_MAX - 1e-6)] }));
|
|
553
|
+
return (_jsxs("div", { className: "graview-zoom", "data-graview-foot": "", role: "group", "aria-label": "Zoom", onPointerDown: stop, onClick: stop, children: [button("out", "−", 1 / 1.25, zoom <= ZOOM_MIN + 1e-6), _jsxs("span", { className: "graview-zoom-level", "data-testid": "zoom-level", "aria-live": "polite", children: [Math.round(zoom * 100), "%"] }), button("in", "+", 1.25, zoom >= ZOOM_MAX - 1e-6), _jsx("button", { type: "button", className: "graview-zoom-button graview-zoom-above", "data-testid": "from-above", "aria-pressed": above, title: above ? "See the city at an angle again" : "See the city from straight above", onClick: () => setAbove(!above), children: "From above" })] }));
|
|
526
554
|
}
|
|
527
555
|
/**
|
|
528
556
|
* The spatial scene: one `<canvas layoutsubtree>` with the views as its
|
|
@@ -535,16 +563,14 @@ function ZoomControls({ zoom, zoomBy }) {
|
|
|
535
563
|
export function Scene({ options, renderer = "auto", attachRenderer, className, style, animate = true, children, }) {
|
|
536
564
|
const context = useGraview();
|
|
537
565
|
const { store, scheme, views, view, setView, selection, setSelection, setMenuAt, emphasis, brand, noteMoved, robots, seatTalk, motion } = context;
|
|
566
|
+
// A panel beside the map shows what is chosen from altitude, a record in focus among it (`mapChoice`).
|
|
567
|
+
const besideTheMap = useContext(BesideTheMap);
|
|
538
568
|
/* Whether an ask field stands at the picture's foot: the layout keeps that strip clear of cards. */
|
|
539
569
|
const seated = useSeatDrawn(seatTalk);
|
|
540
570
|
const canvasRef = useRef(null);
|
|
541
571
|
const wrapperRef = useRef(null);
|
|
542
572
|
const size = useElementSize(wrapperRef);
|
|
543
573
|
const unit = useRootUnit();
|
|
544
|
-
// The marquee's names measured in the face the scene draws them in (FR-118), for the room under each signpost.
|
|
545
|
-
const nameWidth = useMarqueeNameWidth(wrapperRef, unit, brand);
|
|
546
|
-
// The room the layout reserves for a marquee of those names, handed to the districts that draw one (132: the district's own width, unused).
|
|
547
|
-
const marqueeRoom = useMemo(() => (titles) => marqueeHeightFor(titles, 132, nameWidth), [nameWidth]);
|
|
548
574
|
// Pinch and ctrl+wheel: altitude from the ground, zoom about the pointer from above; the plain wheel pans. See scene-hand.ts.
|
|
549
575
|
const { zoomAbout, panBy } = useWheelAndPinch({ stage: wrapperRef, view, setView });
|
|
550
576
|
// `nodes` is a cached snapshot that only changes when the graph does, so
|
|
@@ -556,12 +582,28 @@ export function Scene({ options, renderer = "auto", attachRenderer, className, s
|
|
|
556
582
|
* bigger on its plot, and the camera keeps the picture in view. Derived
|
|
557
583
|
* from the stop, never stored: leaving the picture flies back out.
|
|
558
584
|
*/
|
|
559
|
-
|
|
585
|
+
/*
|
|
586
|
+
* A PICTURE CHOSEN FROM ALTITUDE OPENS BESIDE THE MAP, at a size a person
|
|
587
|
+
* reads (the lens panel), so the camera no longer flies closer to a
|
|
588
|
+
* billboard on the plot: the city stays where it was, with the district
|
|
589
|
+
* the picture is of lit and the rest receded.
|
|
590
|
+
*/
|
|
591
|
+
const closer = 1;
|
|
560
592
|
const [zoom, zoomLive, setZoom] = useHandZoom(view.overview);
|
|
561
|
-
const [
|
|
562
|
-
const
|
|
593
|
+
const [above, setAbove] = useFromAbove();
|
|
594
|
+
const seenFrom = useMemo(() => (above ? { ...options, tilt: TOP_DOWN } : options), [options, above]);
|
|
595
|
+
const { probe: dockProbe, dock } = useDock();
|
|
596
|
+
/*
|
|
597
|
+
* THE TRAIL'S ROOM. On the ground, where the picture's trail says
|
|
598
|
+
* something — "zoomed in ×", "the past ×", a raised kind — the focused
|
|
599
|
+
* card begins below it rather than under it: its top, 14 pixels down, and
|
|
600
|
+
* a chip of the reader's text size (the tenth walk: "Shifts ×" over "Sort").
|
|
601
|
+
*/
|
|
602
|
+
const headroom = !view.overview && (view.zoom || view.past || view.relation) ? Math.ceil(14 + (34 * unit) / 16 + 6) : 0;
|
|
603
|
+
const sized = useLayoutOptions(seenFrom, size, unit, seated, closer * zoom, dock, nodes, headroom);
|
|
563
604
|
const [panned, seen, panWithin, cameraLive, setCamera] = useCamera(view);
|
|
564
|
-
|
|
605
|
+
// A district cannot be placed by hand from altitude, so no plot wears the pinned curb for one (a pin in an old bookmark is ignored).
|
|
606
|
+
const pinnedIds = useMemo(() => new Set(Object.keys(view.pins).filter((id) => kindOfCard(id) === null)), [view.pins]);
|
|
565
607
|
// A district under the hand is placed here while it is dragged, and written into the view on release.
|
|
566
608
|
const district = useHeldDistrict();
|
|
567
609
|
const [result, inPlace] = useLaidOut(store, view, district.held, sized, nodes, seen.pan);
|
|
@@ -576,11 +618,11 @@ export function Scene({ options, renderer = "auto", attachRenderer, className, s
|
|
|
576
618
|
const touched = useTouched();
|
|
577
619
|
const seatWork = useSeatWork();
|
|
578
620
|
// Where the camera goes on its own: to a drive-in off the edge, and down into a village. See scene-camera.ts.
|
|
579
|
-
const
|
|
580
|
-
const flights = useCameraFlights({ view, result, frame, panned, closer, screenId, setCamera });
|
|
621
|
+
const flights = useCameraFlights({ view, result, frame, panned, closer, screenId: null, setCamera });
|
|
581
622
|
useLentWhereIs(context, frame, wrapperRef);
|
|
582
623
|
usePointerReport(context.pointer, wrapperRef);
|
|
583
|
-
|
|
624
|
+
const [onDom, drawOnDom] = useDrawnOnDom();
|
|
625
|
+
useAttachedRenderer(renderer, attachRenderer, canvasRef, frame, touched, drawOnDom);
|
|
584
626
|
const useDom = renderer === "dom" || (renderer === "auto" && !attachRenderer);
|
|
585
627
|
const activityOf = useActivityOf();
|
|
586
628
|
/* THE HAND: dragging the ground to look around and a district to place it — see scene-hand.ts. */
|
|
@@ -621,7 +663,9 @@ export function Scene({ options, renderer = "auto", attachRenderer, className, s
|
|
|
621
663
|
steer();
|
|
622
664
|
setZoom(next);
|
|
623
665
|
const box = wrapperRef.current?.getBoundingClientRect();
|
|
624
|
-
|
|
666
|
+
// The middle of what the reader sees: a panel over the scene is not part of it.
|
|
667
|
+
const seenBox = result.visible ?? result;
|
|
668
|
+
const center = { x: seenBox.width / 2, y: seenBox.height / 2 };
|
|
625
669
|
const pointer = box && clientX !== undefined && clientY !== undefined ? { x: clientX - box.left, y: clientY - box.top } : center;
|
|
626
670
|
const ratio = next / current;
|
|
627
671
|
setView((current) => withPan(current, panForZoom(current.pan ?? { x: 0, y: 0 }, cameraLive.current, pointer, center, ratio)));
|
|
@@ -629,6 +673,8 @@ export function Scene({ options, renderer = "auto", attachRenderer, className, s
|
|
|
629
673
|
};
|
|
630
674
|
/** The view's own pan moved by so much, held within the camera's reach. */
|
|
631
675
|
const nudge = (dx, dy, limit = cameraLimit(result)) => setView((current) => withPan(current, panWithin(limit, { x: (current.pan?.x ?? 0) + dx, y: (current.pan?.y ?? 0) + dy })));
|
|
676
|
+
// Tab passes over a card the panel covers, and the camera brings the one it lands on into the part that is seen.
|
|
677
|
+
useKeyboardKeepsToWhatIsSeen(wrapperRef, dock, (dx, dy) => nudge(dx, dy));
|
|
632
678
|
panBy.current = (dx, dy) => {
|
|
633
679
|
steer();
|
|
634
680
|
const limit = cameraLimit(result);
|
|
@@ -649,114 +695,134 @@ export function Scene({ options, renderer = "auto", attachRenderer, className, s
|
|
|
649
695
|
noteMoved();
|
|
650
696
|
};
|
|
651
697
|
const actsOn = useCardActs();
|
|
698
|
+
// Whether the kit draws a relation at all, for the ribbons (handed in: see ribbons-layer.tsx).
|
|
699
|
+
const kit = useKit();
|
|
700
|
+
const kitDraws = useCallback((edge) => kitConnector(kit, edge).connector.visible, [kit]);
|
|
652
701
|
const drawnActivity = (id) => activityOf(frame.nodes.find((node) => node.id === id));
|
|
653
702
|
/** How far down its card a plot's foot (`share` 1) or middle (2) stands, from the city's own origin and the pan. */
|
|
654
|
-
const plotY = (city, node, plot, share) => city.originY + panned.y + toIso(plot.col + plot.side /
|
|
703
|
+
const plotY = (city, node, plot, share) => city.originY + panned.y + toIso(plot.col + plot.side / share, plot.row + plot.side / share, city.cell, city.tilt).y - node.y;
|
|
655
704
|
// The stage's extent: what the ground, its layers and its lines are all drawn over.
|
|
656
705
|
const extent = { width: result.width, height: result.height };
|
|
657
706
|
const stage = { position: "relative", zIndex: SCENE_LAYERS.stage, ...extent };
|
|
658
707
|
const whereIs = (id) => whereIsIn(frame, wrapperRef.current, scheme, views, id);
|
|
659
|
-
|
|
708
|
+
/*
|
|
709
|
+
* THE BILLBOARD IS NOT DRAWN. A picture chosen from altitude was its lens
|
|
710
|
+
* drawn at the city's scale on its plot — eight-pixel text over the
|
|
711
|
+
* districts, unreadable until it was opened. It opens beside the map now,
|
|
712
|
+
* at full size (the lens panel); the stop is the same, so an address, the
|
|
713
|
+
* bar and Back still name it.
|
|
714
|
+
*/
|
|
715
|
+
// On the capture path a view the renderer handed on is drawn as the DOM path draws it.
|
|
716
|
+
const hostedOnDom = (id) => useDom || onDom.has(id);
|
|
717
|
+
const hosts = frame.nodes.filter((node) => node.screenOf === undefined).map((node) => (_jsx(SceneViewHost, { node: node, crowded: crowded(node), useDom: hostedOnDom(node.id), ...(node.plot && frame.city && Math.round(node.plane) === 2
|
|
660
718
|
? { frontY: plotY(frame.city, node, node.plot, 1), centerY: plotY(frame.city, node, node.plot, 2) }
|
|
661
|
-
: {}),
|
|
662
|
-
return (
|
|
719
|
+
: {}), touched: touched.has(node.id), ...withActivity(activityOf(node)), scheme: scheme, canvasWidth: result.width, canvasHeight: result.height, selected: selection.includes(node.id), ...actsOn(node), onMenu: setMenuAt, selection: selection, onDragStart: (event) => onCardDown(node, event), onDragMove: onDragMove, onDragEnd: onDragUp, swallowClick: swallow, children: node.beyond ? (_jsx(BeyondCard, { kinds: node.beyond })) : node.aggregate?.opens ? (_jsx(BandCard, { node: node })) : (_jsx(SettledView, { node: node, mode: "scene", selected: selection.includes(node.id), ...(crowded(node) ? { fidelity: "glyph" } : {}) })) }, node.id)));
|
|
720
|
+
return (_jsxs("div", { ref: wrapperRef, className: `graview-ground${className ? ` ${className}` : ""}`, "data-graview-altitude": view.overview ? "" : undefined, "data-graview-reach": frame.city ? reachOf(cameraLimit(result)) : undefined, "data-graview-settled": frame.t >= 1 && !flights.gliding() ? "" : undefined, "data-graview-box": size ? `${size.width}x${size.height}` : undefined, onPointerDown: onGroundDown, onPointerMove: onDragMove, onPointerUp: onDragUp, onPointerCancel: onDragUp,
|
|
721
|
+
/*
|
|
722
|
+
* CLICKING EMPTY GROUND puts the selection down — the gesture every
|
|
723
|
+
* canvas tool teaches, and the graceful half of deselection the ×
|
|
724
|
+
* and Escape were carrying alone. Only the bare ground: a card, a
|
|
725
|
+
* control or a piece of chrome keeps its own meaning, and a drag
|
|
726
|
+
* that ends on the ground is still a pan, not a deselection.
|
|
727
|
+
*/
|
|
728
|
+
onClick: (event) => {
|
|
729
|
+
if (swallow.current)
|
|
730
|
+
return;
|
|
731
|
+
const target = event.target;
|
|
732
|
+
// The panes are labeled sections now, not asides (FR-40); chrome is chrome either way.
|
|
733
|
+
if (target.closest("[data-graview-view], button, aside, section[aria-label], [data-graview-offstage], a, input, select"))
|
|
734
|
+
return;
|
|
735
|
+
setSelection([]);
|
|
736
|
+
setMenuAt(null);
|
|
737
|
+
}, style: {
|
|
738
|
+
position: "relative",
|
|
663
739
|
/*
|
|
664
|
-
*
|
|
665
|
-
*
|
|
666
|
-
*
|
|
667
|
-
*
|
|
668
|
-
*
|
|
740
|
+
* THE SCENE FILLS THE WHOLE PICTURE; A PANEL FLOATS OVER IT. The
|
|
741
|
+
* ground, the grid, the depth and the tint run under the panel and
|
|
742
|
+
* show in the gap around it. What a panel takes from the map is the
|
|
743
|
+
* city's fit and the camera's bounds (`useDock`, `--graview-dock`,
|
|
744
|
+
* `--graview-sheet`), never the scene's own box.
|
|
669
745
|
*/
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
}
|
|
700
|
-
: {}),
|
|
701
|
-
...(dragging ? { userSelect: "none" } : {}),
|
|
702
|
-
// The stage is sized to the measurement, but a stale measurement
|
|
703
|
-
// during a resize can briefly exceed it. Clipping keeps the scene
|
|
704
|
-
// inside its own bounds instead of pushing the page taller and
|
|
705
|
-
// cutting off anything floating over it.
|
|
706
|
-
overflow: "hidden",
|
|
707
|
-
...style,
|
|
708
|
-
}, children: [_jsx(Plots, { frame: frame, ...extent,
|
|
746
|
+
width: "100%",
|
|
747
|
+
height: "100%",
|
|
748
|
+
cursor: dragging ? "grabbing" : "grab",
|
|
749
|
+
touchAction: "none",
|
|
750
|
+
["--graview-altitude"]: view.overview ? 1 : 0,
|
|
751
|
+
/*
|
|
752
|
+
* THE LATTICE THE CITY STANDS ON. The ground draws its diamonds at
|
|
753
|
+
* the cell the map was placed with, anchored where cell (0,0) meets
|
|
754
|
+
* the canvas — and the anchor pans with the picture, since the pan
|
|
755
|
+
* is baked into every coordinate — so a plot sits on a grid line a
|
|
756
|
+
* person can see. The kit's own size stands when no city is drawn.
|
|
757
|
+
*/
|
|
758
|
+
...(frame.city
|
|
759
|
+
? {
|
|
760
|
+
["--graview-lattice-cell"]: `${frame.city.cell.toFixed(2)}px`,
|
|
761
|
+
["--graview-lattice-tilt"]: (frame.city.tilt ?? 0.5).toFixed(3),
|
|
762
|
+
// The tween's own pan, not the live one: the ground moves with the cards it is under.
|
|
763
|
+
["--graview-lattice-x"]: `${(frame.city.originX + frame.city.pan.x).toFixed(1)}px`,
|
|
764
|
+
["--graview-lattice-y"]: `${(frame.city.originY + frame.city.pan.y).toFixed(1)}px`,
|
|
765
|
+
}
|
|
766
|
+
: {}),
|
|
767
|
+
...(dragging ? { userSelect: "none" } : {}),
|
|
768
|
+
// The stage is sized to the measurement, but a stale measurement
|
|
769
|
+
// during a resize can briefly exceed it. Clipping keeps the scene
|
|
770
|
+
// inside its own bounds instead of pushing the page taller and
|
|
771
|
+
// cutting off anything floating over it.
|
|
772
|
+
overflow: "hidden",
|
|
773
|
+
...style,
|
|
774
|
+
}, children: [view.overview && frame.city ? (_jsx(Suspense, { fallback: null, children: _jsx(Ribbons, { frame: frame, ...extent, pan: frame.city.pan, store: store, graphVersion: nodes, selection: selection, view: { overview: view.overview, focusId: view.focusId, beside: besideTheMap }, emphasis: emphasis, hiddenKinds: context.hiddenKinds, drawn: kitDraws }) })) : null, _jsx("div", { ref: dockProbe, "aria-hidden": "true", style: { position: "absolute", top: 0, right: 0, width: "var(--graview-dock, 0px)", height: "var(--graview-sheet, 0px)", visibility: "hidden", pointerEvents: "none" } }), frame.city ? (_jsx(Suspense, { fallback: null, children: _jsx(Plots, { frame: frame, ...extent,
|
|
709
775
|
// The tween's own pan: the tiles, the villages and the roads move with the cards on them.
|
|
710
|
-
pan: frame.city ? frame.city.pan : panned, brand: brand, pinned: pinnedIds, swallowed: swallow,
|
|
776
|
+
pan: frame.city ? frame.city.pan : panned, brand: brand, pinned: pinnedIds, close: closer * zoom > 1.2, swallowed: swallow, ...(view.overview ? { chosen: mapChoice(selection, { overview: view.overview, focusId: view.focusId, beside: besideTheMap }, (id) => store.graph.getNode(id)?.kind).records } : {}),
|
|
711
777
|
// A tile is its district: focusing it means the kind's aggregate, never the card's own id.
|
|
712
778
|
onFocus: (id) => {
|
|
713
779
|
const kind = kindOfCard(id);
|
|
714
780
|
if (kind !== null)
|
|
715
781
|
setView((current) => withFocus(current, aggregateId(kind)));
|
|
716
|
-
} }), useDom ? (
|
|
782
|
+
} }) })) : null, useDom ? (
|
|
783
|
+
/*
|
|
784
|
+
* The DOM path uses an ORDINARY container, not a capture canvas.
|
|
785
|
+
*
|
|
786
|
+
* `layoutsubtree` exists so the GPU can capture these elements, and
|
|
787
|
+
* it changes how the browser lays them out. Combining it with the CSS
|
|
788
|
+
* transforms and filters this path applies crashes the renderer
|
|
789
|
+
* process in Chromium 154 — silently, on first paint. Since the DOM
|
|
790
|
+
* path never captures anything, the canvas has no job here, and not
|
|
791
|
+
* creating one removes the whole interaction.
|
|
792
|
+
*/
|
|
793
|
+
_jsx("div", { "data-graview-stage": "dom", "data-graview-world": "", style: { ...stage, overflow: "hidden" }, children: hosts })) : (_jsxs("div", { style: stage, children: [_jsx("div", { "data-graview-stage": "dom", "data-graview-under-canvas": "", style: { position: "absolute", left: 0, top: 0, ...extent, overflow: "hidden" }, children: hosts.filter((host) => onDom.has(String(host.key))) }), _jsx("canvas", { ref: canvasRef, "data-graview-stage": "gpu", content: "drawable", layoutsubtree: "", ...extent, style: { display: "block", position: "relative", ...extent }, children: hosts.filter((host) => !onDom.has(String(host.key))) })] })), view.overview && !dragging ? (
|
|
794
|
+
/* What the window does not reach, named on the edge it lies past (see edge-signs.tsx). */
|
|
795
|
+
_jsx(EdgeSigns, { result: result, schema: store.schema, ...(sized.chrome ? { chrome: sized.chrome } : {}), onGo: (by) => {
|
|
796
|
+
nudge(by.x, by.y);
|
|
797
|
+
noteMoved();
|
|
798
|
+
} })) : null, view.overview ? _jsx(ZoomControls, { zoom: zoom, zoomBy: (factor) => zoomAbout.current(factor), above: above, setAbove: setAbove }) : null, _jsx(Lines, { frame: frame, focusId: view.focusId, beside: besideTheMap, holding: inPlace, ...extent, scheme: scheme, overview: !!view.overview, selection: selection, emphasis: emphasis, stageRef: wrapperRef, store: store, graphNodes: nodes, ...((provisional) => (provisional ? { provisional } : {}))(provisionalLines(store)),
|
|
717
799
|
/*
|
|
718
|
-
*
|
|
719
|
-
*
|
|
720
|
-
*
|
|
721
|
-
*
|
|
722
|
-
*
|
|
723
|
-
* process in Chromium 154 — silently, on first paint. Since the DOM
|
|
724
|
-
* path never captures anything, the canvas has no job here, and not
|
|
725
|
-
* creating one removes the whole interaction.
|
|
800
|
+
* A LINE IS A THING. Clicking one that stands for exactly one edge
|
|
801
|
+
* selects the relation itself — the inspector then says what it is
|
|
802
|
+
* and what may lawfully be done to it; right-click opens the same
|
|
803
|
+
* actions at the pointer. Bundled lines stay scenery: "some of
|
|
804
|
+
* these" is not an honest thing to act on.
|
|
726
805
|
*/
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
806
|
+
onPickEdge: (edgeId, at) => {
|
|
807
|
+
// A pan that happened to start on a line is a pan, not a pick.
|
|
808
|
+
if (swallow.current)
|
|
809
|
+
return;
|
|
810
|
+
setSelection([edgeId]);
|
|
811
|
+
setMenuAt(at ? { ...at, on: edgeId } : null);
|
|
812
|
+
}, liveOf: (connector) => {
|
|
733
813
|
/*
|
|
734
|
-
* A
|
|
735
|
-
*
|
|
736
|
-
*
|
|
737
|
-
*
|
|
738
|
-
*
|
|
814
|
+
* A relation PULSES where it was just made or broken.
|
|
815
|
+
*
|
|
816
|
+
* An edge write touches both of its ends, so a connector is live
|
|
817
|
+
* exactly when both of the things it joins were written in the same
|
|
818
|
+
* window — which is what making or breaking a relation looks like in
|
|
819
|
+
* the log, and is not what changing one node's field looks like.
|
|
739
820
|
*/
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
}, liveOf: (connector) => {
|
|
747
|
-
/*
|
|
748
|
-
* A relation PULSES where it was just made or broken.
|
|
749
|
-
*
|
|
750
|
-
* An edge write touches both of its ends, so a connector is live
|
|
751
|
-
* exactly when both of the things it joins were written in the same
|
|
752
|
-
* window — which is what making or breaking a relation looks like in
|
|
753
|
-
* the log, and is not what changing one node's field looks like.
|
|
754
|
-
*/
|
|
755
|
-
const from = drawnActivity(connector.from);
|
|
756
|
-
const to = drawnActivity(connector.to);
|
|
757
|
-
return from?.wrote && to?.wrote ? (from.at > to.at ? from : to) : undefined;
|
|
758
|
-
} }), _jsx(Occupants, { width: result.width, whereIs: whereIs }), _jsx(SeatMarks, { marks: seatWork.marks, questions: [...robots.values()]
|
|
759
|
-
.filter((one) => one.mode === "asking" && one.at !== null && one.say)
|
|
760
|
-
.map((one) => ({ id: one.at, who: one.who, asks: one.say })), whereIs: whereIs, stageRef: wrapperRef, ...extent }), _jsx(RelationCaptions, { nodes: frame.nodes, moving: frame.t < 1, holding: inPlace, scheme: scheme, width: result.width, stageRef: wrapperRef }), children] }) }));
|
|
821
|
+
const from = drawnActivity(connector.from);
|
|
822
|
+
const to = drawnActivity(connector.to);
|
|
823
|
+
return from?.wrote && to?.wrote ? (from.at > to.at ? from : to) : undefined;
|
|
824
|
+
} }), _jsx(Occupants, { width: result.width, whereIs: whereIs }), _jsx(SeatMarks, { marks: seatWork.marks, questions: [...robots.values()]
|
|
825
|
+
.filter((one) => one.mode === "asking" && one.at !== null && one.say)
|
|
826
|
+
.map((one) => ({ id: one.at, who: one.who, asks: one.say })), whereIs: whereIs, stageRef: wrapperRef, ...extent }), _jsx(RelationCaptions, { nodes: frame.nodes, moving: frame.t < 1, holding: inPlace, scheme: scheme, width: result.width, stageRef: wrapperRef }), children] }));
|
|
761
827
|
}
|
|
762
828
|
//# sourceMappingURL=scene-root.js.map
|