@orbat-mapper/tactical-draw 0.2.0-alpha.34 → 0.2.0-alpha.36
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/index.d.mts +60 -445
- package/dist/index.mjs +533 -437
- package/dist/map-adapter-meRo5VY2.d.mts +361 -0
- package/dist/testing.d.mts +25 -0
- package/dist/testing.mjs +247 -0
- package/package.json +11 -2
package/dist/index.mjs
CHANGED
|
@@ -1,219 +1,4 @@
|
|
|
1
1
|
import { applyBoxTransformOptions, canonicalTextAmplifierKey, cloneControlMeasure, controlMeasureIdFromFeature, freezeOrientationOptions, getControlMeasureMetadata, getDefaultOptions, renderControlMeasure, resolveAmplifierPlacement, resolveStyleHints, roundToFixed } from "@orbat-mapper/control-measures";
|
|
2
|
-
//#region src/base-map-adapter.ts
|
|
3
|
-
/**
|
|
4
|
-
* Abstract base class shared by all `MapAdapter` implementations.
|
|
5
|
-
*
|
|
6
|
-
* Owns shared bookkeeping (layer registry, feature cache, event-token
|
|
7
|
-
* tracking) and orchestrates the public methods over a small
|
|
8
|
-
* protected primitive surface that subclasses implement. See
|
|
9
|
-
* `docs/adr/0001-map-adapter-base-class.md` for the rationale, in particular
|
|
10
|
-
* around the `applyFeatureOps` primitive shape.
|
|
11
|
-
*/
|
|
12
|
-
var BaseMapAdapter = class {
|
|
13
|
-
layers = /* @__PURE__ */ new Map();
|
|
14
|
-
eventTokens = /* @__PURE__ */ new Map();
|
|
15
|
-
viewChangeTokens = /* @__PURE__ */ new Map();
|
|
16
|
-
featureCache = /* @__PURE__ */ new Map();
|
|
17
|
-
featuresChangedListeners = /* @__PURE__ */ new Map();
|
|
18
|
-
iconResources = /* @__PURE__ */ new Map();
|
|
19
|
-
layerIconResourceIds = /* @__PURE__ */ new Map();
|
|
20
|
-
layerCounter = 0;
|
|
21
|
-
destroyed = false;
|
|
22
|
-
nextLayerId() {
|
|
23
|
-
return `layer-${++this.layerCounter}`;
|
|
24
|
-
}
|
|
25
|
-
addVectorLayer(options) {
|
|
26
|
-
const id = this.nextLayerId();
|
|
27
|
-
const handle = this.createLayer(id, options?.style);
|
|
28
|
-
this.layers.set(id, handle);
|
|
29
|
-
this.featureCache.set(id, []);
|
|
30
|
-
return id;
|
|
31
|
-
}
|
|
32
|
-
removeLayer(layerId) {
|
|
33
|
-
const handle = this.layers.get(layerId);
|
|
34
|
-
if (!handle) return;
|
|
35
|
-
this.destroyLayer(handle);
|
|
36
|
-
this.layers.delete(layerId);
|
|
37
|
-
this.featureCache.delete(layerId);
|
|
38
|
-
this.featuresChangedListeners.delete(layerId);
|
|
39
|
-
this.layerIconResourceIds.delete(layerId);
|
|
40
|
-
this.pruneIconResources();
|
|
41
|
-
}
|
|
42
|
-
reconcileLayerIconResources(layerId, resources) {
|
|
43
|
-
if (!this.layers.has(layerId)) return;
|
|
44
|
-
const ids = /* @__PURE__ */ new Set();
|
|
45
|
-
for (const resource of resources) {
|
|
46
|
-
ids.add(resource.id);
|
|
47
|
-
this.iconResources.set(resource.id, resource);
|
|
48
|
-
}
|
|
49
|
-
this.layerIconResourceIds.set(layerId, ids);
|
|
50
|
-
this.applyIconResources(resources);
|
|
51
|
-
this.pruneIconResources();
|
|
52
|
-
}
|
|
53
|
-
setLayerFeatures(layerId, features) {
|
|
54
|
-
if (!this.layers.has(layerId)) return;
|
|
55
|
-
const next = [...features];
|
|
56
|
-
this.featureCache.set(layerId, next);
|
|
57
|
-
this.applyFeatureOps(layerId, {
|
|
58
|
-
adds: next,
|
|
59
|
-
updates: [],
|
|
60
|
-
full: next,
|
|
61
|
-
replace: true
|
|
62
|
-
});
|
|
63
|
-
this.emitLayerFeaturesChanged(layerId, next);
|
|
64
|
-
}
|
|
65
|
-
updateLayerFeatures(layerId, features) {
|
|
66
|
-
if (!this.layers.has(layerId)) return;
|
|
67
|
-
const existing = this.featureCache.get(layerId) ?? [];
|
|
68
|
-
const existingIds = /* @__PURE__ */ new Set();
|
|
69
|
-
for (const f of existing) if (f.id != null) existingIds.add(f.id);
|
|
70
|
-
const adds = [];
|
|
71
|
-
const updates = [];
|
|
72
|
-
const updateById = /* @__PURE__ */ new Map();
|
|
73
|
-
for (const f of features) {
|
|
74
|
-
if (f.id == null) continue;
|
|
75
|
-
if (existingIds.has(f.id)) updates.push(f);
|
|
76
|
-
else adds.push(f);
|
|
77
|
-
updateById.set(f.id, f);
|
|
78
|
-
}
|
|
79
|
-
const merged = [...updateById.values()];
|
|
80
|
-
const removed = [];
|
|
81
|
-
for (const f of existing) if (f.id == null) merged.push(f);
|
|
82
|
-
else if (!updateById.has(f.id)) removed.push(f);
|
|
83
|
-
this.featureCache.set(layerId, merged);
|
|
84
|
-
this.applyFeatureOps(layerId, {
|
|
85
|
-
adds,
|
|
86
|
-
updates,
|
|
87
|
-
removes: removed,
|
|
88
|
-
full: merged,
|
|
89
|
-
replace: false
|
|
90
|
-
});
|
|
91
|
-
this.emitLayerFeaturesChanged(layerId, merged);
|
|
92
|
-
}
|
|
93
|
-
removeFeatures(layerId, ids) {
|
|
94
|
-
if (!this.layers.has(layerId)) return;
|
|
95
|
-
if (ids.length === 0) return;
|
|
96
|
-
const existing = this.featureCache.get(layerId) ?? [];
|
|
97
|
-
const idSet = new Set(ids);
|
|
98
|
-
const merged = [];
|
|
99
|
-
const removed = [];
|
|
100
|
-
for (const f of existing) if (typeof f.id === "string" && idSet.has(f.id)) removed.push(f);
|
|
101
|
-
else merged.push(f);
|
|
102
|
-
if (removed.length === 0) return;
|
|
103
|
-
this.featureCache.set(layerId, merged);
|
|
104
|
-
this.applyFeatureOps(layerId, {
|
|
105
|
-
adds: [],
|
|
106
|
-
updates: [],
|
|
107
|
-
removes: removed,
|
|
108
|
-
full: merged,
|
|
109
|
-
replace: false
|
|
110
|
-
});
|
|
111
|
-
this.emitLayerFeaturesChanged(layerId, merged);
|
|
112
|
-
}
|
|
113
|
-
clearLayer(layerId) {
|
|
114
|
-
if (!this.layers.has(layerId)) return;
|
|
115
|
-
this.featureCache.set(layerId, []);
|
|
116
|
-
this.applyFeatureOps(layerId, {
|
|
117
|
-
adds: [],
|
|
118
|
-
updates: [],
|
|
119
|
-
full: [],
|
|
120
|
-
replace: true
|
|
121
|
-
});
|
|
122
|
-
this.emitLayerFeaturesChanged(layerId, []);
|
|
123
|
-
}
|
|
124
|
-
on(eventType, handler) {
|
|
125
|
-
const entry = this.attachNativeEvent(eventType, (lonLat, pixel, originalEvent) => {
|
|
126
|
-
handler({
|
|
127
|
-
type: eventType,
|
|
128
|
-
coordinate: lonLat,
|
|
129
|
-
pixel,
|
|
130
|
-
originalEvent
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
this.eventTokens.set(handler, entry);
|
|
134
|
-
}
|
|
135
|
-
off(eventType, handler) {
|
|
136
|
-
const entry = this.eventTokens.get(handler);
|
|
137
|
-
if (!entry) return;
|
|
138
|
-
this.detachNativeEvent(entry);
|
|
139
|
-
this.eventTokens.delete(handler);
|
|
140
|
-
}
|
|
141
|
-
onViewChange(handler) {
|
|
142
|
-
const token = this.attachViewChange(handler);
|
|
143
|
-
this.viewChangeTokens.set(handler, token);
|
|
144
|
-
}
|
|
145
|
-
offViewChange(handler) {
|
|
146
|
-
const token = this.viewChangeTokens.get(handler);
|
|
147
|
-
if (token === void 0) return;
|
|
148
|
-
this.detachViewChange(token);
|
|
149
|
-
this.viewChangeTokens.delete(handler);
|
|
150
|
-
}
|
|
151
|
-
destroy() {
|
|
152
|
-
if (this.destroyed) return;
|
|
153
|
-
try {
|
|
154
|
-
for (const entry of this.eventTokens.values()) this.detachNativeEvent(entry);
|
|
155
|
-
this.eventTokens.clear();
|
|
156
|
-
for (const token of this.viewChangeTokens.values()) this.detachViewChange(token);
|
|
157
|
-
this.viewChangeTokens.clear();
|
|
158
|
-
for (const handle of this.layers.values()) this.destroyLayer(handle);
|
|
159
|
-
this.layers.clear();
|
|
160
|
-
this.featureCache.clear();
|
|
161
|
-
this.featuresChangedListeners.clear();
|
|
162
|
-
for (const id of this.iconResources.keys()) this.removeIconResource(id);
|
|
163
|
-
this.iconResources.clear();
|
|
164
|
-
this.layerIconResourceIds.clear();
|
|
165
|
-
this.destroyEngineResources();
|
|
166
|
-
} finally {
|
|
167
|
-
this.destroyed = true;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
emitLayerFeaturesChanged(layerId, features) {
|
|
171
|
-
const listeners = this.featuresChangedListeners.get(layerId);
|
|
172
|
-
if (!listeners) return;
|
|
173
|
-
for (const fn of listeners) fn(features);
|
|
174
|
-
}
|
|
175
|
-
onLayerFeaturesChanged(layerId, listener) {
|
|
176
|
-
let listeners = this.featuresChangedListeners.get(layerId);
|
|
177
|
-
if (!listeners) {
|
|
178
|
-
listeners = /* @__PURE__ */ new Set();
|
|
179
|
-
this.featuresChangedListeners.set(layerId, listeners);
|
|
180
|
-
}
|
|
181
|
-
listeners.add(listener);
|
|
182
|
-
return () => {
|
|
183
|
-
const set = this.featuresChangedListeners.get(layerId);
|
|
184
|
-
if (!set) return;
|
|
185
|
-
set.delete(listener);
|
|
186
|
-
if (set.size === 0) this.featuresChangedListeners.delete(layerId);
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
destroyEngineResources() {}
|
|
190
|
-
applyIconResources(resources) {}
|
|
191
|
-
getIconResource(id) {
|
|
192
|
-
return this.iconResources.get(id);
|
|
193
|
-
}
|
|
194
|
-
removeIconResource(id) {}
|
|
195
|
-
pruneIconResources() {
|
|
196
|
-
const active = /* @__PURE__ */ new Set();
|
|
197
|
-
for (const ids of this.layerIconResourceIds.values()) for (const id of ids) active.add(id);
|
|
198
|
-
if (this.iconResources.size <= active.size + 32) return;
|
|
199
|
-
for (const id of this.iconResources.keys()) {
|
|
200
|
-
if (active.has(id)) continue;
|
|
201
|
-
this.iconResources.delete(id);
|
|
202
|
-
this.removeIconResource(id);
|
|
203
|
-
if (this.iconResources.size <= active.size + 32) break;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* Default for engines/views that don't rotate (Leaflet). Rotating engines
|
|
208
|
-
* (MapLibre, OpenLayers) override this.
|
|
209
|
-
*/
|
|
210
|
-
getBearing() {
|
|
211
|
-
return 0;
|
|
212
|
-
}
|
|
213
|
-
getLayerFeatures(layerId) {
|
|
214
|
-
return this.featureCache.get(layerId) ?? [];
|
|
215
|
-
}
|
|
216
|
-
};
|
|
217
2
|
function pickPointerType(originalEvent) {
|
|
218
3
|
if (typeof PointerEvent !== "undefined" && originalEvent instanceof PointerEvent) {
|
|
219
4
|
if (originalEvent.pointerType === "touch") return "touch";
|
|
@@ -314,11 +99,9 @@ function mergeNativeAndCachedPickFeatures(nativeFeatures, cachedFeatures) {
|
|
|
314
99
|
* primary candidate.
|
|
315
100
|
*/
|
|
316
101
|
function evaluatePickCandidates(inputs, context) {
|
|
102
|
+
const candidates = eligibleCandidates(inputs, context);
|
|
317
103
|
let best = null;
|
|
318
|
-
for (const
|
|
319
|
-
const candidate = buildCandidate(input, context);
|
|
320
|
-
if (candidate && (best === null || compareCandidates(candidate, best) < 0)) best = candidate;
|
|
321
|
-
}
|
|
104
|
+
for (const candidate of candidates) if (best === null || compareCandidates(candidate, best) < 0) best = candidate;
|
|
322
105
|
return best;
|
|
323
106
|
}
|
|
324
107
|
function buildCandidate(input, context) {
|
|
@@ -333,10 +116,17 @@ function buildCandidate(input, context) {
|
|
|
333
116
|
viewport: context.viewport,
|
|
334
117
|
presentation,
|
|
335
118
|
visualOrder: input.visualOrder,
|
|
119
|
+
...input.featureGroupOrder !== void 0 ? { featureGroupOrder: input.featureGroupOrder } : {},
|
|
336
120
|
...evaluated.iconFootprint ? { iconFootprint: evaluated.iconFootprint } : {},
|
|
337
121
|
...input.labelExtent ? { labelExtent: input.labelExtent } : {}
|
|
338
122
|
};
|
|
339
123
|
}
|
|
124
|
+
function eligibleCandidates(inputs, context) {
|
|
125
|
+
const candidates = inputs.map((input) => buildCandidate(input, context)).filter((candidate) => candidate !== null);
|
|
126
|
+
const opaqueCovers = candidates.filter(isOpaqueFillInterior);
|
|
127
|
+
if (opaqueCovers.length === 0) return candidates;
|
|
128
|
+
return candidates.filter((candidate) => !opaqueCovers.some((cover) => cover.featureGroupOrder !== void 0 && candidate.featureGroupOrder !== void 0 && cover.featureGroupOrder < candidate.featureGroupOrder));
|
|
129
|
+
}
|
|
340
130
|
function compareCandidates(a, b) {
|
|
341
131
|
const rankA = selectionSurfaceRank(a);
|
|
342
132
|
const rankDifference = rankA - selectionSurfaceRank(b);
|
|
@@ -345,8 +135,13 @@ function compareCandidates(a, b) {
|
|
|
345
135
|
const distanceDifference = a.hit.distance - b.hit.distance;
|
|
346
136
|
if (distanceDifference !== 0) return distanceDifference;
|
|
347
137
|
}
|
|
138
|
+
if (a.featureGroupOrder !== void 0 && b.featureGroupOrder !== void 0 && a.featureGroupOrder !== b.featureGroupOrder) return a.featureGroupOrder - b.featureGroupOrder;
|
|
348
139
|
return a.visualOrder - b.visualOrder;
|
|
349
140
|
}
|
|
141
|
+
function isOpaqueFillInterior(candidate) {
|
|
142
|
+
const fill = candidate.presentation.fill;
|
|
143
|
+
return candidate.hit.kind === "polygon-interior" && fill?.opacity === 1 && (fill.pattern === void 0 || fill.pattern === "solid");
|
|
144
|
+
}
|
|
350
145
|
function selectionSurfaceRank(candidate) {
|
|
351
146
|
if (candidate.hit.kind !== "polygon-interior") return 0;
|
|
352
147
|
return (candidate.presentation.fill?.opacity ?? 0) > 0 ? 1 : 2;
|
|
@@ -641,6 +436,196 @@ function numberArray(value) {
|
|
|
641
436
|
return Array.isArray(value) && value.every((item) => typeof item === "number") ? [...value] : void 0;
|
|
642
437
|
}
|
|
643
438
|
//#endregion
|
|
439
|
+
//#region src/base-map-adapter.ts
|
|
440
|
+
/**
|
|
441
|
+
* Abstract base class shared by all `MapAdapter` implementations.
|
|
442
|
+
*
|
|
443
|
+
* Owns shared bookkeeping (layer registry, feature cache, event-token
|
|
444
|
+
* tracking) and orchestrates the public methods over a small protected
|
|
445
|
+
* grouped desired-state primitive that subclasses implement.
|
|
446
|
+
*/
|
|
447
|
+
var BaseMapAdapter = class {
|
|
448
|
+
layers = /* @__PURE__ */ new Map();
|
|
449
|
+
eventTokens = /* @__PURE__ */ new Map();
|
|
450
|
+
viewChangeTokens = /* @__PURE__ */ new Map();
|
|
451
|
+
featureCache = /* @__PURE__ */ new Map();
|
|
452
|
+
featuresChangedListeners = /* @__PURE__ */ new Map();
|
|
453
|
+
iconResources = /* @__PURE__ */ new Map();
|
|
454
|
+
featureGroupState = /* @__PURE__ */ new Map();
|
|
455
|
+
layerIconResourceIds = /* @__PURE__ */ new Map();
|
|
456
|
+
layerCounter = 0;
|
|
457
|
+
destroyed = false;
|
|
458
|
+
nextLayerId() {
|
|
459
|
+
return `layer-${++this.layerCounter}`;
|
|
460
|
+
}
|
|
461
|
+
addVectorLayer(options) {
|
|
462
|
+
const id = this.nextLayerId();
|
|
463
|
+
const handle = this.createLayer(id, options?.style);
|
|
464
|
+
this.layers.set(id, handle);
|
|
465
|
+
this.featureCache.set(id, []);
|
|
466
|
+
this.featureGroupState.set(id, {
|
|
467
|
+
groups: [],
|
|
468
|
+
orderByFeatureId: /* @__PURE__ */ new Map()
|
|
469
|
+
});
|
|
470
|
+
return id;
|
|
471
|
+
}
|
|
472
|
+
removeLayer(layerId) {
|
|
473
|
+
const handle = this.layers.get(layerId);
|
|
474
|
+
if (!handle) return;
|
|
475
|
+
this.destroyLayer(handle);
|
|
476
|
+
this.layers.delete(layerId);
|
|
477
|
+
this.featureCache.delete(layerId);
|
|
478
|
+
this.featureGroupState.delete(layerId);
|
|
479
|
+
this.featuresChangedListeners.delete(layerId);
|
|
480
|
+
this.layerIconResourceIds.delete(layerId);
|
|
481
|
+
this.pruneIconResources();
|
|
482
|
+
}
|
|
483
|
+
setLayerFeatureGroups(layerId, groups) {
|
|
484
|
+
validateFeatureGroups(groups);
|
|
485
|
+
if (!this.layers.has(layerId)) return;
|
|
486
|
+
const nextGroups = groups.map((group) => ({
|
|
487
|
+
id: group.id,
|
|
488
|
+
features: [...group.features],
|
|
489
|
+
iconResources: [...group.iconResources]
|
|
490
|
+
}));
|
|
491
|
+
const nextFeatures = nextGroups.flatMap((group) => group.features);
|
|
492
|
+
const nextResourcesById = /* @__PURE__ */ new Map();
|
|
493
|
+
for (const group of nextGroups) for (const resource of group.iconResources) nextResourcesById.set(resource.id, resource);
|
|
494
|
+
const nextResources = [...nextResourcesById.values()];
|
|
495
|
+
const resourceIds = new Set(nextResourcesById.keys());
|
|
496
|
+
for (const resource of nextResources) this.iconResources.set(resource.id, resource);
|
|
497
|
+
this.layerIconResourceIds.set(layerId, resourceIds);
|
|
498
|
+
this.applyIconResources(nextResources);
|
|
499
|
+
this.featureGroupState.set(layerId, {
|
|
500
|
+
groups: nextGroups,
|
|
501
|
+
orderByFeatureId: indexFeatureGroupOrder(nextGroups)
|
|
502
|
+
});
|
|
503
|
+
this.featureCache.set(layerId, nextFeatures);
|
|
504
|
+
this.applyFeatureGroups(layerId, nextGroups);
|
|
505
|
+
this.emitLayerFeaturesChanged(layerId, nextFeatures);
|
|
506
|
+
this.pruneIconResources(true);
|
|
507
|
+
}
|
|
508
|
+
on(eventType, handler) {
|
|
509
|
+
const entry = this.attachNativeEvent(eventType, (lonLat, pixel, originalEvent) => {
|
|
510
|
+
handler({
|
|
511
|
+
type: eventType,
|
|
512
|
+
coordinate: lonLat,
|
|
513
|
+
pixel,
|
|
514
|
+
originalEvent
|
|
515
|
+
});
|
|
516
|
+
});
|
|
517
|
+
this.eventTokens.set(handler, entry);
|
|
518
|
+
}
|
|
519
|
+
off(eventType, handler) {
|
|
520
|
+
const entry = this.eventTokens.get(handler);
|
|
521
|
+
if (!entry) return;
|
|
522
|
+
this.detachNativeEvent(entry);
|
|
523
|
+
this.eventTokens.delete(handler);
|
|
524
|
+
}
|
|
525
|
+
onViewChange(handler) {
|
|
526
|
+
const token = this.attachViewChange(handler);
|
|
527
|
+
this.viewChangeTokens.set(handler, token);
|
|
528
|
+
}
|
|
529
|
+
offViewChange(handler) {
|
|
530
|
+
const token = this.viewChangeTokens.get(handler);
|
|
531
|
+
if (token === void 0) return;
|
|
532
|
+
this.detachViewChange(token);
|
|
533
|
+
this.viewChangeTokens.delete(handler);
|
|
534
|
+
}
|
|
535
|
+
destroy() {
|
|
536
|
+
if (this.destroyed) return;
|
|
537
|
+
try {
|
|
538
|
+
for (const entry of this.eventTokens.values()) this.detachNativeEvent(entry);
|
|
539
|
+
this.eventTokens.clear();
|
|
540
|
+
for (const token of this.viewChangeTokens.values()) this.detachViewChange(token);
|
|
541
|
+
this.viewChangeTokens.clear();
|
|
542
|
+
for (const handle of this.layers.values()) this.destroyLayer(handle);
|
|
543
|
+
this.layers.clear();
|
|
544
|
+
this.featureCache.clear();
|
|
545
|
+
this.featureGroupState.clear();
|
|
546
|
+
this.featuresChangedListeners.clear();
|
|
547
|
+
for (const id of this.iconResources.keys()) this.removeIconResource(id);
|
|
548
|
+
this.iconResources.clear();
|
|
549
|
+
this.layerIconResourceIds.clear();
|
|
550
|
+
this.destroyEngineResources();
|
|
551
|
+
} finally {
|
|
552
|
+
this.destroyed = true;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
emitLayerFeaturesChanged(layerId, features) {
|
|
556
|
+
const listeners = this.featuresChangedListeners.get(layerId);
|
|
557
|
+
if (!listeners) return;
|
|
558
|
+
for (const fn of listeners) fn(features);
|
|
559
|
+
}
|
|
560
|
+
onLayerFeaturesChanged(layerId, listener) {
|
|
561
|
+
let listeners = this.featuresChangedListeners.get(layerId);
|
|
562
|
+
if (!listeners) {
|
|
563
|
+
listeners = /* @__PURE__ */ new Set();
|
|
564
|
+
this.featuresChangedListeners.set(layerId, listeners);
|
|
565
|
+
}
|
|
566
|
+
listeners.add(listener);
|
|
567
|
+
return () => {
|
|
568
|
+
const set = this.featuresChangedListeners.get(layerId);
|
|
569
|
+
if (!set) return;
|
|
570
|
+
set.delete(listener);
|
|
571
|
+
if (set.size === 0) this.featuresChangedListeners.delete(layerId);
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
destroyEngineResources() {}
|
|
575
|
+
applyIconResources(resources) {}
|
|
576
|
+
getIconResource(id) {
|
|
577
|
+
return this.iconResources.get(id);
|
|
578
|
+
}
|
|
579
|
+
getFeatureGroupOrder(layerId, featureId) {
|
|
580
|
+
if (featureId === void 0) return void 0;
|
|
581
|
+
return this.featureGroupState.get(layerId)?.orderByFeatureId.get(featureId);
|
|
582
|
+
}
|
|
583
|
+
removeIconResource(id) {}
|
|
584
|
+
pruneIconResources(removeAllUnused = false) {
|
|
585
|
+
const active = /* @__PURE__ */ new Set();
|
|
586
|
+
for (const ids of this.layerIconResourceIds.values()) for (const id of ids) active.add(id);
|
|
587
|
+
if (!removeAllUnused && this.iconResources.size <= active.size + 32) return;
|
|
588
|
+
for (const id of this.iconResources.keys()) {
|
|
589
|
+
if (active.has(id)) continue;
|
|
590
|
+
this.iconResources.delete(id);
|
|
591
|
+
this.removeIconResource(id);
|
|
592
|
+
if (!removeAllUnused && this.iconResources.size <= active.size + 32) break;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Default for engines/views that don't rotate (Leaflet). Rotating engines
|
|
597
|
+
* (MapLibre, OpenLayers) override this.
|
|
598
|
+
*/
|
|
599
|
+
getBearing() {
|
|
600
|
+
return 0;
|
|
601
|
+
}
|
|
602
|
+
getLayerFeatures(layerId) {
|
|
603
|
+
return this.featureCache.get(layerId) ?? [];
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
function indexFeatureGroupOrder(groups) {
|
|
607
|
+
const orderByFeatureId = /* @__PURE__ */ new Map();
|
|
608
|
+
groups.forEach((group, index) => {
|
|
609
|
+
const visualOrder = renderOrderToVisualOrder(index, groups.length);
|
|
610
|
+
for (const feature of group.features) if (feature.id != null) orderByFeatureId.set(feature.id, visualOrder);
|
|
611
|
+
});
|
|
612
|
+
return orderByFeatureId;
|
|
613
|
+
}
|
|
614
|
+
function validateFeatureGroups(groups) {
|
|
615
|
+
const groupIds = /* @__PURE__ */ new Set();
|
|
616
|
+
const featureIds = /* @__PURE__ */ new Set();
|
|
617
|
+
for (const group of groups) {
|
|
618
|
+
if (typeof group.id !== "string" || group.id.trim().length === 0) throw new Error("Feature group id must be a non-empty string");
|
|
619
|
+
if (groupIds.has(group.id)) throw new Error(`Duplicate feature group id: ${group.id}`);
|
|
620
|
+
groupIds.add(group.id);
|
|
621
|
+
for (const feature of group.features) {
|
|
622
|
+
if (feature.id == null) throw new Error(`Feature in group "${group.id}" is missing an id`);
|
|
623
|
+
if (featureIds.has(feature.id)) throw new Error(`Duplicate feature id: ${String(feature.id)}`);
|
|
624
|
+
featureIds.add(feature.id);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
//#endregion
|
|
644
629
|
//#region src/feature-style.ts
|
|
645
630
|
/**
|
|
646
631
|
* Coerce a raw `properties.style` value into a `HandleStyle` for the
|
|
@@ -1258,23 +1243,15 @@ function runInteraction(spec) {
|
|
|
1258
1243
|
resolveFn = resolve;
|
|
1259
1244
|
rejectFn = reject;
|
|
1260
1245
|
});
|
|
1261
|
-
const
|
|
1262
|
-
for (const layer of layers) idsByLayer.set(layer, /* @__PURE__ */ new Set());
|
|
1246
|
+
const ownedLayers = new Set(layers);
|
|
1263
1247
|
let controllerDetach = null;
|
|
1264
1248
|
let controllerOnAbort = null;
|
|
1265
|
-
function writeLayer(layer,
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
if (!set) return;
|
|
1269
|
-
set.clear();
|
|
1270
|
-
for (const f of features) if (typeof f.id === "string") set.add(f.id);
|
|
1249
|
+
function writeLayer(layer, groups) {
|
|
1250
|
+
if (!ownedLayers.has(layer)) return;
|
|
1251
|
+
adapter.setLayerFeatureGroups(layer, groups);
|
|
1271
1252
|
}
|
|
1272
1253
|
function clearLayers() {
|
|
1273
|
-
for (const
|
|
1274
|
-
if (set.size === 0) continue;
|
|
1275
|
-
adapter.removeFeatures(layer, [...set]);
|
|
1276
|
-
set.clear();
|
|
1277
|
-
}
|
|
1254
|
+
for (const layer of ownedLayers) adapter.setLayerFeatureGroups(layer, []);
|
|
1278
1255
|
}
|
|
1279
1256
|
const handleKeydown = (event) => {
|
|
1280
1257
|
if (settled) return;
|
|
@@ -1812,6 +1789,7 @@ function createCommitEmitter(listenerErrorsMessage) {
|
|
|
1812
1789
|
//#endregion
|
|
1813
1790
|
//#region src/tactical-draw/draw-controller.ts
|
|
1814
1791
|
const PREVIEW_CM_ID$1 = "__td_preview__";
|
|
1792
|
+
const GUIDE_GROUP_ID = "__td_draw_guide__";
|
|
1815
1793
|
const PREVIEW_CURSOR$1 = "crosshair";
|
|
1816
1794
|
/**
|
|
1817
1795
|
* Drive an interactive fixed-length draw against a `MapAdapter`.
|
|
@@ -1926,16 +1904,32 @@ function runFixedLengthDraw(options) {
|
|
|
1926
1904
|
function renderPreview() {
|
|
1927
1905
|
const raw = rubberBand !== void 0 ? [...committed, rubberBand] : [...committed];
|
|
1928
1906
|
if (raw.length === 0) {
|
|
1929
|
-
ctx.writeLayer(previewLayer, [
|
|
1930
|
-
|
|
1907
|
+
ctx.writeLayer(previewLayer, [{
|
|
1908
|
+
id: PREVIEW_CM_ID$1,
|
|
1909
|
+
features: [],
|
|
1910
|
+
iconResources: []
|
|
1911
|
+
}]);
|
|
1912
|
+
ctx.writeLayer(guideLayer, [{
|
|
1913
|
+
id: GUIDE_GROUP_ID,
|
|
1914
|
+
features: [],
|
|
1915
|
+
iconResources: []
|
|
1916
|
+
}]);
|
|
1931
1917
|
return;
|
|
1932
1918
|
}
|
|
1933
1919
|
const previewThreshold = rule?.minimumPreviewPoints ?? required;
|
|
1934
1920
|
const features = buildPreviewFeatures$1(adapter, kind, rule && raw.length >= previewThreshold ? rule.derive(raw) : raw, measureOptions, measureStyle, measureTextAmplifiers, renderOpts);
|
|
1935
|
-
ctx.writeLayer(previewLayer,
|
|
1921
|
+
ctx.writeLayer(previewLayer, [{
|
|
1922
|
+
id: PREVIEW_CM_ID$1,
|
|
1923
|
+
features,
|
|
1924
|
+
iconResources: []
|
|
1925
|
+
}]);
|
|
1936
1926
|
const guidePoints = rule?.guidePoints ? rule.guidePoints(raw) : raw;
|
|
1937
1927
|
const guideFeatures = guideEnabled ? buildGuideFeatures(guidePoints, isAreaKind) : [];
|
|
1938
|
-
ctx.writeLayer(guideLayer,
|
|
1928
|
+
ctx.writeLayer(guideLayer, [{
|
|
1929
|
+
id: GUIDE_GROUP_ID,
|
|
1930
|
+
features: guideFeatures,
|
|
1931
|
+
iconResources: []
|
|
1932
|
+
}]);
|
|
1939
1933
|
}
|
|
1940
1934
|
function rescaleOnViewChange() {
|
|
1941
1935
|
if (!measureRendersFromViewState({
|
|
@@ -2214,9 +2208,17 @@ function runVariableLengthDraw(options) {
|
|
|
2214
2208
|
} else points = [...committed];
|
|
2215
2209
|
if (rule) points = rule.derive(points);
|
|
2216
2210
|
const previewFeatures = points.length === 0 ? [] : buildPreviewFeatures(adapter, kind, points, measureOptions, measureStyle, measureTextAmplifiers, renderOpts);
|
|
2217
|
-
ctx.writeLayer(previewLayer,
|
|
2211
|
+
ctx.writeLayer(previewLayer, [{
|
|
2212
|
+
id: "__td_variable_draw_preview__",
|
|
2213
|
+
features: previewFeatures,
|
|
2214
|
+
iconResources: []
|
|
2215
|
+
}]);
|
|
2218
2216
|
const guideFeatures = points.length === 0 || !guideEnabled ? [] : buildGuideFeatures(points, isAreaKind, trailingFixed);
|
|
2219
|
-
ctx.writeLayer(guideLayer,
|
|
2217
|
+
ctx.writeLayer(guideLayer, [{
|
|
2218
|
+
id: "__td_variable_draw_guide__",
|
|
2219
|
+
features: guideFeatures,
|
|
2220
|
+
iconResources: []
|
|
2221
|
+
}]);
|
|
2220
2222
|
}
|
|
2221
2223
|
function rescaleOnViewChange() {
|
|
2222
2224
|
if (!measureRendersFromViewState({
|
|
@@ -2382,6 +2384,7 @@ function runEditInteraction(spec) {
|
|
|
2382
2384
|
spec.onForeignPick?.(localGraphics);
|
|
2383
2385
|
return;
|
|
2384
2386
|
}
|
|
2387
|
+
if (localGraphics && spec.ownsGraphic(localGraphics.id)) return;
|
|
2385
2388
|
if (localPreview) return;
|
|
2386
2389
|
if (wasBodyClick) return;
|
|
2387
2390
|
spec.close();
|
|
@@ -3570,15 +3573,15 @@ const ALLOWED_EDIT_MODES = new Set([
|
|
|
3570
3573
|
* Drive an interactive edit against a `MapAdapter`. Implements the edit
|
|
3571
3574
|
* lifecycle (issue #48) plus the ADR-0017 transform box:
|
|
3572
3575
|
*
|
|
3573
|
-
* -
|
|
3574
|
-
* -
|
|
3576
|
+
* - working-render publication into the host-owned Graphic stack slot
|
|
3577
|
+
* - reshape-handle rendering and the oriented transform box
|
|
3575
3578
|
* (corner scale handles, rotate grip, drag-anywhere-inside body translate)
|
|
3576
3579
|
* - dirty tracking via [[DIRTY_EPSILON]] tolerance
|
|
3577
3580
|
* - default click-away close policy
|
|
3578
3581
|
* - close / abort / signal / preempt / destroy paths
|
|
3579
3582
|
*/
|
|
3580
3583
|
function runEdit(opts) {
|
|
3581
|
-
const { adapter, measure, graphicsLayer, previewLayer, guideLayer, handlesLayer, graphicsStyle, signal, closeOnClickAway = true, modes: initialModes, interactionStyle, onSession, handleIdPrefix, guide: guideOpt, settleToGraphics,
|
|
3584
|
+
const { adapter, measure, graphicsLayer, previewLayer, guideLayer, handlesLayer, graphicsStyle, signal, closeOnClickAway = true, modes: initialModes, interactionStyle, onSession, handleIdPrefix, guide: guideOpt, settleToGraphics, publishWorkingGraphic, onSettled, onClickAwayGraphicsPick, snap } = opts;
|
|
3582
3585
|
const renderOpts = graphicsStyle ? { graphicsStyle } : void 0;
|
|
3583
3586
|
let sizeAnchor = opts.sizeAnchor ?? "ground";
|
|
3584
3587
|
const metadata = getControlMeasureMetadata(measure.kind);
|
|
@@ -3691,8 +3694,8 @@ function runEdit(opts) {
|
|
|
3691
3694
|
* while a box gesture (`activeBoxDrag`) is in flight.
|
|
3692
3695
|
*/
|
|
3693
3696
|
let box = null;
|
|
3694
|
-
/** The
|
|
3695
|
-
let
|
|
3697
|
+
/** The last working-render features, reused to refit `box` without a second render pass. */
|
|
3698
|
+
let lastWorkingFeatures = [];
|
|
3696
3699
|
let ctx;
|
|
3697
3700
|
let driver;
|
|
3698
3701
|
let shell;
|
|
@@ -3706,7 +3709,7 @@ function runEdit(opts) {
|
|
|
3706
3709
|
resolution: adapter.getResolution(),
|
|
3707
3710
|
bearing: adapter.getBearing()
|
|
3708
3711
|
}),
|
|
3709
|
-
features: () =>
|
|
3712
|
+
features: () => lastWorkingFeatures,
|
|
3710
3713
|
snapshotAmplifierPlacements,
|
|
3711
3714
|
handleIdPrefix,
|
|
3712
3715
|
interactionStyle
|
|
@@ -3742,7 +3745,7 @@ function runEdit(opts) {
|
|
|
3742
3745
|
});
|
|
3743
3746
|
}
|
|
3744
3747
|
/**
|
|
3745
|
-
* Re-fit `box` from `
|
|
3748
|
+
* Re-fit `box` from `lastWorkingFeatures`. A no-op while a box gesture is
|
|
3746
3749
|
* in flight — that drag paints the box rigidly from its own snapshot
|
|
3747
3750
|
* (`BoxDragSnapshot.box`) instead of a re-fit, so a corner/rotate/body drag
|
|
3748
3751
|
* doesn't fight its own paint. Text is the exception: its box follows the
|
|
@@ -3751,14 +3754,9 @@ function runEdit(opts) {
|
|
|
3751
3754
|
*/
|
|
3752
3755
|
function refitBox() {
|
|
3753
3756
|
if (activeBoxDrag && !isTextGraphic) return;
|
|
3754
|
-
box = fitBoxFromFeatures(
|
|
3757
|
+
box = fitBoxFromFeatures(lastWorkingFeatures);
|
|
3755
3758
|
}
|
|
3756
|
-
function
|
|
3757
|
-
if (!wasOnGraphicsLayer) return;
|
|
3758
|
-
const originalFeatureIds = renderMeasureForAdapter(adapter, measure, renderOpts).features.map((f) => f.id).filter((id) => typeof id === "string");
|
|
3759
|
-
if (originalFeatureIds.length > 0) adapter.removeFeatures(graphicsLayer, originalFeatureIds);
|
|
3760
|
-
}
|
|
3761
|
-
function renderPreview() {
|
|
3759
|
+
function renderWorkingGraphic() {
|
|
3762
3760
|
if (ctx.settled) return;
|
|
3763
3761
|
const working = {
|
|
3764
3762
|
...measure,
|
|
@@ -3776,19 +3774,23 @@ function runEdit(opts) {
|
|
|
3776
3774
|
} catch {
|
|
3777
3775
|
features = [];
|
|
3778
3776
|
}
|
|
3779
|
-
|
|
3780
|
-
|
|
3777
|
+
publishWorkingGraphic(working, features);
|
|
3778
|
+
lastWorkingFeatures = features;
|
|
3781
3779
|
refitBox();
|
|
3782
3780
|
const guidePoints = rule?.guidePoints ? rule.guidePoints(workingPoints) : workingPoints;
|
|
3783
3781
|
const guideFeatures = guideEnabled ? buildGuideFeatures(guidePoints, isAreaKind, trailingFixedSlots) : [];
|
|
3784
|
-
ctx.writeLayer(guideLayer,
|
|
3782
|
+
ctx.writeLayer(guideLayer, [{
|
|
3783
|
+
id: `__td_edit_guide__:${measure.id}`,
|
|
3784
|
+
features: guideFeatures,
|
|
3785
|
+
iconResources: []
|
|
3786
|
+
}]);
|
|
3785
3787
|
}
|
|
3786
3788
|
function rescaleOnViewChange() {
|
|
3787
3789
|
if (ctx.settled) return;
|
|
3788
3790
|
if (measureRendersFromViewState({
|
|
3789
3791
|
kind: measure.kind,
|
|
3790
3792
|
options: workingOptions
|
|
3791
|
-
}))
|
|
3793
|
+
})) renderWorkingGraphic();
|
|
3792
3794
|
else refitBox();
|
|
3793
3795
|
renderHandlesLayer();
|
|
3794
3796
|
}
|
|
@@ -3807,10 +3809,14 @@ function runEdit(opts) {
|
|
|
3807
3809
|
features.push(...toFeatures(boxToHandles(liveBox, dragProjection.fromPixel), handleIdPrefix, interactionStyle));
|
|
3808
3810
|
}
|
|
3809
3811
|
}
|
|
3810
|
-
ctx.writeLayer(handlesLayer,
|
|
3812
|
+
ctx.writeLayer(handlesLayer, [{
|
|
3813
|
+
id: `__td_edit_handles__:${measure.id}`,
|
|
3814
|
+
features,
|
|
3815
|
+
iconResources: []
|
|
3816
|
+
}]);
|
|
3811
3817
|
}
|
|
3812
3818
|
/**
|
|
3813
|
-
* Repaint the preview and, in lockstep, the handles. `
|
|
3819
|
+
* Repaint the preview and, in lockstep, the handles. `renderWorkingGraphic` re-fits
|
|
3814
3820
|
* `box` (for Text, the footprint depends on options — size/rotation/
|
|
3815
3821
|
* alignment/text), so the transform-box outline and its corner/rotate handles
|
|
3816
3822
|
* must repaint to match; otherwise they stay where the old options placed them
|
|
@@ -3818,8 +3824,8 @@ function runEdit(opts) {
|
|
|
3818
3824
|
* (#scale-handle desync). Used by the non-geometry option/style/amplifier
|
|
3819
3825
|
* setters, which change what the preview renders without moving a point.
|
|
3820
3826
|
*/
|
|
3821
|
-
function
|
|
3822
|
-
|
|
3827
|
+
function renderWorkingGraphicAndHandles() {
|
|
3828
|
+
renderWorkingGraphic();
|
|
3823
3829
|
renderHandlesLayer();
|
|
3824
3830
|
}
|
|
3825
3831
|
/**
|
|
@@ -3900,7 +3906,7 @@ function runEdit(opts) {
|
|
|
3900
3906
|
workingAmplifierPlacements = previousAmplifierPlacements;
|
|
3901
3907
|
boxAngle = previousBoxAngle;
|
|
3902
3908
|
nonGeometryDirty = previousNonGeometryDirty;
|
|
3903
|
-
|
|
3909
|
+
renderWorkingGraphic();
|
|
3904
3910
|
renderHandlesLayer();
|
|
3905
3911
|
recomputeDirty();
|
|
3906
3912
|
},
|
|
@@ -3939,7 +3945,7 @@ function runEdit(opts) {
|
|
|
3939
3945
|
drainPendingModes();
|
|
3940
3946
|
throw err;
|
|
3941
3947
|
}
|
|
3942
|
-
|
|
3948
|
+
renderWorkingGraphic();
|
|
3943
3949
|
renderHandlesLayer();
|
|
3944
3950
|
recomputeDirty();
|
|
3945
3951
|
dragInFlight = false;
|
|
@@ -3967,7 +3973,7 @@ function runEdit(opts) {
|
|
|
3967
3973
|
replaceWorkingOptions(previousOptions);
|
|
3968
3974
|
throw err;
|
|
3969
3975
|
}
|
|
3970
|
-
|
|
3976
|
+
renderWorkingGraphicAndHandles();
|
|
3971
3977
|
nonGeometryDirty = true;
|
|
3972
3978
|
dirty = true;
|
|
3973
3979
|
emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered, previousTextAmplifiers, previousAmplifierPlacements, "programmatic", "Change options");
|
|
@@ -3993,7 +3999,7 @@ function runEdit(opts) {
|
|
|
3993
3999
|
currentStyle = previousStyle;
|
|
3994
4000
|
throw err;
|
|
3995
4001
|
}
|
|
3996
|
-
|
|
4002
|
+
renderWorkingGraphicAndHandles();
|
|
3997
4003
|
nonGeometryDirty = true;
|
|
3998
4004
|
dirty = true;
|
|
3999
4005
|
emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered, previousTextAmplifiers, previousAmplifierPlacements, "programmatic", "Change style");
|
|
@@ -4003,7 +4009,7 @@ function runEdit(opts) {
|
|
|
4003
4009
|
* [[setOptions]] exactly (dirty tracking, re-render, change emission) —
|
|
4004
4010
|
* see that function for the shared shape. A merge value of `undefined` or
|
|
4005
4011
|
* an empty/whitespace-only string deletes the key; the record collapses to
|
|
4006
|
-
* `undefined` once empty so `
|
|
4012
|
+
* `undefined` once empty so `renderWorkingGraphic`/`rebuildRenderedWith` omit the
|
|
4007
4013
|
* key rather than persist `{}`.
|
|
4008
4014
|
*
|
|
4009
4015
|
* Each patch key resolves to its canonical field key and evicts every
|
|
@@ -4039,7 +4045,7 @@ function runEdit(opts) {
|
|
|
4039
4045
|
workingTextAmplifiers = previousTextAmplifiers;
|
|
4040
4046
|
throw err;
|
|
4041
4047
|
}
|
|
4042
|
-
|
|
4048
|
+
renderWorkingGraphicAndHandles();
|
|
4043
4049
|
nonGeometryDirty = true;
|
|
4044
4050
|
dirty = true;
|
|
4045
4051
|
emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered, previousTextAmplifiers, previousAmplifierPlacements, "programmatic", "Change text");
|
|
@@ -4066,7 +4072,7 @@ function runEdit(opts) {
|
|
|
4066
4072
|
workingAmplifierPlacements = Object.keys(next).length > 0 ? next : void 0;
|
|
4067
4073
|
if (JSON.stringify(workingAmplifierPlacements) === JSON.stringify(previousAmplifierPlacements)) return;
|
|
4068
4074
|
const currentRendered = rebuildRendered();
|
|
4069
|
-
|
|
4075
|
+
renderWorkingGraphicAndHandles();
|
|
4070
4076
|
nonGeometryDirty = true;
|
|
4071
4077
|
dirty = true;
|
|
4072
4078
|
emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered, previousTextAmplifiers, previousAmplifierPlacements, source, historyLabel);
|
|
@@ -4074,7 +4080,7 @@ function runEdit(opts) {
|
|
|
4074
4080
|
/**
|
|
4075
4081
|
* Flip the size anchor for the measure under edit (ADR-0020). Re-renders the
|
|
4076
4082
|
* emitted snapshot through `rebuildRendered` (which reads `sizeAnchor` live)
|
|
4077
|
-
* and emits a change so hosts see the re-anchored measure. The
|
|
4083
|
+
* and emits a change so hosts see the re-anchored measure. The working render
|
|
4078
4084
|
* is unaffected — it renders from `workingOptions`, which still carry the
|
|
4079
4085
|
* pixel size, so the in-flight symbol stays screen-locked until commit.
|
|
4080
4086
|
*/
|
|
@@ -4128,7 +4134,7 @@ function runEdit(opts) {
|
|
|
4128
4134
|
apply(from, to, source) {
|
|
4129
4135
|
const previousRendered = rebuildRendered();
|
|
4130
4136
|
restoreHistory(to);
|
|
4131
|
-
|
|
4137
|
+
renderWorkingGraphic();
|
|
4132
4138
|
renderHandlesLayer();
|
|
4133
4139
|
recomputeDirty();
|
|
4134
4140
|
emitChange(previousRendered, from.points, from.options, from.style, from.nonGeometryDirty, from.sizeAnchor, rebuildRendered(), from.textAmplifiers, from.amplifierPlacements, source, "Edit", from.boxAngle);
|
|
@@ -4204,7 +4210,7 @@ function runEdit(opts) {
|
|
|
4204
4210
|
workingTextAmplifiers = next.textAmplifiers ? { ...next.textAmplifiers } : void 0;
|
|
4205
4211
|
workingAmplifierPlacements = next.amplifierPlacements ? structuredClone(next.amplifierPlacements) : void 0;
|
|
4206
4212
|
nonGeometryDirty = true;
|
|
4207
|
-
|
|
4213
|
+
renderWorkingGraphic();
|
|
4208
4214
|
renderHandlesLayer();
|
|
4209
4215
|
recomputeDirty();
|
|
4210
4216
|
emitChange(previous, previous.graphic.controlPoints.map((point) => [...point]), previous.graphic.options, previous.graphic.style, previousNonGeometryDirty, previousSizeAnchor, rebuildRendered(), previous.graphic.textAmplifiers, previous.graphic.amplifierPlacements, "programmatic", "Update graphic");
|
|
@@ -4360,7 +4366,7 @@ function runEdit(opts) {
|
|
|
4360
4366
|
emitTransient("start", fsm.kind === "midpoint" ? initialPoints : preDragPoints, activeDrag.controlPointIndex);
|
|
4361
4367
|
if (fsm.kind === "midpoint") {
|
|
4362
4368
|
workingPoints.splice(0, workingPoints.length, ...initialPoints);
|
|
4363
|
-
|
|
4369
|
+
renderWorkingGraphic();
|
|
4364
4370
|
renderHandlesLayer();
|
|
4365
4371
|
}
|
|
4366
4372
|
dragInFlight = true;
|
|
@@ -4373,7 +4379,7 @@ function runEdit(opts) {
|
|
|
4373
4379
|
const preDragOptions = { ...workingOptions ?? {} };
|
|
4374
4380
|
if (boxHit.kind === "rotate") {
|
|
4375
4381
|
freezeOrientationForRotate();
|
|
4376
|
-
|
|
4382
|
+
renderWorkingGraphic();
|
|
4377
4383
|
renderHandlesLayer();
|
|
4378
4384
|
}
|
|
4379
4385
|
const preDragPoints = workingPoints.map((p) => [...p]);
|
|
@@ -4407,7 +4413,7 @@ function runEdit(opts) {
|
|
|
4407
4413
|
const resolvedPixel = snappedPixel(pixel, info, "reshape", activeDrag.controlPointIndex);
|
|
4408
4414
|
const snapshot = activeDrag.fsm.move(resolvedPixel);
|
|
4409
4415
|
workingPoints.splice(0, workingPoints.length, ...snapshot.points);
|
|
4410
|
-
|
|
4416
|
+
renderWorkingGraphic();
|
|
4411
4417
|
renderHandlesLayer();
|
|
4412
4418
|
recomputeDirty();
|
|
4413
4419
|
emitTransient("move", snapshot.points, activeDrag.controlPointIndex);
|
|
@@ -4440,7 +4446,7 @@ function runEdit(opts) {
|
|
|
4440
4446
|
workingPoints.splice(0, workingPoints.length, ...activeBoxDrag.preDragPoints);
|
|
4441
4447
|
workingOptions = foldTextTransform(activeBoxDrag.preDragBox, snapshot.box, activeBoxDrag.fsm.kind, activeBoxDrag.preDragOptions);
|
|
4442
4448
|
nonGeometryDirty = true;
|
|
4443
|
-
|
|
4449
|
+
renderWorkingGraphic();
|
|
4444
4450
|
renderHandlesLayer();
|
|
4445
4451
|
recomputeDirty();
|
|
4446
4452
|
emitTransient("move", activeBoxDrag.preDragPoints, null);
|
|
@@ -4448,7 +4454,7 @@ function runEdit(opts) {
|
|
|
4448
4454
|
}
|
|
4449
4455
|
workingPoints.splice(0, workingPoints.length, ...snapshot.points);
|
|
4450
4456
|
workingAmplifierPlacements = transformAmplifierPlacements(activeBoxDrag.preDragAmplifierPlacements, activeBoxDrag.preDragBox, snapshot.box, driver);
|
|
4451
|
-
|
|
4457
|
+
renderWorkingGraphic();
|
|
4452
4458
|
renderHandlesLayer();
|
|
4453
4459
|
recomputeDirty();
|
|
4454
4460
|
emitTransient("move", snapshot.points, null);
|
|
@@ -4463,7 +4469,7 @@ function runEdit(opts) {
|
|
|
4463
4469
|
...workingAmplifierPlacements ?? {},
|
|
4464
4470
|
...merged !== void 0 ? { [patch.field]: merged } : {}
|
|
4465
4471
|
};
|
|
4466
|
-
|
|
4472
|
+
renderWorkingGraphic();
|
|
4467
4473
|
renderHandlesLayer();
|
|
4468
4474
|
}
|
|
4469
4475
|
} else if (activeDrag) paintDragSnapshot(pixel, info);
|
|
@@ -4483,7 +4489,7 @@ function runEdit(opts) {
|
|
|
4483
4489
|
if (labelOutcome.kind === "rotate") setAmplifierPlacements({ [labelOutcome.key]: { rotation: labelOutcome.rotation } }, "gesture", "Rotate label");
|
|
4484
4490
|
else if (labelOutcome.kind === "move") setAmplifierPlacements({ [labelOutcome.field]: labelOutcome.position }, "gesture", "Move label");
|
|
4485
4491
|
else {
|
|
4486
|
-
|
|
4492
|
+
renderWorkingGraphic();
|
|
4487
4493
|
renderHandlesLayer();
|
|
4488
4494
|
}
|
|
4489
4495
|
shell.suppressNextClickAway();
|
|
@@ -4521,7 +4527,7 @@ function runEdit(opts) {
|
|
|
4521
4527
|
replaceWorkingPoints(preDragPoints);
|
|
4522
4528
|
workingAmplifierPlacements = preDragAmplifierPlacements;
|
|
4523
4529
|
emitTransient("end", preDragPoints, null);
|
|
4524
|
-
|
|
4530
|
+
renderWorkingGraphic();
|
|
4525
4531
|
renderHandlesLayer();
|
|
4526
4532
|
recomputeDirty();
|
|
4527
4533
|
endDragBookkeeping();
|
|
@@ -4551,11 +4557,7 @@ function runEdit(opts) {
|
|
|
4551
4557
|
const core = runEditInteraction({
|
|
4552
4558
|
adapter,
|
|
4553
4559
|
signal,
|
|
4554
|
-
layers: [
|
|
4555
|
-
previewLayer,
|
|
4556
|
-
guideLayer,
|
|
4557
|
-
handlesLayer
|
|
4558
|
-
],
|
|
4560
|
+
layers: [guideLayer, handlesLayer],
|
|
4559
4561
|
graphicsLayer,
|
|
4560
4562
|
previewLayer,
|
|
4561
4563
|
handlesLayer,
|
|
@@ -4589,11 +4591,7 @@ function runEdit(opts) {
|
|
|
4589
4591
|
ctx = interaction;
|
|
4590
4592
|
driver = createdDriver;
|
|
4591
4593
|
shell = createdShell;
|
|
4592
|
-
|
|
4593
|
-
if (wasOnGraphicsLayer) {
|
|
4594
|
-
adapter.deferLayerRemovalUntilLayerUpdate?.(graphicsLayer, previewLayer);
|
|
4595
|
-
liftOriginalFromGraphics();
|
|
4596
|
-
}
|
|
4594
|
+
renderWorkingGraphic();
|
|
4597
4595
|
renderHandlesLayer();
|
|
4598
4596
|
recomputeDirty();
|
|
4599
4597
|
if (onSession) onSession(session);
|
|
@@ -4604,6 +4602,7 @@ function runEdit(opts) {
|
|
|
4604
4602
|
get settled() {
|
|
4605
4603
|
return core.settled;
|
|
4606
4604
|
},
|
|
4605
|
+
usesGraphicStack: true,
|
|
4607
4606
|
measureId: measure.id,
|
|
4608
4607
|
abort(reason, cause) {
|
|
4609
4608
|
core.abort(reason, cause);
|
|
@@ -4612,7 +4611,7 @@ function runEdit(opts) {
|
|
|
4612
4611
|
if (core.settled) return;
|
|
4613
4612
|
if (stylesEqual(currentStyle, next.style)) return;
|
|
4614
4613
|
currentStyle = next.style;
|
|
4615
|
-
|
|
4614
|
+
renderWorkingGraphic();
|
|
4616
4615
|
}
|
|
4617
4616
|
};
|
|
4618
4617
|
}
|
|
@@ -4779,8 +4778,8 @@ function scalePointSymbolStates(pre, scale, rotation) {
|
|
|
4779
4778
|
* position, scales its size value, and rotates its rotation — its persisted
|
|
4780
4779
|
* size unit is never converted.
|
|
4781
4780
|
*
|
|
4782
|
-
* -
|
|
4783
|
-
* -
|
|
4781
|
+
* - replace each host-provided Graphic slot with its per-member working copy
|
|
4782
|
+
* - use the preview layer only for members that have no host-provided slot
|
|
4784
4783
|
* - group box = fit over the union of every member's rendered pixel footprint
|
|
4785
4784
|
* at the session `boxAngle`; each member also gets its own thin outline
|
|
4786
4785
|
* - dirty tracking, close/abort/signal/preempt/destroy, click-away — same
|
|
@@ -4789,7 +4788,7 @@ function scalePointSymbolStates(pre, scale, rotation) {
|
|
|
4789
4788
|
* a live session without a session boundary
|
|
4790
4789
|
*/
|
|
4791
4790
|
function runGroupTransform(opts) {
|
|
4792
|
-
const { adapter, capability, graphics, graphicsLayer, previewLayer, handlesLayer, graphicsStyle, signal, closeOnClickAway = true, interactionStyle, onSession, handleIdPrefix,
|
|
4791
|
+
const { adapter, capability, graphics, graphicsLayer, previewLayer, handlesLayer, graphicsStyle, signal, closeOnClickAway = true, interactionStyle, onSession, handleIdPrefix, publishWorkingGraphics, settleWorkingGraphics, isOnGraphicsLayer, onSettled, onClickAwayGraphicsPick } = opts;
|
|
4793
4792
|
assertCapabilityFor(graphics);
|
|
4794
4793
|
const renderOpts = graphicsStyle ? { graphicsStyle } : void 0;
|
|
4795
4794
|
let sizeAnchor = opts.sizeAnchor ?? "ground";
|
|
@@ -4815,7 +4814,8 @@ function runGroupTransform(opts) {
|
|
|
4815
4814
|
workingAmplifierPlacements: void 0,
|
|
4816
4815
|
optionsOverride: null,
|
|
4817
4816
|
box: null,
|
|
4818
|
-
lastBoxPixels: []
|
|
4817
|
+
lastBoxPixels: [],
|
|
4818
|
+
usesGraphicStack: isOnGraphicsLayer(graphic.id)
|
|
4819
4819
|
};
|
|
4820
4820
|
}
|
|
4821
4821
|
const editStartPoints = graphic.controlPoints.map((p) => [...p]);
|
|
@@ -4830,17 +4830,12 @@ function runGroupTransform(opts) {
|
|
|
4830
4830
|
workingAmplifierPlacements: graphic.amplifierPlacements ? structuredClone(graphic.amplifierPlacements) : void 0,
|
|
4831
4831
|
optionsOverride: null,
|
|
4832
4832
|
box: null,
|
|
4833
|
-
lastBoxPixels: []
|
|
4833
|
+
lastBoxPixels: [],
|
|
4834
|
+
usesGraphicStack: isOnGraphicsLayer(graphic.id)
|
|
4834
4835
|
};
|
|
4835
4836
|
}
|
|
4836
4837
|
/** The single mutable source of truth for the current member set. */
|
|
4837
4838
|
let members = graphics.map(createMemberState);
|
|
4838
|
-
/**
|
|
4839
|
-
* `true` once any member has been lifted off the graphics layer (initial or a
|
|
4840
|
-
* mid-session `setGraphics` add). Gates the abort-path restore so a session
|
|
4841
|
-
* that never touched the graphics layer doesn't rewrite it on abort.
|
|
4842
|
-
*/
|
|
4843
|
-
let anyLiftedFromGraphics = false;
|
|
4844
4839
|
/** A member's own options merged with an override patch (a null override leaves them unchanged). */
|
|
4845
4840
|
function mergeOptions(base, override) {
|
|
4846
4841
|
if (!override) return base;
|
|
@@ -4889,7 +4884,7 @@ function runGroupTransform(opts) {
|
|
|
4889
4884
|
render: renderMeasureForAdapter(adapter, committed, renderOpts)
|
|
4890
4885
|
};
|
|
4891
4886
|
}
|
|
4892
|
-
/** The working graphic for member `i` (measure or point symbol), for
|
|
4887
|
+
/** The working graphic for member `i` (measure or point symbol), for paint and `workingGraphics`. */
|
|
4893
4888
|
function workingGraphicFor(i) {
|
|
4894
4889
|
const member = members[i];
|
|
4895
4890
|
if (isPointSymbolMember(member)) return buildWorkingPointSymbol(member);
|
|
@@ -4944,20 +4939,6 @@ function runGroupTransform(opts) {
|
|
|
4944
4939
|
toPixel: (lonLat) => driver.toPixel(lonLat),
|
|
4945
4940
|
fromPixel: (pixel) => driver.fromPixel(pixel)
|
|
4946
4941
|
};
|
|
4947
|
-
/**
|
|
4948
|
-
* Lift `member` off the graphics layer when the host reports it currently
|
|
4949
|
-
* there, flipping `anyLiftedFromGraphics` so the abort path knows to restore.
|
|
4950
|
-
* Used for both initial and mid-session-added members.
|
|
4951
|
-
*/
|
|
4952
|
-
function liftMemberFromGraphics(member) {
|
|
4953
|
-
if (!isOnGraphicsLayer(member.graphic.id)) return;
|
|
4954
|
-
if (isPointSymbol(member.graphic)) adapter.removeFeatures(graphicsLayer, [`${member.graphic.id}:point-symbol`]);
|
|
4955
|
-
else {
|
|
4956
|
-
const ids = renderMeasureForAdapter(adapter, member.graphic, renderOpts).features.map((f) => f.id).filter((id) => typeof id === "string");
|
|
4957
|
-
if (ids.length > 0) adapter.removeFeatures(graphicsLayer, ids);
|
|
4958
|
-
}
|
|
4959
|
-
anyLiftedFromGraphics = true;
|
|
4960
|
-
}
|
|
4961
4942
|
function pixelsFromFeatures(features) {
|
|
4962
4943
|
const positions = [];
|
|
4963
4944
|
for (const f of features) if (f.geometry) collectFeaturePositions(f.geometry, positions);
|
|
@@ -5027,16 +5008,17 @@ function runGroupTransform(opts) {
|
|
|
5027
5008
|
}
|
|
5028
5009
|
function renderAllPreviews() {
|
|
5029
5010
|
if (ctx.settled) return;
|
|
5030
|
-
const
|
|
5031
|
-
const
|
|
5011
|
+
const previewGroups = [];
|
|
5012
|
+
const workingGroups = [];
|
|
5032
5013
|
members.forEach((member, i) => {
|
|
5033
5014
|
let features;
|
|
5015
|
+
let iconResources = [];
|
|
5034
5016
|
let boxPixels = [];
|
|
5035
5017
|
try {
|
|
5036
5018
|
if (isPointSymbolMember(member)) {
|
|
5037
5019
|
const symbol = buildWorkingPointSymbol(member);
|
|
5038
5020
|
const plan = renderPointSymbolForAdapter(adapter, capability, symbol);
|
|
5039
|
-
|
|
5021
|
+
iconResources = [plan.resource];
|
|
5040
5022
|
features = [plan.feature];
|
|
5041
5023
|
const footprint = pointSymbolFootprint(symbol, plan.render, adapter);
|
|
5042
5024
|
if (footprint) boxPixels = footprint.corners;
|
|
@@ -5050,10 +5032,19 @@ function runGroupTransform(opts) {
|
|
|
5050
5032
|
boxPixels = [];
|
|
5051
5033
|
}
|
|
5052
5034
|
member.lastBoxPixels = boxPixels;
|
|
5053
|
-
|
|
5035
|
+
if (member.usesGraphicStack) workingGroups.push({
|
|
5036
|
+
id: member.graphic.id,
|
|
5037
|
+
features,
|
|
5038
|
+
iconResources
|
|
5039
|
+
});
|
|
5040
|
+
else previewGroups.push({
|
|
5041
|
+
id: member.graphic.id,
|
|
5042
|
+
features,
|
|
5043
|
+
iconResources
|
|
5044
|
+
});
|
|
5054
5045
|
});
|
|
5055
|
-
|
|
5056
|
-
ctx.writeLayer(previewLayer,
|
|
5046
|
+
publishWorkingGraphics(workingGroups);
|
|
5047
|
+
ctx.writeLayer(previewLayer, previewGroups);
|
|
5057
5048
|
refitBoxes();
|
|
5058
5049
|
}
|
|
5059
5050
|
function rescaleOnViewChange() {
|
|
@@ -5074,7 +5065,11 @@ function runGroupTransform(opts) {
|
|
|
5074
5065
|
features.push(...toFeatures(boxToHandles(liveGroupBox, dragProjection.fromPixel), handleIdPrefix, interactionStyle));
|
|
5075
5066
|
}
|
|
5076
5067
|
features.push(...memberOutlineFeatures(members.flatMap((member) => member.box ? [[member.graphic.id, member.box]] : []), dragProjection.fromPixel, handleIdPrefix, interactionStyle?.boxOutline));
|
|
5077
|
-
ctx.writeLayer(handlesLayer,
|
|
5068
|
+
ctx.writeLayer(handlesLayer, [{
|
|
5069
|
+
id: "__td_transform_handles__",
|
|
5070
|
+
features,
|
|
5071
|
+
iconResources: []
|
|
5072
|
+
}]);
|
|
5078
5073
|
}
|
|
5079
5074
|
/**
|
|
5080
5075
|
* Builds every member's committed snapshot against a given override array
|
|
@@ -5307,13 +5302,13 @@ function runGroupTransform(opts) {
|
|
|
5307
5302
|
for (const graphic of next) {
|
|
5308
5303
|
const existing = currentById.get(graphic.id);
|
|
5309
5304
|
if (existing) nextMembers.push(existing);
|
|
5310
|
-
else
|
|
5311
|
-
const member = createMemberState(graphic);
|
|
5312
|
-
liftMemberFromGraphics(member);
|
|
5313
|
-
nextMembers.push(member);
|
|
5314
|
-
}
|
|
5305
|
+
else nextMembers.push(createMemberState(graphic));
|
|
5315
5306
|
}
|
|
5316
5307
|
members = nextMembers;
|
|
5308
|
+
if (removedSnapshots.length > 0) settleWorkingGraphics(removedSnapshots.map((snapshot) => ({
|
|
5309
|
+
id: snapshot.graphic.id,
|
|
5310
|
+
committed: snapshot.graphic
|
|
5311
|
+
})));
|
|
5317
5312
|
renderAllPreviews();
|
|
5318
5313
|
renderHandlesLayer();
|
|
5319
5314
|
recomputeDirty();
|
|
@@ -5332,6 +5327,28 @@ function runGroupTransform(opts) {
|
|
|
5332
5327
|
if (notificationErrors.length > 1) throw new AggregateError(notificationErrors, "TransformSession.setGraphics notification errors");
|
|
5333
5328
|
return removedSnapshots;
|
|
5334
5329
|
}
|
|
5330
|
+
function syncHostGraphics(hostGraphics) {
|
|
5331
|
+
if (ctx.settled) return;
|
|
5332
|
+
const hostIds = new Set(hostGraphics.map((graphic) => graphic.id));
|
|
5333
|
+
const withdrawnIds = members.filter((member) => !hostIds.has(member.graphic.id)).map((member) => member.graphic.id);
|
|
5334
|
+
const membershipChanged = withdrawnIds.length > 0;
|
|
5335
|
+
const surfaceChanged = members.some((member) => member.usesGraphicStack !== hostIds.has(member.graphic.id));
|
|
5336
|
+
if (!membershipChanged && !surfaceChanged) return;
|
|
5337
|
+
cancelActiveBoxDrag();
|
|
5338
|
+
if (withdrawnIds.length > 0) {
|
|
5339
|
+
settleWorkingGraphics(withdrawnIds.map((id) => ({ id })));
|
|
5340
|
+
members = members.filter((member) => hostIds.has(member.graphic.id));
|
|
5341
|
+
}
|
|
5342
|
+
for (const member of members) member.usesGraphicStack = true;
|
|
5343
|
+
if (members.length === 0) {
|
|
5344
|
+
ctx.fail(new TacticalDrawAbortError("removed"));
|
|
5345
|
+
return;
|
|
5346
|
+
}
|
|
5347
|
+
renderAllPreviews();
|
|
5348
|
+
renderHandlesLayer();
|
|
5349
|
+
recomputeDirty();
|
|
5350
|
+
if (membershipChanged) history.reset(captureHistory());
|
|
5351
|
+
}
|
|
5335
5352
|
function close() {
|
|
5336
5353
|
return commitClose();
|
|
5337
5354
|
}
|
|
@@ -5374,6 +5391,7 @@ function runGroupTransform(opts) {
|
|
|
5374
5391
|
setSizeAnchor(next);
|
|
5375
5392
|
},
|
|
5376
5393
|
setGraphics,
|
|
5394
|
+
syncHostGraphics,
|
|
5377
5395
|
close,
|
|
5378
5396
|
abort() {
|
|
5379
5397
|
if (ctx.settled) return;
|
|
@@ -5494,6 +5512,12 @@ function runGroupTransform(opts) {
|
|
|
5494
5512
|
handlesLayer,
|
|
5495
5513
|
closeOnClickAway,
|
|
5496
5514
|
onViewChange: rescaleOnViewChange,
|
|
5515
|
+
onBeforeTeardown: (settlement) => {
|
|
5516
|
+
settleWorkingGraphics(settlement.status === "committed" ? settlement.value.map((snapshot) => ({
|
|
5517
|
+
id: snapshot.graphic.id,
|
|
5518
|
+
committed: snapshot.graphic
|
|
5519
|
+
})) : members.map((member) => ({ id: member.graphic.id })));
|
|
5520
|
+
},
|
|
5497
5521
|
onDown: onDriverDown,
|
|
5498
5522
|
onMove: onDriverMove,
|
|
5499
5523
|
onUp: onDriverUp,
|
|
@@ -5513,16 +5537,11 @@ function runGroupTransform(opts) {
|
|
|
5513
5537
|
activeBoxDrag = null;
|
|
5514
5538
|
dragInFlight = false;
|
|
5515
5539
|
}
|
|
5516
|
-
adapter.reconcileLayerIconResources(previewLayer, []);
|
|
5517
|
-
},
|
|
5518
|
-
onAbort: () => {
|
|
5519
|
-
if (anyLiftedFromGraphics) restoreOriginalToGraphics();
|
|
5520
5540
|
},
|
|
5521
5541
|
attach(interaction, createdDriver, createdShell) {
|
|
5522
5542
|
ctx = interaction;
|
|
5523
5543
|
driver = createdDriver;
|
|
5524
5544
|
shell = createdShell;
|
|
5525
|
-
for (const member of members) liftMemberFromGraphics(member);
|
|
5526
5545
|
renderAllPreviews();
|
|
5527
5546
|
renderHandlesLayer();
|
|
5528
5547
|
recomputeDirty();
|
|
@@ -5539,7 +5558,8 @@ function runGroupTransform(opts) {
|
|
|
5539
5558
|
},
|
|
5540
5559
|
abort(reason, cause) {
|
|
5541
5560
|
core.abort(reason, cause);
|
|
5542
|
-
}
|
|
5561
|
+
},
|
|
5562
|
+
syncHostGraphics
|
|
5543
5563
|
};
|
|
5544
5564
|
}
|
|
5545
5565
|
//#endregion
|
|
@@ -5562,8 +5582,11 @@ function runPointSymbolDraw(options) {
|
|
|
5562
5582
|
const renderPreview = () => {
|
|
5563
5583
|
if (!latestPosition) return;
|
|
5564
5584
|
const plan = renderPointSymbolForAdapter(adapter, capability, build(PREVIEW_ID, latestPosition));
|
|
5565
|
-
|
|
5566
|
-
|
|
5585
|
+
ctx.writeLayer(previewLayer, [{
|
|
5586
|
+
id: PREVIEW_ID,
|
|
5587
|
+
features: [plan.feature],
|
|
5588
|
+
iconResources: [plan.resource]
|
|
5589
|
+
}]);
|
|
5567
5590
|
};
|
|
5568
5591
|
const handlePointerMove = (event) => {
|
|
5569
5592
|
latestPosition = snappedDrawPosition(snap, event);
|
|
@@ -5590,7 +5613,6 @@ function runPointSymbolDraw(options) {
|
|
|
5590
5613
|
interaction.onTeardown(() => {
|
|
5591
5614
|
adapter.off("pointermove", handlePointerMove);
|
|
5592
5615
|
adapter.off("click", handleClick);
|
|
5593
|
-
adapter.reconcileLayerIconResources(previewLayer, []);
|
|
5594
5616
|
adapter.setCursor(savedCursor);
|
|
5595
5617
|
});
|
|
5596
5618
|
adapter.setCursor("crosshair");
|
|
@@ -5607,8 +5629,8 @@ function normalisePointModes(input) {
|
|
|
5607
5629
|
for (const mode of modes) if (mode !== "transform" && mode !== "delete") throw new TypeError(`Point-symbol editing does not support mode "${mode}"`);
|
|
5608
5630
|
return modes.includes("delete") ? ["delete"] : [...new Set(modes)];
|
|
5609
5631
|
}
|
|
5610
|
-
function symbolBox(adapter,
|
|
5611
|
-
const footprint = pointSymbolFootprint(symbol,
|
|
5632
|
+
function symbolBox(adapter, symbol, rendered, padding = 0) {
|
|
5633
|
+
const footprint = pointSymbolFootprint(symbol, rendered, adapter);
|
|
5612
5634
|
if (!footprint) return null;
|
|
5613
5635
|
return {
|
|
5614
5636
|
...footprint.box,
|
|
@@ -5617,8 +5639,9 @@ function symbolBox(adapter, capability, symbol, padding = 0) {
|
|
|
5617
5639
|
};
|
|
5618
5640
|
}
|
|
5619
5641
|
function runPointSymbolEdit(opts) {
|
|
5620
|
-
const { adapter, capability, symbol, graphicsLayer, previewLayer, handlesLayer, signal, closeOnClickAway = true, interactionStyle, onSession, handleIdPrefix, settleToGraphics, wasOnGraphicsLayer, onSettled, onClickAwayGraphicsPick, snap } = opts;
|
|
5642
|
+
const { adapter, capability, symbol, graphicsLayer, previewLayer, handlesLayer, signal, closeOnClickAway = true, interactionStyle, onSession, handleIdPrefix, settleToGraphics, publishWorkingGraphic, wasOnGraphicsLayer, onSettled, onClickAwayGraphicsPick, snap } = opts;
|
|
5621
5643
|
let modes = normalisePointModes(opts.modes);
|
|
5644
|
+
let usesGraphicStack = wasOnGraphicsLayer;
|
|
5622
5645
|
let working = clonePointSymbol(symbol);
|
|
5623
5646
|
let dirty = false;
|
|
5624
5647
|
let box = null;
|
|
@@ -5643,15 +5666,29 @@ function runPointSymbolEdit(opts) {
|
|
|
5643
5666
|
const render = () => {
|
|
5644
5667
|
if (ctx.settled) return;
|
|
5645
5668
|
const plan = renderPointSymbolForAdapter(adapter, capability, working);
|
|
5646
|
-
adapter
|
|
5647
|
-
ctx.writeLayer(previewLayer, [plan.feature]);
|
|
5648
|
-
box = symbolBox(adapter, capability, working, interactionStyle?.boxPadding);
|
|
5669
|
+
const nextBox = symbolBox(adapter, working, plan.render, interactionStyle?.boxPadding);
|
|
5649
5670
|
const handles = [];
|
|
5650
|
-
if (modes.includes("transform") &&
|
|
5651
|
-
handles.push(boxOutlineFeature(
|
|
5652
|
-
handles.push(...toFeatures(boxToHandles(
|
|
5653
|
-
}
|
|
5654
|
-
|
|
5671
|
+
if (modes.includes("transform") && nextBox) {
|
|
5672
|
+
handles.push(boxOutlineFeature(nextBox, (pixel) => driver.fromPixel(pixel), `${handleIdPrefix}:box-outline`, interactionStyle?.boxOutline));
|
|
5673
|
+
handles.push(...toFeatures(boxToHandles(nextBox, (pixel) => driver.fromPixel(pixel)), handleIdPrefix, interactionStyle));
|
|
5674
|
+
}
|
|
5675
|
+
publishWorkingGraphic({
|
|
5676
|
+
id: working.id,
|
|
5677
|
+
features: [plan.feature],
|
|
5678
|
+
iconResources: [plan.resource]
|
|
5679
|
+
});
|
|
5680
|
+
if (usesGraphicStack) ctx.writeLayer(previewLayer, []);
|
|
5681
|
+
else ctx.writeLayer(previewLayer, [{
|
|
5682
|
+
id: `__td_point_symbol_edit_preview__:${working.id}`,
|
|
5683
|
+
features: [plan.feature],
|
|
5684
|
+
iconResources: [plan.resource]
|
|
5685
|
+
}]);
|
|
5686
|
+
box = nextBox;
|
|
5687
|
+
ctx.writeLayer(handlesLayer, [{
|
|
5688
|
+
id: `__td_point_symbol_edit_handles__:${working.id}`,
|
|
5689
|
+
features: handles,
|
|
5690
|
+
iconResources: []
|
|
5691
|
+
}]);
|
|
5655
5692
|
};
|
|
5656
5693
|
const emitChange = (previousState, source, onAccepted) => {
|
|
5657
5694
|
const previous = {
|
|
@@ -5816,16 +5853,11 @@ function runPointSymbolEdit(opts) {
|
|
|
5816
5853
|
historySettle: () => history.settle(),
|
|
5817
5854
|
facadeOnSettled: onSettled,
|
|
5818
5855
|
emitSettle: (settlement) => settleFromInteraction(settlement, commitEmitter, settleEmitter),
|
|
5819
|
-
extraTeardown: () => adapter.reconcileLayerIconResources(previewLayer, []),
|
|
5820
5856
|
attach(interaction, createdDriver, createdShell) {
|
|
5821
5857
|
ctx = interaction;
|
|
5822
5858
|
driver = createdDriver;
|
|
5823
5859
|
shell = createdShell;
|
|
5824
5860
|
render();
|
|
5825
|
-
if (wasOnGraphicsLayer) {
|
|
5826
|
-
adapter.deferLayerRemovalUntilLayerUpdate?.(graphicsLayer, previewLayer);
|
|
5827
|
-
adapter.removeFeatures(graphicsLayer, [`${symbol.id}:point-symbol`]);
|
|
5828
|
-
}
|
|
5829
5861
|
onSession?.(session);
|
|
5830
5862
|
}
|
|
5831
5863
|
});
|
|
@@ -5834,11 +5866,55 @@ function runPointSymbolEdit(opts) {
|
|
|
5834
5866
|
get settled() {
|
|
5835
5867
|
return handle.settled;
|
|
5836
5868
|
},
|
|
5869
|
+
get usesGraphicStack() {
|
|
5870
|
+
return usesGraphicStack;
|
|
5871
|
+
},
|
|
5837
5872
|
measureId: symbol.id,
|
|
5838
|
-
abort: handle.abort
|
|
5873
|
+
abort: handle.abort,
|
|
5874
|
+
syncHostMembership(present) {
|
|
5875
|
+
if (handle.settled || usesGraphicStack === present) return;
|
|
5876
|
+
const previous = usesGraphicStack;
|
|
5877
|
+
usesGraphicStack = present;
|
|
5878
|
+
try {
|
|
5879
|
+
render();
|
|
5880
|
+
} catch (error) {
|
|
5881
|
+
usesGraphicStack = previous;
|
|
5882
|
+
throw error;
|
|
5883
|
+
}
|
|
5884
|
+
}
|
|
5839
5885
|
};
|
|
5840
5886
|
}
|
|
5841
5887
|
//#endregion
|
|
5888
|
+
//#region src/tactical-draw/graphic-stack-coordinator.ts
|
|
5889
|
+
/**
|
|
5890
|
+
* Combines host-owned Graphic order with live or settled render overrides.
|
|
5891
|
+
*
|
|
5892
|
+
* The host always owns membership and order. A working override only replaces
|
|
5893
|
+
* the contents of its matching host slot; a committed override survives until
|
|
5894
|
+
* the host next acknowledges that Graphic in `render()`.
|
|
5895
|
+
*/
|
|
5896
|
+
var GraphicStackCoordinator = class {
|
|
5897
|
+
overrides = /* @__PURE__ */ new Map();
|
|
5898
|
+
acknowledgeHostRender(graphics) {
|
|
5899
|
+
for (const graphic of graphics) if (this.overrides.get(graphic.id)?.settled) this.overrides.delete(graphic.id);
|
|
5900
|
+
}
|
|
5901
|
+
publishWorking(group) {
|
|
5902
|
+
this.overrides.set(group.id, {
|
|
5903
|
+
group,
|
|
5904
|
+
settled: false
|
|
5905
|
+
});
|
|
5906
|
+
}
|
|
5907
|
+
settle(graphicId, committed) {
|
|
5908
|
+
const override = this.overrides.get(graphicId);
|
|
5909
|
+
if (!override) return;
|
|
5910
|
+
if (committed) override.settled = true;
|
|
5911
|
+
else this.overrides.delete(graphicId);
|
|
5912
|
+
}
|
|
5913
|
+
desiredGroups(graphics, renderHostGraphic) {
|
|
5914
|
+
return graphics.map((graphic) => this.overrides.get(graphic.id)?.group ?? renderHostGraphic(graphic));
|
|
5915
|
+
}
|
|
5916
|
+
};
|
|
5917
|
+
//#endregion
|
|
5842
5918
|
//#region src/tactical-draw/tactical-draw.ts
|
|
5843
5919
|
/**
|
|
5844
5920
|
* `TacticalDraw` façade — slice #6 (issue #45).
|
|
@@ -5861,6 +5937,7 @@ var TacticalDraw = class {
|
|
|
5861
5937
|
pointSymbols;
|
|
5862
5938
|
snappingOptions;
|
|
5863
5939
|
resolveSnap;
|
|
5940
|
+
graphicStack = new GraphicStackCoordinator();
|
|
5864
5941
|
destroyed = false;
|
|
5865
5942
|
hasRendered = false;
|
|
5866
5943
|
activeInteraction = new ActiveInteractionTracker();
|
|
@@ -5904,12 +5981,7 @@ var TacticalDraw = class {
|
|
|
5904
5981
|
idCounter = 0;
|
|
5905
5982
|
editCounter = 0;
|
|
5906
5983
|
transformCounter = 0;
|
|
5907
|
-
/**
|
|
5908
|
-
* Snapshot of the host's last `render()` argument. The edit slice uses it
|
|
5909
|
-
* to recompute the graphics-layer state during a lift / restore — the
|
|
5910
|
-
* façade owns the policy because controllers shouldn't need to know the
|
|
5911
|
-
* surrounding collection state.
|
|
5912
|
-
*/
|
|
5984
|
+
/** Snapshot of the host's latest authoritative Graphic membership and order. */
|
|
5913
5985
|
lastRenderedMeasures = [];
|
|
5914
5986
|
snapGeometryFeatures = [];
|
|
5915
5987
|
/**
|
|
@@ -5959,53 +6031,65 @@ var TacticalDraw = class {
|
|
|
5959
6031
|
clearSnapResult() {
|
|
5960
6032
|
this.snappingOptions?.onSnap?.(null);
|
|
5961
6033
|
}
|
|
5962
|
-
/**
|
|
5963
|
-
|
|
5964
|
-
* Routes every measure through [[renderMeasureForAdapter]] so pixel sizing is
|
|
5965
|
-
* resolved against the live map. Callers own any edit-exclusion policy — see
|
|
5966
|
-
* `render()` (excludes the edited measure, owned by the preview layer) versus
|
|
5967
|
-
* `settleToGraphics` (no exclusion, the edit is ending).
|
|
5968
|
-
*/
|
|
5969
|
-
renderMeasuresToGraphics(graphics) {
|
|
5970
|
-
const features = [];
|
|
5971
|
-
const resources = /* @__PURE__ */ new Map();
|
|
6034
|
+
/** Render one atomic Graphic group for the engine-neutral graphics stack. */
|
|
6035
|
+
renderGraphicGroup(graphic) {
|
|
5972
6036
|
const renderOpts = { graphicsStyle: this.graphicsStyle };
|
|
5973
|
-
|
|
6037
|
+
if (isPointSymbol(graphic)) {
|
|
5974
6038
|
if (!this.pointSymbols) throw new PointSymbolCapabilityRequiredError();
|
|
5975
6039
|
const plan = renderPointSymbolForAdapter(this.adapter, this.pointSymbols, graphic);
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
6040
|
+
return {
|
|
6041
|
+
id: graphic.id,
|
|
6042
|
+
features: [plan.feature],
|
|
6043
|
+
iconResources: [plan.resource]
|
|
6044
|
+
};
|
|
6045
|
+
}
|
|
6046
|
+
const render = renderMeasureForAdapter(this.adapter, graphic, renderOpts);
|
|
6047
|
+
return {
|
|
6048
|
+
id: graphic.id,
|
|
6049
|
+
features: render.features,
|
|
6050
|
+
iconResources: []
|
|
6051
|
+
};
|
|
6052
|
+
}
|
|
6053
|
+
/** Submit one complete grouped desired state for the host stack plus overrides. */
|
|
6054
|
+
reconcileGraphicsStack(graphics = this.lastRenderedMeasures) {
|
|
6055
|
+
const groups = this.graphicStack.desiredGroups(graphics, (graphic) => this.renderGraphicGroup(graphic));
|
|
6056
|
+
this.adapter.setLayerFeatureGroups(this.graphicsLayer, groups);
|
|
6057
|
+
this.snapGeometryFeatures = groups.flatMap((group) => group.features);
|
|
6058
|
+
}
|
|
6059
|
+
/** Atomically publish several live renders into their host-owned Graphic slots. */
|
|
6060
|
+
publishWorkingGraphics(groups) {
|
|
6061
|
+
for (const group of groups) this.graphicStack.publishWorking(group);
|
|
6062
|
+
this.reconcileGraphicsStack();
|
|
6063
|
+
}
|
|
6064
|
+
/** Settle several stack overrides in one graphics-layer reconciliation. */
|
|
6065
|
+
settleWorkingGraphics(settlements) {
|
|
6066
|
+
for (const { id, committed } of settlements) {
|
|
6067
|
+
if (committed) this.graphicStack.publishWorking(this.renderGraphicGroup(committed));
|
|
6068
|
+
this.graphicStack.settle(id, committed !== void 0);
|
|
5981
6069
|
}
|
|
5982
|
-
this.
|
|
5983
|
-
this.adapter.updateLayerFeatures(this.graphicsLayer, features);
|
|
5984
|
-
this.snapGeometryFeatures = features;
|
|
6070
|
+
this.reconcileGraphicsStack();
|
|
5985
6071
|
}
|
|
5986
6072
|
/**
|
|
5987
6073
|
* Re-render the graphics layer on a view (zoom) change so pixel-denominated
|
|
5988
6074
|
* and not-yet-committed label-sized measures rescale live. Skipped entirely
|
|
5989
6075
|
* when nothing rendered still reads live view state — meter geometry with a
|
|
5990
6076
|
* captured label size is zoom-independent, so a rebuild would be wasted
|
|
5991
|
-
* layer writes.
|
|
5992
|
-
*
|
|
5993
|
-
*
|
|
6077
|
+
* layer writes. A single-control-measure edit publishes through the stack
|
|
6078
|
+
* coordinator; point symbols and transform members already present in the
|
|
6079
|
+
* host stack do the same. Graphics without a host slot remain on preview.
|
|
5994
6080
|
*/
|
|
5995
6081
|
handleViewChange = () => {
|
|
5996
6082
|
if (this.destroyed || !this.hasRendered) return;
|
|
5997
6083
|
this.renderHighlights();
|
|
5998
|
-
const excluded = this.
|
|
6084
|
+
const excluded = this.graphicsStackExcludedIds();
|
|
5999
6085
|
const visible = excluded.size > 0 ? this.lastRenderedMeasures.filter((m) => !excluded.has(m.id)) : this.lastRenderedMeasures;
|
|
6000
6086
|
const nativeGroundIconScaling = this.adapter.scalesGroundPointSymbolsNatively === true;
|
|
6001
6087
|
if (!visible.some((g) => graphicRendersFromViewState(g, nativeGroundIconScaling))) return;
|
|
6002
|
-
this.
|
|
6088
|
+
this.reconcileGraphicsStack(visible);
|
|
6003
6089
|
};
|
|
6004
6090
|
/**
|
|
6005
|
-
* Every
|
|
6006
|
-
*
|
|
6007
|
-
* reconciliation because the preview layer (not graphics) owns their
|
|
6008
|
-
* working copy. Empty while idle.
|
|
6091
|
+
* Every Graphic id owned by an active interaction. Used for highlight and
|
|
6092
|
+
* pick routing; not every such Graphic is excluded from stack reconciliation.
|
|
6009
6093
|
*/
|
|
6010
6094
|
excludedInteractionIds() {
|
|
6011
6095
|
const ids = /* @__PURE__ */ new Set();
|
|
@@ -6013,6 +6097,13 @@ var TacticalDraw = class {
|
|
|
6013
6097
|
for (const id of this.transformMemberIds() ?? []) ids.add(id);
|
|
6014
6098
|
return ids;
|
|
6015
6099
|
}
|
|
6100
|
+
/** Interaction ids still rendered on the legacy preview surface. */
|
|
6101
|
+
graphicsStackExcludedIds() {
|
|
6102
|
+
const ids = this.excludedInteractionIds();
|
|
6103
|
+
if (this.activeEdit?.usesGraphicStack) ids.delete(this.activeEdit.measureId);
|
|
6104
|
+
for (const id of this.transformMemberIds() ?? []) ids.delete(id);
|
|
6105
|
+
return ids;
|
|
6106
|
+
}
|
|
6016
6107
|
/**
|
|
6017
6108
|
* Live member ids of the active group transform, or `undefined` when none.
|
|
6018
6109
|
* Falls back to the session ref because `onSession` fires from inside the
|
|
@@ -6031,24 +6122,24 @@ var TacticalDraw = class {
|
|
|
6031
6122
|
* Idempotent. Throws synchronously on duplicate measure ids *before*
|
|
6032
6123
|
* mutating any layer. An empty list before any prior render is a no-op.
|
|
6033
6124
|
*
|
|
6034
|
-
* Cross-layer semantics with an active edit
|
|
6035
|
-
* - The
|
|
6036
|
-
*
|
|
6037
|
-
* must stay in place.
|
|
6125
|
+
* Cross-layer semantics with an active edit:
|
|
6126
|
+
* - The stack coordinator replaces the edited Graphic's host-owned slot
|
|
6127
|
+
* with its working render; membership and order remain host-authoritative.
|
|
6038
6128
|
* - If the edited measure is absent from `measures`, the pending `edit`
|
|
6039
6129
|
* promise rejects on the **next microtask** with reason `"removed"`.
|
|
6040
6130
|
* `render` itself returns normally so host collection ops stay synchronous.
|
|
6041
|
-
* - If the edited measure is present and its `style` changed,
|
|
6042
|
-
* picks up the new style on this render. `controlPoints` changes on the
|
|
6131
|
+
* - If the edited measure is present and its `style` changed, its working
|
|
6132
|
+
* group picks up the new style on this render. `controlPoints` changes on the
|
|
6043
6133
|
* edited measure are routed nowhere — the user's in-flight drag is never
|
|
6044
|
-
* yanked out from under them.
|
|
6134
|
+
* yanked out from under them. A point symbol's complete working group remains
|
|
6135
|
+
* authoritative until settle, including any changed icon resource.
|
|
6045
6136
|
*
|
|
6046
6137
|
* Cross-layer semantics with an active draw: `render` only touches the
|
|
6047
6138
|
* graphics layer, so the draw's preview is undisturbed.
|
|
6048
6139
|
*
|
|
6049
6140
|
* Reentrancy: calling `td.render(...)` from inside `EditSession.onChange` is
|
|
6050
|
-
* safe — `render` never re-enters the active edit or its
|
|
6051
|
-
*
|
|
6141
|
+
* safe — `render` never re-enters the active edit or its handle layer (the
|
|
6142
|
+
* only edit-facing side effect, `notifyExternalMeasure`, is a
|
|
6052
6143
|
* pure style replay).
|
|
6053
6144
|
*/
|
|
6054
6145
|
render(measures) {
|
|
@@ -6058,39 +6149,36 @@ var TacticalDraw = class {
|
|
|
6058
6149
|
if (seen.has(m.id)) throw new Error(`TacticalDraw.render: duplicate graphic id "${m.id}"`);
|
|
6059
6150
|
seen.add(m.id);
|
|
6060
6151
|
}
|
|
6061
|
-
const
|
|
6152
|
+
const activeEdit = this.activeEdit;
|
|
6153
|
+
const constructingSession = activeEdit ? null : this.activeInteraction.session;
|
|
6154
|
+
const constructingGraphic = constructingSession && "workingGraphic" in constructingSession ? constructingSession.workingGraphic : void 0;
|
|
6155
|
+
const editedId = activeEdit?.measureId ?? constructingGraphic?.id;
|
|
6062
6156
|
let externalEdited;
|
|
6063
6157
|
let editedRemoved = false;
|
|
6064
6158
|
if (editedId !== void 0) {
|
|
6065
6159
|
const candidate = measures.find((m) => m.id === editedId);
|
|
6066
|
-
|
|
6067
|
-
|
|
6160
|
+
if (activeEdit && "notifyExternalMeasure" in activeEdit || constructingGraphic !== void 0 && !isPointSymbol(constructingGraphic)) {
|
|
6161
|
+
externalEdited = candidate && !isPointSymbol(candidate) ? candidate : void 0;
|
|
6162
|
+
editedRemoved = externalEdited === void 0;
|
|
6163
|
+
} else editedRemoved = candidate === void 0 || !isPointSymbol(candidate);
|
|
6068
6164
|
}
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6165
|
+
this.lastRenderedMeasures = measures.slice();
|
|
6166
|
+
this.graphicStack.acknowledgeHostRender(measures);
|
|
6167
|
+
if (activeEdit && "syncHostMembership" in activeEdit && measures.some((graphic) => graphic.id === activeEdit.measureId && isPointSymbol(graphic))) activeEdit.syncHostMembership(true);
|
|
6168
|
+
(this.activeTransform ?? this.activeTransformSessionRef)?.syncHostGraphics(measures);
|
|
6169
|
+
const excluded = this.graphicsStackExcludedIds();
|
|
6072
6170
|
const visible = excluded.size > 0 ? measures.filter((m) => !excluded.has(m.id)) : measures;
|
|
6073
6171
|
if (!(visible.length === 0 && !this.hasRendered)) {
|
|
6074
|
-
this.
|
|
6172
|
+
this.reconcileGraphicsStack(visible);
|
|
6075
6173
|
this.hasRendered = true;
|
|
6076
6174
|
}
|
|
6077
|
-
this.lastRenderedMeasures = measures.slice();
|
|
6078
6175
|
if (externalEdited && this.activeEdit && "notifyExternalMeasure" in this.activeEdit) this.activeEdit.notifyExternalMeasure(externalEdited);
|
|
6079
|
-
if (editedRemoved
|
|
6176
|
+
if (editedRemoved) {
|
|
6080
6177
|
const editHandle = this.activeEdit;
|
|
6081
|
-
queueMicrotask(() => {
|
|
6178
|
+
if (editHandle) queueMicrotask(() => {
|
|
6082
6179
|
editHandle.abort("removed");
|
|
6083
6180
|
});
|
|
6084
|
-
|
|
6085
|
-
if (transformMemberRemoved) {
|
|
6086
|
-
const sessionRef = this.activeTransformSessionRef;
|
|
6087
|
-
queueMicrotask(() => {
|
|
6088
|
-
const handle = this.activeTransform;
|
|
6089
|
-
if (!handle || handle.settled) return;
|
|
6090
|
-
if (this.activeTransformSessionRef !== sessionRef) return;
|
|
6091
|
-
const rendered = new Set(this.lastRenderedMeasures.map((m) => m.id));
|
|
6092
|
-
if (handle.graphicIds.some((id) => !rendered.has(id))) handle.abort("removed");
|
|
6093
|
-
});
|
|
6181
|
+
else if (this.activeInteraction.isStarting) this.activeInteraction.cancel("removed");
|
|
6094
6182
|
}
|
|
6095
6183
|
this.renderHighlights();
|
|
6096
6184
|
}
|
|
@@ -6099,7 +6187,7 @@ var TacticalDraw = class {
|
|
|
6099
6187
|
* idempotent (mirrors {@link render}): a non-interactive outline box is drawn
|
|
6100
6188
|
* around each still-rendered graphic on the dedicated highlight layer — no
|
|
6101
6189
|
* draw / edit / transform session, no promise, no pick subscription. Passing
|
|
6102
|
-
* `[]` clears every highlight. Unknown ids and ids currently
|
|
6190
|
+
* `[]` clears every highlight. Unknown ids and ids currently active in an
|
|
6103
6191
|
* active edit / transform session are skipped (the live session's own visuals
|
|
6104
6192
|
* supersede the passive outline). Hosts use it for Excalidraw-style
|
|
6105
6193
|
* "locked selection" chrome.
|
|
@@ -6112,9 +6200,9 @@ var TacticalDraw = class {
|
|
|
6112
6200
|
/**
|
|
6113
6201
|
* Project the current {@link highlightedIds} into one outline `LineString`
|
|
6114
6202
|
* per graphic and reconcile them onto the highlight layer. Idempotent and
|
|
6115
|
-
*
|
|
6116
|
-
*
|
|
6117
|
-
* unknown (not in the last render) or
|
|
6203
|
+
* grouped by graphic id, so desired-state replacement removes stale outlines
|
|
6204
|
+
* and an unchanged set is a cheap no-op write. Skips ids that are
|
|
6205
|
+
* unknown (not in the last render) or active in an interaction session, and
|
|
6118
6206
|
* bails gracefully when the adapter cannot invert pixels to coordinates
|
|
6119
6207
|
* (`getCoordinateFromPixel` is optional in the MapAdapter ABI).
|
|
6120
6208
|
*/
|
|
@@ -6147,7 +6235,11 @@ var TacticalDraw = class {
|
|
|
6147
6235
|
const feature = boxOutlineFeature(box, invert, `__td_highlight__:${id}`, style);
|
|
6148
6236
|
if (!invertFailed) features.push(feature);
|
|
6149
6237
|
}
|
|
6150
|
-
this.adapter.
|
|
6238
|
+
this.adapter.setLayerFeatureGroups(this.highlightLayer, features.map((feature) => ({
|
|
6239
|
+
id: String(feature.id),
|
|
6240
|
+
features: [feature],
|
|
6241
|
+
iconResources: []
|
|
6242
|
+
})));
|
|
6151
6243
|
this.hasHighlightOutlines = features.length > 0;
|
|
6152
6244
|
}
|
|
6153
6245
|
/**
|
|
@@ -6349,10 +6441,10 @@ var TacticalDraw = class {
|
|
|
6349
6441
|
if (preempt) return preempt;
|
|
6350
6442
|
const wasOnGraphicsLayer = this.lastRenderedMeasures.some((m) => m.id === measure.id);
|
|
6351
6443
|
const settleToGraphics = (settledSymbol) => {
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6444
|
+
this.settleWorkingGraphics([{
|
|
6445
|
+
id: measure.id,
|
|
6446
|
+
committed: settledSymbol
|
|
6447
|
+
}]);
|
|
6356
6448
|
};
|
|
6357
6449
|
const handleIdPrefix = `__td_edit_${++this.editCounter}__`;
|
|
6358
6450
|
return this.runWithSlot((onSettled) => runPointSymbolEdit({
|
|
@@ -6366,14 +6458,16 @@ var TacticalDraw = class {
|
|
|
6366
6458
|
closeOnClickAway: options?.closeOnClickAway,
|
|
6367
6459
|
modes: options?.modes,
|
|
6368
6460
|
interactionStyle: this.resolveInteractionStyle(options?.interactionStyle),
|
|
6369
|
-
onSession: options?.onSession,
|
|
6461
|
+
onSession: this.trackSession(options?.onSession),
|
|
6370
6462
|
handleIdPrefix,
|
|
6371
6463
|
settleToGraphics,
|
|
6464
|
+
publishWorkingGraphic: (group) => this.publishWorkingGraphics([group]),
|
|
6372
6465
|
wasOnGraphicsLayer,
|
|
6373
6466
|
onSettled,
|
|
6374
6467
|
onClickAwayGraphicsPick: (event) => this.dispatchHostPick(this.graphicsLayer, event),
|
|
6375
6468
|
snap: this.resolveSnap
|
|
6376
6469
|
}), (handle) => {
|
|
6470
|
+
handle.syncHostMembership(this.lastRenderedMeasures.some((graphic) => graphic.id === handle.measureId && isPointSymbol(graphic)));
|
|
6377
6471
|
this.activeEdit = handle;
|
|
6378
6472
|
return () => {
|
|
6379
6473
|
if (this.activeEdit === handle) this.activeEdit = null;
|
|
@@ -6383,12 +6477,11 @@ var TacticalDraw = class {
|
|
|
6383
6477
|
normaliseModes(options?.modes);
|
|
6384
6478
|
const preempt = this.guardAndPreempt();
|
|
6385
6479
|
if (preempt) return preempt;
|
|
6386
|
-
const wasOnGraphicsLayer = this.lastRenderedMeasures.some((m) => m.id === measure.id);
|
|
6387
6480
|
const settleToGraphics = (settledMeasure) => {
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6481
|
+
this.settleWorkingGraphics([{
|
|
6482
|
+
id: measure.id,
|
|
6483
|
+
committed: settledMeasure
|
|
6484
|
+
}]);
|
|
6392
6485
|
};
|
|
6393
6486
|
const handleIdPrefix = `__td_edit_${++this.editCounter}__`;
|
|
6394
6487
|
const mergedInteractionStyle = this.resolveInteractionStyle(options?.interactionStyle);
|
|
@@ -6413,7 +6506,11 @@ var TacticalDraw = class {
|
|
|
6413
6506
|
guide: options?.guide,
|
|
6414
6507
|
sizeAnchor: options?.sizeAnchor,
|
|
6415
6508
|
settleToGraphics,
|
|
6416
|
-
|
|
6509
|
+
publishWorkingGraphic: (graphic, features) => this.publishWorkingGraphics([{
|
|
6510
|
+
id: graphic.id,
|
|
6511
|
+
features,
|
|
6512
|
+
iconResources: []
|
|
6513
|
+
}]),
|
|
6417
6514
|
onSettled,
|
|
6418
6515
|
onClickAwayGraphicsPick,
|
|
6419
6516
|
snap: this.resolveSnap
|
|
@@ -6436,9 +6533,6 @@ var TacticalDraw = class {
|
|
|
6436
6533
|
const preempt = this.guardAndPreempt();
|
|
6437
6534
|
if (preempt) return preempt;
|
|
6438
6535
|
const isOnGraphicsLayer = (id) => this.lastRenderedMeasures.some((m) => m.id === id);
|
|
6439
|
-
const restoreOriginalToGraphics = () => {
|
|
6440
|
-
this.renderMeasuresToGraphics(this.lastRenderedMeasures);
|
|
6441
|
-
};
|
|
6442
6536
|
const handleIdPrefix = `__td_transform_${++this.transformCounter}__`;
|
|
6443
6537
|
const mergedInteractionStyle = this.resolveInteractionStyle(options?.interactionStyle);
|
|
6444
6538
|
const onClickAwayGraphicsPick = (event) => {
|
|
@@ -6461,7 +6555,8 @@ var TacticalDraw = class {
|
|
|
6461
6555
|
onSession: this.trackTransformSession(options?.onSession),
|
|
6462
6556
|
handleIdPrefix,
|
|
6463
6557
|
sizeAnchor: options?.sizeAnchor,
|
|
6464
|
-
|
|
6558
|
+
publishWorkingGraphics: (groups) => this.publishWorkingGraphics(groups),
|
|
6559
|
+
settleWorkingGraphics: (settlements) => this.settleWorkingGraphics(settlements),
|
|
6465
6560
|
isOnGraphicsLayer,
|
|
6466
6561
|
onSettled,
|
|
6467
6562
|
onClickAwayGraphicsPick
|
|
@@ -6743,7 +6838,8 @@ var TacticalDraw = class {
|
|
|
6743
6838
|
*/
|
|
6744
6839
|
routePick(layerId, event) {
|
|
6745
6840
|
const excluded = this.excludedInteractionIds();
|
|
6746
|
-
if (layerId === this.graphicsLayer && excluded.size > 0
|
|
6841
|
+
if (layerId === this.graphicsLayer && excluded.size > 0) {
|
|
6842
|
+
if (excluded.has(event.id)) return;
|
|
6747
6843
|
this.deferredGraphicsPick = event;
|
|
6748
6844
|
return;
|
|
6749
6845
|
}
|