@witchcraft/layout 0.3.1 → 0.4.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 +62 -70
- package/dist/module.json +1 -1
- package/dist/module.mjs +6 -1
- package/dist/runtime/components/FrameDragHandle.vue +1 -1
- package/dist/runtime/components/LayoutDecosEdges.d.vue.ts +15 -0
- package/dist/runtime/components/LayoutDecosEdges.vue +34 -0
- package/dist/runtime/components/LayoutDecosEdges.vue.d.ts +15 -0
- package/dist/runtime/components/LayoutDecosRects.d.vue.ts +15 -0
- package/dist/runtime/components/LayoutDecosRects.vue +37 -0
- package/dist/runtime/components/LayoutDecosRects.vue.d.ts +15 -0
- package/dist/runtime/components/LayoutDragEdgeGrabbed.d.vue.ts +2 -0
- package/dist/runtime/components/LayoutDragEdgeGrabbed.vue +43 -0
- package/dist/runtime/components/LayoutDragEdgeGrabbed.vue.d.ts +2 -0
- package/dist/runtime/components/LayoutDragEdgeHandle.d.vue.ts +9 -0
- package/dist/runtime/components/LayoutDragEdgeHandle.vue +44 -0
- package/dist/runtime/components/LayoutDragEdgeHandle.vue.d.ts +9 -0
- package/dist/runtime/components/{LayoutDecos.d.vue.ts → LayoutDragEdgeVisible.d.vue.ts} +3 -2
- package/dist/runtime/components/LayoutDragEdgeVisible.vue +38 -0
- package/dist/runtime/components/{LayoutDecos.vue.d.ts → LayoutDragEdgeVisible.vue.d.ts} +3 -2
- package/dist/runtime/components/LayoutDragEdges.d.vue.ts +21 -0
- package/dist/runtime/components/LayoutDragEdges.vue +67 -0
- package/dist/runtime/components/LayoutDragEdges.vue.d.ts +21 -0
- package/dist/runtime/components/LayoutEdgesActiveFrame.d.vue.ts +2 -0
- package/dist/runtime/components/LayoutEdgesActiveFrame.vue +35 -0
- package/dist/runtime/components/LayoutEdgesActiveFrame.vue.d.ts +2 -0
- package/dist/runtime/components/LayoutFrame.d.vue.ts +7 -6
- package/dist/runtime/components/LayoutFrame.vue +18 -14
- package/dist/runtime/components/LayoutFrame.vue.d.ts +7 -6
- package/dist/runtime/components/LayoutIntersectionHandle.d.vue.ts +10 -0
- package/dist/runtime/components/LayoutIntersectionHandle.vue +36 -0
- package/dist/runtime/components/LayoutIntersectionHandle.vue.d.ts +10 -0
- package/dist/runtime/components/LayoutIntersectionVisible.d.vue.ts +6 -0
- package/dist/runtime/components/LayoutIntersectionVisible.vue +34 -0
- package/dist/runtime/components/LayoutIntersectionVisible.vue.d.ts +6 -0
- package/dist/runtime/components/LayoutIntersections.d.vue.ts +21 -0
- package/dist/runtime/components/LayoutIntersections.vue +52 -0
- package/dist/runtime/components/LayoutIntersections.vue.d.ts +21 -0
- package/dist/runtime/components/{LayoutShapeSquare.d.vue.ts → LayoutShapeRect.d.vue.ts} +2 -2
- package/dist/runtime/components/{LayoutShapeSquare.vue.d.ts → LayoutShapeRect.vue.d.ts} +2 -2
- package/dist/runtime/components/LayoutWindow.d.vue.ts +252 -14
- package/dist/runtime/components/LayoutWindow.vue +27 -48
- package/dist/runtime/components/LayoutWindow.vue.d.ts +252 -14
- package/dist/runtime/composables/useFrames.d.ts +859 -2
- package/dist/runtime/composables/useFrames.js +2 -0
- package/dist/runtime/demo/App.vue +122 -74
- package/dist/runtime/demo/DemoControls.vue +7 -5
- package/dist/runtime/demo/index.html +12 -0
- package/dist/runtime/demo/tailwind.css +1 -1
- package/dist/runtime/drag/CloseAction.d.ts +1 -2
- package/dist/runtime/drag/CloseAction.js +1 -1
- package/dist/runtime/drag/DragActionHandler.d.ts +5 -3
- package/dist/runtime/drag/DragActionHandler.js +40 -6
- package/dist/runtime/drag/FrameDragAction.js +6 -7
- package/dist/runtime/drag/SplitAction.d.ts +1 -2
- package/dist/runtime/drag/SplitAction.js +1 -1
- package/dist/runtime/drag/createDefaultHandlers.d.ts +1 -1
- package/dist/runtime/drag/defaultDragActions.d.ts +1 -1
- package/dist/runtime/helpers/consoleDebugWindow.d.ts +43 -0
- package/dist/runtime/helpers/consoleDebugWindow.js +222 -0
- package/dist/runtime/helpers/convertLayoutWindowToWorkspace.js +2 -2
- package/dist/runtime/helpers/framesRedistributeFix.d.ts +87 -0
- package/dist/runtime/helpers/framesRedistributeFix.js +54 -0
- package/dist/runtime/helpers/getMoveEdgeInfo.d.ts +6 -2
- package/dist/runtime/helpers/getMoveEdgeInfo.js +11 -2
- package/dist/runtime/helpers/getPinnedEdgesForCollapsedFrames.d.ts +5 -0
- package/dist/runtime/helpers/getPinnedEdgesForCollapsedFrames.js +18 -0
- package/dist/runtime/helpers/getShapeRectCss.d.ts +7 -0
- package/dist/runtime/helpers/{getShapeSquareCss.js → getShapeRectCss.js} +1 -1
- package/dist/runtime/helpers/index.d.ts +3 -3
- package/dist/runtime/helpers/index.js +3 -3
- package/dist/runtime/helpers/moveEdge.js +3 -1
- package/dist/runtime/helpers/pxSizeToScaledSize.d.ts +2 -0
- package/dist/runtime/helpers/{numberToScaledSize.js → pxSizeToScaledSize.js} +1 -1
- package/dist/runtime/helpers/updateWindowSizeWithEvent.d.ts +3 -0
- package/dist/runtime/helpers/validateLayoutShape.d.ts +5 -0
- package/dist/runtime/helpers/validateLayoutShape.js +28 -0
- package/dist/runtime/layout/applyFrameChanges.js +6 -0
- package/dist/runtime/layout/createSplitDecoShapes.js +1 -1
- package/dist/runtime/layout/getFrameCollapseInfo.d.ts +5 -2
- package/dist/runtime/layout/getFrameCollapseInfo.js +16 -46
- package/dist/runtime/layout/getFrameDockInfo.d.ts +1 -1
- package/dist/runtime/layout/getFrameDockInfo.js +5 -1
- package/dist/runtime/layout/getFrameUncollapseInfo.d.ts +5 -2
- package/dist/runtime/layout/getFrameUncollapseInfo.js +16 -50
- package/dist/runtime/layout/getFramesRedistributeInfo.d.ts +20 -4
- package/dist/runtime/layout/getFramesRedistributeInfo.js +31 -2
- package/dist/runtime/layout/getRelaxFramesInfo.d.ts +7 -0
- package/dist/runtime/layout/getRelaxFramesInfo.js +91 -0
- package/dist/runtime/layout/getUpdateWindowSizeInfo.d.ts +13 -0
- package/dist/runtime/layout/getUpdateWindowSizeInfo.js +65 -0
- package/dist/runtime/layout/index.d.ts +1 -0
- package/dist/runtime/layout/index.js +1 -0
- package/dist/runtime/layout/resizeFrame.js +1 -3
- package/dist/runtime/settings.d.ts +26 -19
- package/dist/runtime/settings.js +57 -22
- package/dist/runtime/types/index.d.ts +1243 -30
- package/dist/runtime/types/index.js +22 -8
- package/dist/runtime/types/vue.d.ts +41 -0
- package/dist/runtime/{drag/types.js → types/vue.js} +1 -0
- package/dist/runtime/utils/cssToKey.d.ts +5 -0
- package/dist/runtime/utils/cssToKey.js +3 -0
- package/package.json +11 -12
- package/src/module.ts +6 -1
- package/src/runtime/components/FrameDragHandle.vue +1 -1
- package/src/runtime/components/LayoutDecosEdges.vue +38 -0
- package/src/runtime/components/LayoutDecosRects.vue +42 -0
- package/src/runtime/components/LayoutDragEdgeGrabbed.vue +50 -0
- package/src/runtime/components/LayoutDragEdgeHandle.vue +52 -0
- package/src/runtime/components/LayoutDragEdgeVisible.vue +45 -0
- package/src/runtime/components/LayoutDragEdges.vue +78 -0
- package/src/runtime/components/LayoutEdgesActiveFrame.vue +43 -0
- package/src/runtime/components/LayoutFrame.vue +22 -15
- package/src/runtime/components/LayoutIntersectionHandle.vue +45 -0
- package/src/runtime/components/LayoutIntersectionVisible.vue +41 -0
- package/src/runtime/components/LayoutIntersections.vue +64 -0
- package/src/runtime/components/{LayoutShapeSquare.vue → LayoutShapeRect.vue} +2 -2
- package/src/runtime/components/LayoutWindow.vue +29 -49
- package/src/runtime/composables/useFrames.ts +7 -2
- package/src/runtime/demo/App.vue +128 -78
- package/src/runtime/demo/DemoControls.vue +7 -5
- package/src/runtime/demo/index.html +12 -0
- package/src/runtime/demo/tailwind.css +3 -0
- package/src/runtime/drag/CloseAction.ts +2 -3
- package/src/runtime/drag/DragActionHandler.ts +48 -10
- package/src/runtime/drag/FrameDragAction.ts +4 -5
- package/src/runtime/drag/SplitAction.ts +4 -3
- package/src/runtime/drag/createDefaultHandlers.ts +2 -1
- package/src/runtime/drag/defaultDragActions.ts +2 -1
- package/src/runtime/helpers/consoleDebugWindow.ts +331 -0
- package/src/runtime/helpers/convertLayoutWindowToWorkspace.ts +2 -2
- package/src/runtime/helpers/framesRedistributeFix.ts +162 -0
- package/src/runtime/helpers/getMoveEdgeInfo.ts +22 -5
- package/src/runtime/helpers/getPinnedEdgesForCollapsedFrames.ts +29 -0
- package/src/runtime/helpers/{getShapeSquareCss.ts → getShapeRectCss.ts} +3 -3
- package/src/runtime/helpers/index.ts +4 -3
- package/src/runtime/helpers/moveEdge.ts +3 -1
- package/src/runtime/helpers/{numberToScaledSize.ts → pxSizeToScaledSize.ts} +2 -2
- package/src/runtime/helpers/updateWindowSizeWithEvent.ts +3 -0
- package/src/runtime/helpers/validateLayoutShape.ts +46 -0
- package/src/runtime/layout/applyFrameChanges.ts +5 -0
- package/src/runtime/layout/createSplitDecoShapes.ts +1 -1
- package/src/runtime/layout/getFrameCollapseInfo.ts +26 -85
- package/src/runtime/layout/getFrameDockInfo.ts +8 -1
- package/src/runtime/layout/getFrameUncollapseInfo.ts +29 -96
- package/src/runtime/layout/getFramesRedistributeInfo.ts +60 -5
- package/src/runtime/layout/getRelaxFramesInfo.ts +139 -0
- package/src/runtime/layout/getUpdateWindowSizeInfo.ts +105 -0
- package/src/runtime/layout/index.ts +1 -0
- package/src/runtime/layout/resizeFrame.ts +4 -3
- package/src/runtime/settings.ts +78 -25
- package/src/runtime/types/index.ts +205 -33
- package/src/runtime/types/vue.ts +41 -0
- package/src/runtime/utils/cssToKey.ts +8 -0
- package/dist/runtime/components/LayoutDecos.vue +0 -26
- package/dist/runtime/components/LayoutEdges.d.vue.ts +0 -17
- package/dist/runtime/components/LayoutEdges.vue +0 -144
- package/dist/runtime/components/LayoutEdges.vue.d.ts +0 -17
- package/dist/runtime/drag/types.d.ts +0 -161
- package/dist/runtime/helpers/clampNumber.d.ts +0 -1
- package/dist/runtime/helpers/clampNumber.js +0 -3
- package/dist/runtime/helpers/getShapeSquareCss.d.ts +0 -7
- package/dist/runtime/helpers/numberToScaledSize.d.ts +0 -2
- package/src/runtime/components/LayoutDecos.vue +0 -28
- package/src/runtime/components/LayoutEdges.vue +0 -170
- package/src/runtime/drag/types.ts +0 -177
- package/src/runtime/helpers/clampNumber.ts +0 -9
- /package/dist/runtime/components/{LayoutShapeSquare.vue → LayoutShapeRect.vue} +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { DragActionHandler } from "./DragActionHandler.js"
|
|
2
|
-
import type { ActionDragChangeResult, DragState, IDragAction } from "./types.js"
|
|
3
2
|
|
|
4
3
|
import { dirToOrientation } from "../helpers/dirToOrientation.js"
|
|
5
4
|
import { getEdgeOrientation } from "../helpers/getEdgeOrientation.js"
|
|
@@ -7,7 +6,7 @@ import { oppositeSide } from "../helpers/oppositeSide.js"
|
|
|
7
6
|
import { applyFrameChanges } from "../layout/applyFrameChanges.js"
|
|
8
7
|
import { findFramesTouchingEdge } from "../layout/findFramesTouchingEdge.js"
|
|
9
8
|
import { getCloseFrameInfo } from "../layout/getCloseFrameInfo.js"
|
|
10
|
-
import type { CloseDeco } from "../types/index.js"
|
|
9
|
+
import type { ActionDragChangeResult, CloseDeco, DragState, IDragAction } from "../types/index.js"
|
|
11
10
|
import type { KnownError } from "../utils/KnownError.js"
|
|
12
11
|
|
|
13
12
|
export type CloseInfo = Exclude<ReturnType<typeof getCloseFrameInfo>, KnownError>
|
|
@@ -94,7 +93,7 @@ export class CloseAction implements IDragAction {
|
|
|
94
93
|
force,
|
|
95
94
|
shapes: [
|
|
96
95
|
{
|
|
97
|
-
type: "
|
|
96
|
+
type: "rect",
|
|
98
97
|
data: { x: _.x, y: _.y, width: _.width, height: _.height },
|
|
99
98
|
attrs: {
|
|
100
99
|
class: force
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { get, type RecordFromArray } from "@alanscodelog/utils"
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
import type { LayoutShape } from "../types/index.js"
|
|
3
|
+
import { isWindowEdge } from "../helpers/isWindowEdge.js"
|
|
4
|
+
import { findFramesTouchingEdge } from "../layout/findFramesTouchingEdge.js"
|
|
5
|
+
import type { DragChangeHandler, DragChangeResult, DragState, Edge, IDragAction, LayoutFrame, LayoutShape } from "../types/index.js"
|
|
6
|
+
import { LAYOUT_ERROR } from "../types/index.js"
|
|
7
|
+
import { KnownError } from "../utils/KnownError.js"
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Handles the lifecycle of a drag actions {@link IDragAction} and provides additional hooks.
|
|
@@ -21,10 +23,18 @@ export class DragActionHandler<
|
|
|
21
23
|
|
|
22
24
|
boundCancel: (e: PointerEvent | KeyboardEvent | undefined, state: DragState) => void
|
|
23
25
|
|
|
24
|
-
defaultOnDragChange: DragChangeHandler = (type, _e, state, _forceRecalculateEdges, _cancel) =>
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
defaultOnDragChange: DragChangeHandler = (type, _e, state, _forceRecalculateEdges, _cancel) => {
|
|
27
|
+
const isTouchingCollapsedFrameEdge = type === "move"
|
|
28
|
+
&& state.isDragging === "edge"
|
|
29
|
+
&& state.touchingFramesArrays.some(frames =>
|
|
30
|
+
// we check with a falsy check on PURPOSE, frames collapsed to 0 aren't an issue, they are ignored anyways
|
|
31
|
+
Object.values(frames).some(f => f.collapsed)
|
|
32
|
+
)
|
|
33
|
+
return {
|
|
34
|
+
updateEdges: type === "move" ? !(state.isDraggingFromWindowEdge || isTouchingCollapsedFrameEdge) : true,
|
|
35
|
+
shapes: []
|
|
36
|
+
}
|
|
37
|
+
}
|
|
28
38
|
|
|
29
39
|
hooks: {
|
|
30
40
|
/** Called while dragging during dragChange events. You can use this to update the dragging edges. */
|
|
@@ -54,6 +64,8 @@ export class DragActionHandler<
|
|
|
54
64
|
* Default onDragChange handler for when no action can handle the request.
|
|
55
65
|
*
|
|
56
66
|
* Should return true to allow the edges to be moved, or false to prevent it.
|
|
67
|
+
*
|
|
68
|
+
* The default prevents movement when the edge is a window edge and when the edge is touching a collapsed frame.
|
|
57
69
|
*/
|
|
58
70
|
defaultOnDragChange?: DragChangeHandler
|
|
59
71
|
) {
|
|
@@ -150,6 +162,32 @@ export class DragActionHandler<
|
|
|
150
162
|
}
|
|
151
163
|
}
|
|
152
164
|
|
|
165
|
+
annotateEdges(edges: Edge[], frames: LayoutFrame[]): void {
|
|
166
|
+
for (const edge of edges) {
|
|
167
|
+
// edge already has an error
|
|
168
|
+
if (edge.error) continue
|
|
169
|
+
for (const action of Object.values<IDragAction>(this.actions)) {
|
|
170
|
+
action.annotateEdge?.(edge, frames)
|
|
171
|
+
if (edge.error) break
|
|
172
|
+
}
|
|
173
|
+
// window edges don't resize anything
|
|
174
|
+
if (!edge.error && !isWindowEdge(edge)) {
|
|
175
|
+
const touching = findFramesTouchingEdge(edge, frames)
|
|
176
|
+
if (touching) {
|
|
177
|
+
// we check with a falsy check on PURPOSE, frames collapsed to 0 aren't an issue, they are ignored anyways
|
|
178
|
+
const collapsedFrame = touching.find(f => f.frame.collapsed)
|
|
179
|
+
if (collapsedFrame) {
|
|
180
|
+
edge.error = new KnownError(
|
|
181
|
+
LAYOUT_ERROR.CANT_RESIZE_COLLAPSED_FRAME,
|
|
182
|
+
"Cannot Move: Can't Resize Collapsed Frame",
|
|
183
|
+
{ frame: (collapsedFrame.frame as LayoutFrame) }
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
153
191
|
onDragApply(
|
|
154
192
|
state: DragState,
|
|
155
193
|
forceRecalculateEdges: () => void
|
|
@@ -177,14 +215,14 @@ export class DragActionHandler<
|
|
|
177
215
|
type: "before" | "after" | string,
|
|
178
216
|
state: DragState,
|
|
179
217
|
pluginState: Record<string, any> = {},
|
|
180
|
-
/** Object key to filter the state by, e.g. state.win.frames. If
|
|
218
|
+
/** Object key to filter the state by, e.g. state.win.frames. If false is ignored. The idea is you pass this.debug and users can set this.debug to a string to filter. */
|
|
181
219
|
key?: string | boolean
|
|
182
220
|
): void {
|
|
221
|
+
if (key === false) return
|
|
183
222
|
let res = { state, pluginState }
|
|
184
|
-
let pickedRes: any = {}
|
|
185
223
|
if (typeof key === "string" && key !== "") {
|
|
186
|
-
pickedRes = {} as any
|
|
187
224
|
const paths = key.split(",").map(_ => _.trim()).filter(_ => _)
|
|
225
|
+
const pickedRes: Record<string, any> = {}
|
|
188
226
|
for (const path of paths) {
|
|
189
227
|
pickedRes[path] = get(res, path.split("."))
|
|
190
228
|
}
|
|
@@ -96,12 +96,11 @@ export class FrameDragAction implements IDragAction {
|
|
|
96
96
|
state: DragState,
|
|
97
97
|
result: LayoutChange<"split" | "swap" | "rearrange" | "dock"> | KnownError | undefined
|
|
98
98
|
): FrameDragDeco[] {
|
|
99
|
-
let decos: FrameDragDeco[] = []
|
|
100
99
|
const isError = result instanceof Error
|
|
101
100
|
const frame = state.dragHoveredFrame
|
|
102
101
|
|
|
103
102
|
if (!matchedZone || !frame) {
|
|
104
|
-
decos = []
|
|
103
|
+
const decos: FrameDragDeco[] = []
|
|
105
104
|
this.modifyDecos(decos)
|
|
106
105
|
return decos
|
|
107
106
|
}
|
|
@@ -112,13 +111,13 @@ export class FrameDragAction implements IDragAction {
|
|
|
112
111
|
? `deco-frame-drag deco-frame-drag-error deco-frame-drag-${matchedZone.type}-${matchedZone.side} bg-red-500/50`
|
|
113
112
|
: `deco-frame-drag deco-frame-drag-${matchedZone.type}-${matchedZone.side} bg-blue-500/50`
|
|
114
113
|
|
|
115
|
-
decos = [{
|
|
114
|
+
const decos: FrameDragDeco[] = [{
|
|
116
115
|
id: frame.id,
|
|
117
116
|
type: "drop",
|
|
118
117
|
position: matchedZone.side ?? "center",
|
|
119
118
|
shapes: [
|
|
120
119
|
{
|
|
121
|
-
type: "
|
|
120
|
+
type: "rect",
|
|
122
121
|
data: matchedZone,
|
|
123
122
|
attrs: clipPath
|
|
124
123
|
? { class: classes, style: `clip-path:${clipPath}` }
|
|
@@ -160,7 +159,7 @@ export class FrameDragAction implements IDragAction {
|
|
|
160
159
|
// for the hints
|
|
161
160
|
if (!(this.state.lastReturn instanceof Error)) this.state.lastReturn.info = "swap"
|
|
162
161
|
}
|
|
163
|
-
} else if (matchedZone.type === "window") {
|
|
162
|
+
} else if (matchedZone.type === "window" && dragHoveredFrame && !dragHoveredFrame.docked) {
|
|
164
163
|
this.state.lastReturn = getFrameDockInfo(win, draggingFrameId!, matchedZone.side)
|
|
165
164
|
// for the hints
|
|
166
165
|
if (!(this.state.lastReturn instanceof Error)) this.state.lastReturn.info = "dock"
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { DragActionHandler } from "./DragActionHandler.js"
|
|
2
|
-
import type { ActionDragChangeResult, DragState, IDragAction } from "./types.js"
|
|
3
2
|
|
|
4
3
|
import { getEdgeOrientation } from "../helpers/getEdgeOrientation.js"
|
|
5
4
|
import { oppositeSide } from "../helpers/oppositeSide.js"
|
|
6
5
|
import { applyFrameChanges } from "../layout/applyFrameChanges.js"
|
|
7
6
|
import { createSplitDecoFromDrag } from "../layout/createSplitDecoFromDrag.js"
|
|
8
7
|
import { getFrameSplitInfo } from "../layout/getFrameSplitInfo.js"
|
|
9
|
-
import type { LayoutShape, Point, SplitDeco } from "../types/index.js"
|
|
8
|
+
import type { ActionDragChangeResult, DragState, IDragAction, LayoutShape, Point, SplitDeco } from "../types/index.js"
|
|
10
9
|
import type { KnownError } from "../utils/KnownError.js"
|
|
11
10
|
|
|
12
11
|
export type SplitInfo = Exclude<ReturnType<typeof getFrameSplitInfo>, KnownError>
|
|
13
12
|
|
|
13
|
+
// note that we dont annotateEdges because is possible to forexample drag from a window edge to a frame that can be split, moving over a collapsed frame that was sharing the edge but can't be split
|
|
14
|
+
|
|
14
15
|
export class SplitAction implements IDragAction {
|
|
15
16
|
name = "split" as const
|
|
16
17
|
|
|
@@ -180,7 +181,7 @@ export class SplitAction implements IDragAction {
|
|
|
180
181
|
if (!ok) {
|
|
181
182
|
if (dragHoveredFrame && dragHoveredFrame.docked) {
|
|
182
183
|
const errorDeco: LayoutShape = {
|
|
183
|
-
type: "
|
|
184
|
+
type: "rect",
|
|
184
185
|
data: {
|
|
185
186
|
x: dragHoveredFrame.x,
|
|
186
187
|
y: dragHoveredFrame.y,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { CloseAction } from "./CloseAction"
|
|
2
2
|
import { FrameDragAction } from "./FrameDragAction.js"
|
|
3
3
|
import { SplitAction } from "./SplitAction.js"
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
import type { IDragAction } from "../types/index.js"
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Creates the default drag actions (Split, Close, FrameDrag).
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { CloseAction } from "./CloseAction"
|
|
2
2
|
import { FrameDragAction } from "./FrameDragAction.js"
|
|
3
3
|
import { SplitAction } from "./SplitAction.js"
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
import type { IDragAction } from "../types/index.js"
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Creates the default drag actions (Split, Close, FrameDrag).
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { castType } from "@alanscodelog/utils/castType"
|
|
2
|
+
|
|
3
|
+
import { settings } from "../settings.js"
|
|
4
|
+
import type { LayoutWindow, Point, Size } from "../types/index.js"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* See also {@link createDebugLabeler} for customizing the labeling function, for
|
|
8
|
+
* example to debug the pixel sizes instead of position/size:
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* consoleDebugWindow(clone, 50, createDebugLabeler({
|
|
12
|
+
* showPxSize: true,
|
|
13
|
+
* showSize: false,
|
|
14
|
+
* showPos: false,
|
|
15
|
+
* getWin: () => clone
|
|
16
|
+
* }))
|
|
17
|
+
*
|
|
18
|
+
* @experimental
|
|
19
|
+
*/
|
|
20
|
+
export function consoleDebugWindow(
|
|
21
|
+
win: LayoutWindow,
|
|
22
|
+
targetWidth = 100,
|
|
23
|
+
labelFunc = createDebugLabeler()
|
|
24
|
+
) {
|
|
25
|
+
const consoleCharAspect = 0.5
|
|
26
|
+
|
|
27
|
+
const windowAspect = win.pxWidth / win.pxHeight
|
|
28
|
+
|
|
29
|
+
const effectiveRatio = (1 / windowAspect) * consoleCharAspect
|
|
30
|
+
// eslint-disable-next-line no-console
|
|
31
|
+
console.log(boxesToText(Object.values(win.frames), targetWidth, {
|
|
32
|
+
heightScale: effectiveRatio,
|
|
33
|
+
labelFunc
|
|
34
|
+
}))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type Border = {
|
|
38
|
+
horizontal: string
|
|
39
|
+
vertical: string
|
|
40
|
+
topLeft: string
|
|
41
|
+
topRight: string
|
|
42
|
+
bottomLeft: string
|
|
43
|
+
bottomRight: string
|
|
44
|
+
cross: string
|
|
45
|
+
fill: string
|
|
46
|
+
labelBg: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function boxesToText<T extends Size & Point & { id?: string }>(
|
|
50
|
+
boxes: T[],
|
|
51
|
+
targetWidth = 100,
|
|
52
|
+
{
|
|
53
|
+
border = {} as any,
|
|
54
|
+
heightScale = 1,
|
|
55
|
+
labelFunc = () => ""
|
|
56
|
+
}: {
|
|
57
|
+
border?: Partial<Border>
|
|
58
|
+
heightScale?: number
|
|
59
|
+
labelFunc?: (box: T) => string
|
|
60
|
+
} = {}
|
|
61
|
+
) {
|
|
62
|
+
border = {
|
|
63
|
+
horizontal: "─",
|
|
64
|
+
vertical: "│",
|
|
65
|
+
topLeft: "┌",
|
|
66
|
+
topRight: "┐",
|
|
67
|
+
bottomLeft: "└",
|
|
68
|
+
bottomRight: "┘",
|
|
69
|
+
cross: "┼",
|
|
70
|
+
fill: " ",
|
|
71
|
+
labelBg: " ",
|
|
72
|
+
...border
|
|
73
|
+
}
|
|
74
|
+
castType<Border>(border)
|
|
75
|
+
|
|
76
|
+
const normBoxes = boxes.map(box => ({
|
|
77
|
+
...box,
|
|
78
|
+
original: box,
|
|
79
|
+
id: box.id,
|
|
80
|
+
x: box.x / 100,
|
|
81
|
+
y: box.y / 100,
|
|
82
|
+
width: box.width / 100,
|
|
83
|
+
height: box.height / 100
|
|
84
|
+
}))
|
|
85
|
+
|
|
86
|
+
let minX = Infinity, minY = Infinity
|
|
87
|
+
let maxX = -Infinity, maxY = -Infinity
|
|
88
|
+
|
|
89
|
+
normBoxes.forEach(box => {
|
|
90
|
+
minX = Math.min(minX, box.x)
|
|
91
|
+
minY = Math.min(minY, box.y)
|
|
92
|
+
maxX = Math.max(maxX, box.x + box.width)
|
|
93
|
+
maxY = Math.max(maxY, box.y + box.height)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const availableWidth = targetWidth
|
|
97
|
+
const availableHeight = Math.floor(availableWidth * heightScale)
|
|
98
|
+
|
|
99
|
+
const totalWidth = maxX - minX || 1
|
|
100
|
+
const totalHeight = maxY - minY || 1
|
|
101
|
+
|
|
102
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
103
|
+
const scaleX_final = availableWidth / totalWidth
|
|
104
|
+
const scaleY_final = availableHeight / totalHeight
|
|
105
|
+
/* eslint-enable @typescript-eslint/naming-convention */
|
|
106
|
+
|
|
107
|
+
const scaledBoxes = normBoxes.map(box => {
|
|
108
|
+
const idPart = box.id !== undefined && box.id !== null && box.id !== ""
|
|
109
|
+
? String(box.id)
|
|
110
|
+
: "??"
|
|
111
|
+
const funcLabel = labelFunc(box.original)
|
|
112
|
+
const label = funcLabel && funcLabel.trim()
|
|
113
|
+
? `${idPart} ${funcLabel}`
|
|
114
|
+
: idPart
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
id: box.id,
|
|
118
|
+
x: (box.x - minX) * scaleX_final,
|
|
119
|
+
y: (box.y - minY) * scaleY_final,
|
|
120
|
+
width: box.width * scaleX_final,
|
|
121
|
+
height: box.height * scaleY_final,
|
|
122
|
+
label: label,
|
|
123
|
+
original: box
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
const cols = Math.min(availableWidth, Math.floor(totalWidth * scaleX_final))
|
|
128
|
+
const rows = Math.min(availableHeight, Math.floor(totalHeight * scaleY_final))
|
|
129
|
+
|
|
130
|
+
if (cols < 1 || rows < 1) {
|
|
131
|
+
return new Error("Grid too small to display")
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
type CellData = {
|
|
135
|
+
char: string
|
|
136
|
+
priority: number
|
|
137
|
+
type: "horizontal" | "vertical" | "corner" | "cross" | "fill" | "label"
|
|
138
|
+
boxIndex: number
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const grid: CellData[][] = Array(rows).fill(null).map(() =>
|
|
142
|
+
Array(cols).fill(null).map(() => ({
|
|
143
|
+
char: border.fill,
|
|
144
|
+
priority: 0,
|
|
145
|
+
type: "fill" as const,
|
|
146
|
+
boxIndex: -1
|
|
147
|
+
}))
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
function setCell(x: number, y: number, char: string, priority: number, type: CellData["type"], boxIndex: number) {
|
|
151
|
+
if (x < 0 || y < 0 || x >= cols || y >= rows) return
|
|
152
|
+
|
|
153
|
+
const current = grid[y][x]
|
|
154
|
+
|
|
155
|
+
if (boxIndex > current.boxIndex
|
|
156
|
+
|| (boxIndex === current.boxIndex && priority > current.priority)) {
|
|
157
|
+
grid[y][x] = { char, priority, type, boxIndex }
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function getCell(x: number, y: number): CellData | undefined {
|
|
162
|
+
if (x < 0 || y < 0 || x >= cols || y >= rows) return undefined
|
|
163
|
+
return grid[y][x]
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
for (let boxIndex = scaledBoxes.length - 1; boxIndex >= 0; boxIndex--) {
|
|
167
|
+
const box = scaledBoxes[boxIndex]
|
|
168
|
+
const x1 = Math.round(box.x)
|
|
169
|
+
const y1 = Math.round(box.y)
|
|
170
|
+
const x2 = Math.round(box.x + box.width)
|
|
171
|
+
const y2 = Math.round(box.y + box.height)
|
|
172
|
+
|
|
173
|
+
const clampedX1 = Math.max(0, Math.min(cols - 1, x1))
|
|
174
|
+
const clampedY1 = Math.max(0, Math.min(rows - 1, y1))
|
|
175
|
+
const clampedX2 = Math.max(0, Math.min(cols - 1, x2))
|
|
176
|
+
const clampedY2 = Math.max(0, Math.min(rows - 1, y2))
|
|
177
|
+
|
|
178
|
+
if (clampedX1 >= clampedX2 || clampedY1 >= clampedY2) continue
|
|
179
|
+
|
|
180
|
+
const edgePriority = 10
|
|
181
|
+
|
|
182
|
+
if (clampedY1 >= 0 && clampedY1 < rows) {
|
|
183
|
+
for (let x = clampedX1 + 1; x < clampedX2; x++) {
|
|
184
|
+
const cell = getCell(x, clampedY1)
|
|
185
|
+
if (cell && cell.type === "vertical" && cell.boxIndex !== boxIndex) {
|
|
186
|
+
setCell(x, clampedY1, border.cross, edgePriority, "cross", boxIndex)
|
|
187
|
+
} else {
|
|
188
|
+
setCell(x, clampedY1, border.horizontal, edgePriority, "horizontal", boxIndex)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (clampedY2 >= 0 && clampedY2 < rows && clampedY2 !== clampedY1) {
|
|
194
|
+
for (let x = clampedX1 + 1; x < clampedX2; x++) {
|
|
195
|
+
const cell = getCell(x, clampedY2)
|
|
196
|
+
if (cell && cell.type === "vertical" && cell.boxIndex !== boxIndex) {
|
|
197
|
+
setCell(x, clampedY2, border.cross, edgePriority, "cross", boxIndex)
|
|
198
|
+
} else {
|
|
199
|
+
setCell(x, clampedY2, border.horizontal, edgePriority, "horizontal", boxIndex)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (clampedX1 >= 0 && clampedX1 < cols) {
|
|
205
|
+
for (let y = clampedY1 + 1; y < clampedY2; y++) {
|
|
206
|
+
const cell = getCell(clampedX1, y)
|
|
207
|
+
if (cell && (cell.type === "horizontal" || cell.type === "cross") && cell.boxIndex !== boxIndex) {
|
|
208
|
+
setCell(clampedX1, y, border.cross, edgePriority, "cross", boxIndex)
|
|
209
|
+
} else {
|
|
210
|
+
setCell(clampedX1, y, border.vertical, edgePriority, "vertical", boxIndex)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (clampedX2 >= 0 && clampedX2 < cols && clampedX2 !== clampedX1) {
|
|
216
|
+
for (let y = clampedY1 + 1; y < clampedY2; y++) {
|
|
217
|
+
const cell = getCell(clampedX2, y)
|
|
218
|
+
if (cell && (cell.type === "horizontal" || cell.type === "cross") && cell.boxIndex !== boxIndex) {
|
|
219
|
+
setCell(clampedX2, y, border.cross, edgePriority, "cross", boxIndex)
|
|
220
|
+
} else {
|
|
221
|
+
setCell(clampedX2, y, border.vertical, edgePriority, "vertical", boxIndex)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const cornerPriority = 20
|
|
227
|
+
const corners = [
|
|
228
|
+
{ x: clampedX1, y: clampedY1, char: border.topLeft, type: "corner" as const },
|
|
229
|
+
{ x: clampedX2, y: clampedY1, char: border.topRight, type: "corner" as const },
|
|
230
|
+
{ x: clampedX1, y: clampedY2, char: border.bottomLeft, type: "corner" as const },
|
|
231
|
+
{ x: clampedX2, y: clampedY2, char: border.bottomRight, type: "corner" as const }
|
|
232
|
+
]
|
|
233
|
+
|
|
234
|
+
corners.forEach(corner => {
|
|
235
|
+
if (corner.x < 0 || corner.y < 0 || corner.x >= cols || corner.y >= rows) return
|
|
236
|
+
|
|
237
|
+
const cell = getCell(corner.x, corner.y)
|
|
238
|
+
if (cell && cell.boxIndex !== boxIndex && cell.type !== "fill") {
|
|
239
|
+
if (cell.type === "horizontal" || cell.type === "vertical" || cell.type === "cross") {
|
|
240
|
+
setCell(corner.x, corner.y, border.cross, cornerPriority, "cross", boxIndex)
|
|
241
|
+
} else {
|
|
242
|
+
setCell(corner.x, corner.y, corner.char, cornerPriority, corner.type, boxIndex)
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
setCell(corner.x, corner.y, corner.char, cornerPriority, corner.type, boxIndex)
|
|
246
|
+
}
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
const labelPriority = 30
|
|
250
|
+
if (box.label && box.label.trim().length > 0) {
|
|
251
|
+
const labelText = String(box.label)
|
|
252
|
+
|
|
253
|
+
const boxWidth = clampedX2 - clampedX1
|
|
254
|
+
const boxHeight = clampedY2 - clampedY1
|
|
255
|
+
|
|
256
|
+
const interiorWidth = boxWidth - 2
|
|
257
|
+
const interiorHeight = boxHeight - 2
|
|
258
|
+
|
|
259
|
+
if (interiorWidth >= 1 && interiorHeight >= 0) {
|
|
260
|
+
let displayText = labelText
|
|
261
|
+
if (displayText.length > interiorWidth) {
|
|
262
|
+
displayText = displayText.substring(0, interiorWidth)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const interiorLeft = clampedX1 + 1
|
|
266
|
+
const interiorRight = clampedX2 - 1
|
|
267
|
+
const interiorTop = clampedY1 + 1
|
|
268
|
+
const interiorBottom = clampedY2 - 1
|
|
269
|
+
|
|
270
|
+
const interiorCenterX = Math.floor((interiorLeft + interiorRight) / 2)
|
|
271
|
+
const interiorCenterY = Math.floor((interiorTop + interiorBottom) / 2)
|
|
272
|
+
|
|
273
|
+
let startX = interiorCenterX - Math.floor(displayText.length / 2)
|
|
274
|
+
|
|
275
|
+
if (startX < interiorLeft) {
|
|
276
|
+
startX = interiorLeft
|
|
277
|
+
}
|
|
278
|
+
if (startX + displayText.length > interiorRight) {
|
|
279
|
+
startX = interiorRight - displayText.length
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
for (let i = 0; i < displayText.length; i++) {
|
|
283
|
+
const xPos = startX + i
|
|
284
|
+
const yPos = interiorCenterY
|
|
285
|
+
|
|
286
|
+
if (xPos >= interiorLeft && xPos < interiorRight
|
|
287
|
+
&& yPos >= interiorTop && yPos < interiorBottom) {
|
|
288
|
+
setCell(xPos, yPos, border.labelBg, labelPriority, "label", boxIndex)
|
|
289
|
+
setCell(xPos, yPos, displayText[i], labelPriority + 1, "label", boxIndex)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const displayGrid = grid.map(row => row.map(cell => cell.char).join("")).join("\n")
|
|
297
|
+
return displayGrid
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
export function createDebugLabeler({
|
|
302
|
+
showPxSize = false,
|
|
303
|
+
showCollapsed = true,
|
|
304
|
+
showDocked = true,
|
|
305
|
+
showSize = true,
|
|
306
|
+
showPos = true,
|
|
307
|
+
getWin
|
|
308
|
+
}: {
|
|
309
|
+
showPxSize?: boolean
|
|
310
|
+
showCollapsed?: boolean
|
|
311
|
+
showDocked?: boolean
|
|
312
|
+
showSize?: boolean
|
|
313
|
+
showPos?: boolean
|
|
314
|
+
getWin?: (f: LayoutWindow["frames"][string]) => Pick<LayoutWindow, "pxWidth" | "pxHeight">
|
|
315
|
+
} = {}) {
|
|
316
|
+
return function (f: LayoutWindow["frames"][string]) {
|
|
317
|
+
if (showPxSize && !getWin) throw new Error("showPxSize is true but getWin is not provided")
|
|
318
|
+
const win = showPxSize ? getWin?.(f) : undefined
|
|
319
|
+
|
|
320
|
+
const widthPx = showPxSize ? Math.round(f.width / settings.maxInt * win!.pxWidth) : undefined
|
|
321
|
+
const heightPx = showPxSize ? Math.round(f.height / settings.maxInt * win!.pxHeight) : undefined
|
|
322
|
+
const isCollapsed = showCollapsed && typeof f.collapsed === "number" ? "~" : ""
|
|
323
|
+
const isDocked = showDocked && f.docked ? "*" : ""
|
|
324
|
+
const size = showSize ? ` ${f.width}x${f.height}` : ""
|
|
325
|
+
const pos = showPos ? ` ${f.x},${f.y}` : ""
|
|
326
|
+
const pxSize = showPxSize ? ` ${widthPx}x${heightPx}px` : ""
|
|
327
|
+
|
|
328
|
+
return `${isCollapsed}${isDocked}${size}${pos}${pxSize}`
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { walk } from "@alanscodelog/utils/walk"
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
4
|
LayoutWindow,
|
|
@@ -8,7 +8,7 @@ import type {
|
|
|
8
8
|
export function convertLayoutWindowToWorkspace(layout: LayoutWindow | Workspace): Workspace {
|
|
9
9
|
// clone to avoid accidental property removal issues
|
|
10
10
|
// cast to be able to delete the properties
|
|
11
|
-
const l =
|
|
11
|
+
const l = walk(layout, undefined, { save: true }) as Partial<LayoutWindow>
|
|
12
12
|
delete l.id
|
|
13
13
|
delete l.pxHeight
|
|
14
14
|
delete l.pxWidth
|