@lazlon-platform/html-editor 0.6.0 → 0.7.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/lib/hooks/actions.ts +89 -67
- package/lib/hooks/batch.ts +9 -5
- package/lib/hooks/index.ts +7 -7
- package/lib/hooks/page.ts +2 -4
- package/lib/hooks/pointer/useMovePoint.ts +100 -0
- package/lib/hooks/pointer/{moveable.ts → useMoveable.ts} +30 -36
- package/lib/hooks/pointer/{pointer.ts → usePointer.ts} +2 -2
- package/lib/hooks/pointer/useResize/index.ts +31 -0
- package/lib/hooks/pointer/useResize/multi.ts +161 -0
- package/lib/hooks/pointer/useResize/multiLineNode.ts +99 -0
- package/lib/hooks/pointer/useResize/multiRegularNode.ts +109 -0
- package/lib/hooks/pointer/useResize/multiTextNode.ts +108 -0
- package/lib/hooks/pointer/useResize/singleRegularNode.ts +91 -0
- package/lib/hooks/pointer/useResize/singleTextNode.ts +115 -0
- package/lib/hooks/pointer/useRotation.ts +102 -0
- package/lib/hooks/pointer/{selector.ts → useSelector.ts} +2 -1
- package/lib/hooks/pointer/{snap.ts → useSnap.ts} +3 -2
- package/lib/hooks/{pointer/selectionFrame.ts → selectionFrame.ts} +9 -6
- package/lib/hooks/textMarks.ts +30 -19
- package/lib/model/editor.ts +18 -4
- package/lib/model/geometry/math.ts +8 -20
- package/lib/model/index.ts +9 -1
- package/lib/model/node/imageNode.ts +1 -1
- package/lib/model/node/lineNode.ts +41 -20
- package/lib/model/node/textNode.ts +5 -14
- package/lib/model/node.ts +18 -9
- package/lib/model/page.ts +3 -2
- package/lib/ui/node/EditableContent/index.tsx +6 -4
- package/lib/ui/node/LineContent.tsx +1 -3
- package/lib/ui/selection.ts +6 -5
- package/package.json +1 -1
- package/lib/hooks/pointer/movePoint.ts +0 -75
- package/lib/hooks/pointer/resize.ts +0 -247
- package/lib/hooks/pointer/rotation.ts +0 -138
package/lib/hooks/actions.ts
CHANGED
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
GroupNode,
|
|
5
5
|
type HistoryAction,
|
|
6
6
|
type Node,
|
|
7
|
-
type NodeProps,
|
|
8
7
|
type Page,
|
|
9
8
|
} from "../model"
|
|
10
9
|
import type { Editor, NodeConstructor } from "../model/editor"
|
|
@@ -19,10 +18,17 @@ import { selectionBox } from "../ui/selection"
|
|
|
19
18
|
import { useBatchSet } from "./batch"
|
|
20
19
|
import { useEditor } from "./editor"
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
function getSelection(editor: Editor) {
|
|
22
|
+
return editor.selection
|
|
23
|
+
.values()
|
|
24
|
+
.toArray()
|
|
25
|
+
.filter((n) => !n.locked)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function clone<N extends Node>(
|
|
23
29
|
editor: Editor,
|
|
24
|
-
node:
|
|
25
|
-
props?: ((props:
|
|
30
|
+
node: N,
|
|
31
|
+
props?: ((props: N["props"]) => Partial<N["props"]>) | Partial<N["props"]>,
|
|
26
32
|
) {
|
|
27
33
|
const copy = node.props
|
|
28
34
|
const newProps = typeof props === "function" ? props(copy) : props
|
|
@@ -48,7 +54,8 @@ export function useAddNodeAction(page?: Page) {
|
|
|
48
54
|
) {
|
|
49
55
|
const [firstPage] = editor.pages.values()
|
|
50
56
|
const targetPage = page ?? firstPage
|
|
51
|
-
if (!targetPage)
|
|
57
|
+
if (!targetPage) throw Error("missing Page")
|
|
58
|
+
|
|
52
59
|
const { width, height, nodes } = targetPage
|
|
53
60
|
|
|
54
61
|
const node = new NodeClass(targetPage, {
|
|
@@ -76,7 +83,7 @@ export function useMoveAction() {
|
|
|
76
83
|
|
|
77
84
|
return function move(props: { x?: number; y?: number }) {
|
|
78
85
|
const { x, y } = props
|
|
79
|
-
const selection = editor
|
|
86
|
+
const selection = getSelection(editor)
|
|
80
87
|
if (x) batchSet(selection, (n) => ({ x: n.x + x }))
|
|
81
88
|
if (y) batchSet(selection, (n) => ({ y: n.y + y }))
|
|
82
89
|
}
|
|
@@ -86,17 +93,18 @@ export function useTrashAction() {
|
|
|
86
93
|
const editor = useEditor()
|
|
87
94
|
|
|
88
95
|
return function trash() {
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
if (selection.
|
|
96
|
+
const page = editor.selectionPage
|
|
97
|
+
const selection = getSelection(editor)
|
|
98
|
+
if (selection.length === 0 || !page) return
|
|
92
99
|
|
|
93
100
|
editor.history.push({
|
|
94
|
-
redo: ["delete-node", [page.id,
|
|
95
|
-
undo: ["add-node", [page.id,
|
|
101
|
+
redo: ["delete-node", [page.id, selection.map((node) => node.id)]],
|
|
102
|
+
undo: ["add-node", [page.id, selection.map((n) => editor.serializeNode(n))]],
|
|
96
103
|
})
|
|
97
104
|
|
|
105
|
+
const current = editor.selection
|
|
98
106
|
editor.selection = new Set()
|
|
99
|
-
page.nodes = new Map(page.nodes.entries().filter(([, node]) => !
|
|
107
|
+
page.nodes = new Map(page.nodes.entries().filter(([, node]) => !current.has(node)))
|
|
100
108
|
}
|
|
101
109
|
}
|
|
102
110
|
|
|
@@ -104,9 +112,11 @@ export function useGroupAction() {
|
|
|
104
112
|
const editor = useEditor()
|
|
105
113
|
|
|
106
114
|
function group() {
|
|
107
|
-
const
|
|
115
|
+
const page = editor.selectionPage
|
|
116
|
+
const selection = getSelection(editor)
|
|
117
|
+
if (selection.length === 0 || !page) return
|
|
118
|
+
|
|
108
119
|
const { width, height, x, y } = boxBounds(selectionBox(selection))
|
|
109
|
-
if (selection.size === 0 || !page) return
|
|
110
120
|
|
|
111
121
|
const group = new GroupNode(page, {
|
|
112
122
|
id: editor.id(),
|
|
@@ -128,11 +138,12 @@ export function useGroupAction() {
|
|
|
128
138
|
.toArray(),
|
|
129
139
|
)
|
|
130
140
|
|
|
141
|
+
const current = editor.selection
|
|
131
142
|
editor.selection = new Set()
|
|
132
143
|
page.nodes = new Map([
|
|
133
144
|
...page.nodes
|
|
134
145
|
.values()
|
|
135
|
-
.filter((n) => !
|
|
146
|
+
.filter((n) => !current.has(n))
|
|
136
147
|
.map((n) => [n.id, n] as const),
|
|
137
148
|
[group.id, group],
|
|
138
149
|
])
|
|
@@ -174,6 +185,8 @@ export function useGroupAction() {
|
|
|
174
185
|
}
|
|
175
186
|
|
|
176
187
|
function ungroup(group: GroupNode) {
|
|
188
|
+
if (group.locked) return
|
|
189
|
+
|
|
177
190
|
const page = group.page
|
|
178
191
|
const groupCenter = rectCenter(group)
|
|
179
192
|
const nodes = group.nodes
|
|
@@ -231,30 +244,24 @@ export function useDuplicateAction() {
|
|
|
231
244
|
const editor = useEditor()
|
|
232
245
|
|
|
233
246
|
return function duplicate(props?: { page?: Page; nodes?: Set<Node>; offset?: number }) {
|
|
234
|
-
const selection = props?.nodes ?? editor.selection
|
|
235
247
|
const offset = props?.offset ?? 10
|
|
236
248
|
const page = props?.page ?? editor.selectionPage
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
)
|
|
247
|
-
.toArray()
|
|
249
|
+
const nodes = Array.from(props?.nodes ?? getSelection(editor))
|
|
250
|
+
if (nodes.length === 0 || !page) return
|
|
251
|
+
|
|
252
|
+
const dup = nodes.map((node) =>
|
|
253
|
+
clone(editor, node, (n) => ({
|
|
254
|
+
x: n.x + offset,
|
|
255
|
+
y: n.y + offset,
|
|
256
|
+
})),
|
|
257
|
+
)
|
|
248
258
|
|
|
249
|
-
page.nodes = new Map([
|
|
250
|
-
...page.nodes,
|
|
251
|
-
...nodes.map((node) => [node.id, node] as const),
|
|
252
|
-
])
|
|
259
|
+
page.nodes = new Map([...page.nodes, ...dup.map((node) => [node.id, node] as const)])
|
|
253
260
|
|
|
254
|
-
editor.selection = new Set(
|
|
261
|
+
editor.selection = new Set(dup)
|
|
255
262
|
editor.history.push({
|
|
256
|
-
redo: ["add-node", [page.id,
|
|
257
|
-
undo: ["delete-node", [page.id,
|
|
263
|
+
redo: ["add-node", [page.id, dup.map((n) => editor.serializeNode(n))]],
|
|
264
|
+
undo: ["delete-node", [page.id, dup.map((node) => node.id)]],
|
|
258
265
|
})
|
|
259
266
|
}
|
|
260
267
|
}
|
|
@@ -264,26 +271,15 @@ export function useToggleLockAction() {
|
|
|
264
271
|
|
|
265
272
|
return function toggleLock() {
|
|
266
273
|
const selection = editor.selection.values().toArray()
|
|
267
|
-
const
|
|
268
|
-
for (const node of selection) {
|
|
269
|
-
node.locked = !isLocked
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
274
|
+
const nextLocked = selection.some((n) => !n.locked)
|
|
273
275
|
|
|
274
|
-
export function useAlignAction() {
|
|
275
|
-
type AlignProps = { x: number } | { y: number }
|
|
276
|
-
|
|
277
|
-
const editor = useEditor()
|
|
278
|
-
|
|
279
|
-
function align(fn: (node: Node) => { prev: AlignProps; next: AlignProps }) {
|
|
280
276
|
const undo: HistoryAction[] = []
|
|
281
277
|
const redo: HistoryAction[] = []
|
|
282
278
|
|
|
283
|
-
for (const node of
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
279
|
+
for (const node of selection) {
|
|
280
|
+
undo.push(["set-node-props", [node.id, { locked: node.locked }]])
|
|
281
|
+
redo.push(["set-node-props", [node.id, { locked: nextLocked }]])
|
|
282
|
+
node.locked = nextLocked
|
|
287
283
|
}
|
|
288
284
|
|
|
289
285
|
editor.history.push({
|
|
@@ -291,6 +287,24 @@ export function useAlignAction() {
|
|
|
291
287
|
redo: ["batch", redo],
|
|
292
288
|
})
|
|
293
289
|
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function useAlignAction() {
|
|
293
|
+
type AlignProps = { x: number } | { y: number }
|
|
294
|
+
|
|
295
|
+
const editor = useEditor()
|
|
296
|
+
|
|
297
|
+
function align(set: (node: Node) => { prev: AlignProps; next: AlignProps }) {
|
|
298
|
+
editor.pushHistory(
|
|
299
|
+
getSelection(editor).map((node) => {
|
|
300
|
+
const { prev, next } = set(node)
|
|
301
|
+
return {
|
|
302
|
+
undo: ["set-node-props", [node.id, prev]],
|
|
303
|
+
redo: ["set-node-props", [node.id, next]],
|
|
304
|
+
}
|
|
305
|
+
}),
|
|
306
|
+
)
|
|
307
|
+
}
|
|
294
308
|
|
|
295
309
|
function halign(getX: (node: Node) => number) {
|
|
296
310
|
align((node) => {
|
|
@@ -324,20 +338,22 @@ export function useDistributeAction() {
|
|
|
324
338
|
const editor = useEditor()
|
|
325
339
|
|
|
326
340
|
function distribute(pos: "x" | "y", size: "width" | "height") {
|
|
341
|
+
const selection = getSelection(editor)
|
|
342
|
+
if (selection.length === 0) return
|
|
343
|
+
|
|
327
344
|
const rect = boxBounds(selectionBox(editor.selection))
|
|
328
|
-
const array = editor.selection.values().toArray()
|
|
329
345
|
|
|
330
|
-
const undo: HistoryAction[] =
|
|
346
|
+
const undo: HistoryAction[] = selection.map((node) => [
|
|
331
347
|
"set-node-props",
|
|
332
348
|
[node.id, { [pos]: node[pos] }],
|
|
333
349
|
])
|
|
334
350
|
|
|
335
|
-
const total =
|
|
336
|
-
const gap = (rect[size] - total) / (
|
|
351
|
+
const total = selection.reduce((sum, it) => sum + it[size], 0)
|
|
352
|
+
const gap = (rect[size] - total) / (selection.length - 1)
|
|
337
353
|
|
|
338
354
|
let cursor = rect[pos]
|
|
339
355
|
|
|
340
|
-
const redo: HistoryAction[] =
|
|
356
|
+
const redo: HistoryAction[] = selection
|
|
341
357
|
.toSorted((a, b) => a[pos] - b[pos])
|
|
342
358
|
.map((node) => {
|
|
343
359
|
node[pos] = cursor
|
|
@@ -402,15 +418,17 @@ export function useStackOrderAction() {
|
|
|
402
418
|
}
|
|
403
419
|
|
|
404
420
|
const canBringBackward = useComputed(() => {
|
|
405
|
-
const
|
|
406
|
-
|
|
421
|
+
const page = editor.selectionPage
|
|
422
|
+
const selection = getSelection(editor)
|
|
423
|
+
if (selection.length === 0 || !page) return false
|
|
407
424
|
const nodes = page.nodes.values().toArray()
|
|
408
425
|
return selection.values().some((n) => n !== nodes.at(0))
|
|
409
426
|
})
|
|
410
427
|
|
|
411
428
|
const canBringForward = useComputed(() => {
|
|
412
|
-
const
|
|
413
|
-
|
|
429
|
+
const page = editor.selectionPage
|
|
430
|
+
const selection = getSelection(editor)
|
|
431
|
+
if (selection.length === 0 || !page) return false
|
|
414
432
|
const nodes = page.nodes.values().toArray()
|
|
415
433
|
return selection.values().some((n) => n !== nodes.at(-1))
|
|
416
434
|
})
|
|
@@ -419,17 +437,20 @@ export function useStackOrderAction() {
|
|
|
419
437
|
canBringBackward,
|
|
420
438
|
canBringForward,
|
|
421
439
|
bringBackward() {
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
440
|
+
const page = editor.selectionPage
|
|
441
|
+
const selection = getSelection(editor)
|
|
442
|
+
if (selection.length === 0 || !page) return
|
|
443
|
+
order(page, moveBackward(page.nodes.values().toArray(), new Set(selection)))
|
|
425
444
|
},
|
|
426
445
|
bringForward() {
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
-
|
|
446
|
+
const page = editor.selectionPage
|
|
447
|
+
const selection = getSelection(editor)
|
|
448
|
+
if (selection.length === 0 || !page) return
|
|
449
|
+
order(page, moveForward(page.nodes.values().toArray(), new Set(selection)))
|
|
430
450
|
},
|
|
431
451
|
bringToBack() {
|
|
432
|
-
const
|
|
452
|
+
const page = editor.selectionPage
|
|
453
|
+
const selection = new Set(getSelection(editor))
|
|
433
454
|
if (selection.size === 0 || !page) return
|
|
434
455
|
order(page, [
|
|
435
456
|
...selection,
|
|
@@ -437,7 +458,8 @@ export function useStackOrderAction() {
|
|
|
437
458
|
])
|
|
438
459
|
},
|
|
439
460
|
bringToFront() {
|
|
440
|
-
const
|
|
461
|
+
const page = editor.selectionPage
|
|
462
|
+
const selection = new Set(getSelection(editor))
|
|
441
463
|
if (selection.size === 0 || !page) return
|
|
442
464
|
order(page, [
|
|
443
465
|
...page.nodes.values().filter((node) => !selection.has(node)),
|
package/lib/hooks/batch.ts
CHANGED
|
@@ -18,10 +18,12 @@ export function useNodeField<N extends Node, K extends keyof N>(
|
|
|
18
18
|
key: K,
|
|
19
19
|
fallback: N[K],
|
|
20
20
|
) {
|
|
21
|
-
const values = Array.from(nodes).map((node) => node[key])
|
|
22
21
|
return useComputed<N[K]>({
|
|
23
22
|
equals: isEqual,
|
|
24
|
-
fn: () =>
|
|
23
|
+
fn: () => {
|
|
24
|
+
const values = Array.from(nodes).map((node) => node[key])
|
|
25
|
+
return reduce(values, fallback)
|
|
26
|
+
},
|
|
25
27
|
})
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -92,12 +94,10 @@ export function useBatchSet() {
|
|
|
92
94
|
nodes: Iterable<N>,
|
|
93
95
|
set: Props<N> | ((n: N) => Props<N>),
|
|
94
96
|
) {
|
|
95
|
-
let push = false
|
|
96
97
|
const prev: HistoryAction[] = []
|
|
97
98
|
const next: HistoryAction[] = []
|
|
98
99
|
|
|
99
100
|
for (const node of nodes) {
|
|
100
|
-
push = true
|
|
101
101
|
const nextValues = typeof set === "function" ? set(node) : set
|
|
102
102
|
const keys = Object.keys(nextValues) as WritableKeys<N>[]
|
|
103
103
|
const prevValues = Object.fromEntries(keys.map((key) => [key, node[key]]))
|
|
@@ -106,8 +106,12 @@ export function useBatchSet() {
|
|
|
106
106
|
Object.assign(node, nextValues)
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
if (
|
|
109
|
+
if (next.length > 1) {
|
|
110
110
|
history.push({ undo: ["batch", prev], redo: ["batch", next] })
|
|
111
|
+
} else if (next.length > 0) {
|
|
112
|
+
const [undo] = prev
|
|
113
|
+
const [redo] = next
|
|
114
|
+
history.push({ undo, redo })
|
|
111
115
|
}
|
|
112
116
|
}
|
|
113
117
|
}
|
package/lib/hooks/index.ts
CHANGED
|
@@ -12,13 +12,13 @@ export {
|
|
|
12
12
|
} from "./actions"
|
|
13
13
|
export { useBatchSet, useNodeField, useNodeFieldBatch } from "./batch"
|
|
14
14
|
export { EditorContext, PageContext, useEditor, usePage } from "./editor"
|
|
15
|
-
export { useMoveable } from "./pointer/
|
|
16
|
-
export { useMovePoint } from "./pointer/
|
|
17
|
-
export { usePointer } from "./pointer/
|
|
18
|
-
export { useResize } from "./pointer/
|
|
19
|
-
export { useRotation } from "./pointer/
|
|
20
|
-
export { useSelectionFrame } from "./
|
|
21
|
-
export { useSelector } from "./pointer/
|
|
15
|
+
export { useMoveable } from "./pointer/useMoveable"
|
|
16
|
+
export { useMovePoint } from "./pointer/useMovePoint"
|
|
17
|
+
export { usePointer } from "./pointer/usePointer"
|
|
18
|
+
export { useResize } from "./pointer/useResize"
|
|
19
|
+
export { useRotation } from "./pointer/useRotation"
|
|
20
|
+
export { useSelectionFrame } from "./selectionFrame"
|
|
21
|
+
export { useSelector } from "./pointer/useSelector"
|
|
22
22
|
export { useTextMarks } from "./textMarks"
|
|
23
23
|
export { useEditPage } from "./page"
|
|
24
24
|
export { useVisualPositionBatch } from "./node"
|
package/lib/hooks/page.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { useRef } from "react"
|
|
2
|
-
import type { Page } from "../model"
|
|
3
|
-
import { useEditor } from "./editor"
|
|
4
2
|
import { useStore } from "react-bolt"
|
|
3
|
+
import type { Page } from "../model"
|
|
5
4
|
|
|
6
5
|
export function useEditPage<Key extends keyof Page>(page: Page, key: Key) {
|
|
7
|
-
const editor = useEditor()
|
|
8
6
|
const init = useRef(page[key])
|
|
9
7
|
const value = useStore(page, key)
|
|
10
8
|
|
|
@@ -14,7 +12,7 @@ export function useEditPage<Key extends keyof Page>(page: Page, key: Key) {
|
|
|
14
12
|
page[key] = value
|
|
15
13
|
},
|
|
16
14
|
onChangeEnd(value: Page[Key]) {
|
|
17
|
-
editor.history.push({
|
|
15
|
+
page.editor.history.push({
|
|
18
16
|
redo: ["set-page-props", [page.id, { [key]: value }]],
|
|
19
17
|
undo: ["set-page-props", [page.id, { [key]: init.current }]],
|
|
20
18
|
})
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { useRef } from "react"
|
|
2
|
+
import type { LineNode } from "../../model"
|
|
3
|
+
import {
|
|
4
|
+
deg,
|
|
5
|
+
floatNorm,
|
|
6
|
+
point,
|
|
7
|
+
pointSubtract,
|
|
8
|
+
rect,
|
|
9
|
+
rectCenter,
|
|
10
|
+
rotatePoint,
|
|
11
|
+
type Point,
|
|
12
|
+
} from "../../model/geometry/math"
|
|
13
|
+
import { useEditor } from "../editor"
|
|
14
|
+
import { cursorPosition, usePointer } from "./usePointer"
|
|
15
|
+
import { useSnap } from "./useSnap"
|
|
16
|
+
|
|
17
|
+
export function useMovePoint(props: { lineNode: LineNode; pointIndex: number }) {
|
|
18
|
+
const { lineNode, pointIndex } = props
|
|
19
|
+
const editor = useEditor()
|
|
20
|
+
const page = lineNode.page
|
|
21
|
+
const snap = useSnap(page)
|
|
22
|
+
|
|
23
|
+
const state = useRef({
|
|
24
|
+
cursorOffset: point(),
|
|
25
|
+
baseVertices: [] as Point[],
|
|
26
|
+
basePoints: [] as Point[],
|
|
27
|
+
baseX: 0,
|
|
28
|
+
baseY: 0,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
function unrotatedPagePoints(vertices: Point[]): Point[] {
|
|
32
|
+
const rotation = lineNode.rotation
|
|
33
|
+
const origin = point()
|
|
34
|
+
const unrotatedBounds = rect(
|
|
35
|
+
...vertices.map((p) => rotatePoint(p, origin, deg(-rotation))),
|
|
36
|
+
)
|
|
37
|
+
const center = rotatePoint(rectCenter(unrotatedBounds), origin, rotation)
|
|
38
|
+
return vertices.map((p) => rotatePoint(p, center, deg(-rotation)))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function setLinePoints(pagePoints: Point[]) {
|
|
42
|
+
const bounds = point(rect(...pagePoints))
|
|
43
|
+
lineNode.x = floatNorm(bounds.x)
|
|
44
|
+
lineNode.y = floatNorm(bounds.y)
|
|
45
|
+
lineNode.points = pagePoints.map((p) => pointSubtract(p, bounds))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function redoProps() {
|
|
49
|
+
const { x, y, points } = lineNode
|
|
50
|
+
return { x, y, points }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function undoProps() {
|
|
54
|
+
const { baseX: x, baseY: y, basePoints: points } = state.current
|
|
55
|
+
return { x, y, points }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return usePointer({
|
|
59
|
+
onDown(event) {
|
|
60
|
+
if (lineNode.locked) return false
|
|
61
|
+
|
|
62
|
+
event.preventDefault()
|
|
63
|
+
event.stopPropagation()
|
|
64
|
+
|
|
65
|
+
state.current = {
|
|
66
|
+
baseVertices: lineNode.vertices,
|
|
67
|
+
basePoints: lineNode.points,
|
|
68
|
+
baseX: lineNode.x,
|
|
69
|
+
baseY: lineNode.y,
|
|
70
|
+
cursorOffset: pointSubtract(
|
|
71
|
+
cursorPosition(event, page),
|
|
72
|
+
lineNode.vertices[pointIndex],
|
|
73
|
+
),
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
onMove(event) {
|
|
77
|
+
const { cursorOffset, baseVertices } = state.current
|
|
78
|
+
const cursor = pointSubtract(cursorPosition(event, page), cursorOffset)
|
|
79
|
+
const result = point(snap(!event.shiftKey, rect(cursor)))
|
|
80
|
+
const vertices = baseVertices.map((p, i) => (i === pointIndex ? result : p))
|
|
81
|
+
|
|
82
|
+
setLinePoints(unrotatedPagePoints(vertices))
|
|
83
|
+
|
|
84
|
+
editor.action = { action: "move", payload: result }
|
|
85
|
+
},
|
|
86
|
+
onCancel() {
|
|
87
|
+
editor.action = {}
|
|
88
|
+
page.snapLines = []
|
|
89
|
+
},
|
|
90
|
+
onEnd() {
|
|
91
|
+
editor.action = {}
|
|
92
|
+
page.snapLines = []
|
|
93
|
+
|
|
94
|
+
editor.pushHistory({
|
|
95
|
+
redo: ["set-node-props", [lineNode.id, redoProps()]],
|
|
96
|
+
undo: ["set-node-props", [lineNode.id, undoProps()]],
|
|
97
|
+
})
|
|
98
|
+
},
|
|
99
|
+
})
|
|
100
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useRef } from "react"
|
|
2
|
+
import { LineNode } from "../../model"
|
|
2
3
|
import {
|
|
3
4
|
accessibleLine,
|
|
4
5
|
box,
|
|
@@ -10,13 +11,11 @@ import {
|
|
|
10
11
|
pointSubtract,
|
|
11
12
|
rect,
|
|
12
13
|
} from "../../model/geometry/math"
|
|
13
|
-
import type { HistoryEntry } from "../../model/history"
|
|
14
14
|
import type { Node } from "../../model/node"
|
|
15
15
|
import { selectionBox } from "../../ui/selection"
|
|
16
16
|
import { useEditor, usePage } from "../editor"
|
|
17
|
-
import { cursorPosition, usePointer } from "./
|
|
18
|
-
import { useSnap } from "./
|
|
19
|
-
import { LineNode } from "@lazlon/html-editor/model"
|
|
17
|
+
import { cursorPosition, usePointer } from "./usePointer"
|
|
18
|
+
import { useSnap } from "./useSnap"
|
|
20
19
|
|
|
21
20
|
export function useMoveable() {
|
|
22
21
|
const editor = useEditor()
|
|
@@ -24,11 +23,11 @@ export function useMoveable() {
|
|
|
24
23
|
const snap = useSnap(page)
|
|
25
24
|
|
|
26
25
|
const state = useRef({
|
|
27
|
-
|
|
26
|
+
baseRect: rect(),
|
|
28
27
|
cursorOffset: point(),
|
|
29
28
|
nodes: Array<{
|
|
30
29
|
node: Node
|
|
31
|
-
|
|
30
|
+
basePosition: Point
|
|
32
31
|
selectionOffset: Point
|
|
33
32
|
}>(),
|
|
34
33
|
})
|
|
@@ -64,25 +63,25 @@ export function useMoveable() {
|
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
if (editor.selection.size === 0) return false
|
|
66
|
+
if (!isCursorInSelection(cursor)) return false
|
|
67
67
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return false
|
|
68
|
+
for (const node of editor.selection) {
|
|
69
|
+
if (node.locked) return false
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const baseRect = boxBounds(selectionBox(editor.selection))
|
|
73
|
+
|
|
74
|
+
state.current = {
|
|
75
|
+
baseRect,
|
|
76
|
+
cursorOffset: pointSubtract(cursor, baseRect),
|
|
77
|
+
nodes: editor.selection
|
|
78
|
+
.values()
|
|
79
|
+
.map((node) => ({
|
|
80
|
+
node,
|
|
81
|
+
basePosition: point(node),
|
|
82
|
+
selectionOffset: pointSubtract(point(node), baseRect),
|
|
83
|
+
}))
|
|
84
|
+
.toArray(),
|
|
86
85
|
}
|
|
87
86
|
},
|
|
88
87
|
|
|
@@ -95,8 +94,8 @@ export function useMoveable() {
|
|
|
95
94
|
rect({
|
|
96
95
|
x: target.x,
|
|
97
96
|
y: target.y,
|
|
98
|
-
width: state.current.
|
|
99
|
-
height: state.current.
|
|
97
|
+
width: state.current.baseRect.width,
|
|
98
|
+
height: state.current.baseRect.height,
|
|
100
99
|
}),
|
|
101
100
|
)
|
|
102
101
|
|
|
@@ -117,17 +116,12 @@ export function useMoveable() {
|
|
|
117
116
|
editor.action = {}
|
|
118
117
|
page.snapLines = []
|
|
119
118
|
|
|
120
|
-
|
|
121
|
-
({ node,
|
|
122
|
-
redo: ["set-node-props", [node.id,
|
|
123
|
-
undo: ["set-node-props", [node.id,
|
|
124
|
-
}),
|
|
119
|
+
editor.pushHistory(
|
|
120
|
+
state.current.nodes.map(({ node, basePosition }) => ({
|
|
121
|
+
redo: ["set-node-props", [node.id, point(node)]],
|
|
122
|
+
undo: ["set-node-props", [node.id, basePosition]],
|
|
123
|
+
})),
|
|
125
124
|
)
|
|
126
|
-
|
|
127
|
-
editor.history.push({
|
|
128
|
-
redo: ["batch", entries.map((e) => e.redo)],
|
|
129
|
-
undo: ["batch", entries.map((e) => e.undo)],
|
|
130
|
-
})
|
|
131
125
|
},
|
|
132
126
|
})
|
|
133
127
|
}
|
|
@@ -17,8 +17,8 @@ export function cursorPosition(
|
|
|
17
17
|
const { x, y } = page.ref!.getBoundingClientRect()
|
|
18
18
|
|
|
19
19
|
return {
|
|
20
|
-
x: floatNorm(event.clientX
|
|
21
|
-
y: floatNorm(event.clientY
|
|
20
|
+
x: floatNorm((event.clientX - x) / zoom),
|
|
21
|
+
y: floatNorm((event.clientY - y) / zoom),
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { TextNode } from "@lazlon/html-editor/model"
|
|
2
|
+
import { useComputed, useStore } from "react-bolt"
|
|
3
|
+
import type { Corner, Edge } from "../../../model/geometry/math"
|
|
4
|
+
import { useEditor } from "../../editor"
|
|
5
|
+
import { useMultiResize } from "./multi"
|
|
6
|
+
import { useSingleRegularNodeResize } from "./singleRegularNode"
|
|
7
|
+
import { useSingleTextNodeResize } from "./singleTextNode"
|
|
8
|
+
import type { usePointer } from "../usePointer"
|
|
9
|
+
|
|
10
|
+
export function useResize(direction: Edge | Corner): ReturnType<typeof usePointer> {
|
|
11
|
+
const editor = useEditor()
|
|
12
|
+
const selection = useStore(editor, "selection")
|
|
13
|
+
const [node, ...nodes] = selection
|
|
14
|
+
const singleText = useSingleTextNodeResize(direction)
|
|
15
|
+
const single = useSingleRegularNodeResize(direction)
|
|
16
|
+
const multi = useMultiResize(direction)
|
|
17
|
+
const isLocked = useComputed(() => selection.values().some((node) => node.locked))
|
|
18
|
+
|
|
19
|
+
if (isLocked) {
|
|
20
|
+
return {
|
|
21
|
+
onPointerDown() {},
|
|
22
|
+
isMoving: () => false,
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return nodes.length === 0
|
|
27
|
+
? node instanceof TextNode && direction !== "w" && direction !== "e"
|
|
28
|
+
? singleText
|
|
29
|
+
: single
|
|
30
|
+
: multi
|
|
31
|
+
}
|