mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 03:14:00 +00:00
Move the session annotation board, Draw and Import out of the Cesium tools into sessionTools.js and give Navara its tools seam
This commit is contained in:
1 parent
dea10cac4f
commit
f5c14ec30d
7 files changed
+207
-102
No files matched your search
@@ -2661,6 +2661,7 @@
|
||||
"src/app/stateChannel.js",
|
||||
"src/app/surfaceServices.js",
|
||||
"src/app/tools.js",
|
||||
"src/app/sessionTools.js",
|
||||
"src/app/sessionVoice.js",
|
||||
"src/app/sceneParts.js",
|
||||
"src/app/shellPart.js",
|
||||
@@ -3597,6 +3598,7 @@
|
||||
"src/globe/navara/viewOptions.js",
|
||||
"src/app/engineChoice.js",
|
||||
"src/app/navaraMode.js",
|
||||
"src/app/navaraTools.js",
|
||||
"src/data/render/cesiumProjection.js",
|
||||
"src/data/render/cesiumConstants.js",
|
||||
"src/data/render/cesiumTrailRenderer.js",
|
||||
|
||||
@@ -21,19 +21,25 @@ const tool = () => read('src/annotations/drawTool.js');
|
||||
|
||||
test('the application composition wires the draw tool and owns its teardown', () => {
|
||||
const tools = read('src/app/tools.js');
|
||||
const session = read('src/app/sessionTools.js');
|
||||
const engineAt = tools.indexOf('const annotations = sessionAnnotations({');
|
||||
const drawAt = tools.indexOf(
|
||||
"const drawTool = sessionTool(layerSession, 'draw', defer, () =>\n createSessionDrawTool({\n annotations,\n navigation: styleManager,\n publish: (view) => displayStore.publishDraw(view),",
|
||||
'const drawTool = sessionDrawTool({\n layerSession,\n annotations,',
|
||||
);
|
||||
assert.ok(engineAt >= 0, 'the session annotation board is missing');
|
||||
assert.ok(
|
||||
drawAt > engineAt,
|
||||
'the draw tool must follow the annotation board and receive it',
|
||||
);
|
||||
// The page session owns the tool; its rows answer through the store.
|
||||
assert.match(
|
||||
tools,
|
||||
session,
|
||||
/import \{ createSessionDrawTool \} from '\.\.\/annotations\/drawTool\.js';/,
|
||||
);
|
||||
assert.match(
|
||||
session,
|
||||
/sessionTool\(layerSession, 'draw', defer, \(\) =>\s*createSessionDrawTool\(\{\s*annotations,\s*navigation: styleManager,\s*publish: \(view\) => displayStore\.publishDraw\(view\),/,
|
||||
);
|
||||
// The tool holds a pointer claim, DOM listeners and a data source on the
|
||||
// viewer, so the viewer's attachment ends it — not whoever last pressed the
|
||||
// button; the session tool ends with the page.
|
||||
|
||||
+17
-2
@@ -18,6 +18,11 @@ import {
|
||||
} from '../renderGovernor.js';
|
||||
import { attachSessionVoice } from './sessionVoice.js';
|
||||
import { initNavaraImageryBoxTool } from '../ui/imageryBoxNavara.js';
|
||||
import {
|
||||
attachNavaraAnnotations,
|
||||
attachNavaraDraw,
|
||||
attachNavaraGeoImport,
|
||||
} from './navaraTools.js';
|
||||
import { createRecentImageryPanel } from '../ui/recentImagery.js';
|
||||
|
||||
// Native mesh capability stays at the composition boundary.
|
||||
@@ -461,6 +466,16 @@ export function attachViewerlessTools(
|
||||
});
|
||||
// The notice is this engine's: it goes when the engine does.
|
||||
defer(() => styleManager._clearPersistentStatusNotice?.());
|
||||
// The page session's annotation board, Draw and Import on this engine
|
||||
// (src/app/navaraTools.js).
|
||||
const tools = { engine, view: navaraViews.get(engine) };
|
||||
const context = { operations, placeSearch, layerSession, styleManager };
|
||||
const annotations = attachNavaraAnnotations(tools, context, {
|
||||
signal,
|
||||
defer,
|
||||
});
|
||||
attachNavaraDraw(tools, { ...context, annotations }, { signal, defer });
|
||||
attachNavaraGeoImport(tools, context, { signal, defer });
|
||||
// Voice on the engine (phase 3, Task 9): the W1 tools drive the engine;
|
||||
// the Director uses the shared session; viewer-only tools declare availability.
|
||||
const voiceCommands = attachSessionVoice({
|
||||
@@ -474,10 +489,10 @@ export function attachViewerlessTools(
|
||||
engine,
|
||||
styleManager,
|
||||
sceneDirector,
|
||||
annotations: null,
|
||||
annotations,
|
||||
},
|
||||
signal,
|
||||
defer,
|
||||
});
|
||||
return { annotations: null, voiceCommands };
|
||||
return { annotations, voiceCommands };
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// The page session's annotation board, Draw and Import on the experimental
|
||||
// Navara engine (src/app/sessionTools.js holds the tools themselves). Loaded
|
||||
// with src/app/navaraMode.js, so the Cesium-mode bundle carries none of it.
|
||||
|
||||
/**
|
||||
* The annotation board drawn on this Navara scene. Returns the board, or
|
||||
* null while the engine draws no annotations.
|
||||
*/
|
||||
export function attachNavaraAnnotations() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** UTILITIES ▸ Draw on this Navara scene; null while it has none. */
|
||||
export function attachNavaraDraw() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** UTILITIES ▸ Import on this Navara scene; null while it has none. */
|
||||
export function attachNavaraGeoImport() {
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// The page session's tools that outlive a globe engine: the annotation board,
|
||||
// UTILITIES ▸ Draw and UTILITIES ▸ Import. Each is created once per page
|
||||
// session (destroyed with the page) and attaches its drawing to whichever
|
||||
// engine the page shows: src/app/tools.js on a Cesium viewer,
|
||||
// src/app/navaraTools.js on Navara.
|
||||
|
||||
import { createSessionAnnotations } from '../annotations/index.js';
|
||||
import { createSessionDrawTool } from '../annotations/drawTool.js';
|
||||
import { createGeoFileImport } from '../ui/geoFileImport.js';
|
||||
import { useDisplayStore } from '../ui/stores/display.js';
|
||||
import { applicationPinia } from '../ui/vueRuntime.js';
|
||||
|
||||
/**
|
||||
* The annotation board: the page session's when there is one (created once,
|
||||
* destroyed with the page), else this application's.
|
||||
*/
|
||||
export function sessionAnnotations({
|
||||
layerSession,
|
||||
placeSearch,
|
||||
resolver,
|
||||
defer,
|
||||
}) {
|
||||
const create = () => createSessionAnnotations({ placeSearch, resolver });
|
||||
if (!layerSession) {
|
||||
const annotations = create();
|
||||
defer(() => destroyAnnotations(annotations));
|
||||
return annotations;
|
||||
}
|
||||
if (!layerSession.annotations) {
|
||||
const annotations = (layerSession.annotations = create());
|
||||
layerSession.lifetime.signal.addEventListener(
|
||||
'abort',
|
||||
() => destroyAnnotations(annotations),
|
||||
{ once: true },
|
||||
);
|
||||
}
|
||||
return layerSession.annotations;
|
||||
}
|
||||
|
||||
/**
|
||||
* A tool of the page session (created once, destroyed with the page) when
|
||||
* there is one, else of this application. `create()` may return null.
|
||||
*/
|
||||
export function sessionTool(layerSession, name, defer, create) {
|
||||
if (!layerSession) {
|
||||
const tool = create();
|
||||
defer(() => tool?.destroy());
|
||||
return tool;
|
||||
}
|
||||
const tools = (layerSession.tools ??= {});
|
||||
if (!(name in tools)) {
|
||||
const tool = (tools[name] = create());
|
||||
layerSession.lifetime.signal.addEventListener(
|
||||
'abort',
|
||||
() => tool?.destroy(),
|
||||
{ once: true },
|
||||
);
|
||||
}
|
||||
return tools[name];
|
||||
}
|
||||
|
||||
function destroyAnnotations(annotations) {
|
||||
if (window.__gevAnnotations === annotations) delete window.__gevAnnotations;
|
||||
annotations.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Display panel's store and `connectDisplay(actions)`: the panel's
|
||||
* controls answer through the store; returns the disconnect.
|
||||
*/
|
||||
export function displayControls() {
|
||||
const displayStore = useDisplayStore(applicationPinia());
|
||||
const connectDisplay = (actions) => {
|
||||
const token = displayStore.connect(actions);
|
||||
return () => displayStore.detach(token);
|
||||
};
|
||||
return { displayStore, connectDisplay };
|
||||
}
|
||||
|
||||
/**
|
||||
* UTILITIES ▸ Draw: the same whiteboard, drawn by hand. The marks and the
|
||||
* chosen shape belong to the page session; the tool claims the attached
|
||||
* globe's pointer while a sketch is open, so an engine switch ends the
|
||||
* sketch. A row's fly-to goes through the shell's navigation policy, as
|
||||
* Import's does. Null on a page without the Draw controls.
|
||||
*/
|
||||
export function sessionDrawTool({
|
||||
layerSession,
|
||||
annotations,
|
||||
styleManager,
|
||||
defer,
|
||||
}) {
|
||||
const { displayStore, connectDisplay } = displayControls();
|
||||
return sessionTool(layerSession, 'draw', defer, () =>
|
||||
createSessionDrawTool({
|
||||
annotations,
|
||||
navigation: styleManager,
|
||||
publish: (view) => displayStore.publishDraw(view),
|
||||
connect: connectDisplay,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* UTILITIES ▸ Import: the person's own GeoJSON/KML/KMZ/GPX files, each its
|
||||
* own data source. They live for the page session: an engine switch takes
|
||||
* them off the globe, and the next globe reads them again. The drop
|
||||
* listeners belong to the attached globe. Null on a page without the
|
||||
* Import control.
|
||||
*/
|
||||
export function sessionGeoImport({
|
||||
layerSession,
|
||||
styleManager,
|
||||
operations,
|
||||
defer,
|
||||
}) {
|
||||
const { displayStore, connectDisplay } = displayControls();
|
||||
return sessionTool(layerSession, 'geoImport', defer, () =>
|
||||
createGeoFileImport({
|
||||
navigation: styleManager,
|
||||
ground: operations.surface?.groundFloor,
|
||||
publish: (view) => displayStore.publishGeoImport(view),
|
||||
connect: connectDisplay,
|
||||
}),
|
||||
);
|
||||
}
|
||||
+21
-90
@@ -1,12 +1,12 @@
|
||||
import { SceneDirector } from '../scenes/director.js';
|
||||
import { createSessionAnnotations } from '../annotations/index.js';
|
||||
import { createSessionDrawTool } from '../annotations/drawTool.js';
|
||||
import { createGeoFileImport } from '../ui/geoFileImport.js';
|
||||
import { useDisplayStore } from '../ui/stores/display.js';
|
||||
import { applicationPinia } from '../ui/vueRuntime.js';
|
||||
import { initImageryBoxTool } from '../ui/imageryBoxTool.js';
|
||||
import { createRecentImageryPanel } from '../ui/recentImagery.js';
|
||||
import { attachSessionVoice } from './sessionVoice.js';
|
||||
import {
|
||||
sessionAnnotations,
|
||||
sessionDrawTool,
|
||||
sessionGeoImport,
|
||||
} from './sessionTools.js';
|
||||
import {
|
||||
attachScenePart,
|
||||
currentSceneOf,
|
||||
@@ -23,60 +23,6 @@ import {
|
||||
releaseContinuousRender,
|
||||
} from '../renderGovernor.js';
|
||||
|
||||
/**
|
||||
* The annotation board: the page session's when there is one (created once,
|
||||
* destroyed with the page), else this application's.
|
||||
*/
|
||||
export function sessionAnnotations({
|
||||
layerSession,
|
||||
placeSearch,
|
||||
resolver,
|
||||
defer,
|
||||
}) {
|
||||
const create = () => createSessionAnnotations({ placeSearch, resolver });
|
||||
if (!layerSession) {
|
||||
const annotations = create();
|
||||
defer(() => destroyAnnotations(annotations));
|
||||
return annotations;
|
||||
}
|
||||
if (!layerSession.annotations) {
|
||||
const annotations = (layerSession.annotations = create());
|
||||
layerSession.lifetime.signal.addEventListener(
|
||||
'abort',
|
||||
() => destroyAnnotations(annotations),
|
||||
{ once: true },
|
||||
);
|
||||
}
|
||||
return layerSession.annotations;
|
||||
}
|
||||
|
||||
/**
|
||||
* A tool of the page session (created once, destroyed with the page) when
|
||||
* there is one, else of this application. `create()` may return null.
|
||||
*/
|
||||
function sessionTool(layerSession, name, defer, create) {
|
||||
if (!layerSession) {
|
||||
const tool = create();
|
||||
defer(() => tool?.destroy());
|
||||
return tool;
|
||||
}
|
||||
const tools = (layerSession.tools ??= {});
|
||||
if (!(name in tools)) {
|
||||
const tool = (tools[name] = create());
|
||||
layerSession.lifetime.signal.addEventListener(
|
||||
'abort',
|
||||
() => tool?.destroy(),
|
||||
{ once: true },
|
||||
);
|
||||
}
|
||||
return tools[name];
|
||||
}
|
||||
|
||||
function destroyAnnotations(annotations) {
|
||||
if (window.__gevAnnotations === annotations) delete window.__gevAnnotations;
|
||||
annotations.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach scene tools, rendering listeners and the application debug handle.
|
||||
* The Director, the annotation board, Draw, Import, voice and the debug
|
||||
@@ -305,39 +251,24 @@ function attachViewerTools(
|
||||
});
|
||||
annotations.attachScene({ viewer, tileset });
|
||||
defer(() => annotations.detachScene());
|
||||
const displayStore = useDisplayStore(applicationPinia());
|
||||
// The Display panel's controls answer through the store; returns the
|
||||
// disconnect.
|
||||
const connectDisplay = (actions) => {
|
||||
const token = displayStore.connect(actions);
|
||||
return () => displayStore.detach(token);
|
||||
};
|
||||
// UTILITIES ▸ Draw: the same whiteboard, drawn by hand. The marks and the
|
||||
// chosen shape belong to the page session; the tool claims this viewer's
|
||||
// pointer while a sketch is open, so an engine switch ends the sketch. A
|
||||
// row's fly-to goes through the shell's navigation policy, as Import's does.
|
||||
const drawTool = sessionTool(layerSession, 'draw', defer, () =>
|
||||
createSessionDrawTool({
|
||||
annotations,
|
||||
navigation: styleManager,
|
||||
publish: (view) => displayStore.publishDraw(view),
|
||||
connect: connectDisplay,
|
||||
}),
|
||||
);
|
||||
// UTILITIES ▸ Draw on this viewer (src/app/sessionTools.js): the tool
|
||||
// claims this viewer's pointer while a sketch is open.
|
||||
const drawTool = sessionDrawTool({
|
||||
layerSession,
|
||||
annotations,
|
||||
styleManager,
|
||||
defer,
|
||||
});
|
||||
drawTool?.attachScene({ viewer, navigation: styleManager });
|
||||
defer(() => drawTool?.detachScene());
|
||||
// UTILITIES ▸ Import: the person's own GeoJSON/KML/KMZ/GPX files, each its
|
||||
// own data source. They live for the page session: an engine switch takes
|
||||
// them off this viewer, and the next Cesium viewer reads them again. The
|
||||
// drop listeners belong to this viewer.
|
||||
const geoFileImport = sessionTool(layerSession, 'geoImport', defer, () =>
|
||||
createGeoFileImport({
|
||||
navigation: styleManager,
|
||||
ground: operations.surface?.groundFloor,
|
||||
publish: (view) => displayStore.publishGeoImport(view),
|
||||
connect: connectDisplay,
|
||||
}),
|
||||
);
|
||||
// UTILITIES ▸ Import on this viewer: the next Cesium viewer reads the
|
||||
// session's files again. The drop listeners belong to this viewer.
|
||||
const geoFileImport = sessionGeoImport({
|
||||
layerSession,
|
||||
styleManager,
|
||||
operations,
|
||||
defer,
|
||||
});
|
||||
geoFileImport?.attachScene({
|
||||
viewer,
|
||||
navigation: styleManager,
|
||||
|
||||
@@ -328,16 +328,16 @@ test('the application composition wires the import and owns its teardown', () =>
|
||||
new URL('../app/tools.js', import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
const drawAt = tools.indexOf(
|
||||
"const drawTool = sessionTool(layerSession, 'draw'",
|
||||
);
|
||||
const importAt = tools.indexOf(
|
||||
"const geoFileImport = sessionTool(layerSession, 'geoImport', defer, () =>",
|
||||
const session = fs.readFileSync(
|
||||
new URL('../app/sessionTools.js', import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
const drawAt = tools.indexOf('const drawTool = sessionDrawTool({');
|
||||
const importAt = tools.indexOf('const geoFileImport = sessionGeoImport({');
|
||||
assert.ok(importAt > drawAt && drawAt >= 0);
|
||||
// Fly-to goes through the application shell's navigation policy.
|
||||
assert.match(
|
||||
tools.slice(importAt, importAt + 200),
|
||||
tools.slice(importAt, importAt + 400),
|
||||
/navigation: styleManager/,
|
||||
);
|
||||
// The session owns the files; this viewer only their drawing.
|
||||
@@ -345,9 +345,13 @@ test('the application composition wires the import and owns its teardown', () =>
|
||||
tools.indexOf('defer(() => geoFileImport?.detachScene());') > importAt,
|
||||
);
|
||||
// The list renders from the store and its controls answer through it.
|
||||
const sessionAt = session.indexOf(
|
||||
"sessionTool(layerSession, 'geoImport', defer, () =>",
|
||||
);
|
||||
assert.ok(sessionAt >= 0);
|
||||
assert.match(
|
||||
tools.slice(importAt, importAt + 400),
|
||||
/publish: \(view\) => displayStore\.publishGeoImport\(view\),\s*connect: connectDisplay,/,
|
||||
session.slice(sessionAt, sessionAt + 400),
|
||||
/navigation: styleManager,[\s\S]*publish: \(view\) => displayStore\.publishGeoImport\(view\),\s*connect: connectDisplay,/,
|
||||
);
|
||||
const markup = fs.readFileSync(
|
||||
new URL('./views/panels/GeoImport.vue', import.meta.url),
|
||||
|
||||
Reference in new issue
Block a user