@oh-just-another/state 0.59.0 → 0.60.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/CHANGELOG.md +91 -0
- package/README.md +2 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/actions/actionArrange.js +4 -4
- package/dist/actions/actionArrange.js.map +1 -1
- package/dist/actions/actionClipboard.d.ts.map +1 -1
- package/dist/actions/actionClipboard.js +5 -0
- package/dist/actions/actionClipboard.js.map +1 -1
- package/dist/actions/actionKeyboard.d.ts.map +1 -1
- package/dist/actions/actionKeyboard.js +77 -9
- package/dist/actions/actionKeyboard.js.map +1 -1
- package/dist/actions/actionMode.d.ts +4 -0
- package/dist/actions/actionMode.d.ts.map +1 -1
- package/dist/actions/actionMode.js +54 -0
- package/dist/actions/actionMode.js.map +1 -1
- package/dist/actions/actionSelection.d.ts.map +1 -1
- package/dist/actions/actionSelection.js +1 -0
- package/dist/actions/actionSelection.js.map +1 -1
- package/dist/actions/actionView.d.ts +6 -0
- package/dist/actions/actionView.d.ts.map +1 -1
- package/dist/actions/actionView.js +19 -1
- package/dist/actions/actionView.js.map +1 -1
- package/dist/actions/actionZoom.d.ts.map +1 -1
- package/dist/actions/actionZoom.js +5 -0
- package/dist/actions/actionZoom.js.map +1 -1
- package/dist/actions/index.d.ts +3 -3
- package/dist/actions/index.d.ts.map +1 -1
- package/dist/actions/index.js +3 -3
- package/dist/actions/index.js.map +1 -1
- package/dist/actions/registry.d.ts.map +1 -1
- package/dist/actions/registry.js +13 -0
- package/dist/actions/registry.js.map +1 -1
- package/dist/actions/types.d.ts +9 -0
- package/dist/actions/types.d.ts.map +1 -1
- package/dist/actions/types.js.map +1 -1
- package/dist/clipboard.d.ts +0 -14
- package/dist/clipboard.d.ts.map +1 -1
- package/dist/clipboard.js +1 -1
- package/dist/clipboard.js.map +1 -1
- package/dist/constants.d.ts +160 -18
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +162 -2
- package/dist/constants.js.map +1 -1
- package/dist/dom-events.d.ts +1 -2
- package/dist/dom-events.d.ts.map +1 -1
- package/dist/dom-events.js.map +1 -1
- package/dist/editor/animation-scene.d.ts +15 -0
- package/dist/editor/animation-scene.d.ts.map +1 -1
- package/dist/editor/animation-scene.js +123 -0
- package/dist/editor/animation-scene.js.map +1 -1
- package/dist/editor/applies/create.d.ts +0 -6
- package/dist/editor/applies/create.d.ts.map +1 -1
- package/dist/editor/applies/create.js +1 -1
- package/dist/editor/applies/create.js.map +1 -1
- package/dist/editor/applies/link-move.d.ts +0 -8
- package/dist/editor/applies/link-move.d.ts.map +1 -1
- package/dist/editor/applies/link-move.js +1 -1
- package/dist/editor/applies/link-move.js.map +1 -1
- package/dist/editor/interaction-state.d.ts +184 -0
- package/dist/editor/interaction-state.d.ts.map +1 -0
- package/dist/editor/interaction-state.js +139 -0
- package/dist/editor/interaction-state.js.map +1 -0
- package/dist/editor/link-handle-drag.d.ts +152 -0
- package/dist/editor/link-handle-drag.d.ts.map +1 -0
- package/dist/editor/link-handle-drag.js +300 -0
- package/dist/editor/link-handle-drag.js.map +1 -0
- package/dist/editor/pointer-binding.d.ts +9 -11
- package/dist/editor/pointer-binding.d.ts.map +1 -1
- package/dist/editor/pointer-binding.js +1185 -901
- package/dist/editor/pointer-binding.js.map +1 -1
- package/dist/editor/public/brush.d.ts +31 -12
- package/dist/editor/public/brush.d.ts.map +1 -1
- package/dist/editor/public/brush.js +66 -17
- package/dist/editor/public/brush.js.map +1 -1
- package/dist/editor/public/cursor.d.ts +1 -15
- package/dist/editor/public/cursor.d.ts.map +1 -1
- package/dist/editor/public/cursor.js +14 -3
- package/dist/editor/public/cursor.js.map +1 -1
- package/dist/editor/public/eraser.d.ts +55 -0
- package/dist/editor/public/eraser.d.ts.map +1 -0
- package/dist/editor/public/eraser.js +65 -0
- package/dist/editor/public/eraser.js.map +1 -0
- package/dist/editor/public/laser.d.ts +38 -0
- package/dist/editor/public/laser.d.ts.map +1 -0
- package/dist/editor/public/laser.js +42 -0
- package/dist/editor/public/laser.js.map +1 -0
- package/dist/editor/public/placement.d.ts.map +1 -1
- package/dist/editor/public/placement.js +9 -1
- package/dist/editor/public/placement.js.map +1 -1
- package/dist/editor/public/selection-ops.d.ts +12 -0
- package/dist/editor/public/selection-ops.d.ts.map +1 -1
- package/dist/editor/public/selection-ops.js +19 -1
- package/dist/editor/public/selection-ops.js.map +1 -1
- package/dist/editor/public/stroke-eraser-coverage.d.ts +37 -0
- package/dist/editor/public/stroke-eraser-coverage.d.ts.map +1 -0
- package/dist/editor/public/stroke-eraser-coverage.js +168 -0
- package/dist/editor/public/stroke-eraser-coverage.js.map +1 -0
- package/dist/editor/public/stroke-eraser.d.ts +50 -0
- package/dist/editor/public/stroke-eraser.d.ts.map +1 -0
- package/dist/editor/public/stroke-eraser.js +176 -0
- package/dist/editor/public/stroke-eraser.js.map +1 -0
- package/dist/editor/public/stroke-smoothing.d.ts +21 -0
- package/dist/editor/public/stroke-smoothing.d.ts.map +1 -0
- package/dist/editor/public/stroke-smoothing.js +37 -0
- package/dist/editor/public/stroke-smoothing.js.map +1 -0
- package/dist/editor/public/tool-ops.d.ts +130 -0
- package/dist/editor/public/tool-ops.d.ts.map +1 -0
- package/dist/editor/public/tool-ops.js +392 -0
- package/dist/editor/public/tool-ops.js.map +1 -0
- package/dist/editor/public/z-order.d.ts +1 -12
- package/dist/editor/public/z-order.d.ts.map +1 -1
- package/dist/editor/public/z-order.js +1 -1
- package/dist/editor/public/z-order.js.map +1 -1
- package/dist/editor/public/zoom-pan.d.ts +9 -0
- package/dist/editor/public/zoom-pan.d.ts.map +1 -1
- package/dist/editor/public/zoom-pan.js +31 -0
- package/dist/editor/public/zoom-pan.js.map +1 -1
- package/dist/editor/render-orchestrator.d.ts +177 -5
- package/dist/editor/render-orchestrator.d.ts.map +1 -1
- package/dist/editor/render-orchestrator.js +517 -351
- package/dist/editor/render-orchestrator.js.map +1 -1
- package/dist/editor/text-edit.d.ts +136 -0
- package/dist/editor/text-edit.d.ts.map +1 -0
- package/dist/editor/text-edit.js +328 -0
- package/dist/editor/text-edit.js.map +1 -0
- package/dist/editor.d.ts +504 -239
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +1557 -706
- package/dist/editor.js.map +1 -1
- package/dist/handle.d.ts +0 -2
- package/dist/handle.d.ts.map +1 -1
- package/dist/handle.js +0 -6
- package/dist/handle.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/modes.d.ts +15 -1
- package/dist/modes.d.ts.map +1 -1
- package/dist/modes.js.map +1 -1
- package/dist/overlay.d.ts +91 -2
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +450 -93
- package/dist/overlay.js.map +1 -1
- package/dist/search.d.ts +37 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +45 -0
- package/dist/search.js.map +1 -0
- package/package.json +4 -4
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type BrushElement, type Scene, type Patch } from "@oh-just-another/scene";
|
|
2
|
+
import { type ElementId, type Vec2 } from "@oh-just-another/types";
|
|
3
|
+
import { type Interval } from "./stroke-eraser-coverage.js";
|
|
4
|
+
/**
|
|
5
|
+
* Grow a brush's accumulated covered spans by the eraser segment `a → b` (world)
|
|
6
|
+
* at `radius` and return the merged result. INCREMENTAL: the live gesture calls
|
|
7
|
+
* this once per pointer move with only the new segment, so a drag costs
|
|
8
|
+
* O(points) per move instead of re-scanning the whole path each frame. `radius`
|
|
9
|
+
* is the visible cursor ring in world units. Pure — returns a new interval list.
|
|
10
|
+
*/
|
|
11
|
+
export declare const markErasedIntervals: (brush: BrushElement, existing: readonly Interval[], a: Vec2, b: Vec2, radius: number) => Interval[];
|
|
12
|
+
/**
|
|
13
|
+
* STROKE-ERASER core (pure). Cut a single brush against an eraser path: the arc
|
|
14
|
+
* spans of the brush polyline within `radius` of the path are removed and the
|
|
15
|
+
* survivors split into fragment {@link BrushElement}s (endpoints on the ring).
|
|
16
|
+
* When nothing is covered, `erasedAny` is `false` and `fragments` is `[brush]`
|
|
17
|
+
* unchanged so the caller can skip the swap. One-shot (scans the whole path) —
|
|
18
|
+
* the live gesture uses the incremental {@link markErasedIntervals} instead.
|
|
19
|
+
*/
|
|
20
|
+
export declare const computeEraseBrushStroke: (brush: BrushElement, eraserPathWorld: readonly Vec2[], radius: number, makeId: () => ElementId) => {
|
|
21
|
+
fragments: BrushElement[];
|
|
22
|
+
erasedAny: boolean;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Commit the accumulated stroke-erase: `erased` maps each touched brush id to the
|
|
26
|
+
* covered arc-length spans built incrementally by {@link markErasedIntervals}.
|
|
27
|
+
* Each such brush is replaced with its fragments (remove original + add fragments)
|
|
28
|
+
* and any link bound to it is detached (drop the binding — don't re-bind to a
|
|
29
|
+
* fragment). Returns the next scene + patches for ONE history batch, or `null`
|
|
30
|
+
* when nothing is cut.
|
|
31
|
+
*/
|
|
32
|
+
export declare const computeEraseFromMasks: (scene: Scene, erased: ReadonlyMap<ElementId, readonly Interval[]>, makeId: () => ElementId) => {
|
|
33
|
+
readonly scene: Scene;
|
|
34
|
+
readonly patches: Patch[];
|
|
35
|
+
readonly removedIds: ElementId[];
|
|
36
|
+
readonly addedIds: ElementId[];
|
|
37
|
+
} | null;
|
|
38
|
+
/**
|
|
39
|
+
* Live-preview counterpart of {@link computeEraseFromMasks}: the cut fragments for
|
|
40
|
+
* every brush with covered spans so far, WITHOUT mutating the scene or consuming
|
|
41
|
+
* real ids. `fragments` are drawn on the overlay and `hidden` (the touched
|
|
42
|
+
* originals) suppressed in the main pass, so the user sees the cut before
|
|
43
|
+
* releasing. Fragment ids are deterministic per frame (never enter history).
|
|
44
|
+
* `null` when nothing is cut yet.
|
|
45
|
+
*/
|
|
46
|
+
export declare const computeStrokeErasePreviewFromMasks: (scene: Scene, erased: ReadonlyMap<ElementId, readonly Interval[]>) => {
|
|
47
|
+
fragments: BrushElement[];
|
|
48
|
+
hidden: Set<ElementId>;
|
|
49
|
+
} | null;
|
|
50
|
+
//# sourceMappingURL=stroke-eraser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stroke-eraser.d.ts","sourceRoot":"","sources":["../../../src/editor/public/stroke-eraser.ts"],"names":[],"mappings":"AACA,OAAO,EAOL,KAAK,YAAY,EAEjB,KAAK,KAAK,EACV,KAAK,KAAK,EACX,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAEL,KAAK,SAAS,EAEd,KAAK,IAAI,EACV,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAML,KAAK,QAAQ,EACd,MAAM,6BAA6B,CAAC;AA+DrC;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAC9B,OAAO,YAAY,EACnB,UAAU,SAAS,QAAQ,EAAE,EAC7B,GAAG,IAAI,EACP,GAAG,IAAI,EACP,QAAQ,MAAM,KACb,QAAQ,EAIV,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,GAClC,OAAO,YAAY,EACnB,iBAAiB,SAAS,IAAI,EAAE,EAChC,QAAQ,MAAM,EACd,QAAQ,MAAM,SAAS,KACtB;IAAE,SAAS,EAAE,YAAY,EAAE,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAajD,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,GAChC,OAAO,KAAK,EACZ,QAAQ,WAAW,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,CAAC,EACnD,QAAQ,MAAM,SAAS,KACtB;IACD,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,SAAS,EAAE,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,CAAC;CAChC,GAAG,IAkCH,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,kCAAkC,GAC7C,OAAO,KAAK,EACZ,QAAQ,WAAW,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,CAAC,KAClD;IAAE,SAAS,EAAE,YAAY,EAAE,CAAC;IAAC,MAAM,EAAE,GAAG,CAAC,SAAS,CAAC,CAAA;CAAE,GAAG,IAgB1D,CAAC"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { req } from "../../util.js";
|
|
2
|
+
import { addElement, removeElement, removeLink, orderBetween, isBrush, endpointElementId, } from "@oh-just-another/scene";
|
|
3
|
+
import { elementId as castElementId, } from "@oh-just-another/types";
|
|
4
|
+
import { ERASE_FRAGMENT_MIN_ARC } from "../../constants.js";
|
|
5
|
+
import { brushArc, coveredArcAgainstSegment, complementIntervals, mergeIntervals, localPointAtArc, } from "./stroke-eraser-coverage.js";
|
|
6
|
+
const EPS = 1e-6;
|
|
7
|
+
/**
|
|
8
|
+
* Rebuild a brush's surviving pieces from the COVERED arc-length spans the eraser
|
|
9
|
+
* removed. The kept spans (complement of `covered`) each become a fresh fragment
|
|
10
|
+
* {@link BrushElement}: the span endpoints — already pinned to the eraser ring by
|
|
11
|
+
* {@link coveredArcAgainstSegment}'s bisection — are the fragment's first/last
|
|
12
|
+
* points, with the original vertices strictly inside the span in between. Points
|
|
13
|
+
* are re-localised to the fragment's first world position, per-point widths kept,
|
|
14
|
+
* fractional `order`s around the original's slot, never `closed`.
|
|
15
|
+
*
|
|
16
|
+
* Cutting by ARC LENGTH (not vertex index) is what makes the eraser remove the
|
|
17
|
+
* geometry it visibly covers regardless of how densely the stroke was sampled — a
|
|
18
|
+
* big disc grazing a sparse line no longer slips between two far-apart vertices. A
|
|
19
|
+
* kept span shorter than {@link ERASE_FRAGMENT_MIN_ARC} is dropped (litter).
|
|
20
|
+
*/
|
|
21
|
+
const buildFragmentsFromCoverage = (brush, covered, makeId) => {
|
|
22
|
+
const arc = brushArc(brush);
|
|
23
|
+
if (arc.total === 0)
|
|
24
|
+
return []; // a single-point brush leaves nothing once cut
|
|
25
|
+
const kept = complementIntervals(covered, arc.total);
|
|
26
|
+
const fragments = [];
|
|
27
|
+
let prevOrder = brush.order;
|
|
28
|
+
let idx = 0;
|
|
29
|
+
for (const [L0, L1] of kept) {
|
|
30
|
+
if (L1 - L0 < ERASE_FRAGMENT_MIN_ARC)
|
|
31
|
+
continue; // drop stray nubs
|
|
32
|
+
const runPts = [localPointAtArc(arc, L0)];
|
|
33
|
+
for (let i = 0; i < arc.pts.length; i++) {
|
|
34
|
+
const c = req(arc.cum[i]);
|
|
35
|
+
if (c > L0 + EPS && c < L1 - EPS)
|
|
36
|
+
runPts.push({ ...req(arc.pts[i]) });
|
|
37
|
+
}
|
|
38
|
+
runPts.push(localPointAtArc(arc, L1));
|
|
39
|
+
if (runPts.length < 2)
|
|
40
|
+
continue;
|
|
41
|
+
const anchor = req(runPts[0]);
|
|
42
|
+
const worldFirst = { x: brush.position.x + anchor.x, y: brush.position.y + anchor.y };
|
|
43
|
+
const localPoints = runPts.map((p) => ({
|
|
44
|
+
x: p.x - anchor.x,
|
|
45
|
+
y: p.y - anchor.y,
|
|
46
|
+
width: p.width,
|
|
47
|
+
}));
|
|
48
|
+
const order = idx === 0 ? brush.order : orderBetween(prevOrder, null);
|
|
49
|
+
prevOrder = order;
|
|
50
|
+
idx++;
|
|
51
|
+
fragments.push({
|
|
52
|
+
id: makeId(),
|
|
53
|
+
layerId: brush.layerId,
|
|
54
|
+
type: "brush",
|
|
55
|
+
position: worldFirst,
|
|
56
|
+
rotation: 0,
|
|
57
|
+
scale: { x: 1, y: 1 },
|
|
58
|
+
order,
|
|
59
|
+
style: brush.style,
|
|
60
|
+
points: localPoints,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return fragments;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Grow a brush's accumulated covered spans by the eraser segment `a → b` (world)
|
|
67
|
+
* at `radius` and return the merged result. INCREMENTAL: the live gesture calls
|
|
68
|
+
* this once per pointer move with only the new segment, so a drag costs
|
|
69
|
+
* O(points) per move instead of re-scanning the whole path each frame. `radius`
|
|
70
|
+
* is the visible cursor ring in world units. Pure — returns a new interval list.
|
|
71
|
+
*/
|
|
72
|
+
export const markErasedIntervals = (brush, existing, a, b, radius) => {
|
|
73
|
+
const add = coveredArcAgainstSegment(brushArc(brush), a, b, radius);
|
|
74
|
+
if (add.length === 0)
|
|
75
|
+
return existing.length > 0 ? mergeIntervals(existing) : [];
|
|
76
|
+
return mergeIntervals([...existing, ...add]);
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* STROKE-ERASER core (pure). Cut a single brush against an eraser path: the arc
|
|
80
|
+
* spans of the brush polyline within `radius` of the path are removed and the
|
|
81
|
+
* survivors split into fragment {@link BrushElement}s (endpoints on the ring).
|
|
82
|
+
* When nothing is covered, `erasedAny` is `false` and `fragments` is `[brush]`
|
|
83
|
+
* unchanged so the caller can skip the swap. One-shot (scans the whole path) —
|
|
84
|
+
* the live gesture uses the incremental {@link markErasedIntervals} instead.
|
|
85
|
+
*/
|
|
86
|
+
export const computeEraseBrushStroke = (brush, eraserPathWorld, radius, makeId) => {
|
|
87
|
+
if (eraserPathWorld.length === 0)
|
|
88
|
+
return { fragments: [brush], erasedAny: false };
|
|
89
|
+
const arc = brushArc(brush);
|
|
90
|
+
let covered = [];
|
|
91
|
+
const segs = Math.max(1, eraserPathWorld.length - 1);
|
|
92
|
+
for (let i = 0; i < segs; i++) {
|
|
93
|
+
const a = req(eraserPathWorld[i]);
|
|
94
|
+
const b = req(eraserPathWorld[Math.min(i + 1, eraserPathWorld.length - 1)]);
|
|
95
|
+
const add = coveredArcAgainstSegment(arc, a, b, radius);
|
|
96
|
+
if (add.length > 0)
|
|
97
|
+
covered = mergeIntervals([...covered, ...add]);
|
|
98
|
+
}
|
|
99
|
+
if (covered.length === 0)
|
|
100
|
+
return { fragments: [brush], erasedAny: false };
|
|
101
|
+
return { fragments: buildFragmentsFromCoverage(brush, covered, makeId), erasedAny: true };
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Commit the accumulated stroke-erase: `erased` maps each touched brush id to the
|
|
105
|
+
* covered arc-length spans built incrementally by {@link markErasedIntervals}.
|
|
106
|
+
* Each such brush is replaced with its fragments (remove original + add fragments)
|
|
107
|
+
* and any link bound to it is detached (drop the binding — don't re-bind to a
|
|
108
|
+
* fragment). Returns the next scene + patches for ONE history batch, or `null`
|
|
109
|
+
* when nothing is cut.
|
|
110
|
+
*/
|
|
111
|
+
export const computeEraseFromMasks = (scene, erased, makeId) => {
|
|
112
|
+
let s = scene;
|
|
113
|
+
const patches = [];
|
|
114
|
+
const removedIds = [];
|
|
115
|
+
const addedIds = [];
|
|
116
|
+
const droppedLinks = new Set();
|
|
117
|
+
for (const [brushId, covered] of erased) {
|
|
118
|
+
if (covered.length === 0)
|
|
119
|
+
continue;
|
|
120
|
+
const el = scene.elements.get(brushId);
|
|
121
|
+
if (el === undefined || !isBrush(el))
|
|
122
|
+
continue;
|
|
123
|
+
const fragments = buildFragmentsFromCoverage(el, covered, makeId);
|
|
124
|
+
// Detach links bound to this brush before removing it.
|
|
125
|
+
for (const edge of [...s.links.values()]) {
|
|
126
|
+
if (droppedLinks.has(edge.id))
|
|
127
|
+
continue;
|
|
128
|
+
if (endpointElementId(edge.from) === el.id || endpointElementId(edge.to) === el.id) {
|
|
129
|
+
const r = removeLink(s, edge.id);
|
|
130
|
+
s = r.scene;
|
|
131
|
+
patches.push(r.patch);
|
|
132
|
+
droppedLinks.add(edge.id);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const removed = removeElement(s, el.id);
|
|
136
|
+
s = removed.scene;
|
|
137
|
+
patches.push(removed.patch);
|
|
138
|
+
removedIds.push(el.id);
|
|
139
|
+
for (const frag of fragments) {
|
|
140
|
+
const added = addElement(s, frag);
|
|
141
|
+
s = added.scene;
|
|
142
|
+
patches.push(added.patch);
|
|
143
|
+
addedIds.push(frag.id);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (patches.length === 0)
|
|
147
|
+
return null;
|
|
148
|
+
return { scene: s, patches, removedIds, addedIds };
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Live-preview counterpart of {@link computeEraseFromMasks}: the cut fragments for
|
|
152
|
+
* every brush with covered spans so far, WITHOUT mutating the scene or consuming
|
|
153
|
+
* real ids. `fragments` are drawn on the overlay and `hidden` (the touched
|
|
154
|
+
* originals) suppressed in the main pass, so the user sees the cut before
|
|
155
|
+
* releasing. Fragment ids are deterministic per frame (never enter history).
|
|
156
|
+
* `null` when nothing is cut yet.
|
|
157
|
+
*/
|
|
158
|
+
export const computeStrokeErasePreviewFromMasks = (scene, erased) => {
|
|
159
|
+
const fragments = [];
|
|
160
|
+
const hidden = new Set();
|
|
161
|
+
for (const [brushId, covered] of erased) {
|
|
162
|
+
if (covered.length === 0)
|
|
163
|
+
continue;
|
|
164
|
+
const el = scene.elements.get(brushId);
|
|
165
|
+
if (el === undefined || !isBrush(el))
|
|
166
|
+
continue;
|
|
167
|
+
let n = 0;
|
|
168
|
+
const parts = buildFragmentsFromCoverage(el, covered, () => castElementId(`stroke-erase-preview-${el.id}-${n++}`));
|
|
169
|
+
hidden.add(el.id);
|
|
170
|
+
fragments.push(...parts);
|
|
171
|
+
}
|
|
172
|
+
if (hidden.size === 0)
|
|
173
|
+
return null;
|
|
174
|
+
return { fragments, hidden };
|
|
175
|
+
};
|
|
176
|
+
//# sourceMappingURL=stroke-eraser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stroke-eraser.js","sourceRoot":"","sources":["../../../src/editor/public/stroke-eraser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type Vec2 } from "@oh-just-another/types";
|
|
2
|
+
/** Any point that carries at least a world position. */
|
|
3
|
+
export interface SmoothablePoint {
|
|
4
|
+
readonly x: number;
|
|
5
|
+
readonly y: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Resample a sparse polyline into a smooth, dense point list by fitting a
|
|
9
|
+
* Catmull-Rom spline through the captured points (reusing `catmullRomBeziers`
|
|
10
|
+
* from `@oh-just-another/scene` — one spline implementation for the whole repo)
|
|
11
|
+
* and sampling `perSegment` sub-points per span.
|
|
12
|
+
*
|
|
13
|
+
* Freehand input (laser trail, brush stroke) is captured sparsely — one point
|
|
14
|
+
* per pointer-move — so the raw polyline is angular. The `blend` callback
|
|
15
|
+
* rebuilds each resampled point from its span endpoints and the interpolation
|
|
16
|
+
* parameter `u`, so per-point payload beyond position (timestamp for the laser,
|
|
17
|
+
* width for the brush) is carried across smoothly. Fewer than three points
|
|
18
|
+
* can't form a curve, so they pass through unchanged.
|
|
19
|
+
*/
|
|
20
|
+
export declare const smoothStrokePoints: <P extends SmoothablePoint>(points: readonly P[], perSegment: number, blend: (a: P, b: P, pos: Vec2, u: number) => P) => P[];
|
|
21
|
+
//# sourceMappingURL=stroke-smoothing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stroke-smoothing.d.ts","sourceRoot":"","sources":["../../../src/editor/public/stroke-smoothing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAIxD,wDAAwD;AACxD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,GAAI,CAAC,SAAS,eAAe,EAC1D,QAAQ,SAAS,CAAC,EAAE,EACpB,YAAY,MAAM,EAClB,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC,KAC7C,CAAC,EAkBH,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { req } from "@oh-just-another/types";
|
|
2
|
+
import { bezier } from "@oh-just-another/math";
|
|
3
|
+
import { catmullRomBeziers } from "@oh-just-another/scene";
|
|
4
|
+
/**
|
|
5
|
+
* Resample a sparse polyline into a smooth, dense point list by fitting a
|
|
6
|
+
* Catmull-Rom spline through the captured points (reusing `catmullRomBeziers`
|
|
7
|
+
* from `@oh-just-another/scene` — one spline implementation for the whole repo)
|
|
8
|
+
* and sampling `perSegment` sub-points per span.
|
|
9
|
+
*
|
|
10
|
+
* Freehand input (laser trail, brush stroke) is captured sparsely — one point
|
|
11
|
+
* per pointer-move — so the raw polyline is angular. The `blend` callback
|
|
12
|
+
* rebuilds each resampled point from its span endpoints and the interpolation
|
|
13
|
+
* parameter `u`, so per-point payload beyond position (timestamp for the laser,
|
|
14
|
+
* width for the brush) is carried across smoothly. Fewer than three points
|
|
15
|
+
* can't form a curve, so they pass through unchanged.
|
|
16
|
+
*/
|
|
17
|
+
export const smoothStrokePoints = (points, perSegment, blend) => {
|
|
18
|
+
if (points.length < 3)
|
|
19
|
+
return points;
|
|
20
|
+
const segments = catmullRomBeziers(points.map((p) => ({ x: p.x, y: p.y })));
|
|
21
|
+
const first = req(points[0]);
|
|
22
|
+
const out = [first];
|
|
23
|
+
let prev = { x: first.x, y: first.y };
|
|
24
|
+
for (let i = 0; i < segments.length; i++) {
|
|
25
|
+
const seg = req(segments[i]);
|
|
26
|
+
const a = req(points[i]);
|
|
27
|
+
const b = req(points[i + 1]);
|
|
28
|
+
for (let j = 1; j <= perSegment; j++) {
|
|
29
|
+
const u = j / perSegment;
|
|
30
|
+
const p = bezier.cubicAt(prev, seg.c1, seg.c2, seg.to, u);
|
|
31
|
+
out.push(blend(a, b, p, u));
|
|
32
|
+
}
|
|
33
|
+
prev = seg.to;
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=stroke-smoothing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stroke-smoothing.js","sourceRoot":"","sources":["../../../src/editor/public/stroke-smoothing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAa,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAQ3D;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAChC,MAAoB,EACpB,UAAkB,EAClB,KAA8C,EACzC,EAAE;IACP,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,MAAa,CAAC;IAC5C,MAAM,QAAQ,GAAG,iBAAiB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5E,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7B,MAAM,GAAG,GAAQ,CAAC,KAAK,CAAC,CAAC;IACzB,IAAI,IAAI,GAAS,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;IAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7B,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QACzB,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC;YACrC,MAAM,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC;YACzB,MAAM,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;YAC1D,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC9B,CAAC;QACD,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;IAChB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC,CAAC"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { type Element, type ImageCrop, type Link, type Patch, type Scene } from "@oh-just-another/scene";
|
|
2
|
+
import type { Bounds, Color, ElementId, LinkId, Vec2 } from "@oh-just-another/types";
|
|
3
|
+
/**
|
|
4
|
+
* The style colour of the top-most shape under `point`, or `null` when the
|
|
5
|
+
* point is empty. `role` chooses which channel to sample: `"fill"` (default)
|
|
6
|
+
* falls back to the stroke when there is no fill, and `"stroke"` the other way
|
|
7
|
+
* round — so clicking a stroke-only outline still yields a usable colour.
|
|
8
|
+
*/
|
|
9
|
+
export declare const pickColorAt: (scene: Scene, point: Vec2, role?: "fill" | "stroke") => Color | null;
|
|
10
|
+
/** Target types accepted by {@link computeConvertType}. `"polygon"` = diamond. */
|
|
11
|
+
export type ConvertTarget = "rectangle" | "ellipse" | "polygon";
|
|
12
|
+
/**
|
|
13
|
+
* Convert every convertible shape in `ids` to `target`, preserving
|
|
14
|
+
* position / rotation / scale / style and the on-canvas footprint. rectangle
|
|
15
|
+
* and ellipse carry `width` × `height`; `"polygon"` renders a diamond
|
|
16
|
+
* inscribed in that box. Shapes already of the target type, and non-convertible
|
|
17
|
+
* types (text, image, …), are skipped. One undo step (batched for 2+). Returns
|
|
18
|
+
* `null` when nothing changed.
|
|
19
|
+
*/
|
|
20
|
+
export declare const computeConvertType: (scene: Scene, ids: Iterable<ElementId>, target: ConvertTarget) => {
|
|
21
|
+
readonly scene: Scene;
|
|
22
|
+
readonly patch: Patch;
|
|
23
|
+
} | null;
|
|
24
|
+
/** Clamp a crop rect into the valid `[0,1]` normalised range (non-empty). */
|
|
25
|
+
export declare const clampCrop: (crop: ImageCrop) => ImageCrop;
|
|
26
|
+
/**
|
|
27
|
+
* Set (or clear, with `null`) the normalised crop of the image `id`. No-op
|
|
28
|
+
* (returns `null`) when the shape is missing or not an image, or when the
|
|
29
|
+
* crop is unchanged. Setting a full-image crop (`0,0,1,1`) clears the field.
|
|
30
|
+
*/
|
|
31
|
+
export declare const computeSetImageCrop: (scene: Scene, id: ElementId, crop: ImageCrop | null) => {
|
|
32
|
+
readonly scene: Scene;
|
|
33
|
+
readonly patch: Patch;
|
|
34
|
+
} | null;
|
|
35
|
+
/**
|
|
36
|
+
* Normalised crop rect covering the whole image. Equivalent to no crop.
|
|
37
|
+
*/
|
|
38
|
+
export declare const FULL_CROP: ImageCrop;
|
|
39
|
+
/** The 8 crop-frame handles: 4 corners + 4 edge midpoints. */
|
|
40
|
+
export type CropHandle = "nw" | "n" | "ne" | "e" | "se" | "s" | "sw" | "w";
|
|
41
|
+
/** Ordered so corner handles win a tie against the adjacent edge midpoints. */
|
|
42
|
+
export declare const CROP_HANDLES: readonly CropHandle[];
|
|
43
|
+
/**
|
|
44
|
+
* Local-space rect the WHOLE (uncropped) bitmap would occupy, aligned so its
|
|
45
|
+
* current crop-window equals the element's box `{0,0,width,height}`. The
|
|
46
|
+
* element currently shows the sub-rectangle `crop` of this virtual full image.
|
|
47
|
+
* Used to draw the crop ghost and to clamp handle drags to the image extent.
|
|
48
|
+
*/
|
|
49
|
+
export declare const cropFullImageLocalRect: (el: Element, crop: ImageCrop) => Bounds;
|
|
50
|
+
/**
|
|
51
|
+
* World-space positions of the 8 crop handles for the element's current box
|
|
52
|
+
* `{0,0,width,height}` (the visible crop window). Honours rotation / scale via
|
|
53
|
+
* `localToWorld`. The window always equals the element bounds, so the crop
|
|
54
|
+
* fraction is not needed here — pass the (pending) element whose `width` /
|
|
55
|
+
* `height` / `position` describe the window.
|
|
56
|
+
*/
|
|
57
|
+
export declare const cropHandleWorldPoints: (el: Element) => Record<CropHandle, Vec2>;
|
|
58
|
+
/**
|
|
59
|
+
* Result of a crop handle / body drag: the pending normalised `crop` plus the
|
|
60
|
+
* element's new world `position` and local `width` × `height`. Rotation / scale
|
|
61
|
+
* are unchanged, so the caller keeps them.
|
|
62
|
+
*/
|
|
63
|
+
export interface CropDragResult {
|
|
64
|
+
readonly crop: ImageCrop;
|
|
65
|
+
readonly position: Vec2;
|
|
66
|
+
readonly width: number;
|
|
67
|
+
readonly height: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Drag one crop handle to `worldPoint`. Moves the window edge(s) that handle
|
|
71
|
+
* controls (the opposite edge stays fixed), clamped so the window stays inside
|
|
72
|
+
* the virtual full image and never shrinks below {@link CROP_MIN_SIZE}. The
|
|
73
|
+
* source is never stretched: shrinking the window hides pixels rather than
|
|
74
|
+
* scaling them. `el` must be the ORIGINAL (unmutated) element so the reference
|
|
75
|
+
* frame stays stable across a multi-move drag.
|
|
76
|
+
*/
|
|
77
|
+
export declare const computeCropHandleDrag: (el: Element, crop: ImageCrop, handle: CropHandle, worldPoint: Vec2) => CropDragResult;
|
|
78
|
+
/**
|
|
79
|
+
* Pan the source under a FIXED window: the element's box (position / size) is
|
|
80
|
+
* unchanged; only which region of the bitmap shows moves. `worldDelta` from
|
|
81
|
+
* `dragStartWorld` → `worldPoint` is projected into local space and converted
|
|
82
|
+
* to a crop shift, clamped so the window stays inside the image. `el` must be
|
|
83
|
+
* the ORIGINAL element and `crop` its ORIGINAL crop (drag-start snapshot).
|
|
84
|
+
*/
|
|
85
|
+
export declare const computeCropBodyPan: (el: Element, crop: ImageCrop, dragStartWorld: Vec2, worldPoint: Vec2) => {
|
|
86
|
+
readonly crop: ImageCrop;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Commit a pending crop drag: write the normalised `crop` AND the new element
|
|
90
|
+
* `position` / `width` / `height` in a single patch (one undo step). A crop
|
|
91
|
+
* that covers the whole image is stored as no crop (field cleared). No-op
|
|
92
|
+
* (`null`) for a missing / non-image shape or when nothing changed.
|
|
93
|
+
*/
|
|
94
|
+
export declare const computeCommitImageCrop: (scene: Scene, id: ElementId, next: CropDragResult) => {
|
|
95
|
+
readonly scene: Scene;
|
|
96
|
+
readonly patch: Patch;
|
|
97
|
+
} | null;
|
|
98
|
+
export type SpawnDirection = "left" | "right" | "up" | "down";
|
|
99
|
+
/**
|
|
100
|
+
* Create a clone of `sourceId` offset by {@link SPAWN_CONNECTED_GAP_PX} in
|
|
101
|
+
* `direction`, plus a link from the source to the new node. The clone keeps the
|
|
102
|
+
* source's type, size, style and rotation. Returns the next scene, both patches
|
|
103
|
+
* (element add + link add), and the new ids; `null` when the source is missing.
|
|
104
|
+
*/
|
|
105
|
+
export declare const computeSpawnConnectedNode: (scene: Scene, sourceId: ElementId, direction: SpawnDirection, newElementId: ElementId, newLinkId: LinkId) => {
|
|
106
|
+
readonly scene: Scene;
|
|
107
|
+
readonly patches: readonly Patch[];
|
|
108
|
+
readonly newElementId: ElementId;
|
|
109
|
+
readonly linkId: LinkId;
|
|
110
|
+
} | null;
|
|
111
|
+
/**
|
|
112
|
+
* Pure geometry for a flowchart CREATE session: `count` clones of `sourceId`,
|
|
113
|
+
* each linked source→clone (floating). Does NOT mutate `scene` or history —
|
|
114
|
+
* returns the pending `elements` + `links` for a PREVIEW; the caller commits
|
|
115
|
+
* them later. Placement matches Excalidraw's `addNewNodes`:
|
|
116
|
+
*
|
|
117
|
+
* - `left` / `right` — the `count` siblings stack vertically, centred on the
|
|
118
|
+
* source (step = source height + {@link SPAWN_CONNECTED_GAP_PX}), all offset
|
|
119
|
+
* by one source-width + gap along the axis.
|
|
120
|
+
* - `up` / `down` — siblings spread horizontally, centred on the source
|
|
121
|
+
* (step = source width + gap), all offset by one source-height + gap.
|
|
122
|
+
*
|
|
123
|
+
* `count === 1` reproduces {@link computeSpawnConnectedNode}'s single placement
|
|
124
|
+
* (zero perpendicular offset). Returns empty arrays when the source is missing.
|
|
125
|
+
*/
|
|
126
|
+
export declare const computeSpawnConnectedNodes: (scene: Scene, sourceId: ElementId, direction: SpawnDirection, count: number, makeElementId: () => ElementId, makeLinkId: () => LinkId) => {
|
|
127
|
+
readonly elements: Element[];
|
|
128
|
+
readonly links: Link[];
|
|
129
|
+
};
|
|
130
|
+
//# sourceMappingURL=tool-ops.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-ops.d.ts","sourceRoot":"","sources":["../../../src/editor/public/tool-ops.ts"],"names":[],"mappings":"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"}
|