@zephytiju/prism-geovision-embed 0.1.0 → 0.2.0
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/README.md +141 -47
- package/dist/EmbedBody.d.ts +20 -7
- package/dist/EmbedBody.d.ts.map +1 -1
- package/dist/EmbedBody.js +52 -64
- package/dist/GeovisionEmbed.d.ts +9 -7
- package/dist/GeovisionEmbed.d.ts.map +1 -1
- package/dist/GeovisionEmbed.js +13 -9
- package/dist/describeViews.d.ts +11 -16
- package/dist/describeViews.d.ts.map +1 -1
- package/dist/describeViews.js +19 -24
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/locales/en.json +2 -7
- package/dist/locales/index.d.ts +2 -12
- package/dist/locales/index.d.ts.map +1 -1
- package/dist/locales/zh-CN.json +2 -7
- package/dist/map/CesiumMapVariant.d.ts +37 -0
- package/dist/map/CesiumMapVariant.d.ts.map +1 -0
- package/dist/map/CesiumMapVariant.js +124 -0
- package/dist/map/CesiumMarkersLayer.d.ts +16 -0
- package/dist/map/CesiumMarkersLayer.d.ts.map +1 -0
- package/dist/map/CesiumMarkersLayer.js +22 -0
- package/dist/map/MapVariant.d.ts +19 -0
- package/dist/map/MapVariant.d.ts.map +1 -0
- package/dist/map/MapVariant.js +48 -0
- package/dist/map/cesiumEngine.d.ts +62 -0
- package/dist/map/cesiumEngine.d.ts.map +1 -0
- package/dist/map/cesiumEngine.js +201 -0
- package/dist/map/cesiumModel.d.ts +51 -0
- package/dist/map/cesiumModel.d.ts.map +1 -0
- package/dist/map/cesiumModel.js +65 -0
- package/dist/map/railState.d.ts +10 -0
- package/dist/map/railState.d.ts.map +1 -0
- package/dist/map/railState.js +17 -0
- package/dist/resolveView.d.ts +6 -3
- package/dist/resolveView.d.ts.map +1 -1
- package/dist/resolveView.js +13 -7
- package/dist/types.d.ts +37 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +6 -1
- package/dist/views.d.ts +29 -0
- package/dist/views.d.ts.map +1 -0
- package/dist/views.js +38 -0
- package/locales/en.json +2 -7
- package/locales/zh-CN.json +2 -7
- package/package.json +13 -5
package/README.md
CHANGED
|
@@ -7,26 +7,44 @@ component of the CLOSED dossier-embed contract (GeoVision ↔ Dossier Editor coo
|
|
|
7
7
|
control (compact layer rail, video-like replay, …) stays inside it — nothing an embed does
|
|
8
8
|
navigates away.
|
|
9
9
|
|
|
10
|
+
**The embed is a DISPATCHER**: one invocation renders exactly ONE variant — never a stacked
|
|
11
|
+
dashboard. The saved view's `previewKind` selects it:
|
|
12
|
+
|
|
13
|
+
| `previewKind` | Renders | Chrome |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| `map` | the **Cesium engine** map (default, `engine="cesium"`): the same zoom/rotate/tilt/pan camera as the globe viewport on an ION-FREE CesiumJS/Resium viewer — an SVG snapshot placeholder crossfading into the lazily-mounted engine with the video-like replay control on top | replay control only — no tools, no rail, no floatboxes, no fusion board, no view-mode cycling, no SAVE |
|
|
16
|
+
| `map` (`engine="svg"`) | the lighter static SVG map (markers / heat / side coloring + video-like replay) | compact layer rail + replay control |
|
|
17
|
+
| `relational` | `@zephytiju/prism-relation-fluxboard` | none — rendered as-is |
|
|
18
|
+
| `timeline` | `@zephytiju/prism-timeline-board` | none — rendered as-is |
|
|
19
|
+
| `stats` | `@zephytiju/prism-stats-panel` | none — rendered as-is |
|
|
20
|
+
|
|
21
|
+
Whatever renders fills the slot's full width/height (a scoped fit rule widens the packages'
|
|
22
|
+
intrinsic standalone widths to the slot); unknown or missing `previewKind` renders the localized
|
|
23
|
+
UNSUPPORTED PREVIEW KIND frame.
|
|
24
|
+
|
|
10
25
|
## The contract (implemented exactly)
|
|
11
26
|
|
|
12
27
|
```tsx
|
|
13
|
-
<GeovisionEmbed sourceFileRef viewId mode? interactionMode? locale? width height />
|
|
28
|
+
<GeovisionEmbed sourceFileRef viewId mode? interactionMode? engine? resolutionScale? locale? width height />
|
|
14
29
|
```
|
|
15
30
|
|
|
16
31
|
| Prop | Type | Default | Contract |
|
|
17
32
|
| --- | --- | --- | --- |
|
|
18
33
|
| `sourceFileRef` | `string` | — | stable ref addressing the saved geovision-view file (`IFileEntry.read`) |
|
|
19
|
-
| `viewId` | `string` | — | the saved view to render
|
|
34
|
+
| `viewId` | `string` | — | the saved view to render — `${sourceFileRef}#${previewKind}`, exactly as `describeViews` lists it; a viewId addressing another file renders UNKNOWN VIEW, and one with an unknown/missing previewKind renders UNSUPPORTED PREVIEW KIND |
|
|
20
35
|
| `mode` | `"static" \| "live"` | `"static"` | v1: `live` is reserved and behaves as `static` (no live fetches) |
|
|
21
36
|
| `interactionMode` | `"rich" \| "static"` | `"rich"` | `rich` captures pointer events inside the slot for inline interactions; `static` renders a static frame (also forced below 240 px height) |
|
|
37
|
+
| `engine` | `"cesium" \| "svg"` | `"cesium"` | the MAP variant's render engine: the CesiumJS engine embed (same zoom/rotate as the globe) or the lighter static SVG variant. Ignored by the other preview kinds; the degradation paths always fall back to the SVG snapshot |
|
|
38
|
+
| `resolutionScale` | `number` | `0.75` | the Cesium engine's render resolution scale — the embed's GPU/retina footprint cap. Ignored by the `"svg"` engine |
|
|
22
39
|
| `locale` | `"en" \| "zh-CN"` | `"en"` | the embed renders in the editor's active locale (unknown locales fall back to en) |
|
|
23
40
|
| `width` / `height` | `number \| string` | `"100%"` / `"100%"` | slot size; below 240 px height the embed degrades to a static frame with no pointer capture |
|
|
24
41
|
|
|
25
42
|
`GeovisionEmbed.describeViews(sourceFileRef)` feeds the editor's VIEW ▾ dropdown: it reads +
|
|
26
|
-
validates the file and returns `{ viewId, name, previewKind }[]`
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
43
|
+
validates the file and returns `{ viewId, name, previewKind }[]` — ONE view per previewKind
|
|
44
|
+
(`map` / `relational` / `timeline` / `stats`), `viewId = ${fileRef}#${previewKind}` (the file
|
|
45
|
+
format is closed and carries no extension fields, so the per-kind views are addressed by
|
|
46
|
+
convention over the stable file ref; the name derives from the recorded provenance because the
|
|
47
|
+
format carries no name field). Fails closed to an empty list for unreadable or invalid files.
|
|
30
48
|
|
|
31
49
|
Agreed coordination record: task list
|
|
32
50
|
https://applink.feishu.cn/client/todo/task_list?guid=33ec590f-7c59-4ace-b069-53c649fc55ab and the
|
|
@@ -42,60 +60,136 @@ authoritative designs:
|
|
|
42
60
|
no live fetches occur beyond the host's own routes.
|
|
43
61
|
2. **Validate** fail-closed through the authoritative format
|
|
44
62
|
(`@zephytiju/lattice-common-bundle/geovisionView`): structure AND digest. Failures render
|
|
45
|
-
localized frames (`not-found` / `invalid` / `unknown-view`).
|
|
63
|
+
localized frames (`not-found` / `invalid` / `unknown-view` / `unknown-preview`).
|
|
46
64
|
3. **Seed** an isolated `<ChannelScope>` — `query-box.entity-results` carries the snapshot
|
|
47
65
|
projection, `search-results.selected-entity` the workspace's selected entity — so every embed
|
|
48
|
-
renders its own file and nothing leaks across embeds on the same page
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
renders its own file and nothing leaks across embeds on the same page (the 2×2 demo grid runs
|
|
67
|
+
four of them side by side).
|
|
68
|
+
4. **Dispatch** on the view's `previewKind` — exactly ONE of:
|
|
69
|
+
- **map, Cesium engine (default)** — the SAME engine/zoom/rotate as the globe viewport
|
|
70
|
+
(`cesium` 1.143.0 + `resium` 1.24.0, identical to `@zephytiju/prism-globe-viewport`),
|
|
71
|
+
wrapped in an EMBED-BUDGET architecture:
|
|
72
|
+
- **snapshot placeholder** — the existing SVG mini-map renders from the same snapshot data
|
|
73
|
+
the moment the embed mounts (zero Cesium cost — the instant visual; see the performance
|
|
74
|
+
strategy below for the lazy handover);
|
|
75
|
+
- **lazy mount** — an `IntersectionObserver` on the embed container boots the Cesium
|
|
76
|
+
`Viewer` only while the slot is on screen (deferred off the observer's own path, so the
|
|
77
|
+
WebGL boot never blocks the interaction that revealed it); once the viewer element
|
|
78
|
+
exists the layers crossfade (SVG out, canvas in). Scrolling the slot out of view — or
|
|
79
|
+
unmounting — unmounts the Resium `<Viewer>`, whose teardown **destroys** the WebGL
|
|
80
|
+
context/workers; re-entry re-observes and boots again;
|
|
81
|
+
- **idle GPU** — `requestRenderMode: true` + `maximumRenderTimeChange: Infinity` keep the
|
|
82
|
+
render loop asleep when nothing changes (Cesium's camera input handlers request renders
|
|
83
|
+
themselves on zoom/rotate/tilt/pan, which stay fully enabled — the
|
|
84
|
+
`ScreenSpaceCameraController` is untouched); `resolutionScale` 0.75 (the embed prop) caps
|
|
85
|
+
the raster footprint, `scene.globe.tileCacheSize` 10 bounds the tile memory, every UI
|
|
86
|
+
widget is disabled (animation/timeline/baseLayerPicker/geocoder/homeButton/
|
|
87
|
+
sceneModePicker/navigationHelpButton/fullscreenButton/infoBox/selectionIndicator) and the
|
|
88
|
+
sky is off (`skyBox: false`, `skyAtmosphere: false`). CesiumJS 1.143 carries no
|
|
89
|
+
`creditsDisplay` constructor option, so the credit strip (the only chrome Cesium still
|
|
90
|
+
paints with everything else disabled) is suppressed by a rule scoped to the embed's
|
|
91
|
+
stage; the imagery credit stays attached to the provider;
|
|
92
|
+
- **ion-free imagery + camera restore** — the saved view's `baseLayer.urlTemplate` (with its
|
|
93
|
+
credit) feeds a `UrlTemplateImageryProvider`, falling back to the
|
|
94
|
+
`OpenStreetMapImageryProvider` default — never a Cesium ion asset; terrain is the default
|
|
95
|
+
ellipsoid. The camera is restored from the saved view's `camera` state
|
|
96
|
+
`{lon, lat, height}` — the same `IGeoView` serialization the globe viewport saves;
|
|
97
|
+
- **markers + replay** — entity markers derive from the snapshot (the same side-color
|
|
98
|
+
derivation the SVG mini-map paints: token-colored points + mono labels, the workspace's
|
|
99
|
+
selected entity emphasized). The video-like replay control overlays the canvas; each
|
|
100
|
+
scrub tick re-derives the marker positions with the SAME track math as the SVG variant
|
|
101
|
+
(linear interpolation between deterministic waypoints) and explicitly calls
|
|
102
|
+
`scene.requestRender()` — under `requestRenderMode` a scrub tick renders exactly one
|
|
103
|
+
frame;
|
|
104
|
+
- **no other chrome** — no tools, no layer rail, no floatboxes, no fusion board, no
|
|
105
|
+
view-mode cycling, no SAVE: just basemap + markers + replay;
|
|
106
|
+
- **map, SVG engine (`engine="svg"`)** — the lighter static variant hosts opt into: the
|
|
107
|
+
static equirectangular SVG mini-map filling the slot with
|
|
108
|
+
- layers from the file — normal markers (overlay), kernel-density blobs per enabled heat
|
|
109
|
+
layer (simplified variant of the globe's heat kernel), side-color mapping applied to marker
|
|
110
|
+
fills/outlines (`solid` / `stripe` / `accent`). Tag matchers apply to the item's stream
|
|
111
|
+
`type` (the kinds≡tags convention the recorded behavior uses for heat selections) because
|
|
112
|
+
the format's `EntityResultItem` declares only `id/type/label/lat/lon`;
|
|
113
|
+
- the compact layer rail — the same click/long-press contract as the globe's rail at compact
|
|
114
|
+
density (click toggles, long-press/right-click opens the anchored opacity options; changes
|
|
115
|
+
are local to the surface, persisted only via the editor's block config);
|
|
116
|
+
- the VIDEO-LIKE replay control — play/pause + progress scrubbing entity positions across the
|
|
117
|
+
snapshot's time-windowed timeline events (tracks interpolate linearly by time between
|
|
118
|
+
deterministic waypoints derived from the events; display-only);
|
|
119
|
+
- **relational / timeline / stats** — the REAL packages mounted inside the scope, rendered
|
|
120
|
+
as-is (their internal fetches flow through the host transport; in the demo the mock executor
|
|
121
|
+
answers from the file's snapshot).
|
|
122
|
+
|
|
123
|
+
### Degradation path
|
|
124
|
+
|
|
125
|
+
`engine="svg"`, `interactionMode="static"` and slots below `MIN_RICH_HEIGHT` (240 px) NEVER
|
|
126
|
+
mount Cesium: the map variant renders the SVG snapshot only (the SVG mini-map plus, on the
|
|
127
|
+
rich-SVG path, its rail/replay chrome), and static frames additionally capture no pointer events.
|
|
128
|
+
Off-screen rich Cesium embeds hold no GPU resources either — the observer gate is the second
|
|
129
|
+
half of the degradation story (visible ⇒ engine, invisible ⇒ destroyed).
|
|
130
|
+
|
|
131
|
+
### Performance strategy (the Cesium engine in one paragraph)
|
|
132
|
+
|
|
133
|
+
Instant SVG from the snapshot (no engine cost at first paint) → the engine boots only when the
|
|
134
|
+
slot is actually visible (IntersectionObserver, boot deferred to an idle callback) → once booted
|
|
135
|
+
the render loop sleeps (`requestRenderMode` + `maximumRenderTimeChange: Infinity`; Cesium's own
|
|
136
|
+
camera handlers wake it for zoom/rotate/tilt/pan) → replay ticks and marker moves render exactly
|
|
137
|
+
one explicit `scene.requestRender()` frame each → the raster is capped (`resolutionScale` 0.75,
|
|
138
|
+
configurable) and tile memory bounded (`tileCacheSize` 10) → leaving the viewport destroys the
|
|
139
|
+
viewer outright (WebGL context + workers freed), and re-entry rebuilds it.
|
|
65
140
|
|
|
66
141
|
## Peer dependencies / registry status
|
|
67
142
|
|
|
68
|
-
The three mounted component packages are REGISTRY PEERS (`>=0.1.0`)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
the ambient npm auth is expired, so they could not be published from this work. Until the first
|
|
72
|
-
release the dev surface installs them from **vendored tarballs** produced by `npm pack` in the
|
|
73
|
-
sibling repos (`vendor/*.tgz`, referenced as `file:` devDependencies):
|
|
143
|
+
The three mounted component packages are REGISTRY PEERS (`>=0.1.0`) and are published on npm;
|
|
144
|
+
the dev surface pins the same registry specifiers as devDependencies (no vendored tarballs
|
|
145
|
+
since 0.2.0):
|
|
74
146
|
|
|
75
|
-
- `.npmrc`
|
|
147
|
+
- `.npmrc` keeps `legacy-peer-deps=true` so npm never auto-installs the peers on top of the
|
|
148
|
+
explicit dev pins;
|
|
76
149
|
- typecheck/tests/demo run against the REAL packaged output (dist) of the siblings, exactly what
|
|
77
|
-
the registry
|
|
78
|
-
|
|
79
|
-
- the EMITTED package keeps the registry specifiers (`import … from
|
|
80
|
-
"@zephytiju/prism-relation-fluxboard"`), so hosts resolve them normally once published;
|
|
81
|
-
- when the siblings land on npm, drop `vendor/`, pin registry versions in devDependencies and
|
|
82
|
-
remove `legacy-peer-deps`.
|
|
150
|
+
the registry serves — the jsdom run inlines them and stubs the `@xyflow/react` stylesheet
|
|
151
|
+
(`vitest.config.ts`).
|
|
83
152
|
|
|
84
|
-
|
|
153
|
+
The Cesium engine comes from the REGISTRY as regular dependencies — `cesium` 1.143.0 +
|
|
154
|
+
`resium` 1.24.0 (the same versions and `@cesium/*` overrides as
|
|
155
|
+
`@zephytiju/prism-globe-viewport`), so hosts install the identical engine the globe runs. The
|
|
156
|
+
demo's vite config serves Cesium's static asset tree (Workers/Assets/ThirdParty/Widgets)
|
|
157
|
+
straight from `node_modules` via `publicDir` with `CESIUM_BASE_URL="/"` — nothing is bundled or
|
|
158
|
+
vendored, and no ion endpoint is configured anywhere.
|
|
159
|
+
|
|
160
|
+
Per the owner gate this package is NOT published or tagged from this work (the npm `0.1.0` is the
|
|
161
|
+
superseded stacked-dashboard build; `0.2.0` is the dispatcher fix, published only after owner
|
|
162
|
+
confirmation).
|
|
85
163
|
|
|
86
164
|
## Development
|
|
87
165
|
|
|
88
166
|
```bash
|
|
89
|
-
npm install #
|
|
90
|
-
npm run typecheck # tsc
|
|
91
|
-
npm test # vitest (jsdom) —
|
|
92
|
-
npm run dev # vite demo (
|
|
167
|
+
npm install # registry peers pinned as devDependencies (see .npmrc) + cesium/resium
|
|
168
|
+
npm run typecheck # tsc (against the REAL cesium/resium types)
|
|
169
|
+
npm test # vitest (jsdom) — 56 tests; cesium/resium stubbed at the resolver level
|
|
170
|
+
npm run dev # vite demo (2×2 grid: map on the Cesium engine + relational/timeline/stats,
|
|
171
|
+
# plus the engine="svg" fallback panel; EN/中文, dark × light)
|
|
93
172
|
npm run build # emits dist/ with registry peer imports
|
|
173
|
+
npm run shot-demo # captures /tmp/guanlan-review/demo-geovision-embed-v3.png
|
|
94
174
|
```
|
|
95
175
|
|
|
176
|
+
**Tests** follow the globe viewport's mocking strategy: jsdom cannot host the ~30MB WebGL
|
|
177
|
+
engine, so `vitest.config.ts` aliases `cesium`/`resium` to `test/stubs/` — plain surface stubs
|
|
178
|
+
that record the Viewer constructor props, `camera.setView` calls, `scene.requestRender` counts,
|
|
179
|
+
the `globe.tileCacheSize` assignment and viewer destroys. The library build (tsc) still
|
|
180
|
+
typechecks against the real packages. `test/setup.ts` additionally installs an always-visible
|
|
181
|
+
IntersectionObserver default; the cesium-engine suite installs its own controllable mock to
|
|
182
|
+
drive enter/exit visibility transitions.
|
|
183
|
+
|
|
96
184
|
Source layout (each module ≤250 lines): `src/GeovisionEmbed.tsx` (contract + failure frames),
|
|
97
|
-
`src/resolveView.ts` (IFileEntry.read + fail-closed validation),
|
|
98
|
-
|
|
99
|
-
`src/
|
|
100
|
-
|
|
101
|
-
|
|
185
|
+
`src/resolveView.ts` (IFileEntry.read + fail-closed validation + previewKind resolution),
|
|
186
|
+
`src/views.ts` (view-id addressing: `viewIdFor` / `previewKindOf` / `resolveViewKind`),
|
|
187
|
+
`src/seed.ts` (ChannelScope seeding), `src/EmbedBody.tsx` (the DISPATCHER — one variant per
|
|
188
|
+
invocation), `src/map/CesiumMapVariant.tsx` (the map variant's engine dispatch: SVG-only
|
|
189
|
+
degradation paths + the Cesium engine surface — observer gate, ready-gate crossfade, replay
|
|
190
|
+
wiring), `src/map/CesiumMarkersLayer.tsx` (the Resium entity layer),
|
|
191
|
+
`src/map/cesiumModel.ts` (pure marker/camera/imagery derivations),
|
|
192
|
+
`src/map/MapVariant.tsx` (the SVG variant + its chrome), `src/map/railState.ts` (shared
|
|
193
|
+
initial-rail derivation), `src/describeViews.ts`, `src/map/` (projection, replay math, heat
|
|
194
|
+
blobs, side coloring, tokens, MiniMap, ReplayControl), `src/rail/LayerRailCompact.tsx`,
|
|
195
|
+
`src/locales/` (en + zh-CN with exact key parity; `stringsForLocale` falls back to en).
|
package/dist/EmbedBody.d.ts
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
import type { GeovisionViewFile } from "@zephytiju/lattice-common-bundle/geovisionView";
|
|
2
|
+
import type { GeovisionPreviewKind, GeovisionMapEngine } from "./types.js";
|
|
2
3
|
import type { GeovisionEmbedStrings } from "./locales/index.js";
|
|
3
4
|
/**
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* The DISPATCHER: ONE invocation renders exactly ONE variant, selected by
|
|
6
|
+
* the saved view's previewKind —
|
|
7
|
+
* - "map" → the Cesium engine map (default) — an SVG snapshot
|
|
8
|
+
* placeholder crossfading into the lazily-mounted ION-FREE
|
|
9
|
+
* Cesium viewer with the video-like replay control on top;
|
|
10
|
+
* engine="svg" opts into the lighter static SVG map (the
|
|
11
|
+
* variant carrying the compact layer rail);
|
|
12
|
+
* - "relational" → the real prism-relation-fluxboard, rendered as-is;
|
|
13
|
+
* - "timeline" → the real prism-timeline-board, rendered as-is;
|
|
14
|
+
* - "stats" → the real prism-stats-panel, rendered as-is.
|
|
15
|
+
* Whatever renders fills the slot's full width/height — no stacking, no
|
|
16
|
+
* sections, no tabs. An ISOLATED ChannelScope seeded from the view's
|
|
17
|
+
* snapshot wraps the variant, so every embed renders its own file and
|
|
18
|
+
* nothing leaks across embeds. Static frames wrap the body with
|
|
19
|
+
* pointerEvents none (no pointer capture at all).
|
|
10
20
|
*/
|
|
11
|
-
export declare function EmbedBody({ file, locale, staticFrame, strings, }: {
|
|
21
|
+
export declare function EmbedBody({ file, previewKind, locale, staticFrame, engine, resolutionScale, strings, }: {
|
|
12
22
|
readonly file: GeovisionViewFile;
|
|
23
|
+
readonly previewKind: GeovisionPreviewKind;
|
|
13
24
|
readonly locale: "en" | "zh-CN";
|
|
14
25
|
readonly staticFrame: boolean;
|
|
26
|
+
readonly engine: GeovisionMapEngine;
|
|
27
|
+
readonly resolutionScale?: number;
|
|
15
28
|
readonly strings: GeovisionEmbedStrings;
|
|
16
29
|
}): React.ReactElement;
|
|
17
30
|
//# sourceMappingURL=EmbedBody.d.ts.map
|
package/dist/EmbedBody.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmbedBody.d.ts","sourceRoot":"","sources":["../src/EmbedBody.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gDAAgD,CAAC;
|
|
1
|
+
{"version":3,"file":"EmbedBody.d.ts","sourceRoot":"","sources":["../src/EmbedBody.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gDAAgD,CAAC;AAMxF,OAAO,KAAK,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAC3E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAyBhE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CAAC,EACxB,IAAI,EACJ,WAAW,EACX,MAAM,EACN,WAAW,EACX,MAAM,EACN,eAAe,EACf,OAAO,GACR,EAAE;IACD,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;IAC3C,QAAQ,CAAC,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;IACpC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,qBAAqB,CAAC;CACzC,GAAG,KAAK,CAAC,YAAY,CA6DrB"}
|
package/dist/EmbedBody.js
CHANGED
|
@@ -1,81 +1,69 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useMemo } from "react";
|
|
3
3
|
import { ChannelScope } from "@zephytiju/prism-react";
|
|
4
4
|
import { RelationFluxboard } from "@zephytiju/prism-relation-fluxboard";
|
|
5
5
|
import { TimelineBoard } from "@zephytiju/prism-timeline-board";
|
|
6
6
|
import { StatsPanel } from "@zephytiju/prism-stats-panel";
|
|
7
7
|
import { seedFromFile } from "./seed.js";
|
|
8
|
-
import {
|
|
9
|
-
import { ReplayControl } from "./map/ReplayControl.js";
|
|
10
|
-
import { hasMotion, buildTracks } from "./map/replay.js";
|
|
11
|
-
import { LayerRailCompact } from "./rail/LayerRailCompact.js";
|
|
8
|
+
import { CesiumMapVariant } from "./map/CesiumMapVariant.js";
|
|
12
9
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* the slot
|
|
18
|
-
*
|
|
10
|
+
* Scoped fit rules (unique to this component's slots): the mounted packages
|
|
11
|
+
* carry their own INTRINSIC inline root widths (460/540/360 px — their
|
|
12
|
+
* standalone panel sizes). Inside an embed the ONE dispatched variant must
|
|
13
|
+
* fill the slot's full width/height, so the direct package root yields to
|
|
14
|
+
* the slot contract (`!important` overrides their inline widths; the
|
|
15
|
+
* packages themselves render as-is, unmodified and un-wrapped in chrome).
|
|
19
16
|
*/
|
|
20
|
-
|
|
17
|
+
const FIT_CSS = [
|
|
18
|
+
".geovision-embed-fit{width:100%;height:100%;min-width:0;min-height:0;overflow:hidden;}",
|
|
19
|
+
'.geovision-embed-fit>[data-testid="relation-fluxboard"],',
|
|
20
|
+
'.geovision-embed-fit>[data-testid="timeline-board"],',
|
|
21
|
+
'.geovision-embed-fit>[data-testid="stats-panel"]',
|
|
22
|
+
"{width:100% !important;height:100% !important;}",
|
|
23
|
+
].join("");
|
|
24
|
+
/** Full-slot wrapper — the ONE rendered variant fills the embed's width/height. */
|
|
25
|
+
function variantSlot(children) {
|
|
26
|
+
return (_jsx("div", { className: "geovision-embed-fit", children: children }));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The DISPATCHER: ONE invocation renders exactly ONE variant, selected by
|
|
30
|
+
* the saved view's previewKind —
|
|
31
|
+
* - "map" → the Cesium engine map (default) — an SVG snapshot
|
|
32
|
+
* placeholder crossfading into the lazily-mounted ION-FREE
|
|
33
|
+
* Cesium viewer with the video-like replay control on top;
|
|
34
|
+
* engine="svg" opts into the lighter static SVG map (the
|
|
35
|
+
* variant carrying the compact layer rail);
|
|
36
|
+
* - "relational" → the real prism-relation-fluxboard, rendered as-is;
|
|
37
|
+
* - "timeline" → the real prism-timeline-board, rendered as-is;
|
|
38
|
+
* - "stats" → the real prism-stats-panel, rendered as-is.
|
|
39
|
+
* Whatever renders fills the slot's full width/height — no stacking, no
|
|
40
|
+
* sections, no tabs. An ISOLATED ChannelScope seeded from the view's
|
|
41
|
+
* snapshot wraps the variant, so every embed renders its own file and
|
|
42
|
+
* nothing leaks across embeds. Static frames wrap the body with
|
|
43
|
+
* pointerEvents none (no pointer capture at all).
|
|
44
|
+
*/
|
|
45
|
+
export function EmbedBody({ file, previewKind, locale, staticFrame, engine, resolutionScale, strings, }) {
|
|
21
46
|
const seeded = useMemo(() => seedFromFile(file), [file]);
|
|
22
|
-
const containerRef = useRef(null);
|
|
23
47
|
// The mounted packages accept only en | "zh-CN" — unknown locales resolve
|
|
24
48
|
// onto the en fallback before crossing the seam.
|
|
25
49
|
const innerLocale = locale === "zh-CN" ? "zh-CN" : "en";
|
|
26
|
-
|
|
27
|
-
if (layer.kind === "overlay") {
|
|
28
|
-
return { id: `overlay-${String(index)}`, kind: "overlay", name: "markers", enabled: true, opacity: layer.opacity };
|
|
29
|
-
}
|
|
30
|
-
if (layer.kind === "heat") {
|
|
31
|
-
return { id: `heat-${String(index)}`, kind: "heat", name: layer.selection.name, enabled: true, opacity: layer.intensity };
|
|
32
|
-
}
|
|
33
|
-
return { id: `side-color-${String(index)}`, kind: "side-color", name: "sides", enabled: true, opacity: 1 };
|
|
34
|
-
}), [file]);
|
|
35
|
-
const [rail, setRail] = useState(initialRail);
|
|
36
|
-
const railForFile = useMemo(() => (rail.length === initialRail.length ? rail : initialRail), [rail, initialRail]);
|
|
37
|
-
const replayWindow = useMemo(() => {
|
|
38
|
-
const start = Date.parse(seeded.window.start);
|
|
39
|
-
const end = Date.parse(seeded.window.end);
|
|
40
|
-
return Number.isFinite(start) && Number.isFinite(end) && end > start
|
|
41
|
-
? { startMs: start, endMs: end }
|
|
42
|
-
: { startMs: 0, endMs: 3600_000 };
|
|
43
|
-
}, [seeded]);
|
|
44
|
-
const [fraction, setFraction] = useState(0);
|
|
45
|
-
const [playing, setPlaying] = useState(false);
|
|
46
|
-
const onFraction = useCallback((next) => {
|
|
47
|
-
const clamped = Math.min(1, Math.max(0, next));
|
|
48
|
-
setFraction(clamped);
|
|
49
|
-
if (clamped >= 1)
|
|
50
|
-
setPlaying(false);
|
|
51
|
-
}, []);
|
|
52
|
-
const located = useMemo(() => (file.snapshot.entityResults?.items ?? []).filter((item) => item.lat !== undefined && item.lon !== undefined), [file]);
|
|
53
|
-
const motion = useMemo(() => hasMotion(buildTracks(located, file.snapshot.timelineEvents, replayWindow)), [located, file, replayWindow]);
|
|
54
|
-
const toggleLayer = useCallback((id) => {
|
|
55
|
-
setRail((previous) => previous.map((layer) => (layer.id === id ? { ...layer, enabled: !layer.enabled } : layer)));
|
|
56
|
-
}, []);
|
|
57
|
-
const setOpacity = useCallback((id, opacity) => {
|
|
58
|
-
setRail((previous) => previous.map((layer) => (layer.id === id ? { ...layer, opacity } : layer)));
|
|
59
|
-
}, []);
|
|
60
|
-
return (_jsx(ChannelScope, { seed: seeded.seed, children: _jsxs("div", { ref: containerRef, "data-testid": "geovision-embed-body", style: {
|
|
50
|
+
return (_jsx(ChannelScope, { seed: seeded.seed, children: _jsxs("div", { "data-testid": "geovision-embed-body", "data-preview-kind": previewKind, style: {
|
|
61
51
|
width: "100%",
|
|
62
52
|
height: "100%",
|
|
63
|
-
display: "flex",
|
|
64
|
-
flexDirection: "column",
|
|
65
53
|
overflow: "hidden",
|
|
54
|
+
position: "relative",
|
|
66
55
|
...(staticFrame ? { pointerEvents: "none" } : {}),
|
|
67
|
-
}, children: [
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
} }), _jsxs("div", { style: { display: "flex", gap: 4, flex: "1 1 30%", minHeight: 96, padding: 4 }, children: [_jsx("div", { style: { flex: 1, minWidth: 0, overflow: "hidden" }, children: _jsx(RelationFluxboard, { locale: innerLocale, title: strings.fluxTitle, subtitle: strings.fluxSubtitle }) }), _jsx("div", { style: { width: 210, flexShrink: 0, overflow: "hidden" }, children: _jsx(StatsPanel, { locale: innerLocale, window: seeded.window, title: strings.statsTitle, subtitle: strings.statsSubtitle }) })] }), _jsx("div", { style: { height: 108, flexShrink: 0, padding: 4, overflow: "hidden" }, children: _jsx(TimelineBoard, { locale: innerLocale, defaultWindow: seeded.window, live: false, title: strings.timelineTitle, subtitle: strings.timelineSubtitle }) })] }) }));
|
|
56
|
+
}, children: [_jsx("style", { children: FIT_CSS }), previewKind === "map" ? (variantSlot(_jsx(CesiumMapVariant, { file: file, seed: seeded, staticFrame: staticFrame, engine: engine, resolutionScale: resolutionScale, strings: strings }))) : previewKind === "relational" ? (variantSlot(_jsx(RelationFluxboard, { locale: innerLocale }))) : previewKind === "timeline" ? (variantSlot(_jsx(TimelineBoard, { locale: innerLocale, defaultWindow: seeded.window, live: false }))) : (variantSlot(_jsx(StatsPanel, { locale: innerLocale, window: seeded.window }))), staticFrame ? (_jsx("div", { "data-testid": "geovision-embed-static-chip", style: {
|
|
57
|
+
position: "absolute",
|
|
58
|
+
right: 6,
|
|
59
|
+
top: 6,
|
|
60
|
+
fontSize: 8,
|
|
61
|
+
padding: "1px 6px",
|
|
62
|
+
borderRadius: 3,
|
|
63
|
+
fontFamily: "var(--mantine-font-family-monospace)",
|
|
64
|
+
background: "var(--mantine-color-input-filled)",
|
|
65
|
+
color: "var(--mantine-color-muted-filled)",
|
|
66
|
+
border: "1px solid var(--mantine-color-border-filled)",
|
|
67
|
+
zIndex: 5,
|
|
68
|
+
}, children: strings.staticFrame })) : null] }) }));
|
|
81
69
|
}
|
package/dist/GeovisionEmbed.d.ts
CHANGED
|
@@ -3,18 +3,20 @@ import type { GeovisionEmbedProps } from "./types.js";
|
|
|
3
3
|
export declare const MIN_RICH_HEIGHT = 240;
|
|
4
4
|
/**
|
|
5
5
|
* Platform Prism geovision-embed micro-UI (component id "geovision-embed")
|
|
6
|
-
* — the agreed dossier-embed render component:
|
|
6
|
+
* — the agreed dossier-embed render component, as a DISPATCHER:
|
|
7
7
|
* `<GeovisionEmbed sourceFileRef viewId mode? interactionMode? locale? width height />`.
|
|
8
8
|
*
|
|
9
9
|
* The component resolves { sourceFileRef, viewId } through the HOST-supplied
|
|
10
10
|
* Lattice transport (IFileEntry.read — the embed installs NO executor of
|
|
11
11
|
* its own, so in static mode no live fetches occur beyond the host's own
|
|
12
12
|
* routes), validates the file against the authoritative geovision-view
|
|
13
|
-
* format (fail-closed: structure + digest), then
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* format (fail-closed: structure + digest), then reads the view's
|
|
14
|
+
* previewKind and renders exactly ONE variant — the simplified SVG map
|
|
15
|
+
* (with layers, heat, side coloring and replay) OR the real
|
|
16
|
+
* relation-fluxboard OR timeline-board OR stats-panel — each filling the
|
|
17
|
+
* slot's full width/height. "live" mode is reserved and behaves as static
|
|
18
|
+
* in v1. Below 240px height or with interactionMode="static" the slot
|
|
19
|
+
* degrades to a static frame (pointer-events captured nowhere).
|
|
18
20
|
*/
|
|
19
|
-
export declare function GeovisionEmbed({ sourceFileRef, viewId, mode, interactionMode, locale, width, height, }: GeovisionEmbedProps): React.ReactElement;
|
|
21
|
+
export declare function GeovisionEmbed({ sourceFileRef, viewId, mode, interactionMode, engine, resolutionScale, locale, width, height, }: GeovisionEmbedProps): React.ReactElement;
|
|
20
22
|
//# sourceMappingURL=GeovisionEmbed.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GeovisionEmbed.d.ts","sourceRoot":"","sources":["../src/GeovisionEmbed.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAItD,yFAAyF;AACzF,eAAO,MAAM,eAAe,MAAM,CAAC;AAEnC
|
|
1
|
+
{"version":3,"file":"GeovisionEmbed.d.ts","sourceRoot":"","sources":["../src/GeovisionEmbed.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAItD,yFAAyF;AACzF,eAAO,MAAM,eAAe,MAAM,CAAC;AAEnC;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAAC,EAC7B,aAAa,EACb,MAAM,EACN,IAAe,EACf,eAAwB,EACxB,MAAiB,EACjB,eAAe,EACf,MAAa,EACb,KAAc,EACd,MAAM,GACP,EAAE,mBAAmB,GAAG,KAAK,CAAC,YAAY,CAoE1C"}
|
package/dist/GeovisionEmbed.js
CHANGED
|
@@ -8,20 +8,22 @@ import { EmbedBody } from "./EmbedBody.js";
|
|
|
8
8
|
export const MIN_RICH_HEIGHT = 240;
|
|
9
9
|
/**
|
|
10
10
|
* Platform Prism geovision-embed micro-UI (component id "geovision-embed")
|
|
11
|
-
* — the agreed dossier-embed render component:
|
|
11
|
+
* — the agreed dossier-embed render component, as a DISPATCHER:
|
|
12
12
|
* `<GeovisionEmbed sourceFileRef viewId mode? interactionMode? locale? width height />`.
|
|
13
13
|
*
|
|
14
14
|
* The component resolves { sourceFileRef, viewId } through the HOST-supplied
|
|
15
15
|
* Lattice transport (IFileEntry.read — the embed installs NO executor of
|
|
16
16
|
* its own, so in static mode no live fetches occur beyond the host's own
|
|
17
17
|
* routes), validates the file against the authoritative geovision-view
|
|
18
|
-
* format (fail-closed: structure + digest), then
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
18
|
+
* format (fail-closed: structure + digest), then reads the view's
|
|
19
|
+
* previewKind and renders exactly ONE variant — the simplified SVG map
|
|
20
|
+
* (with layers, heat, side coloring and replay) OR the real
|
|
21
|
+
* relation-fluxboard OR timeline-board OR stats-panel — each filling the
|
|
22
|
+
* slot's full width/height. "live" mode is reserved and behaves as static
|
|
23
|
+
* in v1. Below 240px height or with interactionMode="static" the slot
|
|
24
|
+
* degrades to a static frame (pointer-events captured nowhere).
|
|
23
25
|
*/
|
|
24
|
-
export function GeovisionEmbed({ sourceFileRef, viewId, mode = "static", interactionMode = "rich", locale = "en", width = "100%", height, }) {
|
|
26
|
+
export function GeovisionEmbed({ sourceFileRef, viewId, mode = "static", interactionMode = "rich", engine = "cesium", resolutionScale, locale = "en", width = "100%", height, }) {
|
|
25
27
|
const strings = stringsForLocale(locale);
|
|
26
28
|
const transport = useLatticeTransport();
|
|
27
29
|
const resolved = useResolvedView(transport, sourceFileRef, viewId);
|
|
@@ -29,7 +31,7 @@ export function GeovisionEmbed({ sourceFileRef, viewId, mode = "static", interac
|
|
|
29
31
|
const numericHeight = typeof height === "number" ? height : undefined;
|
|
30
32
|
const richHeight = numericHeight === undefined || numericHeight >= MIN_RICH_HEIGHT;
|
|
31
33
|
const staticFrame = interactionMode === "static" || !richHeight;
|
|
32
|
-
return (_jsxs("div", { ref: containerRef, "data-testid": "geovision-embed", "data-embed-mode": mode, "data-interaction-mode": staticFrame ? "static" : "rich", "data-file-ref": sourceFileRef, "aria-label": strings.slotAria, style: {
|
|
34
|
+
return (_jsxs("div", { ref: containerRef, "data-testid": "geovision-embed", "data-embed-mode": mode, "data-interaction-mode": staticFrame ? "static" : "rich", "data-file-ref": sourceFileRef, "data-view-id": viewId, "data-preview-kind": resolved.previewKind ?? "", "aria-label": strings.slotAria, style: {
|
|
33
35
|
width,
|
|
34
36
|
height: height ?? "100%",
|
|
35
37
|
position: "relative",
|
|
@@ -40,5 +42,7 @@ export function GeovisionEmbed({ sourceFileRef, viewId, mode = "static", interac
|
|
|
40
42
|
? strings.errNotFound
|
|
41
43
|
: resolved.phase.code === "invalid"
|
|
42
44
|
? strings.errInvalid
|
|
43
|
-
:
|
|
45
|
+
: resolved.phase.code === "unknown-preview"
|
|
46
|
+
? strings.errUnknownPreview
|
|
47
|
+
: strings.errUnknownView }), _jsx("span", { style: { fontSize: 9, color: "var(--mantine-color-muted-filled)" }, children: resolved.phase.message })] })) : null, resolved.phase.phase === "ready" && resolved.file !== null && resolved.previewKind !== null ? (_jsx(EmbedBody, { file: resolved.file, previewKind: resolved.previewKind, locale: locale, staticFrame: staticFrame, engine: engine, resolutionScale: resolutionScale, strings: strings })) : null] }));
|
|
44
48
|
}
|
package/dist/describeViews.d.ts
CHANGED
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* describeViews(sourceFileRef) — the editor's VIEW ▾ dropdown source: reads
|
|
3
3
|
* the file through the host Lattice transport, validates it against the
|
|
4
|
-
* authoritative format, and lists
|
|
5
|
-
* carries
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* authoritative format, and lists the views it can embed. A validated file
|
|
5
|
+
* carries one saved view PER previewKind (map / relational / timeline /
|
|
6
|
+
* stats — the dispatcher's variants), addressed `${fileRef}#${previewKind}`;
|
|
7
|
+
* the descriptor's viewId is exactly what the editor passes back to
|
|
8
|
+
* <GeovisionEmbed viewId=…> and previewKind labels the dropdown row.
|
|
9
9
|
*/
|
|
10
10
|
import type { LatticeTransport } from "@zephytiju/prism-react";
|
|
11
11
|
import type { GeovisionViewDescriptor } from "./types.js";
|
|
12
|
-
/**
|
|
13
|
-
export declare function
|
|
14
|
-
readonly kind: string;
|
|
15
|
-
}[]): GeovisionViewDescriptor["previewKind"];
|
|
16
|
-
/** Descriptor for a validated file (one saved view per file). */
|
|
17
|
-
export declare function describeFile(sourceFileRef: string, savedBy: string, savedAt: string, layers: readonly {
|
|
18
|
-
readonly kind: string;
|
|
19
|
-
}[]): GeovisionViewDescriptor;
|
|
12
|
+
/** One descriptor per previewKind — the file's complete embeddable view list. */
|
|
13
|
+
export declare function describeFile(sourceFileRef: string, savedBy: string, savedAt: string): readonly GeovisionViewDescriptor[];
|
|
20
14
|
/**
|
|
21
|
-
* Lists the file's saved views for the editor dropdown
|
|
22
|
-
* unreadable or invalid file yields an empty
|
|
23
|
-
* nothing to offer) — the EMBED surface
|
|
15
|
+
* Lists the file's saved views for the editor dropdown (one per
|
|
16
|
+
* previewKind). Fails closed: an unreadable or invalid file yields an empty
|
|
17
|
+
* list (the dropdown simply has nothing to offer) — the EMBED surface
|
|
18
|
+
* renders the structured failure.
|
|
24
19
|
*/
|
|
25
20
|
export declare function describeViews(sourceFileRef: string, transport?: LatticeTransport): Promise<readonly GeovisionViewDescriptor[]>;
|
|
26
21
|
//# sourceMappingURL=describeViews.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"describeViews.d.ts","sourceRoot":"","sources":["../src/describeViews.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"describeViews.d.ts","sourceRoot":"","sources":["../src/describeViews.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAK/D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAe1D,iFAAiF;AACjF,wBAAgB,YAAY,CAC1B,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd,SAAS,uBAAuB,EAAE,CAMpC;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,aAAa,EAAE,MAAM,EACrB,SAAS,GAAE,gBAA2C,GACrD,OAAO,CAAC,SAAS,uBAAuB,EAAE,CAAC,CAU7C"}
|