@orbat-mapper/tactical-draw 0.2.0-alpha.16 → 0.2.0-alpha.17
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.mjs +66 -30
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -2024,6 +2024,31 @@ function boxOutlineFeature(box, fromPixel, featureId, style) {
|
|
|
2024
2024
|
};
|
|
2025
2025
|
}
|
|
2026
2026
|
//#endregion
|
|
2027
|
+
//#region src/tactical-draw/transform-amplifier-placements.ts
|
|
2028
|
+
/**
|
|
2029
|
+
* Carry user-adjusted label anchors through a transform-box gesture. Anchors
|
|
2030
|
+
* are transformed in pixel space alongside the control points so the result
|
|
2031
|
+
* remains visually rigid across map projections.
|
|
2032
|
+
*/
|
|
2033
|
+
function transformAmplifierPlacements(placements, from, to, projection) {
|
|
2034
|
+
if (!placements) return void 0;
|
|
2035
|
+
const scaleX = to.halfWidth / from.halfWidth;
|
|
2036
|
+
const scaleY = to.halfHeight / from.halfHeight;
|
|
2037
|
+
const transformed = {};
|
|
2038
|
+
for (const [field, position] of Object.entries(placements)) {
|
|
2039
|
+
if (position === void 0) continue;
|
|
2040
|
+
const pixel = projection.toPixel(position);
|
|
2041
|
+
if (!pixel) {
|
|
2042
|
+
transformed[field] = [...position];
|
|
2043
|
+
continue;
|
|
2044
|
+
}
|
|
2045
|
+
const [localX, localY] = rotate(pixel[0] - from.center[0], pixel[1] - from.center[1], -from.angle);
|
|
2046
|
+
const [outX, outY] = rotate(localX * scaleX, localY * scaleY, to.angle);
|
|
2047
|
+
transformed[field] = projection.fromPixel([to.center[0] + outX, to.center[1] + outY]);
|
|
2048
|
+
}
|
|
2049
|
+
return transformed;
|
|
2050
|
+
}
|
|
2051
|
+
//#endregion
|
|
2027
2052
|
//#region src/tactical-draw/edit-controller.ts
|
|
2028
2053
|
/**
|
|
2029
2054
|
* Coordinate-space tolerance for `EditSession.dirty`. The working copy is
|
|
@@ -2704,24 +2729,6 @@ function runEdit(opts) {
|
|
|
2704
2729
|
* `boxAngle`.
|
|
2705
2730
|
*/
|
|
2706
2731
|
let activeBoxDrag = null;
|
|
2707
|
-
function transformAmplifierPlacements(placements, from, to) {
|
|
2708
|
-
if (!placements) return void 0;
|
|
2709
|
-
const scaleX = to.halfWidth / from.halfWidth;
|
|
2710
|
-
const scaleY = to.halfHeight / from.halfHeight;
|
|
2711
|
-
const transformed = {};
|
|
2712
|
-
for (const [field, position] of Object.entries(placements)) {
|
|
2713
|
-
if (position === void 0) continue;
|
|
2714
|
-
const pixel = driver.toPixel(position);
|
|
2715
|
-
if (!pixel) {
|
|
2716
|
-
transformed[field] = [...position];
|
|
2717
|
-
continue;
|
|
2718
|
-
}
|
|
2719
|
-
const [localX, localY] = rotate(pixel[0] - from.center[0], pixel[1] - from.center[1], -from.angle);
|
|
2720
|
-
const [outX, outY] = rotate(localX * scaleX, localY * scaleY, to.angle);
|
|
2721
|
-
transformed[field] = driver.fromPixel([to.center[0] + outX, to.center[1] + outY]);
|
|
2722
|
-
}
|
|
2723
|
-
return transformed;
|
|
2724
|
-
}
|
|
2725
2732
|
/**
|
|
2726
2733
|
* Whether the vertex at `index` may be deleted (via Alt+click or in the
|
|
2727
2734
|
* exclusive `delete` mode). Deletion is
|
|
@@ -2859,7 +2866,7 @@ function runEdit(opts) {
|
|
|
2859
2866
|
const snapshot = activeBoxDrag.fsm.move(pixel);
|
|
2860
2867
|
activeBoxDrag.lastSnapshot = snapshot;
|
|
2861
2868
|
workingPoints.splice(0, workingPoints.length, ...snapshot.points);
|
|
2862
|
-
workingAmplifierPlacements = transformAmplifierPlacements(activeBoxDrag.preDragAmplifierPlacements, activeBoxDrag.preDragBox, snapshot.box);
|
|
2869
|
+
workingAmplifierPlacements = transformAmplifierPlacements(activeBoxDrag.preDragAmplifierPlacements, activeBoxDrag.preDragBox, snapshot.box, driver);
|
|
2863
2870
|
renderPreview();
|
|
2864
2871
|
renderHandlesLayer();
|
|
2865
2872
|
recomputeDirty();
|
|
@@ -2938,7 +2945,7 @@ function runEdit(opts) {
|
|
|
2938
2945
|
replaceWorkingPoints(preDragPoints);
|
|
2939
2946
|
workingAmplifierPlacements = preDragAmplifierPlacements;
|
|
2940
2947
|
if (fsm.kind === "rotate") boxAngle += finalSnapshot.box.angle - preDragBox.angle;
|
|
2941
|
-
commitGesture(finalSnapshot.points, preDragOptions, transformAmplifierPlacements(preDragAmplifierPlacements, preDragBox, finalSnapshot.box));
|
|
2948
|
+
commitGesture(finalSnapshot.points, preDragOptions, transformAmplifierPlacements(preDragAmplifierPlacements, preDragBox, finalSnapshot.box, driver));
|
|
2942
2949
|
return;
|
|
2943
2950
|
}
|
|
2944
2951
|
};
|
|
@@ -3126,6 +3133,7 @@ function runGroupTransform(opts) {
|
|
|
3126
3133
|
measure,
|
|
3127
3134
|
editStartPoints,
|
|
3128
3135
|
workingPoints: editStartPoints.map((p) => [...p]),
|
|
3136
|
+
workingAmplifierPlacements: measure.amplifierPlacements ? structuredClone(measure.amplifierPlacements) : void 0,
|
|
3129
3137
|
optionsOverride: null,
|
|
3130
3138
|
box: null,
|
|
3131
3139
|
lastPreviewFeatures: []
|
|
@@ -3157,7 +3165,8 @@ function runGroupTransform(opts) {
|
|
|
3157
3165
|
const working = {
|
|
3158
3166
|
...member.measure,
|
|
3159
3167
|
options: mergeOptions(member.measure.options, override),
|
|
3160
|
-
controlPoints: member.workingPoints.map((p) => [...p])
|
|
3168
|
+
controlPoints: member.workingPoints.map((p) => [...p]),
|
|
3169
|
+
...member.workingAmplifierPlacements ? { amplifierPlacements: member.workingAmplifierPlacements } : {}
|
|
3161
3170
|
};
|
|
3162
3171
|
const committed = sizeAnchor === "screen" ? working : bakePixelSizeToGround(working, adapter);
|
|
3163
3172
|
return {
|
|
@@ -3171,7 +3180,8 @@ function runGroupTransform(opts) {
|
|
|
3171
3180
|
return {
|
|
3172
3181
|
...member.measure,
|
|
3173
3182
|
options: effectiveOptions(i),
|
|
3174
|
-
controlPoints: member.workingPoints.map((p) => [...p])
|
|
3183
|
+
controlPoints: member.workingPoints.map((p) => [...p]),
|
|
3184
|
+
...member.workingAmplifierPlacements ? { amplifierPlacements: member.workingAmplifierPlacements } : {}
|
|
3175
3185
|
};
|
|
3176
3186
|
}
|
|
3177
3187
|
let dirty = false;
|
|
@@ -3338,6 +3348,21 @@ function runGroupTransform(opts) {
|
|
|
3338
3348
|
member.workingPoints.splice(0, member.workingPoints.length, ...snapshot[i]);
|
|
3339
3349
|
});
|
|
3340
3350
|
}
|
|
3351
|
+
function cloneAmplifierPlacementsByMember() {
|
|
3352
|
+
return members.map((member) => member.workingAmplifierPlacements ? structuredClone(member.workingAmplifierPlacements) : void 0);
|
|
3353
|
+
}
|
|
3354
|
+
/**
|
|
3355
|
+
* Adopts `placements[i]` by reference — callers hand over ownership (the
|
|
3356
|
+
* sources are freshly built transforms or snapshot arrays that are dead
|
|
3357
|
+
* after the restore), and working placements are only ever replaced
|
|
3358
|
+
* wholesale, never mutated in place. This runs on every pointermove of a
|
|
3359
|
+
* box drag, so it must not deep-clone.
|
|
3360
|
+
*/
|
|
3361
|
+
function applyAmplifierPlacementsByMember(placements) {
|
|
3362
|
+
members.forEach((member, i) => {
|
|
3363
|
+
member.workingAmplifierPlacements = placements[i];
|
|
3364
|
+
});
|
|
3365
|
+
}
|
|
3341
3366
|
function cloneOptionsOverrideByMember() {
|
|
3342
3367
|
return members.map((m) => m.optionsOverride ? { ...m.optionsOverride } : null);
|
|
3343
3368
|
}
|
|
@@ -3369,7 +3394,7 @@ function runGroupTransform(opts) {
|
|
|
3369
3394
|
* instance to all listeners in order, deferred `reject`/`close`/`abort`
|
|
3370
3395
|
* (close/abort override reject), exception collection with `AggregateError`.
|
|
3371
3396
|
*/
|
|
3372
|
-
function emitChange(previousRendered, previousPointsByMember, previousSizeAnchor, previousNonGeometryDirty, previousOptionsOverrideByMember, currentRendered) {
|
|
3397
|
+
function emitChange(previousRendered, previousPointsByMember, previousAmplifierPlacementsByMember, previousSizeAnchor, previousNonGeometryDirty, previousOptionsOverrideByMember, currentRendered) {
|
|
3373
3398
|
if (ctx.settled) return;
|
|
3374
3399
|
let rejectRequested = false;
|
|
3375
3400
|
let closeRequested = false;
|
|
@@ -3399,6 +3424,7 @@ function runGroupTransform(opts) {
|
|
|
3399
3424
|
else if (closeRequested) commitClose();
|
|
3400
3425
|
else if (rejectRequested && !ctx.settled && members === membersAtEmission) {
|
|
3401
3426
|
restorePointsByMember(previousPointsByMember);
|
|
3427
|
+
applyAmplifierPlacementsByMember(previousAmplifierPlacementsByMember);
|
|
3402
3428
|
sizeAnchor = previousSizeAnchor;
|
|
3403
3429
|
nonGeometryDirty = previousNonGeometryDirty;
|
|
3404
3430
|
applyOptionsOverrides(previousOptionsOverrideByMember);
|
|
@@ -3431,17 +3457,20 @@ function runGroupTransform(opts) {
|
|
|
3431
3457
|
}
|
|
3432
3458
|
return snapshots;
|
|
3433
3459
|
}
|
|
3434
|
-
function commitGesture(nextConcatPoints, preGestureOptionsOverrideByMember) {
|
|
3460
|
+
function commitGesture(nextConcatPoints, preGestureOptionsOverrideByMember, nextAmplifierPlacementsByMember) {
|
|
3435
3461
|
if (ctx.settled) return;
|
|
3436
3462
|
const previousPointsByMember = clonePointsByMember();
|
|
3463
|
+
const previousAmplifierPlacementsByMember = cloneAmplifierPlacementsByMember();
|
|
3437
3464
|
const previousOptionsOverrideByMember = preGestureOptionsOverrideByMember ?? cloneOptionsOverrideByMember();
|
|
3438
3465
|
const previousRendered = rebuildRenderedAllWith(previousOptionsOverrideByMember);
|
|
3439
3466
|
applyConcatenatedPoints(nextConcatPoints);
|
|
3467
|
+
if (nextAmplifierPlacementsByMember) applyAmplifierPlacementsByMember(nextAmplifierPlacementsByMember);
|
|
3440
3468
|
let currentRendered;
|
|
3441
3469
|
try {
|
|
3442
3470
|
currentRendered = rebuildRenderedAll();
|
|
3443
3471
|
} catch (err) {
|
|
3444
3472
|
restorePointsByMember(previousPointsByMember);
|
|
3473
|
+
applyAmplifierPlacementsByMember(previousAmplifierPlacementsByMember);
|
|
3445
3474
|
dragInFlight = false;
|
|
3446
3475
|
throw err;
|
|
3447
3476
|
}
|
|
@@ -3449,7 +3478,7 @@ function runGroupTransform(opts) {
|
|
|
3449
3478
|
renderHandlesLayer();
|
|
3450
3479
|
recomputeDirty();
|
|
3451
3480
|
dragInFlight = false;
|
|
3452
|
-
emitChange(previousRendered, previousPointsByMember, sizeAnchor, nonGeometryDirty, previousOptionsOverrideByMember, currentRendered);
|
|
3481
|
+
emitChange(previousRendered, previousPointsByMember, previousAmplifierPlacementsByMember, sizeAnchor, nonGeometryDirty, previousOptionsOverrideByMember, currentRendered);
|
|
3453
3482
|
}
|
|
3454
3483
|
function setSizeAnchor(next) {
|
|
3455
3484
|
if (ctx.settled || sizeAnchor === next) return;
|
|
@@ -3457,12 +3486,13 @@ function runGroupTransform(opts) {
|
|
|
3457
3486
|
const previousSizeAnchor = sizeAnchor;
|
|
3458
3487
|
const previousNonGeometryDirty = nonGeometryDirty;
|
|
3459
3488
|
const previousOptionsOverrideByMember = cloneOptionsOverrideByMember();
|
|
3489
|
+
const previousAmplifierPlacementsByMember = cloneAmplifierPlacementsByMember();
|
|
3460
3490
|
const previousRendered = rebuildRenderedAll();
|
|
3461
3491
|
sizeAnchor = next;
|
|
3462
3492
|
const currentRendered = rebuildRenderedAll();
|
|
3463
3493
|
nonGeometryDirty = true;
|
|
3464
3494
|
dirty = true;
|
|
3465
|
-
emitChange(previousRendered, previousPointsByMember, previousSizeAnchor, previousNonGeometryDirty, previousOptionsOverrideByMember, currentRendered);
|
|
3495
|
+
emitChange(previousRendered, previousPointsByMember, previousAmplifierPlacementsByMember, previousSizeAnchor, previousNonGeometryDirty, previousOptionsOverrideByMember, currentRendered);
|
|
3466
3496
|
}
|
|
3467
3497
|
/**
|
|
3468
3498
|
* Cancel an in-flight box drag without committing it: restore the pre-drag
|
|
@@ -3474,10 +3504,11 @@ function runGroupTransform(opts) {
|
|
|
3474
3504
|
*/
|
|
3475
3505
|
function cancelActiveBoxDrag() {
|
|
3476
3506
|
if (!activeBoxDrag) return;
|
|
3477
|
-
const { preDragPoints, preDragOptionsOverrideByMember } = activeBoxDrag;
|
|
3507
|
+
const { preDragPoints, preDragAmplifierPlacementsByMember, preDragOptionsOverrideByMember } = activeBoxDrag;
|
|
3478
3508
|
activeBoxDrag = null;
|
|
3479
3509
|
driver.setDragPanEnabled(true);
|
|
3480
3510
|
applyConcatenatedPoints(preDragPoints);
|
|
3511
|
+
applyAmplifierPlacementsByMember(preDragAmplifierPlacementsByMember);
|
|
3481
3512
|
applyOptionsOverrides(preDragOptionsOverrideByMember);
|
|
3482
3513
|
dragInFlight = false;
|
|
3483
3514
|
}
|
|
@@ -3567,6 +3598,7 @@ function runGroupTransform(opts) {
|
|
|
3567
3598
|
renderHandlesLayer();
|
|
3568
3599
|
}
|
|
3569
3600
|
const preDragPoints = concatWorkingPoints();
|
|
3601
|
+
const preDragAmplifierPlacementsByMember = cloneAmplifierPlacementsByMember();
|
|
3570
3602
|
activeBoxDrag = {
|
|
3571
3603
|
fsm: beginBoxDrag({
|
|
3572
3604
|
kind: boxHit.kind,
|
|
@@ -3583,7 +3615,8 @@ function runGroupTransform(opts) {
|
|
|
3583
3615
|
points: preDragPoints,
|
|
3584
3616
|
box: groupBox
|
|
3585
3617
|
},
|
|
3586
|
-
preDragOptionsOverrideByMember
|
|
3618
|
+
preDragOptionsOverrideByMember,
|
|
3619
|
+
preDragAmplifierPlacementsByMember
|
|
3587
3620
|
};
|
|
3588
3621
|
dragInFlight = true;
|
|
3589
3622
|
driver.setDragPanEnabled(false);
|
|
@@ -3593,6 +3626,7 @@ function runGroupTransform(opts) {
|
|
|
3593
3626
|
const snapshot = activeBoxDrag.fsm.move(pixel);
|
|
3594
3627
|
activeBoxDrag.lastSnapshot = snapshot;
|
|
3595
3628
|
applyConcatenatedPoints(snapshot.points);
|
|
3629
|
+
applyAmplifierPlacementsByMember(activeBoxDrag.preDragAmplifierPlacementsByMember.map((placements) => transformAmplifierPlacements(placements, activeBoxDrag.preDragBox, snapshot.box, driver)));
|
|
3596
3630
|
renderAllPreviews();
|
|
3597
3631
|
renderHandlesLayer();
|
|
3598
3632
|
recomputeDirty();
|
|
@@ -3609,12 +3643,13 @@ function runGroupTransform(opts) {
|
|
|
3609
3643
|
driver.setDragPanEnabled(true);
|
|
3610
3644
|
return;
|
|
3611
3645
|
}
|
|
3612
|
-
const { fsm, preDragPoints, preDragBox, downPixel, preDragOptionsOverrideByMember } = activeBoxDrag;
|
|
3646
|
+
const { fsm, preDragPoints, preDragBox, downPixel, preDragOptionsOverrideByMember, preDragAmplifierPlacementsByMember } = activeBoxDrag;
|
|
3613
3647
|
activeBoxDrag = null;
|
|
3614
3648
|
driver.setDragPanEnabled(true);
|
|
3615
3649
|
const distance = Math.hypot(pixel[0] - downPixel[0], pixel[1] - downPixel[1]);
|
|
3616
3650
|
if (fsm.kind === "body" && distance <= CLICK_SLOP_PX) {
|
|
3617
3651
|
applyConcatenatedPoints(preDragPoints);
|
|
3652
|
+
applyAmplifierPlacementsByMember(preDragAmplifierPlacementsByMember);
|
|
3618
3653
|
renderAllPreviews();
|
|
3619
3654
|
renderHandlesLayer();
|
|
3620
3655
|
recomputeDirty();
|
|
@@ -3624,8 +3659,9 @@ function runGroupTransform(opts) {
|
|
|
3624
3659
|
}
|
|
3625
3660
|
const finalSnapshot = fsm.move(pixel);
|
|
3626
3661
|
applyConcatenatedPoints(preDragPoints);
|
|
3662
|
+
applyAmplifierPlacementsByMember(preDragAmplifierPlacementsByMember);
|
|
3627
3663
|
if (fsm.kind === "rotate") boxAngle += finalSnapshot.box.angle - preDragBox.angle;
|
|
3628
|
-
commitGesture(finalSnapshot.points, preDragOptionsOverrideByMember);
|
|
3664
|
+
commitGesture(finalSnapshot.points, preDragOptionsOverrideByMember, preDragAmplifierPlacementsByMember.map((placements) => transformAmplifierPlacements(placements, preDragBox, finalSnapshot.box, driver)));
|
|
3629
3665
|
};
|
|
3630
3666
|
let pickPreview;
|
|
3631
3667
|
let pickHandles;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orbat-mapper/tactical-draw",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.17",
|
|
4
4
|
"description": "Engine-agnostic draw/edit core for tactical control measures (MIL-STD-2525 / APP-6). The MapAdapter ABI and bring-your-own-engine entry point for the tactical-draw package family.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Orbat Mapper",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@orbat-mapper/control-measures": "^0.2.0-alpha.
|
|
41
|
+
"@orbat-mapper/control-measures": "^0.2.0-alpha.13"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@arethetypeswrong/core": "^0.18.3",
|