@lazlon-platform/html-editor 0.7.0 → 0.7.2
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/node.ts +1 -10
- package/lib/hooks/pointer/useMoveable.ts +1 -2
- package/lib/hooks/pointer/useResize/multiLineNode.ts +9 -11
- package/lib/hooks/pointer/useResize/multiRegularNode.ts +1 -2
- package/lib/hooks/pointer/useResize/multiTextNode.ts +1 -1
- package/lib/hooks/pointer/useResize/singleRegularNode.ts +1 -1
- package/lib/hooks/pointer/useResize/singleTextNode.ts +1 -1
- package/lib/hooks/pointer/useRotation.ts +1 -1
- package/lib/hooks/pointer/useSelector.ts +6 -6
- package/lib/hooks/pointer/useSnap.ts +3 -3
- package/lib/hooks/selectionFrame.ts +24 -17
- package/lib/model/geometry/math.ts +6 -6
- package/lib/model/node/lineNode.ts +13 -0
- package/lib/model/node.ts +5 -1
- package/lib/ui/node/EditableContent/index.tsx +0 -1
- package/lib/ui/node/ImageContent.tsx +1 -1
- package/lib/ui/node/NodeView.tsx +2 -0
- package/lib/ui/node/ShapeContent/EllipseContent.tsx +0 -1
- package/lib/ui/node/ShapeContent/RectangleContent.tsx +0 -1
- package/lib/ui/selection.ts +3 -13
- package/package.json +1 -1
package/lib/hooks/node.ts
CHANGED
|
@@ -8,16 +8,7 @@ export function useVisualPositionBatch(nodes: Node[]) {
|
|
|
8
8
|
const height = useNodeField(nodes, "height", NaN)
|
|
9
9
|
const x = useNodeFieldBatch(nodes, "x", NaN)
|
|
10
10
|
const y = useNodeFieldBatch(nodes, "y", NaN)
|
|
11
|
-
|
|
12
|
-
const bounds = boxBounds(
|
|
13
|
-
box({
|
|
14
|
-
x: x.value,
|
|
15
|
-
y: y.value,
|
|
16
|
-
width,
|
|
17
|
-
height,
|
|
18
|
-
rotation,
|
|
19
|
-
}),
|
|
20
|
-
)
|
|
11
|
+
const bounds = boxBounds(box({ x: x.value, y: y.value, width, height }, rotation))
|
|
21
12
|
|
|
22
13
|
function withBoundingBox(axis: "x" | "y", fn: (value: number) => void) {
|
|
23
14
|
return function (value: number): void {
|
|
@@ -2,7 +2,6 @@ import { useRef } from "react"
|
|
|
2
2
|
import { LineNode } from "../../model"
|
|
3
3
|
import {
|
|
4
4
|
accessibleLine,
|
|
5
|
-
box,
|
|
6
5
|
boxBounds,
|
|
7
6
|
boxContainsPoint,
|
|
8
7
|
lineContainsPoint,
|
|
@@ -44,7 +43,7 @@ export function useMoveable() {
|
|
|
44
43
|
if (node instanceof LineNode) {
|
|
45
44
|
return lineContainsPoint(accessibleLine(node), cursor)
|
|
46
45
|
}
|
|
47
|
-
return boxContainsPoint(
|
|
46
|
+
return boxContainsPoint(node.toBox(), cursor)
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
return usePointer({
|
|
@@ -65,21 +65,19 @@ export function useMultiLineNodeResize(
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
function undoProps(state: NodeState) {
|
|
68
|
-
const { baseX
|
|
69
|
-
return { x, y, points }
|
|
68
|
+
const { baseX, baseY, basePoints } = state
|
|
69
|
+
return { x: baseX, y: baseY, points: basePoints }
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
return {
|
|
73
73
|
onDown({ baseSelectionRect }) {
|
|
74
|
-
state.current = nodes.map((node) => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
})
|
|
74
|
+
state.current = nodes.map((node) => ({
|
|
75
|
+
node,
|
|
76
|
+
basePoints: node.points,
|
|
77
|
+
baseX: node.x,
|
|
78
|
+
baseY: node.y,
|
|
79
|
+
relativeVertices: node.vertices.map((p) => relativePoint(p, baseSelectionRect)),
|
|
80
|
+
}))
|
|
83
81
|
},
|
|
84
82
|
|
|
85
83
|
onMove({ selectionRect }) {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { useRef } from "react"
|
|
2
2
|
import type { Node } from "../../../model"
|
|
3
3
|
import {
|
|
4
|
-
box,
|
|
5
4
|
boxRect,
|
|
6
5
|
rect,
|
|
7
6
|
rectCenter,
|
|
@@ -78,7 +77,7 @@ export function useMultiRegularNodeResize(
|
|
|
78
77
|
onDown({ relativeSize }) {
|
|
79
78
|
state.current = nodes.map((node) => ({
|
|
80
79
|
node,
|
|
81
|
-
baseSize:
|
|
80
|
+
baseSize: node.toBox(),
|
|
82
81
|
relative: relativeSize(node),
|
|
83
82
|
}))
|
|
84
83
|
},
|
|
@@ -48,7 +48,7 @@ export function useRotation() {
|
|
|
48
48
|
state.current = {
|
|
49
49
|
base: angle(pivot, origin, cursorPosition(event, page)),
|
|
50
50
|
pivot,
|
|
51
|
-
nodes: nodes.map((node) => ({ node, baseBox: box(node) })),
|
|
51
|
+
nodes: nodes.map((node) => ({ node, baseBox: box(node, node.rotation) })),
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
editor.action = {
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { useRef } from "react"
|
|
2
|
+
import { LineNode } from "../../model"
|
|
2
3
|
import {
|
|
3
4
|
type Rect,
|
|
5
|
+
accessibleLine,
|
|
4
6
|
box,
|
|
5
7
|
boxContainsPoint,
|
|
6
|
-
pointSubtract,
|
|
7
|
-
rect,
|
|
8
8
|
boxIntersects,
|
|
9
9
|
lineContainsPoint,
|
|
10
10
|
lineIntersectsBox,
|
|
11
|
-
|
|
11
|
+
pointSubtract,
|
|
12
|
+
rect,
|
|
12
13
|
} from "../../model/geometry/math"
|
|
13
14
|
import { useEditor } from "../editor"
|
|
14
15
|
import { cursorPosition, usePointer } from "./usePointer"
|
|
15
|
-
import { LineNode } from "../../model"
|
|
16
16
|
|
|
17
17
|
function clientPoint(event: { clientX: number; clientY: number }) {
|
|
18
18
|
return { x: event.clientX, y: event.clientY }
|
|
@@ -32,7 +32,7 @@ export function useSelector(view: (props: null | Rect) => void) {
|
|
|
32
32
|
const cursor = cursorPosition(event, node.page)
|
|
33
33
|
if (
|
|
34
34
|
(node instanceof LineNode && lineContainsPoint(accessibleLine(node), cursor)) ||
|
|
35
|
-
(!(node instanceof LineNode) && boxContainsPoint(
|
|
35
|
+
(!(node instanceof LineNode) && boxContainsPoint(node.toBox(), cursor))
|
|
36
36
|
) {
|
|
37
37
|
return false
|
|
38
38
|
}
|
|
@@ -68,7 +68,7 @@ export function useSelector(view: (props: null | Rect) => void) {
|
|
|
68
68
|
if (node instanceof LineNode) {
|
|
69
69
|
return lineIntersectsBox(node, selection)
|
|
70
70
|
} else {
|
|
71
|
-
return boxIntersects(selection,
|
|
71
|
+
return boxIntersects(selection, node.toBox())
|
|
72
72
|
}
|
|
73
73
|
})
|
|
74
74
|
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useStore } from "react-bolt"
|
|
2
|
-
import { type Rect,
|
|
2
|
+
import { type Rect, boxBounds, rect } from "../../model/geometry/math"
|
|
3
3
|
import type { Node } from "../../model/node"
|
|
4
4
|
import type { Page } from "../../model/page"
|
|
5
5
|
import { useEditor } from "../editor"
|
|
@@ -18,7 +18,7 @@ export function useSnap(page: Page) {
|
|
|
18
18
|
|
|
19
19
|
function ySnap(nodes: Node[], rect: Rect): [number, ...Lines] {
|
|
20
20
|
const nodelines = nodes.flatMap((n) => {
|
|
21
|
-
const { y, height } = boxBounds(
|
|
21
|
+
const { y, height } = boxBounds(n.toBox())
|
|
22
22
|
return [y, y + height]
|
|
23
23
|
})
|
|
24
24
|
|
|
@@ -49,7 +49,7 @@ export function useSnap(page: Page) {
|
|
|
49
49
|
|
|
50
50
|
function xSnap(nodes: Node[], rect: Rect): [number, ...Lines] {
|
|
51
51
|
const nodelines = nodes.flatMap((n) => {
|
|
52
|
-
const { x, width } = boxBounds(
|
|
52
|
+
const { x, width } = boxBounds(n.toBox())
|
|
53
53
|
return [x, x + width]
|
|
54
54
|
})
|
|
55
55
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { useEffect, useRef } from "react"
|
|
2
2
|
import { useComputed, useStore } from "react-bolt"
|
|
3
|
-
import {
|
|
4
|
-
import { selectionDOMRect } from "../ui/selection"
|
|
3
|
+
import { boxBounds, type Point, type Size } from "../model/geometry/math"
|
|
5
4
|
import { useEditor } from "./editor"
|
|
6
5
|
|
|
7
6
|
function arraysEqual<T>(a: T[], b: T[]): boolean {
|
|
@@ -25,6 +24,7 @@ function useObserver(onChange: () => void) {
|
|
|
25
24
|
.filter((ref) => ref instanceof HTMLElement)
|
|
26
25
|
.toArray(),
|
|
27
26
|
})
|
|
27
|
+
|
|
28
28
|
const editorRef = useStore(editor, "ref")
|
|
29
29
|
const zoom = useStore(editor, "zoom")
|
|
30
30
|
const pages = useStore(editor, "pages")
|
|
@@ -70,26 +70,33 @@ export function useSelectionFrame<E extends HTMLElement>(props?: {
|
|
|
70
70
|
const selection = useStore(editor, "selection")
|
|
71
71
|
const zoom = useStore(editor, "zoom")
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
function setRefStyle({ x, y, width, height }: Point & Size, rotation?: number) {
|
|
74
74
|
const frame = ref.current
|
|
75
75
|
const stage = editor.ref?.getBoundingClientRect()
|
|
76
|
-
|
|
76
|
+
const page = editor.selectionPage?.ref?.getBoundingClientRect()
|
|
77
|
+
if (!frame || !stage || !page) return
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
console.log(frame, stage, page)
|
|
80
|
+
|
|
81
|
+
const tx = page.x - stage.x + x * zoom
|
|
82
|
+
const ty = page.y - stage.y + y * zoom
|
|
83
|
+
|
|
84
|
+
frame.style.height = `${height * zoom}px`
|
|
85
|
+
frame.style.width = `${width * zoom}px`
|
|
86
|
+
|
|
87
|
+
if (typeof rotation === "number") {
|
|
86
88
|
frame.style.transform = `translate(${tx}px, ${ty}px) rotate(${rotation}deg)`
|
|
87
89
|
} else {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
frame.style.transform = `translate(${tx}px, ${ty}px)`
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
useObserver(() => {
|
|
95
|
+
if (props?.accountForSingleSelection && selection.size === 1) {
|
|
96
|
+
const node = selection.values().next().value!
|
|
97
|
+
setRefStyle(node, node.rotation)
|
|
98
|
+
} else {
|
|
99
|
+
setRefStyle(boxBounds(...selection.values().map((node) => node.toBox())))
|
|
93
100
|
}
|
|
94
101
|
})
|
|
95
102
|
|
|
@@ -95,13 +95,13 @@ export function rect(...args: Array<(Point & Size) | Point>): Rect {
|
|
|
95
95
|
/**
|
|
96
96
|
* Create a rotatable box for a rectangle
|
|
97
97
|
*/
|
|
98
|
-
export function box(input
|
|
98
|
+
export function box(input: Size & Point = rect(), rotation = deg(0)): Box {
|
|
99
99
|
return {
|
|
100
|
-
center: rectCenter(input
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
width: input
|
|
104
|
-
height: input
|
|
100
|
+
center: rectCenter(input),
|
|
101
|
+
pivot: rectCenter(input),
|
|
102
|
+
rotation,
|
|
103
|
+
width: input.width,
|
|
104
|
+
height: input.height,
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { computed, state } from "react-bolt"
|
|
2
2
|
import {
|
|
3
|
+
deg,
|
|
3
4
|
floatNorm,
|
|
4
5
|
pointAdd,
|
|
5
6
|
rect,
|
|
6
7
|
rectCenter,
|
|
7
8
|
rotatePoint,
|
|
9
|
+
type Box,
|
|
8
10
|
type Line,
|
|
9
11
|
type Point,
|
|
10
12
|
} from "../geometry/math"
|
|
@@ -77,4 +79,15 @@ export class LineNode extends Node {
|
|
|
77
79
|
}
|
|
78
80
|
})
|
|
79
81
|
}
|
|
82
|
+
|
|
83
|
+
override toBox(): Box {
|
|
84
|
+
const r = rect(...this.vertices)
|
|
85
|
+
return {
|
|
86
|
+
center: rectCenter(r),
|
|
87
|
+
pivot: rectCenter(r),
|
|
88
|
+
rotation: deg(0),
|
|
89
|
+
width: r.width,
|
|
90
|
+
height: r.height,
|
|
91
|
+
}
|
|
92
|
+
}
|
|
80
93
|
}
|
package/lib/model/node.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { computed, state } from "react-bolt"
|
|
2
|
-
import { deg, type Deg } from "./geometry/math"
|
|
2
|
+
import { box, deg, type Box, type Deg } from "./geometry/math"
|
|
3
3
|
import type { Page } from "./page"
|
|
4
4
|
|
|
5
5
|
export type Schema = Map<string, typeof Node>
|
|
@@ -89,4 +89,8 @@ export abstract class Node {
|
|
|
89
89
|
...(rotation && { rotation }),
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
+
|
|
93
|
+
toBox(): Box {
|
|
94
|
+
return box(this, this.rotation)
|
|
95
|
+
}
|
|
92
96
|
}
|
|
@@ -19,7 +19,7 @@ export function ImageContent(props: { node: ImageNode; fallback?: React.ReactNod
|
|
|
19
19
|
src={url}
|
|
20
20
|
draggable={false}
|
|
21
21
|
className={clsx(
|
|
22
|
-
"size-full",
|
|
22
|
+
"size-full select-none",
|
|
23
23
|
fit === "cover" && "object-cover",
|
|
24
24
|
fit === "contain" && "object-contain",
|
|
25
25
|
fit === "fill" && "object-fill",
|
package/lib/ui/node/NodeView.tsx
CHANGED
package/lib/ui/selection.ts
CHANGED
|
@@ -1,22 +1,12 @@
|
|
|
1
|
-
import { box, boxBounds,
|
|
1
|
+
import { box, boxBounds, type Box } from "../model/geometry/math"
|
|
2
2
|
import type { Node } from "../model/node"
|
|
3
3
|
|
|
4
|
-
export function selectionDOMRect(selection: Set<Node>): Rect {
|
|
5
|
-
const rects = selection
|
|
6
|
-
.values()
|
|
7
|
-
.map((node) => node.ref)
|
|
8
|
-
.filter((dom) => dom instanceof HTMLElement)
|
|
9
|
-
.map((dom) => dom.getBoundingClientRect())
|
|
10
|
-
|
|
11
|
-
return rect(...rects)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
4
|
export function selectionBox(selection: Iterable<Node>): Box {
|
|
15
5
|
const arr = Array.from(selection)
|
|
16
6
|
|
|
17
7
|
if (arr.length === 1) {
|
|
18
|
-
return
|
|
8
|
+
return arr[0].toBox()
|
|
19
9
|
}
|
|
20
10
|
|
|
21
|
-
return box(boxBounds(...arr.map(
|
|
11
|
+
return box(boxBounds(...arr.map((n) => n.toBox())))
|
|
22
12
|
}
|