mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-11 13:52:19 +00:00
feat(pointer): an application's own tool can take the pointer
createApplicationTools returns pointer: claim(owner) takes the pointer as Draw does and returns its lease, or null while another tool holds it; release(lease) gives it back; isFree() lets the application's own click handlers yield, as the layers' do. This replaces drawShape, which this branch added: Draw is as it was.
This commit is contained in:
1 parent
0600d86584
commit
a7f96313e9
5 files changed
+18
-231
No files matched your search
@@ -54,7 +54,6 @@ import {
|
||||
DRAW_COLORS,
|
||||
DRAW_COLOR_CSS,
|
||||
drawMarkActions,
|
||||
drawMarkGeometry,
|
||||
drawMarkActionsFor,
|
||||
drawMarkFallbackName,
|
||||
presentDrawMarks,
|
||||
@@ -121,9 +120,6 @@ export function initDrawTool({
|
||||
let savedDoubleClick = null;
|
||||
let savedSingleClick = null;
|
||||
let cursor = null; // last mouse position on the canvas, for the rubber band
|
||||
// One shape asked for by `drawShape`: `{ resolve, restoreShape, off }`. Its
|
||||
// finish hands the geometry back instead of placing a mark.
|
||||
let request = null;
|
||||
// Bumped by anything that supersedes an in-flight finish: cancelling, clearing
|
||||
// the board, leaving draw mode, teardown. An `annotate()` that resolves after
|
||||
// one of those must not write its outcome over the newer state.
|
||||
@@ -284,20 +280,6 @@ export function initDrawTool({
|
||||
// Named afterwards, in the shape's row.
|
||||
const spec = finishSpec(session, { label: '', color });
|
||||
if (!spec) return null;
|
||||
if (request) {
|
||||
// As a row's actions get it: a shape across the antimeridian has none,
|
||||
// and is drawn again rather than handed back wrapped round the world.
|
||||
const geometry = drawMarkGeometry(spec);
|
||||
if (!geometry) {
|
||||
session = createDrawSession(shape);
|
||||
cursor = null;
|
||||
syncPreview();
|
||||
setHint(() => t('That shape could not be placed.'));
|
||||
return null;
|
||||
}
|
||||
endRequest(geometry);
|
||||
return geometry;
|
||||
}
|
||||
// Start a fresh session BEFORE awaiting, so a second Enter or the second
|
||||
// half of a double-click cannot submit the same shape twice.
|
||||
session = createDrawSession(shape);
|
||||
@@ -425,7 +407,6 @@ export function initDrawTool({
|
||||
generation += 1;
|
||||
session = null;
|
||||
cursor = null;
|
||||
endRequest(null);
|
||||
releaseSceneHandler();
|
||||
releasePointer(lease);
|
||||
lease = null;
|
||||
@@ -551,46 +532,6 @@ export function initDrawTool({
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the asked-for shape with `geometry` (null when there is none): the
|
||||
* shape chosen before it comes back, and draw mode ends.
|
||||
*/
|
||||
function endRequest(geometry) {
|
||||
const open = request;
|
||||
if (!open) return;
|
||||
request = null;
|
||||
open.off();
|
||||
chooseShape(open.restoreShape);
|
||||
open.resolve(geometry);
|
||||
if (active) setActive(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw one area or line and hand back its geometry (a Polygon or a
|
||||
* LineString) without placing a mark. Escape works as in Draw: the first
|
||||
* clears a shape in progress, and one on an empty shape leaves. Resolves null
|
||||
* when draw mode is left, `signal` aborts, or the pointer is taken.
|
||||
* A second request ends the first with null.
|
||||
* @param {{shape?: 'area'|'line', signal?: AbortSignal}} [options]
|
||||
* @returns {Promise<{type: 'Polygon', coordinates: number[][][]}|{type: 'LineString', coordinates: number[][]}|null>}
|
||||
*/
|
||||
function drawShape({ shape: wanted = 'area', signal } = {}) {
|
||||
if (destroyed || signal?.aborted) return Promise.resolve(null);
|
||||
endRequest(null);
|
||||
const restoreShape = shape;
|
||||
return new Promise((resolve) => {
|
||||
const abort = () => endRequest(null);
|
||||
signal?.addEventListener('abort', abort, { once: true });
|
||||
request = {
|
||||
resolve,
|
||||
restoreShape,
|
||||
off: () => signal?.removeEventListener('abort', abort),
|
||||
};
|
||||
chooseShape(normalizeShape(wanted) === 'line' ? 'line' : 'area');
|
||||
if (!active && !setActive(true)) endRequest(null);
|
||||
});
|
||||
}
|
||||
|
||||
/** Draw `next` from now on; an open shape starts over in it. */
|
||||
function chooseShape(next) {
|
||||
shape = normalizeShape(next);
|
||||
@@ -604,10 +545,7 @@ export function initDrawTool({
|
||||
}
|
||||
const disconnect = connect({
|
||||
toggleDraw: () => setActive(!active),
|
||||
// An asked-for shape is the one asked for.
|
||||
setDrawShape: (next) => {
|
||||
if (!request) chooseShape(next);
|
||||
},
|
||||
setDrawShape: chooseShape,
|
||||
setDrawColor: (value) => {
|
||||
color = DRAW_COLORS.includes(value) ? value : 'primary';
|
||||
present();
|
||||
@@ -664,7 +602,6 @@ export function initDrawTool({
|
||||
finish,
|
||||
cancel,
|
||||
clearAll,
|
||||
drawShape,
|
||||
shapes: DRAW_SHAPES,
|
||||
/** What this tool currently holds — for teardown and leak assertions. */
|
||||
diagnostics() {
|
||||
@@ -701,7 +638,6 @@ export function initDrawTool({
|
||||
if (destroyed) return attaching;
|
||||
// Supersede any finish still in flight before anything is torn down.
|
||||
generation += 1;
|
||||
endRequest(null);
|
||||
if (active) setActive(false);
|
||||
destroyed = true;
|
||||
offLocale();
|
||||
|
||||
@@ -704,158 +704,6 @@ test('what a row action answers is its row\'s note; the row is busy until then,
|
||||
}
|
||||
});
|
||||
|
||||
test('drawShape hands back the drawn area as a polygon and places no mark', async () => {
|
||||
const h = harness();
|
||||
try {
|
||||
h.act('setDrawShape', 'line');
|
||||
const drawn = h.tool.drawShape({ shape: 'area' });
|
||||
assert.equal(h.tool.active, true, 'it opens draw mode');
|
||||
assert.equal(h.tool.shape, 'area');
|
||||
h.tool.addVertex(-97.75, 30.26);
|
||||
h.tool.addVertex(-97.74, 30.26);
|
||||
h.tool.addVertex(-97.74, 30.27);
|
||||
h.documentDouble.emit('keydown', {
|
||||
key: 'Enter',
|
||||
target: h.documentDouble.body,
|
||||
});
|
||||
const geometry = await drawn;
|
||||
assert.deepEqual(geometry, {
|
||||
type: 'Polygon',
|
||||
coordinates: [
|
||||
[
|
||||
[-97.75, 30.26],
|
||||
[-97.74, 30.26],
|
||||
[-97.74, 30.27],
|
||||
[-97.75, 30.26],
|
||||
],
|
||||
],
|
||||
});
|
||||
assert.equal(h.annotations.calls.annotate.length, 0, 'no mark is placed');
|
||||
assert.equal(h.tool.active, false, 'draw mode ends');
|
||||
assert.equal(isPointerFree(), true);
|
||||
assert.equal(h.tool.shape, 'line', 'the shape chosen before comes back');
|
||||
} finally {
|
||||
h.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('drawShape hands back a line as a LineString', async () => {
|
||||
const h = harness();
|
||||
try {
|
||||
const drawn = h.tool.drawShape({ shape: 'line' });
|
||||
h.tool.addVertex(-97.75, 30.26);
|
||||
h.tool.addVertex(-97.74, 30.26);
|
||||
h.documentDouble.emit('keydown', {
|
||||
key: 'Enter',
|
||||
target: h.documentDouble.body,
|
||||
});
|
||||
assert.deepEqual(await drawn, {
|
||||
type: 'LineString',
|
||||
coordinates: [
|
||||
[-97.75, 30.26],
|
||||
[-97.74, 30.26],
|
||||
],
|
||||
});
|
||||
} finally {
|
||||
h.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('drawShape resolves null on Escape, on abort, on a newer request and when the pointer is taken', async () => {
|
||||
const h = harness();
|
||||
try {
|
||||
// Escape with a shape started clears it; a second Escape leaves.
|
||||
const escaped = h.tool.drawShape({ shape: 'area' });
|
||||
h.tool.addVertex(-97.75, 30.26);
|
||||
h.documentDouble.emit('keydown', {
|
||||
key: 'Escape',
|
||||
target: h.documentDouble.body,
|
||||
});
|
||||
assert.equal(h.tool.active, true, 'the first Escape only clears the shape');
|
||||
h.documentDouble.emit('keydown', {
|
||||
key: 'Escape',
|
||||
target: h.documentDouble.body,
|
||||
});
|
||||
assert.equal(await escaped, null);
|
||||
assert.equal(h.tool.active, false);
|
||||
|
||||
const controller = new AbortController();
|
||||
const aborted = h.tool.drawShape({
|
||||
shape: 'area',
|
||||
signal: controller.signal,
|
||||
});
|
||||
controller.abort();
|
||||
assert.equal(await aborted, null);
|
||||
assert.equal(isPointerFree(), true, 'an abort gives the pointer back');
|
||||
|
||||
const first = h.tool.drawShape({ shape: 'area' });
|
||||
const second = h.tool.drawShape({ shape: 'line' });
|
||||
assert.equal(await first, null, 'a newer request ends the older one');
|
||||
h.act('toggleDraw');
|
||||
assert.equal(await second, null, 'leaving draw mode ends it');
|
||||
|
||||
const done = new AbortController();
|
||||
done.abort();
|
||||
assert.equal(await h.tool.drawShape({ signal: done.signal }), null);
|
||||
|
||||
const other = claimPointer('directions');
|
||||
assert.equal(
|
||||
await h.tool.drawShape({ shape: 'area' }),
|
||||
null,
|
||||
'a held pointer refuses it',
|
||||
);
|
||||
releasePointer(other);
|
||||
|
||||
// Drawing by hand afterwards places marks as before.
|
||||
h.act('toggleDraw');
|
||||
h.tool.addVertex(-97.75, 30.26);
|
||||
h.tool.addVertex(-97.74, 30.26);
|
||||
h.tool.addVertex(-97.74, 30.27);
|
||||
h.documentDouble.emit('keydown', {
|
||||
key: 'Enter',
|
||||
target: h.documentDouble.body,
|
||||
});
|
||||
await Promise.resolve();
|
||||
assert.equal(h.annotations.calls.annotate.length, 1);
|
||||
} finally {
|
||||
h.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('drawShape keeps its shape, and a shape across the antimeridian is drawn again', async () => {
|
||||
const h = harness();
|
||||
try {
|
||||
const drawn = h.tool.drawShape({ shape: 'line' });
|
||||
h.act('setDrawShape', 'pin');
|
||||
assert.equal(h.tool.shape, 'line', 'the controls cannot change the asked-for shape');
|
||||
h.tool.addVertex(179.5, 10);
|
||||
h.tool.addVertex(-179.5, 10);
|
||||
h.documentDouble.emit('keydown', { key: 'Enter', target: h.documentDouble.body });
|
||||
assert.equal(h.tool.active, true, 'still drawing');
|
||||
assert.equal(h.tool.session.vertices.length, 0, 'drawn again from the start');
|
||||
assert.equal(h.shown.view.hint, 'That shape could not be placed.');
|
||||
h.tool.addVertex(29, 41);
|
||||
h.tool.addVertex(29.1, 41);
|
||||
h.documentDouble.emit('keydown', { key: 'Enter', target: h.documentDouble.body });
|
||||
assert.equal((await drawn).type, 'LineString');
|
||||
h.act('setDrawShape', 'pin');
|
||||
assert.equal(h.tool.shape, 'pin', 'after it, the controls choose again');
|
||||
} finally {
|
||||
h.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('destroy ends a drawShape request with null', async () => {
|
||||
const h = harness();
|
||||
try {
|
||||
const drawn = h.tool.drawShape({ shape: 'area' });
|
||||
h.tool.destroy();
|
||||
assert.equal(await drawn, null);
|
||||
} finally {
|
||||
h.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('Backspace on the page removes the last vertex', () => {
|
||||
const h = harness();
|
||||
try {
|
||||
|
||||
+13
-9
@@ -1,7 +1,11 @@
|
||||
import { SceneDirector } from '../scenes/director.js';
|
||||
import { initAnnotations } from '../annotations/index.js';
|
||||
import { initDrawTool } from '../annotations/drawTool.js';
|
||||
import { isPointerFree } from '../data/inputOwnership.js';
|
||||
import {
|
||||
claimPointer,
|
||||
isPointerFree,
|
||||
releasePointer,
|
||||
} from '../data/inputOwnership.js';
|
||||
import { initGeoFileImport } from '../ui/geoFileImport.js';
|
||||
import { useDisplayStore } from '../ui/stores/display.js';
|
||||
import { applicationPinia } from '../ui/vueRuntime.js';
|
||||
@@ -246,17 +250,17 @@ export function createApplicationTools({
|
||||
signal,
|
||||
}),
|
||||
);
|
||||
// Draw one area or line and get its geometry back, without placing a mark
|
||||
// (annotations/drawTool.js `drawShape`); null without the Draw controls.
|
||||
const drawShape = (options) =>
|
||||
drawTool ? drawTool.drawShape(options) : Promise.resolve(null);
|
||||
// An application's own click handlers yield while a tool holds the pointer,
|
||||
// as the layers' do (data/inputOwnership.js).
|
||||
// An application's own tool takes the pointer as Draw does, and its own
|
||||
// click handlers yield while a tool holds it, as the layers' do
|
||||
// (data/inputOwnership.js).
|
||||
return {
|
||||
sceneDirector,
|
||||
annotations,
|
||||
voiceCommands,
|
||||
drawShape,
|
||||
pointerFree: isPointerFree,
|
||||
pointer: {
|
||||
claim: claimPointer,
|
||||
release: releasePointer,
|
||||
isFree: isPointerFree,
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user