@neta-art/cohub 12.1.1 → 12.3.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/README.md +85 -8
- package/dist/board/editor/clipboard.js +1 -1
- package/dist/board/editor/creation.js +1 -1
- package/dist/board/editor/document.d.ts +3 -1
- package/dist/board/editor/document.js +8 -6
- package/dist/board/editor/editor.d.ts +8 -3
- package/dist/board/editor/editor.js +8 -2
- package/dist/board/editor/index.d.ts +3 -3
- package/dist/board/editor/index.js +2 -2
- package/dist/board/editor/interaction.d.ts +23 -2
- package/dist/board/editor/interaction.js +93 -4
- package/dist/board/editor/query.d.ts +1 -1
- package/dist/board/editor/query.js +3 -7
- package/dist/board/editor/selection.js +1 -1
- package/dist/board/editor/snapshots.js +5 -57
- package/dist/board/editor/tool.d.ts +4 -1
- package/dist/board/editor/tool.js +6 -1
- package/dist/board/export/index.d.ts +2 -2
- package/dist/board/export/index.js +1 -1
- package/dist/board/export/node/fonts.js +4 -0
- package/dist/board/export/node/index.d.ts +2 -0
- package/dist/board/export/node/index.js +8 -1
- package/dist/board/export/node/scripts.js +1 -1
- package/dist/board/export/scene.d.ts +2 -2
- package/dist/board/export/scene.js +2 -2
- package/dist/board/model/asset-key.d.ts +24 -0
- package/dist/board/model/asset-key.js +104 -0
- package/dist/board/model/export-assets.d.ts +7 -3
- package/dist/board/model/export-assets.js +34 -13
- package/dist/board/model/index.d.ts +9 -7
- package/dist/board/model/index.js +22 -20
- package/dist/board/model/items/create.js +3 -3
- package/dist/board/model/items/definition.d.ts +9 -1
- package/dist/board/model/items/index.d.ts +2 -2
- package/dist/board/model/items/registry.d.ts +2 -1
- package/dist/board/model/items/registry.js +1 -0
- package/dist/board/model/media-sizes.d.ts +28 -0
- package/dist/board/model/media-sizes.js +64 -0
- package/dist/board/model/palette.d.ts +1 -0
- package/dist/board/model/palette.js +20 -130
- package/dist/board/model/sampling.js +1 -1
- package/dist/board/model/text-metrics.d.ts +28 -3
- package/dist/board/model/text-metrics.js +36 -2
- package/dist/board/model/themes.d.ts +495 -0
- package/dist/board/model/themes.js +500 -0
- package/dist/board/render/card-tag.js +2 -2
- package/dist/board/render/index.d.ts +11 -7
- package/dist/board/render/index.js +9 -5
- package/dist/board/render/labels.d.ts +15 -0
- package/dist/board/render/labels.js +14 -0
- package/dist/board/render/lod.d.ts +30 -0
- package/dist/board/render/lod.js +33 -0
- package/dist/board/render/media-interaction.d.ts +5 -2
- package/dist/board/render/media-interaction.js +15 -3
- package/dist/board/render/palette.js +2 -25
- package/dist/board/render/renderers/arrow-card-renderer.js +7 -5
- package/dist/board/render/renderers/audio-card-renderer.js +3 -2
- package/dist/board/render/renderers/board-renderer-registry.d.ts +22 -1
- package/dist/board/render/renderers/board-renderer-registry.js +9 -4
- package/dist/board/render/renderers/custom-card-renderer.js +5 -5
- package/dist/board/render/renderers/draw-card-renderer.js +1 -1
- package/dist/board/render/renderers/far-plate.d.ts +14 -1
- package/dist/board/render/renderers/far-plate.js +31 -8
- package/dist/board/render/renderers/file-card-renderer.js +49 -31
- package/dist/board/render/renderers/frame-card-renderer.js +9 -3
- package/dist/board/render/renderers/image-card-renderer.js +34 -8
- package/dist/board/render/renderers/model-card-renderer.js +6 -6
- package/dist/board/render/renderers/shape-card-renderer.js +25 -24
- package/dist/board/render/renderers/task-card-renderer.js +9 -10
- package/dist/board/render/renderers/text-card-renderer.js +86 -41
- package/dist/board/render/renderers/video-card-renderer.js +5 -4
- package/dist/board/render/script-export.js +1 -1
- package/dist/board/render/script-host.js +3 -3
- package/dist/board/render/style.d.ts +8 -2
- package/dist/board/render/style.js +8 -2
- package/dist/board/render/surface.d.ts +5 -0
- package/dist/board/render/surface.js +13 -3
- package/dist/board/render/text-edit.d.ts +32 -0
- package/dist/board/render/text-edit.js +134 -0
- package/dist/board/render/text-fit.d.ts +1 -3
- package/dist/board/render/text-fit.js +6 -49
- package/dist/board/render/text-measurement.d.ts +25 -1
- package/dist/board/render/text-measurement.js +157 -19
- package/dist/board/render/text-resolution.d.ts +1 -6
- package/dist/board/render/text-resolution.js +1 -10
- package/dist/board/render/theme.d.ts +15 -0
- package/dist/board/render/theme.js +15 -0
- package/dist/board/replica/index.d.ts +4 -2
- package/dist/board/replica/index.js +28 -17
- package/dist/board/replica/indexeddb.d.ts +16 -0
- package/dist/board/replica/indexeddb.js +155 -0
- package/dist/board/stage/assets.d.ts +28 -5
- package/dist/board/stage/assets.js +293 -102
- package/dist/board/stage/bench.d.ts +35 -0
- package/dist/board/stage/bench.js +153 -0
- package/dist/board/stage/device.d.ts +41 -0
- package/dist/board/stage/device.js +62 -0
- package/dist/board/stage/frame-budget.d.ts +19 -0
- package/dist/board/stage/frame-budget.js +69 -0
- package/dist/board/stage/idle.d.ts +4 -0
- package/dist/board/stage/idle.js +26 -0
- package/dist/board/stage/index.d.ts +11 -7
- package/dist/board/stage/index.js +7 -4
- package/dist/board/stage/lru.d.ts +13 -9
- package/dist/board/stage/lru.js +38 -20
- package/dist/board/stage/presses.d.ts +21 -0
- package/dist/board/stage/presses.js +66 -0
- package/dist/board/stage/scene.d.ts +15 -1
- package/dist/board/stage/scene.js +248 -141
- package/dist/board/stage/stage.d.ts +28 -2
- package/dist/board/stage/stage.js +404 -112
- package/dist/board/stage/texture-pool.d.ts +10 -1
- package/dist/board/stage/texture-pool.js +72 -2
- package/dist/chunks/environment.d.ts +6 -0
- package/dist/chunks/http.d.ts +9 -1
- package/dist/chunks/http.js +791 -4
- package/dist/index.d.ts +52 -1
- package/dist/index.js +202 -862
- package/dist/media.d.ts +3 -27
- package/dist/media.js +3 -82
- package/dist/protocol/dist/board-constants.js +3 -3
- package/dist/protocol/dist/board-layout.d.ts +17 -3
- package/dist/protocol/dist/board-layout.js +68 -11
- package/dist/protocol/dist/board-model.d.ts +15 -1
- package/dist/protocol/dist/board-model.js +12 -2
- package/dist/protocol/dist/board-patch.js +1 -1
- package/dist/protocol/dist/board-schema.d.ts +2 -1
- package/dist/protocol/dist/board-schema.js +4 -1
- package/dist/protocol/dist/core/media.d.ts +35 -1
- package/dist/protocol/dist/core/media.js +127 -1
- package/dist/protocol/dist/index.d.ts +3 -3
- package/package.json +4 -2
- package/dist/board/model/image-key.d.ts +0 -9
- package/dist/board/model/image-key.js +0 -33
package/README.md
CHANGED
|
@@ -112,7 +112,7 @@ const created = await space.boards.create({
|
|
|
112
112
|
},
|
|
113
113
|
});
|
|
114
114
|
|
|
115
|
-
const board = space.board(created.id);
|
|
115
|
+
const board = space.board(created.id); // or await space.boards.open("boards/plan.board")
|
|
116
116
|
const { items, version } = await board.get({ only: ["items"] });
|
|
117
117
|
await board.apply({ items: { goal: { props: { text: "Shipped" } } } }, { baseVersion: version });
|
|
118
118
|
|
|
@@ -159,7 +159,7 @@ change, so they work directly with `useSyncExternalStore`:
|
|
|
159
159
|
```ts
|
|
160
160
|
import { createBoardRegistry, defineBoardItem, worldPoint } from "@neta-art/cohub/board";
|
|
161
161
|
import { createBoardEditor } from "@neta-art/cohub/board/editor";
|
|
162
|
-
import { createBoardReplica } from "@neta-art/cohub/board/replica";
|
|
162
|
+
import { createBoardReplica, createIndexedDbBoardReplicaStorage } from "@neta-art/cohub/board/replica";
|
|
163
163
|
import { createBoardAssetManager, mountBoardStage } from "@neta-art/cohub/board/stage";
|
|
164
164
|
import { z } from "zod";
|
|
165
165
|
|
|
@@ -172,7 +172,13 @@ const order = defineBoardItem({
|
|
|
172
172
|
capabilities: { canRotate: false },
|
|
173
173
|
});
|
|
174
174
|
|
|
175
|
-
|
|
175
|
+
// Cached documents and unsent writes survive a reload; clear them on sign-out
|
|
176
|
+
// with `clearBoardReplicaStorage({ userKey })`. The replica reads the Board again
|
|
177
|
+
// after a realtime reconnect and resends pending writes when the network returns.
|
|
178
|
+
const replica = createBoardReplica({
|
|
179
|
+
remote: space.board(boardId),
|
|
180
|
+
storage: createIndexedDbBoardReplicaStorage({ userKey, spaceId: space.id, boardId }),
|
|
181
|
+
});
|
|
176
182
|
await replica.start();
|
|
177
183
|
|
|
178
184
|
const editor = createBoardEditor({
|
|
@@ -230,6 +236,74 @@ rewritten by older ones. For a canvas look instead of DOM, pass renderers to
|
|
|
230
236
|
`createBoardCardRendererResolver` from `board/render` and hand the result to
|
|
231
237
|
the stage or to `renderBoardExport` as `renderers`.
|
|
232
238
|
|
|
239
|
+
### Hosting a Board
|
|
240
|
+
|
|
241
|
+
Compose the SDK instead of wrapping it, and each release's loading, level of
|
|
242
|
+
detail and text work reaches your Board without code changes.
|
|
243
|
+
|
|
244
|
+
Pictures your own items show go through `itemAsset`. They share the built-in
|
|
245
|
+
loader: CDN variants sized to the screen, loads ordered by where the view is
|
|
246
|
+
heading, prefetch, compact copies when panned away, device budgets, one cache
|
|
247
|
+
and exports. Call `invalidateItemAssets()` when the data it reads changes:
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
const assets = createBoardAssetManager({
|
|
251
|
+
resolveFileUrl: source.resolveFileUrl,
|
|
252
|
+
// undefined keeps the built-in rule; null shows no picture.
|
|
253
|
+
itemAsset: (item) => {
|
|
254
|
+
if (item.type !== "acme.order") return undefined;
|
|
255
|
+
const cover = covers.get(item.props.orderId as string);
|
|
256
|
+
return cover ? { url: cover, fit: "cover" } : null;
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
- **Budgets.** Leave `lod`, `prefetch`, `concurrency` and `lruBudget` unset; they
|
|
262
|
+
follow `defaultBoardDeviceBudget()`. Force a tier with
|
|
263
|
+
`boardDeviceBudget(readBoardDeviceSignals(), "low")`.
|
|
264
|
+
- **Canvas cards.** Declare `lod` (`{ kind: "media" }` for pictures) and
|
|
265
|
+
`renderFar`, drawn with `drawFarPlate(graphics, item.frame, { fill:
|
|
266
|
+
farPictureTone(item, context) ?? paper })`. Wrap card text with
|
|
267
|
+
`layoutTextLines(text, font, width, { whiteSpace: "normal", maxLines: 2 })`
|
|
268
|
+
and apply `context.textResolution` in the `resolution` hook.
|
|
269
|
+
- **Opening cards.** Give a type `activation: "tap"` and handle `onActivate`: a
|
|
270
|
+
quick press opens the card without selecting it, a drag moves it (or pans with
|
|
271
|
+
the hand tool or read-only), and hovering shows a pointer instead of ports. A
|
|
272
|
+
press held in place past `BOARD_TAP_MAX_MS` does nothing: it was a pick-up. A
|
|
273
|
+
press on a selected card reports `selected: true`. `onLongPress` fires for a still
|
|
274
|
+
touch or pen; call `editor.pickUp()` there to pick the card up and drag it. The
|
|
275
|
+
stage drops the click a touch screen sends after a tap that opened something.
|
|
276
|
+
- **Card keys.** A renderer's `hitAt(item, point, context)` returns `{ action, cursor? }`
|
|
277
|
+
for keys it draws; the stage shows the cursor, passes the hovered key to renderers as
|
|
278
|
+
`context.hoveredHit` and sends presses to `onHit` (return `false` to let the editor
|
|
279
|
+
have them). Media cards report `BOARD_PLAY_MEDIA` this way.
|
|
280
|
+
- **Previews.** Write `preview: { title, excerpt, cover, src }` on your items so
|
|
281
|
+
clients without your renderer, exports and agents show them as cards. Keep it out
|
|
282
|
+
of undo with `editor.commitPatch(patch, { record: false })`.
|
|
283
|
+
- **Text editing.** Place the in-place editor with `boardTextEditLayout`, so its
|
|
284
|
+
caret sits on the canvas text:
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
const layout = boardTextEditLayout(item, { viewport: editor.camera, scene: editor.scene, style: stage.theme.style, draft });
|
|
288
|
+
// A content-box textarea at layout.left/top, sized layout.width × height, padded by
|
|
289
|
+
// layout.padding, with `rotate(${layout.rotation}deg) scale(${layout.scale})` about its center.
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
- **Themes.** Start from a built-in theme and override what differs:
|
|
293
|
+
`{ key: "neta-pixel", ...boardRenderTheme("neta-pixel") }`. `style.frameLabelSize`
|
|
294
|
+
sets frame titles; the editor's title hit box and text editor follow it.
|
|
295
|
+
- **Fonts.** Board text measures in `"Geist Variable"`; load
|
|
296
|
+
`@fontsource-variable/geist` so lines match the CLI export.
|
|
297
|
+
- **Agent context.** `onViewStateChange` on the editor reports `visibleRect` and
|
|
298
|
+
`selectedNodes`; send them with a prompt as
|
|
299
|
+
`buildViewportContentBlock([{ kind: "board", path, boardId, visibleRect, selectedNodes }])`
|
|
300
|
+
so the agent knows which Board, where and what the user is looking at.
|
|
301
|
+
- **Measuring.** `runBoardStageBench(stage)` drives the stage with wheel input and
|
|
302
|
+
reports frame and settle times; `stage.stats()` shows live, far and texture counts.
|
|
303
|
+
|
|
304
|
+
Release notes for Board changes end with `Hosts:`, naming what an embedding app
|
|
305
|
+
must change, or `Hosts: no action`.
|
|
306
|
+
|
|
233
307
|
### Images
|
|
234
308
|
|
|
235
309
|
In the browser, `renderBoardExport(renderer, document, options)` from
|
|
@@ -242,11 +316,14 @@ const renderer = await createNodeBoardRenderer();
|
|
|
242
316
|
const result = exportBoardImageBytes(renderer, document, { format: "png", scale: 2 });
|
|
243
317
|
```
|
|
244
318
|
|
|
245
|
-
Text
|
|
246
|
-
and
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
`
|
|
319
|
+
Text layout follows the same split. The renderers measure through a real canvas
|
|
320
|
+
and break lines where the browser breaks them in a `<textarea>`
|
|
321
|
+
(`white-space: pre-wrap; overflow-wrap: break-word`), so canvas, editor and
|
|
322
|
+
selection agree. Called straight off `@neta-art/cohub/board` with no renderer in
|
|
323
|
+
play, `layoutBoardText` estimates widths per character — fine for laying out a
|
|
324
|
+
board on a server; call `installBoardTextMeasurement` from `board/render` first
|
|
325
|
+
if the lines have to match what the editor draws. In a browser, call it again
|
|
326
|
+
when a web font Board text uses finishes loading (the stage does this itself).
|
|
250
327
|
|
|
251
328
|
## Session subscriptions
|
|
252
329
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseBoardItem } from "../../protocol/dist/board-model.js";
|
|
2
|
-
import { arrowBindings, isArrowBinding } from "../../protocol/dist/board-layout.js";
|
|
3
2
|
import { BOARD_CLIPBOARD_KIND, BOARD_CLIPBOARD_MIME } from "../../protocol/dist/board.js";
|
|
3
|
+
import { arrowBindings, isArrowBinding } from "../../protocol/dist/board-layout.js";
|
|
4
4
|
import { createBoardItemId } from "../model/items/id.js";
|
|
5
5
|
//#region src/board/editor/clipboard.ts
|
|
6
6
|
/** How far a duplicate or repeated paste lands from its source. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IDENTITY_MATRIX, applyMatrix, invertMatrix } from "../../protocol/dist/board-layout.js";
|
|
2
|
-
import { rectCenter } from "../model/geometry.js";
|
|
3
2
|
import { buildBoardScene } from "../model/scene.js";
|
|
3
|
+
import { rectCenter } from "../model/geometry.js";
|
|
4
4
|
import { isShapeKind } from "../model/shape-types.js";
|
|
5
5
|
import { createBoardItemId } from "../model/items/id.js";
|
|
6
6
|
import { createAppBoardItem, createArrowBoardItem, createDrawBoardItem, createFileNodeForPath, createFrameBoardItem, createMediaBoardItem, createShapeBoardItem, createTaskBoardItem, createTextBoardItem } from "../model/items/create.js";
|
|
@@ -15,7 +15,9 @@ declare function createDocumentModule(ctx: EditorContext): {
|
|
|
15
15
|
readonly hasScriptItems: boolean;
|
|
16
16
|
setScriptItems: (items: ReadonlyMap<string, BoardItem>) => void;
|
|
17
17
|
animationItems: () => Map<string, BoardItem>;
|
|
18
|
-
commitPatch: (patch: BoardPatch
|
|
18
|
+
commitPatch: (patch: BoardPatch, { record }?: {
|
|
19
|
+
record?: boolean;
|
|
20
|
+
}) => Promise<void> | undefined;
|
|
19
21
|
rescene: (ids?: Iterable<string>) => void;
|
|
20
22
|
reset: (next: BoardDocument) => void;
|
|
21
23
|
setDraftItem: (id: string, item: BoardItem | null) => void;
|
|
@@ -206,7 +206,7 @@ function createDocumentModule(ctx) {
|
|
|
206
206
|
for (const [id, item] of changes) setDraftItem(id, item);
|
|
207
207
|
commit({}, record);
|
|
208
208
|
}
|
|
209
|
-
function commitPatch(patch) {
|
|
209
|
+
function commitPatch(patch, { record = true } = {}) {
|
|
210
210
|
if (options.readonly) throw new Error("Board is read-only.");
|
|
211
211
|
const before = state.base;
|
|
212
212
|
const result = applyBoardPatchToDocument(before, patch);
|
|
@@ -214,11 +214,13 @@ function createDocumentModule(ctx) {
|
|
|
214
214
|
const ids = Object.keys(patch.items ?? {});
|
|
215
215
|
const redo = diffBoardEdits(before, result.document, ids);
|
|
216
216
|
if (isEmptyBoardPatch(redo)) return;
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
217
|
+
if (record) {
|
|
218
|
+
state.undoStack = [...state.undoStack.slice(-199), {
|
|
219
|
+
undo: diffBoardEdits(result.document, before, ids),
|
|
220
|
+
redo
|
|
221
|
+
}];
|
|
222
|
+
state.redoStack = [];
|
|
223
|
+
}
|
|
222
224
|
adopt(result.document, ids);
|
|
223
225
|
return send(redo).then((error) => {
|
|
224
226
|
if (error) throw error;
|
|
@@ -11,7 +11,7 @@ import "../model/index.js";
|
|
|
11
11
|
import { AlignMode, DistributeAxis } from "./align.js";
|
|
12
12
|
import { BoardFocusOptions } from "./camera.js";
|
|
13
13
|
import { BoardSelectionTransform, BoardTransformControl } from "./selection-transform.js";
|
|
14
|
-
import { BoardInteraction, BoardPointerEvent, BoardTransformEnd } from "./interaction.js";
|
|
14
|
+
import { BoardActivation, BoardInteraction, BoardPointerEvent, BoardTransformEnd } from "./interaction.js";
|
|
15
15
|
import { BoardAppMetadata, BoardTaskSource } from "./creation.js";
|
|
16
16
|
import { BoardClipboardPayload } from "./clipboard.js";
|
|
17
17
|
import { BoardNaturalSize } from "./snapshots.js";
|
|
@@ -136,11 +136,13 @@ declare function createBoardEditor(options: BoardEditorOptions): {
|
|
|
136
136
|
setPlayedItems: (items: ReadonlyMap<string, BoardItem>) => void;
|
|
137
137
|
setScriptItems: (items: ReadonlyMap<string, BoardItem>) => void;
|
|
138
138
|
animationItems: () => Map<string, BoardItem>;
|
|
139
|
-
commitPatch: (patch: BoardPatch
|
|
139
|
+
commitPatch: (patch: BoardPatch, { record }?: {
|
|
140
|
+
record?: boolean;
|
|
141
|
+
}) => Promise<void> | undefined;
|
|
140
142
|
consumeRecentlyAdded: () => string[];
|
|
141
143
|
itemAt: (point: WorldPoint, exclude?: ReadonlySet<string> | undefined, includeTemporary?: boolean | undefined) => BoardSceneItem | null;
|
|
142
144
|
itemById: (id: string) => BoardSceneItem | null;
|
|
143
|
-
labelItemAt: (point: WorldPoint) => BoardSceneItem | null;
|
|
145
|
+
labelItemAt: (point: WorldPoint, frameLabelSize?: number | undefined) => BoardSceneItem | null;
|
|
144
146
|
idsInRect: (rect: Rect, includeTemporary?: boolean | undefined) => string[];
|
|
145
147
|
setCamera: (viewport: BoardViewport) => void;
|
|
146
148
|
setCameraPolicy: (policy: BoardCameraPolicy) => void;
|
|
@@ -188,6 +190,7 @@ declare function createBoardEditor(options: BoardEditorOptions): {
|
|
|
188
190
|
commitTextEdit: (id: string, text: string) => void;
|
|
189
191
|
updateText: (id: string, text: string, created?: boolean) => void;
|
|
190
192
|
previewTextLayout: (id: string, text: string) => void;
|
|
193
|
+
relayoutText: () => void;
|
|
191
194
|
deleteSelection: () => void;
|
|
192
195
|
deleteItem: (id: string) => void;
|
|
193
196
|
duplicateSelection: () => void;
|
|
@@ -218,6 +221,8 @@ declare function createBoardEditor(options: BoardEditorOptions): {
|
|
|
218
221
|
pointerUp: (event: BoardPointerEvent) => void;
|
|
219
222
|
pointerLeave: () => void;
|
|
220
223
|
subscribeTransforms: (listener: (event: BoardTransformEnd) => void) => () => void;
|
|
224
|
+
subscribeActivations: (listener: (event: BoardActivation) => void) => () => void;
|
|
225
|
+
pickUp: () => boolean;
|
|
221
226
|
cancelPointerInteraction: () => void;
|
|
222
227
|
destroy(): void;
|
|
223
228
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { clampBoardStrokeSize } from "../../protocol/dist/board-constants.js";
|
|
2
|
-
import { hasBoardScripts } from "../model/script-index.js";
|
|
3
|
-
import { normalizeViewport } from "../model/geometry.js";
|
|
4
2
|
import { buildBoardScene } from "../model/scene.js";
|
|
3
|
+
import { normalizeViewport } from "../model/geometry.js";
|
|
5
4
|
import { isBoardColorId } from "../model/palette.js";
|
|
5
|
+
import { hasBoardScripts } from "../model/script-index.js";
|
|
6
6
|
import { isShapeKind } from "../model/shape-types.js";
|
|
7
7
|
import { createBoardToolStyles } from "../model/tool-styles.js";
|
|
8
8
|
import { titleForBoardItem } from "../model/items/create.js";
|
|
@@ -407,6 +407,10 @@ function createBoardEditor(options) {
|
|
|
407
407
|
commitTextEdit: creation.commitTextEdit,
|
|
408
408
|
updateText: creation.updateText,
|
|
409
409
|
previewTextLayout: creation.previewTextLayout,
|
|
410
|
+
relayoutText: () => {
|
|
411
|
+
const ids = state.scene.items.filter((item) => item.type === "text").map((item) => item.id);
|
|
412
|
+
if (ids.length > 0) document.rescene(ids);
|
|
413
|
+
},
|
|
410
414
|
deleteSelection: deletion.deleteSelection,
|
|
411
415
|
deleteItem: deletion.deleteItem,
|
|
412
416
|
duplicateSelection: copy.duplicateSelection,
|
|
@@ -434,6 +438,8 @@ function createBoardEditor(options) {
|
|
|
434
438
|
pointerUp: interaction.pointerUp,
|
|
435
439
|
pointerLeave: interaction.pointerLeave,
|
|
436
440
|
subscribeTransforms: interaction.subscribeTransforms,
|
|
441
|
+
subscribeActivations: interaction.subscribeActivations,
|
|
442
|
+
pickUp: interaction.pickUp,
|
|
437
443
|
cancelPointerInteraction: interaction.cancel,
|
|
438
444
|
destroy() {
|
|
439
445
|
stopViewState();
|
|
@@ -2,13 +2,13 @@ import { BOARD_CLIPBOARD_MIME } from "../../protocol/dist/board.js";
|
|
|
2
2
|
import { AlignMode, DistributeAxis, alignFrames, distributeFrames, framesBounds } from "./align.js";
|
|
3
3
|
import { BoardFocusOptions } from "./camera.js";
|
|
4
4
|
import { BoardSelectionTransform, BoardTransformControl, resizeCursorForHandle, resolveSelectionTransform, selectionTransformControlAt } from "./selection-transform.js";
|
|
5
|
-
import { BoardInteraction, BoardPointerEvent, BoardTransformEnd } from "./interaction.js";
|
|
5
|
+
import { BoardActivation, BoardInteraction, BoardPointerEvent, BoardTransformEnd } from "./interaction.js";
|
|
6
6
|
import { BoardAppMetadata, BoardTaskSource } from "./creation.js";
|
|
7
7
|
import { BOARD_CLIPBOARD_VERSION, BoardClipboardPayload, DUPLICATE_OFFSET, MAX_CLIPBOARD_CHARS, MAX_CLIPBOARD_ITEMS, defaultPasteOffset, encodeClipboard, materializeClipboard, parseClipboard, remapItems } from "./clipboard.js";
|
|
8
8
|
import { BoardNaturalSize } from "./snapshots.js";
|
|
9
9
|
import { SnapAxis, SnapGuide, SnapOptions, SnapResult, computeSnap } from "./snapping.js";
|
|
10
10
|
import { BoardEditorChannel, BoardEditorListener } from "./state.js";
|
|
11
|
-
import { BOARD_HAND_TAP_SLOP, BoardToolId, canTapSelectWithHand, defaultBoardTool, isContinuousBoardTool, isCreationBoardTool, isWithinHandTapSlop } from "./tool.js";
|
|
11
|
+
import { BOARD_HAND_TAP_SLOP, BOARD_MOUSE_TAP_SLOP, BOARD_TAP_MAX_MS, BoardToolId, boardTapSlop, canTapSelectWithHand, defaultBoardTool, isContinuousBoardTool, isCreationBoardTool, isWithinHandTapSlop } from "./tool.js";
|
|
12
12
|
import { BoardCameraPolicy, BoardEditorOptions, BoardEditorPreferences, BoardPlayhead, BoardViewState } from "./context.js";
|
|
13
13
|
import { CONNECTION_PORT_HIT_RADIUS, CONNECTION_PORT_OFFSET, CONNECTION_PORT_RADIUS, CONNECTION_PORT_TOUCH_HIT_RADIUS, CONNECTION_SIDES, ConnectionPort, connectionPortAt, connectionPorts, portHitRadius } from "./connection-ports.js";
|
|
14
14
|
import { sameJson } from "../model/json.js";
|
|
@@ -16,4 +16,4 @@ import { BoardEditPlayhead, diffBoardEdits, isEmptyBoardPatch, readPath, routeBo
|
|
|
16
16
|
import { BoardEditor, createBoardEditor } from "./editor.js";
|
|
17
17
|
import { SpatialEntry, SpatialIndex, createSpatialIndex } from "./spatial.js";
|
|
18
18
|
import { normalizeWheelDelta, wheelZoomFactor } from "./wheel.js";
|
|
19
|
-
export { AlignMode, BOARD_CLIPBOARD_MIME, BOARD_CLIPBOARD_VERSION, BOARD_HAND_TAP_SLOP, type BoardAppMetadata, type BoardCameraPolicy, BoardClipboardPayload, BoardEditPlayhead, type BoardEditor, type BoardEditorChannel, type BoardEditorListener, type BoardEditorOptions, type BoardEditorPreferences, type BoardFocusOptions, type BoardInteraction, type BoardNaturalSize, type BoardPlayhead, type BoardPointerEvent, BoardSelectionTransform, type BoardTaskSource, BoardToolId, BoardTransformControl, type BoardTransformEnd, type BoardViewState, CONNECTION_PORT_HIT_RADIUS, CONNECTION_PORT_OFFSET, CONNECTION_PORT_RADIUS, CONNECTION_PORT_TOUCH_HIT_RADIUS, CONNECTION_SIDES, ConnectionPort, DUPLICATE_OFFSET, DistributeAxis, MAX_CLIPBOARD_CHARS, MAX_CLIPBOARD_ITEMS, SnapAxis, SnapGuide, SnapOptions, SnapResult, SpatialEntry, SpatialIndex, alignFrames, canTapSelectWithHand, computeSnap, connectionPortAt, connectionPorts, createBoardEditor, createSpatialIndex, defaultBoardTool, defaultPasteOffset, diffBoardEdits, distributeFrames, encodeClipboard, framesBounds, isContinuousBoardTool, isCreationBoardTool, isEmptyBoardPatch, isWithinHandTapSlop, materializeClipboard, normalizeWheelDelta, parseClipboard, portHitRadius, readPath, remapItems, resizeCursorForHandle, resolveSelectionTransform, routeBoardEdits, sameJson, selectionTransformControlAt, wheelZoomFactor, writePath };
|
|
19
|
+
export { AlignMode, BOARD_CLIPBOARD_MIME, BOARD_CLIPBOARD_VERSION, BOARD_HAND_TAP_SLOP, BOARD_MOUSE_TAP_SLOP, BOARD_TAP_MAX_MS, type BoardActivation, type BoardAppMetadata, type BoardCameraPolicy, BoardClipboardPayload, BoardEditPlayhead, type BoardEditor, type BoardEditorChannel, type BoardEditorListener, type BoardEditorOptions, type BoardEditorPreferences, type BoardFocusOptions, type BoardInteraction, type BoardNaturalSize, type BoardPlayhead, type BoardPointerEvent, BoardSelectionTransform, type BoardTaskSource, BoardToolId, BoardTransformControl, type BoardTransformEnd, type BoardViewState, CONNECTION_PORT_HIT_RADIUS, CONNECTION_PORT_OFFSET, CONNECTION_PORT_RADIUS, CONNECTION_PORT_TOUCH_HIT_RADIUS, CONNECTION_SIDES, ConnectionPort, DUPLICATE_OFFSET, DistributeAxis, MAX_CLIPBOARD_CHARS, MAX_CLIPBOARD_ITEMS, SnapAxis, SnapGuide, SnapOptions, SnapResult, SpatialEntry, SpatialIndex, alignFrames, boardTapSlop, canTapSelectWithHand, computeSnap, connectionPortAt, connectionPorts, createBoardEditor, createSpatialIndex, defaultBoardTool, defaultPasteOffset, diffBoardEdits, distributeFrames, encodeClipboard, framesBounds, isContinuousBoardTool, isCreationBoardTool, isEmptyBoardPatch, isWithinHandTapSlop, materializeClipboard, normalizeWheelDelta, parseClipboard, portHitRadius, readPath, remapItems, resizeCursorForHandle, resolveSelectionTransform, routeBoardEdits, sameJson, selectionTransformControlAt, wheelZoomFactor, writePath };
|
|
@@ -5,9 +5,9 @@ import { diffBoardEdits, isEmptyBoardPatch, readPath, routeBoardEdits, writePath
|
|
|
5
5
|
import { normalizeWheelDelta, wheelZoomFactor } from "./wheel.js";
|
|
6
6
|
import { BOARD_CLIPBOARD_VERSION, DUPLICATE_OFFSET, MAX_CLIPBOARD_CHARS, MAX_CLIPBOARD_ITEMS, defaultPasteOffset, encodeClipboard, materializeClipboard, parseClipboard, remapItems } from "./clipboard.js";
|
|
7
7
|
import { CONNECTION_PORT_HIT_RADIUS, CONNECTION_PORT_OFFSET, CONNECTION_PORT_RADIUS, CONNECTION_PORT_TOUCH_HIT_RADIUS, CONNECTION_SIDES, connectionPortAt, connectionPorts, portHitRadius } from "./connection-ports.js";
|
|
8
|
-
import { BOARD_HAND_TAP_SLOP, canTapSelectWithHand, defaultBoardTool, isContinuousBoardTool, isCreationBoardTool, isWithinHandTapSlop } from "./tool.js";
|
|
8
|
+
import { BOARD_HAND_TAP_SLOP, BOARD_MOUSE_TAP_SLOP, BOARD_TAP_MAX_MS, boardTapSlop, canTapSelectWithHand, defaultBoardTool, isContinuousBoardTool, isCreationBoardTool, isWithinHandTapSlop } from "./tool.js";
|
|
9
9
|
import { resizeCursorForHandle, resolveSelectionTransform, selectionTransformControlAt } from "./selection-transform.js";
|
|
10
10
|
import { computeSnap } from "./snapping.js";
|
|
11
11
|
import { createSpatialIndex } from "./spatial.js";
|
|
12
12
|
import { createBoardEditor } from "./editor.js";
|
|
13
|
-
export { BOARD_CLIPBOARD_MIME, BOARD_CLIPBOARD_VERSION, BOARD_HAND_TAP_SLOP, CONNECTION_PORT_HIT_RADIUS, CONNECTION_PORT_OFFSET, CONNECTION_PORT_RADIUS, CONNECTION_PORT_TOUCH_HIT_RADIUS, CONNECTION_SIDES, DUPLICATE_OFFSET, MAX_CLIPBOARD_CHARS, MAX_CLIPBOARD_ITEMS, alignFrames, canTapSelectWithHand, computeSnap, connectionPortAt, connectionPorts, createBoardEditor, createSpatialIndex, defaultBoardTool, defaultPasteOffset, diffBoardEdits, distributeFrames, encodeClipboard, framesBounds, isContinuousBoardTool, isCreationBoardTool, isEmptyBoardPatch, isWithinHandTapSlop, materializeClipboard, normalizeWheelDelta, parseClipboard, portHitRadius, readPath, remapItems, resizeCursorForHandle, resolveSelectionTransform, routeBoardEdits, sameJson, selectionTransformControlAt, wheelZoomFactor, writePath };
|
|
13
|
+
export { BOARD_CLIPBOARD_MIME, BOARD_CLIPBOARD_VERSION, BOARD_HAND_TAP_SLOP, BOARD_MOUSE_TAP_SLOP, BOARD_TAP_MAX_MS, CONNECTION_PORT_HIT_RADIUS, CONNECTION_PORT_OFFSET, CONNECTION_PORT_RADIUS, CONNECTION_PORT_TOUCH_HIT_RADIUS, CONNECTION_SIDES, DUPLICATE_OFFSET, MAX_CLIPBOARD_CHARS, MAX_CLIPBOARD_ITEMS, alignFrames, boardTapSlop, canTapSelectWithHand, computeSnap, connectionPortAt, connectionPorts, createBoardEditor, createSpatialIndex, defaultBoardTool, defaultPasteOffset, diffBoardEdits, distributeFrames, encodeClipboard, framesBounds, isContinuousBoardTool, isCreationBoardTool, isEmptyBoardPatch, isWithinHandTapSlop, materializeClipboard, normalizeWheelDelta, parseClipboard, portHitRadius, readPath, remapItems, resizeCursorForHandle, resolveSelectionTransform, routeBoardEdits, sameJson, selectionTransformControlAt, wheelZoomFactor, writePath };
|
|
@@ -6,9 +6,19 @@ import "../model/index.js";
|
|
|
6
6
|
import { BoardTransformControl } from "./selection-transform.js";
|
|
7
7
|
import { EditorContext } from "./context.js";
|
|
8
8
|
//#region src/board/editor/interaction.d.ts
|
|
9
|
+
type BoardPress = {
|
|
10
|
+
type: "pressing";
|
|
11
|
+
itemId: string;
|
|
12
|
+
pointerId: number;
|
|
13
|
+
pointerType: string;
|
|
14
|
+
start: ScreenPoint;
|
|
15
|
+
world: WorldPoint;
|
|
16
|
+
camera: BoardViewport;
|
|
17
|
+
at: number;
|
|
18
|
+
};
|
|
9
19
|
type BoardInteraction = {
|
|
10
20
|
type: "idle";
|
|
11
|
-
} | {
|
|
21
|
+
} | BoardPress | {
|
|
12
22
|
type: "panning";
|
|
13
23
|
start: ScreenPoint;
|
|
14
24
|
origin: BoardViewport;
|
|
@@ -22,6 +32,10 @@ type BoardInteraction = {
|
|
|
22
32
|
origin: Map<string, BoardFrame>;
|
|
23
33
|
moved: boolean;
|
|
24
34
|
duplicate: boolean;
|
|
35
|
+
tap?: {
|
|
36
|
+
itemId: string;
|
|
37
|
+
at: number;
|
|
38
|
+
};
|
|
25
39
|
} | {
|
|
26
40
|
type: "resizing";
|
|
27
41
|
handle: ResizeHandle;
|
|
@@ -94,6 +108,11 @@ type BoardPointerEvent = {
|
|
|
94
108
|
pressure: number;
|
|
95
109
|
samples?: readonly BoardDrawInputSample[];
|
|
96
110
|
};
|
|
111
|
+
type BoardActivation = {
|
|
112
|
+
itemId: string;
|
|
113
|
+
pointerType: string;
|
|
114
|
+
selected: boolean;
|
|
115
|
+
};
|
|
97
116
|
type BoardTransformEnd = {
|
|
98
117
|
ids: string[];
|
|
99
118
|
operation: "move" | "resize" | "rotate" | "arrow";
|
|
@@ -117,9 +136,11 @@ declare function createInteractionModule(ctx: EditorContext): {
|
|
|
117
136
|
pointerUp: (event: BoardPointerEvent) => void;
|
|
118
137
|
pointerLeave: () => void;
|
|
119
138
|
cancel: () => void;
|
|
139
|
+
pickUp: () => boolean;
|
|
120
140
|
subscribeTransforms(listener: (event: BoardTransformEnd) => void): () => void;
|
|
141
|
+
subscribeActivations(listener: (event: BoardActivation) => void): () => void;
|
|
121
142
|
destroy(): void;
|
|
122
143
|
};
|
|
123
144
|
type InteractionModule = ReturnType<typeof createInteractionModule>;
|
|
124
145
|
//#endregion
|
|
125
|
-
export { BoardInteraction, BoardPointerEvent, BoardTransformEnd, InteractionModule, createInteractionModule };
|
|
146
|
+
export { BoardActivation, BoardInteraction, BoardPointerEvent, BoardTransformEnd, InteractionModule, createInteractionModule };
|
|
@@ -3,7 +3,7 @@ import { resolveSceneArrow } from "../model/arrow-geometry.js";
|
|
|
3
3
|
import { appendBoardDrawSamples } from "../model/draw-input.js";
|
|
4
4
|
import { createBoardItemId } from "../model/items/id.js";
|
|
5
5
|
import { connectionPortAt, connectionPorts } from "./connection-ports.js";
|
|
6
|
-
import { canTapSelectWithHand, isCreationBoardTool, isWithinHandTapSlop } from "./tool.js";
|
|
6
|
+
import { boardTapSlop, canTapSelectWithHand, isCreationBoardTool, isWithinHandTapSlop } from "./tool.js";
|
|
7
7
|
import { selectionTransformControlAt } from "./selection-transform.js";
|
|
8
8
|
import { computeSnap } from "./snapping.js";
|
|
9
9
|
import { memo } from "./state.js";
|
|
@@ -33,6 +33,15 @@ function createInteractionModule(ctx) {
|
|
|
33
33
|
for (const listener of transformListeners) listener(event);
|
|
34
34
|
}
|
|
35
35
|
const { state, options, registry } = ctx;
|
|
36
|
+
const activationListeners = /* @__PURE__ */ new Set();
|
|
37
|
+
function activate(itemId, at, pointerType, selected) {
|
|
38
|
+
if (performance.now() - at > 300 || !state.scene.get(itemId)) return;
|
|
39
|
+
for (const listener of activationListeners) listener({
|
|
40
|
+
itemId,
|
|
41
|
+
pointerType,
|
|
42
|
+
selected
|
|
43
|
+
});
|
|
44
|
+
}
|
|
36
45
|
const activePointers = /* @__PURE__ */ new Map();
|
|
37
46
|
let pinch = null;
|
|
38
47
|
const marquee = memo(() => [state.interaction], (interaction) => {
|
|
@@ -106,6 +115,7 @@ function createInteractionModule(ctx) {
|
|
|
106
115
|
if (!candidateId) return null;
|
|
107
116
|
const item = scene.get(candidateId);
|
|
108
117
|
if (!item || ctx.query.isLocked(item) || item.type === "arrow") return null;
|
|
118
|
+
if (!selection.includes(item.id) && registry.activation(item) === "tap") return null;
|
|
109
119
|
return registry.capabilities(item).canConnect ? item.id : null;
|
|
110
120
|
}
|
|
111
121
|
function visiblePorts() {
|
|
@@ -160,6 +170,66 @@ function createInteractionModule(ctx) {
|
|
|
160
170
|
size: arrow.size
|
|
161
171
|
};
|
|
162
172
|
}
|
|
173
|
+
function plainPrimary(event) {
|
|
174
|
+
return event.button === 0 && !event.shiftKey && !event.metaKey && !event.ctrlKey && !event.altKey;
|
|
175
|
+
}
|
|
176
|
+
function press(event) {
|
|
177
|
+
if (!plainPrimary(event)) return false;
|
|
178
|
+
const item = ctx.query.itemAt(event.world);
|
|
179
|
+
if (!item || state.selection.includes(item.id) || registry.activation(item) !== "tap") return false;
|
|
180
|
+
state.interaction = {
|
|
181
|
+
type: "pressing",
|
|
182
|
+
itemId: item.id,
|
|
183
|
+
pointerId: event.pointerId,
|
|
184
|
+
pointerType: event.pointerType,
|
|
185
|
+
start: event.screen,
|
|
186
|
+
world: event.world,
|
|
187
|
+
camera: { ...state.camera },
|
|
188
|
+
at: performance.now()
|
|
189
|
+
};
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
function releasePress(gesture, event) {
|
|
193
|
+
const dx = event.screen.x - gesture.start.x;
|
|
194
|
+
const dy = event.screen.y - gesture.start.y;
|
|
195
|
+
if (Math.hypot(dx, dy) <= boardTapSlop(gesture.pointerType)) return;
|
|
196
|
+
const item = state.scene.get(gesture.itemId);
|
|
197
|
+
if (!(!options.readonly && state.tool === "select" && item && !ctx.query.isLocked(item) && registry.capabilities(item).canMove)) {
|
|
198
|
+
state.interaction = {
|
|
199
|
+
type: "panning",
|
|
200
|
+
start: gesture.start,
|
|
201
|
+
origin: gesture.camera,
|
|
202
|
+
moved: true,
|
|
203
|
+
tapSelection: null
|
|
204
|
+
};
|
|
205
|
+
ctx.camera.setCamera(panBy(gesture.camera, dx, dy));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const translating = {
|
|
209
|
+
type: "translating",
|
|
210
|
+
start: gesture.world,
|
|
211
|
+
origin: ctx.selection.framesFor([gesture.itemId]),
|
|
212
|
+
moved: false,
|
|
213
|
+
duplicate: false
|
|
214
|
+
};
|
|
215
|
+
state.interaction = translating;
|
|
216
|
+
translate(translating, event);
|
|
217
|
+
}
|
|
218
|
+
function pickUp() {
|
|
219
|
+
const gesture = state.interaction;
|
|
220
|
+
if (options.readonly || gesture.type !== "pressing") return false;
|
|
221
|
+
const item = state.scene.get(gesture.itemId);
|
|
222
|
+
if (!item || ctx.query.isLocked(item) || !registry.capabilities(item).canMove) return false;
|
|
223
|
+
state.selection = [item.id];
|
|
224
|
+
state.interaction = {
|
|
225
|
+
type: "translating",
|
|
226
|
+
start: state.hoverPoint ?? gesture.world,
|
|
227
|
+
origin: ctx.selection.framesFor([item.id]),
|
|
228
|
+
moved: false,
|
|
229
|
+
duplicate: false
|
|
230
|
+
};
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
163
233
|
function pointerDown(event) {
|
|
164
234
|
ctx.camera.cancelAnimation();
|
|
165
235
|
state.hoverPoint = event.world;
|
|
@@ -173,6 +243,7 @@ function createInteractionModule(ctx) {
|
|
|
173
243
|
}
|
|
174
244
|
if (activePointers.size > 2) return;
|
|
175
245
|
const additive = event.shiftKey || event.metaKey || event.ctrlKey;
|
|
246
|
+
if (!spaceHeld && (tool === "hand" || options.readonly) && press(event)) return;
|
|
176
247
|
if (tool === "hand" || spaceHeld || event.button === 1) {
|
|
177
248
|
const tapSelection = tool === "hand" && !spaceHeld && event.button !== 1 && canTapSelectWithHand(event.pointerType) ? { targetId: ctx.query.itemAt(event.world)?.id ?? null } : null;
|
|
178
249
|
state.interaction = {
|
|
@@ -278,17 +349,23 @@ function createInteractionModule(ctx) {
|
|
|
278
349
|
};
|
|
279
350
|
return;
|
|
280
351
|
}
|
|
352
|
+
if (press(event)) return;
|
|
281
353
|
const item = ctx.query.itemAt(event.world);
|
|
282
354
|
if (!item) return brush(event, additive);
|
|
283
355
|
if (additive) ctx.selection.toggle(item.id);
|
|
284
356
|
else if (!state.selection.includes(item.id)) state.selection = [item.id];
|
|
285
357
|
const movable = ctx.selection.movable();
|
|
358
|
+
const tap = plainPrimary(event) && state.selection.length === 1 && registry.activation(item) === "tap" ? {
|
|
359
|
+
itemId: item.id,
|
|
360
|
+
at: performance.now()
|
|
361
|
+
} : void 0;
|
|
286
362
|
state.interaction = movable.length === 0 ? { type: "idle" } : {
|
|
287
363
|
type: "translating",
|
|
288
364
|
start: event.world,
|
|
289
365
|
origin: ctx.selection.framesFor(movable),
|
|
290
366
|
moved: false,
|
|
291
|
-
duplicate: event.altKey
|
|
367
|
+
duplicate: event.altKey,
|
|
368
|
+
...tap ? { tap } : {}
|
|
292
369
|
};
|
|
293
370
|
}
|
|
294
371
|
function pinchMove() {
|
|
@@ -472,6 +549,7 @@ function createInteractionModule(ctx) {
|
|
|
472
549
|
ctx.camera.setCamera(panBy(interaction.origin, dx, dy));
|
|
473
550
|
return;
|
|
474
551
|
}
|
|
552
|
+
case "pressing": return releasePress(interaction, event);
|
|
475
553
|
case "translating": return translate(interaction, event);
|
|
476
554
|
case "resizing": return resize(interaction, event);
|
|
477
555
|
case "rotating": return rotate(interaction, event);
|
|
@@ -532,11 +610,14 @@ function createInteractionModule(ctx) {
|
|
|
532
610
|
state.selection = targetId && state.scene.get(targetId) ? [targetId] : [];
|
|
533
611
|
}
|
|
534
612
|
break;
|
|
613
|
+
case "pressing":
|
|
614
|
+
activate(gesture.itemId, gesture.at, gesture.pointerType, false);
|
|
615
|
+
break;
|
|
535
616
|
case "translating":
|
|
536
617
|
if (gesture.moved) {
|
|
537
|
-
for (const [id, item] of reparentDropped(
|
|
618
|
+
for (const [id, item] of reparentDropped([...gesture.origin.keys()])) ctx.document.setDraftItem(id, item);
|
|
538
619
|
ctx.document.commit();
|
|
539
|
-
}
|
|
620
|
+
} else if (gesture.tap) activate(gesture.tap.itemId, gesture.tap.at, event.pointerType, true);
|
|
540
621
|
break;
|
|
541
622
|
case "resizing":
|
|
542
623
|
case "rotating":
|
|
@@ -610,15 +691,23 @@ function createInteractionModule(ctx) {
|
|
|
610
691
|
pointerUp,
|
|
611
692
|
pointerLeave,
|
|
612
693
|
cancel,
|
|
694
|
+
pickUp,
|
|
613
695
|
subscribeTransforms(listener) {
|
|
614
696
|
transformListeners.add(listener);
|
|
615
697
|
return () => {
|
|
616
698
|
transformListeners.delete(listener);
|
|
617
699
|
};
|
|
618
700
|
},
|
|
701
|
+
subscribeActivations(listener) {
|
|
702
|
+
activationListeners.add(listener);
|
|
703
|
+
return () => {
|
|
704
|
+
activationListeners.delete(listener);
|
|
705
|
+
};
|
|
706
|
+
},
|
|
619
707
|
destroy() {
|
|
620
708
|
activePointers.clear();
|
|
621
709
|
transformListeners.clear();
|
|
710
|
+
activationListeners.clear();
|
|
622
711
|
}
|
|
623
712
|
};
|
|
624
713
|
}
|
|
@@ -18,7 +18,7 @@ declare function createQueryModule(ctx: EditorContext): {
|
|
|
18
18
|
idsInRect: (rect: Rect, includeTemporary?: boolean) => string[];
|
|
19
19
|
frameAt: (point: WorldPoint, exclude?: ReadonlySet<string>) => BoardSceneItem | null;
|
|
20
20
|
itemAt: (point: WorldPoint, exclude?: ReadonlySet<string>, includeTemporary?: boolean) => BoardSceneItem | null;
|
|
21
|
-
labelItemAt: (point: WorldPoint) => BoardSceneItem | null;
|
|
21
|
+
labelItemAt: (point: WorldPoint, frameLabelSize?: number) => BoardSceneItem | null;
|
|
22
22
|
arrowHandleAt: (point: WorldPoint) => "start" | "end" | "mid" | null;
|
|
23
23
|
connectTargetAt: (point: WorldPoint, exclude?: ReadonlySet<string>) => string | null;
|
|
24
24
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseBoardMediaSource } from "../../protocol/dist/board-model.js";
|
|
2
2
|
import { rectContainsPoint, unionRects } from "../model/geometry.js";
|
|
3
3
|
import { arrowHitRadius, arrowPathBounds, distanceToArrow, resolveSceneArrow } from "../model/arrow-geometry.js";
|
|
4
|
+
import { frameLabelHitRect } from "../model/text-metrics.js";
|
|
4
5
|
import { createSpatialIndex } from "./spatial.js";
|
|
5
6
|
//#region src/board/editor/query.ts
|
|
6
7
|
const NO_IDS = /* @__PURE__ */ new Set();
|
|
@@ -139,7 +140,7 @@ function createQueryModule(ctx) {
|
|
|
139
140
|
}
|
|
140
141
|
return null;
|
|
141
142
|
}
|
|
142
|
-
function labelItemAt(point) {
|
|
143
|
+
function labelItemAt(point, frameLabelSize = 12) {
|
|
143
144
|
ensureSpatial();
|
|
144
145
|
const { scene, selection, camera } = state;
|
|
145
146
|
const nearby = spatial.idsAtPoint(point);
|
|
@@ -147,12 +148,7 @@ function createQueryModule(ctx) {
|
|
|
147
148
|
for (const id of new Set(candidates)) {
|
|
148
149
|
const item = scene.get(id);
|
|
149
150
|
if (!item || !state.base.items[id] || isLocked(item)) continue;
|
|
150
|
-
if (item.type === "frame" && rectContainsPoint(
|
|
151
|
-
x: item.frame.x,
|
|
152
|
-
y: item.frame.y - 22,
|
|
153
|
-
width: Math.max(48, item.frame.width * .5),
|
|
154
|
-
height: 24
|
|
155
|
-
}, point)) return item;
|
|
151
|
+
if (item.type === "frame" && rectContainsPoint(frameLabelHitRect(item.frame, frameLabelSize), point)) return item;
|
|
156
152
|
if (item.type === "arrow") {
|
|
157
153
|
const resolved = resolveSceneArrow(item, scene);
|
|
158
154
|
const radius = Math.max(10, (item.props.fontSize ?? 14) * .9) / Math.min(1, camera.zoom);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { selectionBounds } from "../model/geometry.js";
|
|
2
1
|
import { sceneItemToItem } from "../model/scene.js";
|
|
2
|
+
import { selectionBounds } from "../model/geometry.js";
|
|
3
3
|
import { alignFrames, distributeFrames } from "./align.js";
|
|
4
4
|
import { resolveSelectionTransform } from "./selection-transform.js";
|
|
5
5
|
import { memo } from "./state.js";
|