@fieldnotes/core 0.46.0 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +156 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +156 -64
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -698,8 +698,20 @@ var InputFilter = class _InputFilter {
|
|
|
698
698
|
|
|
699
699
|
// src/elements/create-id.ts
|
|
700
700
|
var counter = 0;
|
|
701
|
+
function randomClientComponent() {
|
|
702
|
+
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
|
|
703
|
+
const bytes = new Uint8Array(8);
|
|
704
|
+
crypto.getRandomValues(bytes);
|
|
705
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
706
|
+
}
|
|
707
|
+
return Math.random().toString(36).slice(2, 14);
|
|
708
|
+
}
|
|
709
|
+
var CLIENT = randomClientComponent();
|
|
710
|
+
function formatId(prefix, time, counter2, client) {
|
|
711
|
+
return `${prefix}_${time}_${counter2}_${client}`;
|
|
712
|
+
}
|
|
701
713
|
function createId(prefix) {
|
|
702
|
-
return
|
|
714
|
+
return formatId(prefix, Date.now().toString(36), (counter++).toString(36), CLIENT);
|
|
703
715
|
}
|
|
704
716
|
|
|
705
717
|
// src/core/geometry.ts
|
|
@@ -3312,6 +3324,48 @@ function drawHexPath(ctx, cx, cy, cellSize, orientation) {
|
|
|
3312
3324
|
ctx.closePath();
|
|
3313
3325
|
}
|
|
3314
3326
|
|
|
3327
|
+
// src/elements/renderers/template-measure.ts
|
|
3328
|
+
function renderTemplateFeetLabel(ctx, p) {
|
|
3329
|
+
if (p.feet <= 0) return;
|
|
3330
|
+
const directional = p.templateShape === "cone" || p.templateShape === "line";
|
|
3331
|
+
const dir = directional ? p.angle : 0;
|
|
3332
|
+
const length = p.templateShape === "square" ? p.radius / 2 : p.radius;
|
|
3333
|
+
const cos = Math.cos(dir);
|
|
3334
|
+
const sin = Math.sin(dir);
|
|
3335
|
+
const start = p.position;
|
|
3336
|
+
const end = { x: start.x + length * cos, y: start.y + length * sin };
|
|
3337
|
+
const midX = start.x + length / 2 * cos;
|
|
3338
|
+
const midY = start.y + length / 2 * sin;
|
|
3339
|
+
ctx.save();
|
|
3340
|
+
ctx.globalAlpha = 1;
|
|
3341
|
+
ctx.beginPath();
|
|
3342
|
+
ctx.setLineDash([4, 4]);
|
|
3343
|
+
ctx.strokeStyle = p.color;
|
|
3344
|
+
ctx.lineWidth = 1.5;
|
|
3345
|
+
ctx.moveTo(start.x, start.y);
|
|
3346
|
+
ctx.lineTo(end.x, end.y);
|
|
3347
|
+
ctx.stroke();
|
|
3348
|
+
ctx.setLineDash([]);
|
|
3349
|
+
const label = `${Math.round(p.feet)} ft`;
|
|
3350
|
+
const fontSize = Math.max(10, Math.min(14, p.radius * 0.15));
|
|
3351
|
+
ctx.font = `bold ${fontSize}px system-ui, sans-serif`;
|
|
3352
|
+
ctx.textAlign = "center";
|
|
3353
|
+
ctx.textBaseline = "bottom";
|
|
3354
|
+
const textY = midY - 4;
|
|
3355
|
+
const metrics = ctx.measureText(label);
|
|
3356
|
+
const padX = 4;
|
|
3357
|
+
const padY = 2;
|
|
3358
|
+
const textW = metrics.width + padX * 2;
|
|
3359
|
+
const textH = fontSize + padY * 2;
|
|
3360
|
+
ctx.fillStyle = "rgba(255, 255, 255, 0.85)";
|
|
3361
|
+
ctx.beginPath();
|
|
3362
|
+
ctx.roundRect(midX - textW / 2, textY - textH, textW, textH, 3);
|
|
3363
|
+
ctx.fill();
|
|
3364
|
+
ctx.fillStyle = p.color;
|
|
3365
|
+
ctx.fillText(label, midX, textY - padY);
|
|
3366
|
+
ctx.restore();
|
|
3367
|
+
}
|
|
3368
|
+
|
|
3315
3369
|
// src/elements/renderers/template-renderer.ts
|
|
3316
3370
|
function renderTemplate(ctx, template, store) {
|
|
3317
3371
|
const grid = store?.getElementsByType("grid")[0];
|
|
@@ -3335,9 +3389,6 @@ function renderGeometricTemplate(ctx, template) {
|
|
|
3335
3389
|
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
|
3336
3390
|
ctx.fill();
|
|
3337
3391
|
ctx.stroke();
|
|
3338
|
-
if (template.radiusFeet != null && template.radiusFeet > 0) {
|
|
3339
|
-
renderRadiusMarker(ctx, cx, cy, r, template.radiusFeet);
|
|
3340
|
-
}
|
|
3341
3392
|
break;
|
|
3342
3393
|
case "square":
|
|
3343
3394
|
ctx.fillRect(cx - r / 2, cy - r / 2, r, r);
|
|
@@ -3370,6 +3421,16 @@ function renderGeometricTemplate(ctx, template) {
|
|
|
3370
3421
|
break;
|
|
3371
3422
|
}
|
|
3372
3423
|
}
|
|
3424
|
+
if (template.radiusFeet != null && template.radiusFeet > 0) {
|
|
3425
|
+
renderTemplateFeetLabel(ctx, {
|
|
3426
|
+
position: template.position,
|
|
3427
|
+
radius: template.radius,
|
|
3428
|
+
angle: template.angle,
|
|
3429
|
+
templateShape: template.templateShape,
|
|
3430
|
+
feet: template.radiusFeet,
|
|
3431
|
+
color: template.strokeColor
|
|
3432
|
+
});
|
|
3433
|
+
}
|
|
3373
3434
|
ctx.restore();
|
|
3374
3435
|
}
|
|
3375
3436
|
function renderHexTemplate(ctx, template, cellSize, orientation) {
|
|
@@ -3416,44 +3477,18 @@ function renderHexTemplate(ctx, template, cellSize, orientation) {
|
|
|
3416
3477
|
ctx.lineWidth = template.strokeWidth;
|
|
3417
3478
|
ctx.stroke();
|
|
3418
3479
|
}
|
|
3419
|
-
if (template.
|
|
3420
|
-
|
|
3421
|
-
|
|
3480
|
+
if (template.radiusFeet != null && template.radiusFeet > 0) {
|
|
3481
|
+
renderTemplateFeetLabel(ctx, {
|
|
3482
|
+
position: template.position,
|
|
3483
|
+
radius: template.radius,
|
|
3484
|
+
angle: template.angle,
|
|
3485
|
+
templateShape: template.templateShape,
|
|
3486
|
+
feet: template.radiusFeet,
|
|
3487
|
+
color: template.strokeColor
|
|
3488
|
+
});
|
|
3422
3489
|
}
|
|
3423
3490
|
ctx.restore();
|
|
3424
3491
|
}
|
|
3425
|
-
function renderRadiusMarker(ctx, cx, cy, r, feet) {
|
|
3426
|
-
const markerColor = ctx.strokeStyle;
|
|
3427
|
-
ctx.save();
|
|
3428
|
-
ctx.globalAlpha = 1;
|
|
3429
|
-
ctx.beginPath();
|
|
3430
|
-
ctx.setLineDash([4, 4]);
|
|
3431
|
-
ctx.strokeStyle = markerColor;
|
|
3432
|
-
ctx.lineWidth = 1.5;
|
|
3433
|
-
ctx.moveTo(cx, cy);
|
|
3434
|
-
ctx.lineTo(cx + r, cy);
|
|
3435
|
-
ctx.stroke();
|
|
3436
|
-
ctx.setLineDash([]);
|
|
3437
|
-
const label = `${Math.round(feet)} ft`;
|
|
3438
|
-
const fontSize = Math.max(10, Math.min(14, r * 0.15));
|
|
3439
|
-
ctx.font = `bold ${fontSize}px system-ui, sans-serif`;
|
|
3440
|
-
ctx.textAlign = "center";
|
|
3441
|
-
ctx.textBaseline = "bottom";
|
|
3442
|
-
const textX = cx + r / 2;
|
|
3443
|
-
const textY = cy - 4;
|
|
3444
|
-
const metrics = ctx.measureText(label);
|
|
3445
|
-
const padX = 4;
|
|
3446
|
-
const padY = 2;
|
|
3447
|
-
const textW = metrics.width + padX * 2;
|
|
3448
|
-
const textH = fontSize + padY * 2;
|
|
3449
|
-
ctx.fillStyle = "rgba(255, 255, 255, 0.85)";
|
|
3450
|
-
ctx.beginPath();
|
|
3451
|
-
ctx.roundRect(textX - textW / 2, textY - textH, textW, textH, 3);
|
|
3452
|
-
ctx.fill();
|
|
3453
|
-
ctx.fillStyle = markerColor;
|
|
3454
|
-
ctx.fillText(label, textX, textY - padY);
|
|
3455
|
-
ctx.restore();
|
|
3456
|
-
}
|
|
3457
3492
|
|
|
3458
3493
|
// src/elements/grid-renderer.ts
|
|
3459
3494
|
function getSquareGridLines(bounds, cellSize) {
|
|
@@ -8299,6 +8334,20 @@ function getOverlayLayout(el, zoom) {
|
|
|
8299
8334
|
const rotateHandle = rotatePoint(topMid, center2, angle);
|
|
8300
8335
|
return { center: center2, corners, rotateHandle, angle };
|
|
8301
8336
|
}
|
|
8337
|
+
function templateAimKnob(el, zoom) {
|
|
8338
|
+
if (el.type !== "template") return null;
|
|
8339
|
+
if (el.templateShape !== "cone" && el.templateShape !== "line") return null;
|
|
8340
|
+
const gap = ROTATE_HANDLE_OFFSET / zoom;
|
|
8341
|
+
const dist = el.radius + gap;
|
|
8342
|
+
const origin = el.position;
|
|
8343
|
+
return {
|
|
8344
|
+
origin,
|
|
8345
|
+
knob: {
|
|
8346
|
+
x: origin.x + dist * Math.cos(el.angle),
|
|
8347
|
+
y: origin.y + dist * Math.sin(el.angle)
|
|
8348
|
+
}
|
|
8349
|
+
};
|
|
8350
|
+
}
|
|
8302
8351
|
function getHandlePositions(bounds) {
|
|
8303
8352
|
return [
|
|
8304
8353
|
["nw", { x: bounds.x, y: bounds.y }],
|
|
@@ -8447,6 +8496,22 @@ function renderSelectionBoxes(ctx, p) {
|
|
|
8447
8496
|
handleWorldSize
|
|
8448
8497
|
);
|
|
8449
8498
|
ctx.setLineDash([4 / zoom, 4 / zoom]);
|
|
8499
|
+
if (p.selectedIds.length === 1 && (el.templateShape === "cone" || el.templateShape === "line")) {
|
|
8500
|
+
const aim = templateAimKnob(el, zoom);
|
|
8501
|
+
if (aim) {
|
|
8502
|
+
ctx.beginPath();
|
|
8503
|
+
ctx.moveTo(aim.origin.x, aim.origin.y);
|
|
8504
|
+
ctx.lineTo(aim.knob.x, aim.knob.y);
|
|
8505
|
+
ctx.stroke();
|
|
8506
|
+
ctx.setLineDash([]);
|
|
8507
|
+
ctx.fillStyle = "#ffffff";
|
|
8508
|
+
ctx.beginPath();
|
|
8509
|
+
ctx.arc(aim.knob.x, aim.knob.y, handleWorldSize / 2, 0, Math.PI * 2);
|
|
8510
|
+
ctx.fill();
|
|
8511
|
+
ctx.stroke();
|
|
8512
|
+
ctx.setLineDash([4 / zoom, 4 / zoom]);
|
|
8513
|
+
}
|
|
8514
|
+
}
|
|
8450
8515
|
}
|
|
8451
8516
|
if (p.selectedIds.length === 1 && ROTATABLE_TYPES.has(el.type)) {
|
|
8452
8517
|
const stemStart = topMidpoint(layout);
|
|
@@ -8602,6 +8667,19 @@ function hitTestTemplateResizeHandle(world, ctx, selectedIds) {
|
|
|
8602
8667
|
}
|
|
8603
8668
|
return null;
|
|
8604
8669
|
}
|
|
8670
|
+
function hitTestTemplateAimHandle(world, ctx, selectedIds) {
|
|
8671
|
+
if (selectedIds.length !== 1) return null;
|
|
8672
|
+
const id = selectedIds[0];
|
|
8673
|
+
if (!id) return null;
|
|
8674
|
+
const el = ctx.store.getById(id);
|
|
8675
|
+
if (!el || el.locked) return null;
|
|
8676
|
+
const knob = templateAimKnob(el, ctx.camera.zoom);
|
|
8677
|
+
if (!knob) return null;
|
|
8678
|
+
const r = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / ctx.camera.zoom;
|
|
8679
|
+
const dx = world.x - knob.knob.x;
|
|
8680
|
+
const dy = world.y - knob.knob.y;
|
|
8681
|
+
return dx * dx + dy * dy <= r * r ? { elementId: id } : null;
|
|
8682
|
+
}
|
|
8605
8683
|
function findElementsInRect(marquee, ctx) {
|
|
8606
8684
|
const candidates = ctx.store.queryRect(marquee);
|
|
8607
8685
|
const ids = [];
|
|
@@ -8851,6 +8929,12 @@ var SelectTool = class {
|
|
|
8851
8929
|
ctx.requestRender();
|
|
8852
8930
|
return;
|
|
8853
8931
|
}
|
|
8932
|
+
const aimHit = hitTestTemplateAimHandle(world, ctx, this._selectedIds);
|
|
8933
|
+
if (aimHit) {
|
|
8934
|
+
this.mode = { type: "aiming-template", elementId: aimHit.elementId };
|
|
8935
|
+
ctx.requestRender();
|
|
8936
|
+
return;
|
|
8937
|
+
}
|
|
8854
8938
|
const rotateHit = hitTestRotateHandle(world, ctx, this._selectedIds);
|
|
8855
8939
|
if (rotateHit) {
|
|
8856
8940
|
const el = ctx.store.getById(rotateHit.elementId);
|
|
@@ -8932,6 +9016,19 @@ var SelectTool = class {
|
|
|
8932
9016
|
this.handleTemplateResize(world, ctx);
|
|
8933
9017
|
return;
|
|
8934
9018
|
}
|
|
9019
|
+
if (this.mode.type === "aiming-template") {
|
|
9020
|
+
const el = ctx.store.getById(this.mode.elementId);
|
|
9021
|
+
if (el && el.type === "template" && !el.locked) {
|
|
9022
|
+
let a = Math.atan2(world.y - el.position.y, world.x - el.position.x);
|
|
9023
|
+
if (state.shiftKey) {
|
|
9024
|
+
const snap = ctx.gridType === "hex" ? Math.PI / 3 : ROTATE_SNAP;
|
|
9025
|
+
a = Math.round(a / snap) * snap;
|
|
9026
|
+
}
|
|
9027
|
+
ctx.store.update(this.mode.elementId, { angle: normalizeAngle(a) });
|
|
9028
|
+
ctx.requestRender();
|
|
9029
|
+
}
|
|
9030
|
+
return;
|
|
9031
|
+
}
|
|
8935
9032
|
if (this.mode.type === "rotating") {
|
|
8936
9033
|
const { elementId, center: center2, startPointerAngle, startRotation } = this.mode;
|
|
8937
9034
|
const a = Math.atan2(world.y - center2.y, world.x - center2.x);
|
|
@@ -9138,6 +9235,10 @@ var SelectTool = class {
|
|
|
9138
9235
|
ctx.setCursor?.("nwse-resize");
|
|
9139
9236
|
return null;
|
|
9140
9237
|
}
|
|
9238
|
+
if (hitTestTemplateAimHandle(world, ctx, this._selectedIds)) {
|
|
9239
|
+
ctx.setCursor?.("grab");
|
|
9240
|
+
return null;
|
|
9241
|
+
}
|
|
9141
9242
|
if (hitTestRotateHandle(world, ctx, this._selectedIds)) {
|
|
9142
9243
|
ctx.setCursor?.("grab");
|
|
9143
9244
|
return null;
|
|
@@ -9807,6 +9908,7 @@ var TemplateTool = class {
|
|
|
9807
9908
|
gridType;
|
|
9808
9909
|
hexOrientation;
|
|
9809
9910
|
snapEnabled = false;
|
|
9911
|
+
feetScaleUnit = 0;
|
|
9810
9912
|
templateShape;
|
|
9811
9913
|
fillColor;
|
|
9812
9914
|
strokeColor;
|
|
@@ -9855,6 +9957,7 @@ var TemplateTool = class {
|
|
|
9855
9957
|
this.gridType = ctx.gridType;
|
|
9856
9958
|
this.hexOrientation = ctx.hexOrientation;
|
|
9857
9959
|
this.snapEnabled = !!ctx.gridType || (ctx.snapToGrid ?? false);
|
|
9960
|
+
this.feetScaleUnit = ctx.gridSize && ctx.gridSize > 0 ? ctx.gridType === "hex" ? Math.sqrt(3) * ctx.gridSize : ctx.gridSize : 0;
|
|
9858
9961
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
9859
9962
|
this.origin = this.snapToGrid(world, ctx);
|
|
9860
9963
|
this.current = { ...this.origin };
|
|
@@ -9954,6 +10057,7 @@ var TemplateTool = class {
|
|
|
9954
10057
|
break;
|
|
9955
10058
|
}
|
|
9956
10059
|
}
|
|
10060
|
+
this.drawFeetLabel(ctx, radius);
|
|
9957
10061
|
ctx.restore();
|
|
9958
10062
|
}
|
|
9959
10063
|
renderHexOverlay(ctx, radius) {
|
|
@@ -10004,30 +10108,7 @@ var TemplateTool = class {
|
|
|
10004
10108
|
ctx.lineWidth = this.strokeWidth;
|
|
10005
10109
|
ctx.stroke();
|
|
10006
10110
|
}
|
|
10007
|
-
|
|
10008
|
-
const feet = radiusCells * this.feetPerCell;
|
|
10009
|
-
if (feet > 0) {
|
|
10010
|
-
ctx.globalAlpha = 1;
|
|
10011
|
-
const label = `${Math.round(feet)} ft`;
|
|
10012
|
-
const fontSize = Math.max(10, Math.min(14, radius * 0.15));
|
|
10013
|
-
ctx.font = `bold ${fontSize}px system-ui, sans-serif`;
|
|
10014
|
-
ctx.textAlign = "center";
|
|
10015
|
-
ctx.textBaseline = "bottom";
|
|
10016
|
-
const textX = center2.x;
|
|
10017
|
-
const textY = center2.y - 4;
|
|
10018
|
-
const metrics = ctx.measureText(label);
|
|
10019
|
-
const padX = 4;
|
|
10020
|
-
const padY = 2;
|
|
10021
|
-
const textW = metrics.width + padX * 2;
|
|
10022
|
-
const textH = fontSize + padY * 2;
|
|
10023
|
-
ctx.fillStyle = "rgba(255, 255, 255, 0.85)";
|
|
10024
|
-
ctx.beginPath();
|
|
10025
|
-
ctx.roundRect(textX - textW / 2, textY - textH, textW, textH, 3);
|
|
10026
|
-
ctx.fill();
|
|
10027
|
-
ctx.fillStyle = this.strokeColor;
|
|
10028
|
-
ctx.fillText(label, textX, textY - padY);
|
|
10029
|
-
}
|
|
10030
|
-
}
|
|
10111
|
+
this.drawFeetLabel(ctx, radius);
|
|
10031
10112
|
ctx.restore();
|
|
10032
10113
|
}
|
|
10033
10114
|
computeRadius() {
|
|
@@ -10045,6 +10126,17 @@ var TemplateTool = class {
|
|
|
10045
10126
|
const dy = this.current.y - this.origin.y;
|
|
10046
10127
|
return Math.atan2(dy, dx);
|
|
10047
10128
|
}
|
|
10129
|
+
drawFeetLabel(ctx, radius) {
|
|
10130
|
+
const feet = this.feetScaleUnit > 0 ? radius / this.feetScaleUnit * this.feetPerCell : 0;
|
|
10131
|
+
renderTemplateFeetLabel(ctx, {
|
|
10132
|
+
position: this.origin,
|
|
10133
|
+
radius,
|
|
10134
|
+
angle: this.computeAngle(),
|
|
10135
|
+
templateShape: this.templateShape,
|
|
10136
|
+
feet,
|
|
10137
|
+
color: this.strokeColor
|
|
10138
|
+
});
|
|
10139
|
+
}
|
|
10048
10140
|
snapToGrid(point, ctx) {
|
|
10049
10141
|
if (!ctx.gridSize) return point;
|
|
10050
10142
|
if (ctx.gridType === "hex" && ctx.hexOrientation) {
|
|
@@ -10174,7 +10266,7 @@ var LaserTool = class {
|
|
|
10174
10266
|
};
|
|
10175
10267
|
|
|
10176
10268
|
// src/index.ts
|
|
10177
|
-
var VERSION = "0.
|
|
10269
|
+
var VERSION = "0.47.0";
|
|
10178
10270
|
// Annotate the CommonJS export names for ESM import in node:
|
|
10179
10271
|
0 && (module.exports = {
|
|
10180
10272
|
ArrowTool,
|