@witchcraft/layout 0.1.2 → 0.2.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.
Files changed (190) hide show
  1. package/README.md +27 -24
  2. package/dist/module.json +1 -1
  3. package/dist/runtime/components/FrameDragHandle.d.vue.ts +15 -0
  4. package/dist/runtime/components/FrameDragHandle.vue +28 -0
  5. package/dist/runtime/components/FrameDragHandle.vue.d.ts +15 -0
  6. package/dist/runtime/components/LayoutDecos.d.vue.ts +2 -4
  7. package/dist/runtime/components/LayoutDecos.vue +10 -29
  8. package/dist/runtime/components/LayoutDecos.vue.d.ts +2 -4
  9. package/dist/runtime/components/LayoutEdges.d.vue.ts +3 -3
  10. package/dist/runtime/components/LayoutEdges.vue +9 -10
  11. package/dist/runtime/components/LayoutEdges.vue.d.ts +3 -3
  12. package/dist/runtime/components/LayoutFrame.d.vue.ts +1 -1
  13. package/dist/runtime/components/LayoutFrame.vue +1 -4
  14. package/dist/runtime/components/LayoutFrame.vue.d.ts +1 -1
  15. package/dist/runtime/components/LayoutShapeSquare.d.vue.ts +3 -1
  16. package/dist/runtime/components/LayoutShapeSquare.vue +3 -5
  17. package/dist/runtime/components/LayoutShapeSquare.vue.d.ts +3 -1
  18. package/dist/runtime/components/LayoutWindow.d.vue.ts +26 -12
  19. package/dist/runtime/components/LayoutWindow.vue +95 -84
  20. package/dist/runtime/components/LayoutWindow.vue.d.ts +26 -12
  21. package/dist/runtime/composables/useFrames.d.ts +15 -13
  22. package/dist/runtime/composables/useFrames.js +59 -39
  23. package/dist/runtime/demo/App.vue +116 -30
  24. package/dist/runtime/demo/DemoControls.d.vue.ts +4 -1
  25. package/dist/runtime/demo/DemoControls.vue +98 -4
  26. package/dist/runtime/demo/DemoControls.vue.d.ts +4 -1
  27. package/dist/runtime/drag/CloseAction.d.ts +26 -5
  28. package/dist/runtime/drag/CloseAction.js +87 -40
  29. package/dist/runtime/drag/DragActionHandler.d.ts +20 -8
  30. package/dist/runtime/drag/DragActionHandler.js +47 -12
  31. package/dist/runtime/drag/FrameDragAction.d.ts +45 -0
  32. package/dist/runtime/drag/FrameDragAction.js +143 -0
  33. package/dist/runtime/drag/SplitAction.d.ts +32 -11
  34. package/dist/runtime/drag/SplitAction.js +82 -24
  35. package/dist/runtime/drag/createDefaultHandlers.d.ts +9 -0
  36. package/dist/runtime/drag/createDefaultHandlers.js +10 -0
  37. package/dist/runtime/drag/defaultDragActions.d.ts +9 -0
  38. package/dist/runtime/drag/defaultDragActions.js +10 -0
  39. package/dist/runtime/drag/types.d.ts +82 -13
  40. package/dist/runtime/drag/types.js +1 -0
  41. package/dist/runtime/helpers/createZoneSideClipPath.d.ts +12 -0
  42. package/dist/runtime/helpers/createZoneSideClipPath.js +17 -0
  43. package/dist/runtime/helpers/doEdgesOverlap.d.ts +3 -1
  44. package/dist/runtime/helpers/doEdgesOverlap.js +5 -5
  45. package/dist/runtime/helpers/getDockBoundaries.d.ts +19 -0
  46. package/dist/runtime/helpers/getDockBoundaries.js +14 -0
  47. package/dist/runtime/helpers/getEdgeLength.d.ts +2 -0
  48. package/dist/runtime/helpers/getEdgeLength.js +5 -0
  49. package/dist/runtime/helpers/getIntersections.js +2 -2
  50. package/dist/runtime/helpers/getIntersectionsCss.js +2 -2
  51. package/dist/runtime/helpers/getMoveEdgeInfo.js +2 -2
  52. package/dist/runtime/helpers/getResizeLimit.js +2 -2
  53. package/dist/runtime/helpers/getShapeSquareCss.js +2 -2
  54. package/dist/runtime/helpers/getVisualEdgeCss.js +2 -2
  55. package/dist/runtime/helpers/getVisualEdges.d.ts +1 -1
  56. package/dist/runtime/helpers/getVisualEdges.js +4 -3
  57. package/dist/runtime/helpers/index.d.ts +4 -0
  58. package/dist/runtime/helpers/index.js +4 -0
  59. package/dist/runtime/helpers/isEdgeEqual.js +2 -4
  60. package/dist/runtime/helpers/isWindowEdge.js +2 -2
  61. package/dist/runtime/helpers/isWindowEdgePoint.js +2 -2
  62. package/dist/runtime/helpers/moveEdge.js +2 -2
  63. package/dist/runtime/helpers/numberToScaledPercent.d.ts +1 -1
  64. package/dist/runtime/helpers/numberToScaledPercent.js +2 -2
  65. package/dist/runtime/helpers/numberToScaledSize.js +2 -2
  66. package/dist/runtime/helpers/rotateFrames.d.ts +7 -0
  67. package/dist/runtime/helpers/rotateFrames.js +36 -0
  68. package/dist/runtime/helpers/scaledPointToPx.d.ts +13 -0
  69. package/dist/runtime/helpers/scaledPointToPx.js +7 -0
  70. package/dist/runtime/helpers/toWindowCoord.js +2 -2
  71. package/dist/runtime/layout/applyFrameChanges.d.ts +10 -0
  72. package/dist/runtime/layout/applyFrameChanges.js +29 -0
  73. package/dist/runtime/layout/createSplitDecoFromDrag.d.ts +6 -1
  74. package/dist/runtime/layout/createSplitDecoFromDrag.js +4 -4
  75. package/dist/runtime/layout/createSplitDecoShapes.d.ts +7 -0
  76. package/dist/runtime/layout/{createSplitDecoEdge.js → createSplitDecoShapes.js} +6 -3
  77. package/dist/runtime/layout/debugFrame.js +2 -1
  78. package/dist/runtime/layout/findSafeSplitEdge.js +2 -2
  79. package/dist/runtime/layout/frameCreate.js +2 -2
  80. package/dist/runtime/layout/getCloseFrameInfo.d.ts +7 -6
  81. package/dist/runtime/layout/getCloseFrameInfo.js +10 -3
  82. package/dist/runtime/layout/getDragZones.d.ts +8 -0
  83. package/dist/runtime/layout/getDragZones.js +32 -0
  84. package/dist/runtime/layout/getFillEmptySpaceInfo.d.ts +65 -0
  85. package/dist/runtime/layout/getFillEmptySpaceInfo.js +69 -0
  86. package/dist/runtime/layout/getFrameCollapseInfo.d.ts +13 -0
  87. package/dist/runtime/layout/getFrameCollapseInfo.js +93 -0
  88. package/dist/runtime/layout/getFrameDockInfo.d.ts +9 -0
  89. package/dist/runtime/layout/getFrameDockInfo.js +82 -0
  90. package/dist/runtime/layout/getFrameDragZones.d.ts +16 -0
  91. package/dist/runtime/layout/getFrameDragZones.js +74 -0
  92. package/dist/runtime/layout/getFrameRearrangeInfo.d.ts +139 -0
  93. package/dist/runtime/layout/getFrameRearrangeInfo.js +87 -0
  94. package/dist/runtime/layout/getFrameSplitInfo.d.ts +7 -5
  95. package/dist/runtime/layout/getFrameSplitInfo.js +10 -3
  96. package/dist/runtime/layout/getFrameSwapInfo.d.ts +9 -0
  97. package/dist/runtime/layout/getFrameSwapInfo.js +27 -0
  98. package/dist/runtime/layout/getFrameTo.js +2 -2
  99. package/dist/runtime/layout/getFrameUncollapseInfo.d.ts +12 -0
  100. package/dist/runtime/layout/getFrameUncollapseInfo.js +88 -0
  101. package/dist/runtime/layout/getFrameUndockInfo.d.ts +13 -0
  102. package/dist/runtime/layout/getFrameUndockInfo.js +51 -0
  103. package/dist/runtime/layout/getFramesRedistributeInfo.d.ts +29 -0
  104. package/dist/runtime/layout/getFramesRedistributeInfo.js +53 -0
  105. package/dist/runtime/layout/getWindowDragZones.d.ts +6 -0
  106. package/dist/runtime/layout/getWindowDragZones.js +49 -0
  107. package/dist/runtime/layout/index.d.ts +14 -5
  108. package/dist/runtime/layout/index.js +14 -5
  109. package/dist/runtime/layout/isPointInRect.d.ts +7 -0
  110. package/dist/runtime/layout/{isPointInFrame.js → isPointInRect.js} +1 -1
  111. package/dist/runtime/layout/resizeFrame.js +2 -2
  112. package/dist/runtime/settings.d.ts +41 -16
  113. package/dist/runtime/settings.js +95 -53
  114. package/dist/runtime/types/index.d.ts +324 -55
  115. package/dist/runtime/types/index.js +54 -20
  116. package/package.json +28 -29
  117. package/src/runtime/components/FrameDragHandle.vue +30 -0
  118. package/src/runtime/components/LayoutDecos.vue +12 -36
  119. package/src/runtime/components/LayoutEdges.vue +27 -22
  120. package/src/runtime/components/LayoutFrame.vue +6 -5
  121. package/src/runtime/components/LayoutShapeSquare.vue +11 -5
  122. package/src/runtime/components/LayoutWindow.vue +110 -101
  123. package/src/runtime/composables/useFrames.ts +80 -50
  124. package/src/runtime/demo/App.vue +126 -36
  125. package/src/runtime/demo/DemoControls.vue +115 -6
  126. package/src/runtime/drag/CloseAction.ts +106 -44
  127. package/src/runtime/drag/DragActionHandler.ts +71 -20
  128. package/src/runtime/drag/FrameDragAction.ts +202 -0
  129. package/src/runtime/drag/SplitAction.ts +106 -34
  130. package/src/runtime/drag/createDefaultHandlers.ts +19 -0
  131. package/src/runtime/drag/defaultDragActions.ts +19 -0
  132. package/src/runtime/drag/types.ts +90 -20
  133. package/src/runtime/helpers/createZoneSideClipPath.ts +41 -0
  134. package/src/runtime/helpers/doEdgesOverlap.ts +11 -5
  135. package/src/runtime/helpers/getDockBoundaries.ts +36 -0
  136. package/src/runtime/helpers/getEdgeLength.ts +10 -0
  137. package/src/runtime/helpers/getIntersections.ts +2 -2
  138. package/src/runtime/helpers/getIntersectionsCss.ts +2 -2
  139. package/src/runtime/helpers/getMoveEdgeInfo.ts +2 -2
  140. package/src/runtime/helpers/getResizeLimit.ts +2 -2
  141. package/src/runtime/helpers/getShapeSquareCss.ts +2 -2
  142. package/src/runtime/helpers/getVisualEdgeCss.ts +2 -2
  143. package/src/runtime/helpers/getVisualEdges.ts +5 -4
  144. package/src/runtime/helpers/index.ts +4 -0
  145. package/src/runtime/helpers/isEdgeEqual.ts +2 -4
  146. package/src/runtime/helpers/isWindowEdge.ts +2 -2
  147. package/src/runtime/helpers/isWindowEdgePoint.ts +2 -2
  148. package/src/runtime/helpers/moveEdge.ts +2 -2
  149. package/src/runtime/helpers/numberToScaledPercent.ts +3 -3
  150. package/src/runtime/helpers/numberToScaledSize.ts +2 -2
  151. package/src/runtime/helpers/rotateFrames.ts +45 -0
  152. package/src/runtime/helpers/scaledPointToPx.ts +13 -0
  153. package/src/runtime/helpers/toWindowCoord.ts +2 -2
  154. package/src/runtime/layout/applyFrameChanges.ts +39 -0
  155. package/src/runtime/layout/createSplitDecoFromDrag.ts +12 -6
  156. package/src/runtime/layout/{createSplitDecoEdge.ts → createSplitDecoShapes.ts} +17 -7
  157. package/src/runtime/layout/debugFrame.ts +1 -1
  158. package/src/runtime/layout/findSafeSplitEdge.ts +3 -3
  159. package/src/runtime/layout/frameCreate.ts +2 -2
  160. package/src/runtime/layout/getCloseFrameInfo.ts +21 -8
  161. package/src/runtime/layout/getDragZones.ts +48 -0
  162. package/src/runtime/layout/getFillEmptySpaceInfo.ts +177 -0
  163. package/src/runtime/layout/getFrameCollapseInfo.ts +164 -0
  164. package/src/runtime/layout/getFrameDockInfo.ts +126 -0
  165. package/src/runtime/layout/getFrameDragZones.ts +100 -0
  166. package/src/runtime/layout/getFrameRearrangeInfo.ts +261 -0
  167. package/src/runtime/layout/getFrameSplitInfo.ts +21 -8
  168. package/src/runtime/layout/getFrameSwapInfo.ts +45 -0
  169. package/src/runtime/layout/getFrameTo.ts +2 -2
  170. package/src/runtime/layout/getFrameUncollapseInfo.ts +160 -0
  171. package/src/runtime/layout/getFrameUndockInfo.ts +97 -0
  172. package/src/runtime/layout/getFramesRedistributeInfo.ts +98 -0
  173. package/src/runtime/layout/getWindowDragZones.ts +59 -0
  174. package/src/runtime/layout/index.ts +14 -5
  175. package/src/runtime/layout/isPointInRect.ts +7 -0
  176. package/src/runtime/layout/resizeFrame.ts +2 -2
  177. package/src/runtime/settings.ts +69 -49
  178. package/src/runtime/types/index.ts +143 -28
  179. package/dist/runtime/layout/closeFrame.d.ts +0 -5
  180. package/dist/runtime/layout/closeFrame.js +0 -13
  181. package/dist/runtime/layout/closeFrames.d.ts +0 -2
  182. package/dist/runtime/layout/closeFrames.js +0 -8
  183. package/dist/runtime/layout/createSplitDecoEdge.d.ts +0 -2
  184. package/dist/runtime/layout/frameSplit.d.ts +0 -16
  185. package/dist/runtime/layout/frameSplit.js +0 -9
  186. package/dist/runtime/layout/isPointInFrame.d.ts +0 -2
  187. package/src/runtime/layout/closeFrame.ts +0 -33
  188. package/src/runtime/layout/closeFrames.ts +0 -14
  189. package/src/runtime/layout/frameSplit.ts +0 -31
  190. package/src/runtime/layout/isPointInFrame.ts +0 -7
@@ -1,16 +1,27 @@
1
1
  <template>
2
- <WRoot class="gap-2 p-2">
2
+ <!-- not sure why we're needed to specify flex-col, tailwind isn't detecting the class properly in wroot maybe? -->
3
+ <WRoot class="
4
+ gap-2
5
+ p-2
6
+ flex-col
7
+ [&_.frame-drag-ghost]:rounded-md
8
+ ">
3
9
  <DemoControls
10
+ v-if="win"
4
11
  :frames="frames!"
12
+ :win="win"
13
+ :dragActionHandler="dragActionHandler"
5
14
  />
6
- <LayoutWindow v-if="win"
7
- class="
8
- flex-1 w-full
15
+ <LayoutWindow
16
+ ref="layoutComponent"
17
+ v-if="win"
18
+ :class="twMerge(`
19
+ flex-1
20
+ w-full
9
21
  border-1
10
22
  border-neutral-300
11
23
  dark:border-neutral-700
12
24
  rounded-md
13
- flex-1
14
25
  self-stretch
15
26
  [&_.frame]:flex
16
27
  [&_.frame]:flex-col
@@ -21,26 +32,34 @@
21
32
  [&_.grabbed-edge]:bg-accent-500
22
33
  [&.request-split_.grabbed-edge]:hidden
23
34
  [&.request-split_.drag-edge]:hidden
35
+ [&.deco-split-error]:rounded-md
24
36
  [&_.deco-split-new-frame]:rounded-md
25
37
  [&_.deco-close-frame]:rounded-md
26
- [&_.deco-close-frame]:bg-orange-500/50
27
- "
28
- :usage-instructions="usageInstructions"
29
- instructions-teleport-to="#status-bar"
38
+ [&_.deco-close-frame-force]:rounded-md
39
+ [&_.deco-frame-drag]:rounded-md
40
+ `,
41
+ collapsedDocks.top && `border-t-2 border-t-green-500`,
42
+ collapsedDocks.bottom && `border-b-2 border-b-green-500`,
43
+ collapsedDocks.left && `border-l-2 border-l-green-500`,
44
+ collapsedDocks.right && `border-r-2 border-r-green-500`
45
+ )"
46
+ :textHints="textHints"
47
+ textHintsTeleportTo="#status-bar"
30
48
  v-model:win="win"
31
49
  @is-showing-drag="isShowingDrag = $event"
32
50
  @drag-state="dragState = $event"
33
51
  >
34
- <template #[`frame-${f.id}`] v-for="f in frames" :key="f.id">
52
+ <template #[`frame-${f.id}`] v-for="f in frames" :key="f.id" >
35
53
  <div
54
+ v-if="!(f.collapsed && (f.width === 0 || f.height === 0))"
36
55
  :data-is-active="win.activeFrame === f.id"
37
56
  :class="twMerge(`
38
- border-2
39
- border-neutral-500
40
- h-full
41
- rounded-md
42
- overflow-auto
43
- `,
57
+ border-2
58
+ border-neutral-500
59
+ h-full
60
+ rounded-md
61
+ overflow-auto
62
+ `,
44
63
  win.activeFrame === f.id && `border-blue-500`
45
64
  )"
46
65
  @click="win.activeFrame=f.id"
@@ -50,12 +69,43 @@
50
69
  Set it on the first child instead, so that the frame can shrink as small as possible.
51
70
  Too big a border can also be a problem, but usually it's small enough that it's beneath the min frame width/height allowed.
52
71
  -->
53
- <div class="p-2"> {{ debugFrame(f) }} </div>
72
+ <div class="p-2 flex flex-col">
73
+ <FrameDragHandle :frame-id="f.id">
74
+ <div
75
+ class="cursor-grab bg-neutral-200 dark:bg-neutral-700 px-2 py-1 text-xs select-none"
76
+ >
77
+ ⠿ Drag to move frame
78
+ </div>
79
+ </FrameDragHandle>
80
+ <div class="flex gap-1 px-2 py-1 flex-wrap">
81
+ <button
82
+ v-if="f.docked && f.collapsed === false"
83
+ class="bg-neutral-200 dark:bg-neutral-700 px-2 py-0.5 text-xs rounded"
84
+ @click="handleUndock(f.id)"
85
+ >Undock</button>
86
+ <button
87
+ v-if="f.docked && f.collapsed === false"
88
+ class="bg-neutral-200 dark:bg-neutral-700 px-2 py-0.5 text-xs rounded"
89
+ @click="handleCollapse(f.id)"
90
+ >Collapse</button>
91
+ <button
92
+ v-if="f.docked && typeof f.collapsed === 'number'"
93
+ class="bg-neutral-200 dark:bg-neutral-700 px-2 py-0.5 text-xs rounded"
94
+ @click="handleUncollapse(f.id)"
95
+ >Uncollapse</button>
96
+ </div>
97
+ <div class="p-2 whitespace-pre-wrap">
98
+ {{ debugFrame(f) }}
99
+ </div>
100
+ </div>
101
+ </div>
102
+ <div v-else>
54
103
  </div>
55
104
  </template>
56
105
  </LayoutWindow>
57
- </Wroot>
106
+ </WRoot>
58
107
  </template>
108
+
59
109
  <script lang="ts" setup>
60
110
  import { keys } from "@alanscodelog/utils/keys"
61
111
  // playground not resolving???
@@ -67,21 +117,30 @@ import { computed, onBeforeMount, ref } from "vue"
67
117
  import DemoControls from "./DemoControls.vue"
68
118
  import { app } from "./sharedLayoutInstance.js"
69
119
 
120
+ import FrameDragHandle from "../components/FrameDragHandle.vue"
70
121
  import LayoutWindow from "../components/LayoutWindow.vue"
71
122
  import type { DragState } from "../drag/types.js"
123
+ import { applyFrameChanges } from "../layout/applyFrameChanges.js"
72
124
  import { debugFrame } from "../layout/debugFrame.js"
125
+ import { getFrameCollapseInfo } from "../layout/getFrameCollapseInfo.js"
126
+ import { getFrameUncollapseInfo } from "../layout/getFrameUncollapseInfo.js"
127
+ import { getFrameUndockInfo } from "../layout/getFrameUndockInfo.js"
73
128
  import {
74
129
  frameCreate,
75
130
  layoutAddWindow,
76
131
  windowAddFrame,
77
- windowCreate,
132
+ windowCreate
78
133
  } from "../layout/index.js"
79
- import { getMaxInt } from "../settings.js"
80
- import { type Layout, type Pos, type Size } from "../types/index.js"
134
+ import { settings } from "../settings.js"
135
+ import type { EdgeSide, Layout, Pos, Size } from "../types/index.js"
136
+ import { throwIfError } from "@alanscodelog/utils/throwIfError"
137
+ import { useTemplateRef } from "vue"
138
+ import { DragActionHandler } from "../drag/DragActionHandler.js"
81
139
 
82
140
 
83
141
  const winId = ref<string | undefined>(undefined)
84
142
  const win = computed(() => winId.value !== undefined ? app.layout.windows[winId.value] : undefined)
143
+ const layoutComponent = useTemplateRef("layoutComponent")
85
144
 
86
145
  const frames = computed(() => {
87
146
  if (!win.value) return
@@ -92,30 +151,29 @@ onBeforeMount(() => {
92
151
  winId.value = keys(app.layout.windows)[0]
93
152
  })
94
153
 
95
- const isDragging = ref(false)
96
154
  function layoutInitialize(layout: Layout, { defaultPos, defaultSize }: {
97
155
  defaultPos: Pos
98
156
  defaultSize: Size
99
157
  } = {
100
158
  defaultPos: { x: 0, y: 0 },
101
- defaultSize: { width: 0, height: 0 },
159
+ defaultSize: { width: 0, height: 0 }
102
160
  }) {
103
161
  const w = layoutAddWindow(
104
162
  layout,
105
163
  windowCreate({
106
164
  ...defaultPos,
107
165
  ...defaultSize,
108
- frames: {},
166
+ frames: {}
109
167
  })
110
168
  )
111
169
 
112
170
  layout.activeWindow = w.id
113
- const max = getMaxInt()
171
+ const max = settings.maxInt
114
172
  const frame = windowAddFrame(w, frameCreate({
115
173
  x: 0,
116
174
  y: 0,
117
175
  width: max,
118
- height: max,
176
+ height: max
119
177
  }))
120
178
  w.activeFrame = frame.id
121
179
  }
@@ -126,14 +184,46 @@ const isShowingDrag = ref(false)
126
184
  // drag state as returned by useFrames in LayoutWindow
127
185
  const dragState = ref<DragState | undefined>(undefined)
128
186
 
129
- const usageInstructions = computed(() => ({
130
- // names are arbitrary and don't mean anything, they just make things easier
131
- // if a key is undefined, it's ignored
132
- none: !dragState.value?.isDragging ? "Drag from an edge to create a new frame." : undefined,
133
- split: dragState.value?.isDragging ? "Hold Alt to Split" : undefined,
134
- close: dragState.value?.isDragging ? "Shift+Drag to Close" : undefined,
135
- forceClose: dragState.value?.isDragging ? "Ctrl+Shift+Drag to Force Close" : undefined
136
- }))
137
-
138
- </script>
187
+ function handleUndock(frameId: string) {
188
+ const changes = throwIfError(getFrameUndockInfo(win.value!, frameId))
189
+ DragActionHandler.debugState("undock", "before", dragState.value!, {}, undefined)
190
+ applyFrameChanges(win.value!, changes)
191
+ DragActionHandler.debugState("undock", "after", dragState.value!, {}, undefined)
192
+ }
193
+
194
+ function handleCollapse(frameId: string) {
195
+ const changes = throwIfError(getFrameCollapseInfo(win.value!, frameId))
196
+ DragActionHandler.debugState("collapse", "before", dragState.value!, {}, undefined)
197
+ applyFrameChanges(win.value!, changes)
198
+ DragActionHandler.debugState("collapse", "after", dragState.value!, {}, undefined)
199
+ }
139
200
 
201
+ function handleUncollapse(frameId: string) {
202
+ const changes = throwIfError(getFrameUncollapseInfo(win.value!, frameId))
203
+ DragActionHandler.debugState("uncollapse", "before", dragState.value!, {}, undefined)
204
+ applyFrameChanges(win.value!, changes)
205
+ DragActionHandler.debugState("uncollapse", "after", dragState.value!, {}, undefined)
206
+ }
207
+
208
+ const textHints = computed(() => {
209
+ const isDragging = dragState.value?.isDragging
210
+ const textHints = dragActionHandler.value?.textHints ?? {actions:[], errors:[]}
211
+ return [
212
+ ...(!isDragging ? [{classes:"", text:"Drag from an edge to create a new frame."}] : []),
213
+ ...textHints.errors.map(_ => ({classes:"text-red-500", text:_})),
214
+ ...textHints.actions.map(_ => ({ text:_})),
215
+ ]
216
+ })
217
+
218
+ const collapsedDocks = computed(() => {
219
+ const sides: Partial<Record<EdgeSide, true>> = {}
220
+ for (const frame of Object.values(win.value?.frames ?? {})) {
221
+ if (frame.docked) {
222
+ sides[frame.docked] = true
223
+ }
224
+ }
225
+ return sides
226
+ })
227
+
228
+ const dragActionHandler = computed(() => layoutComponent.value?.dragActionHandler)
229
+ </script>
@@ -1,17 +1,126 @@
1
1
  <template>
2
- <div class="border-2 border-neutral-500 p-2 rounded-md flex">
2
+ <div class="border-2 border-neutral-500 p-2 rounded-md flex gap-2 items-center flex-wrap">
3
3
  <div class="">Instruction/status bar:</div>
4
- <div id="status-bar" class="px-2 flex-1 overflow-x-auto scrollbar-hidden"/>
4
+ <div id="status-bar" class="px-2 flex-1 overflow-x-auto scrollbar-hidden whitespace-nowrap"/>
5
+ <WButton class="" @click="uncollapseAll" >Uncollapse All</WButton>
6
+ <WPopover v-model="showDevActions">
7
+ <template #button>
8
+ <WButton @click="showDevActions = !showDevActions">Dev Actions</WButton>
9
+ </template>
10
+ <template #popover>
11
+ <div class="flex flex-col gap-2 rounded-md w-full`">
12
+ <label class="text-xs">Collapse Size (%):</label>
13
+ <input
14
+ type="number"
15
+ class="w-full min-w-0 border border-neutral-300 dark:border-neutral-700 rounded px-1 py-0.5 text-xs bg-neutral-100 dark:bg-neutral-800"
16
+ :min="0"
17
+ :max="100"
18
+ :value="collapseSizeValue"
19
+ @input="collapseSizeValue = Number(($event.target as HTMLInputElement).value); handleCollapseSizeChange(collapseSizeValue)"
20
+ />
21
+ <WButton class="flex-1" @click="rotateLayoutAction">Rotate Layout</WButton>
22
+ <div class="p-2 flex flex-col border-neutral-500/50 border rounded-md gap-2">
23
+ <span>Debug:</span>
24
+ <div class="flex gap-2">
25
+ <WButton class="flex-1" @click="copyState">Copy State</WButton>
26
+ <WCheckbox v-model="renameFrames">Easy Ids</WCheckbox>
27
+ </div>
28
+ <label class="flex flex-col gap-2 text-sm" v-if="dragActionHandler">
29
+ <span>Plugin Debug Logs (true | debug path):</span>
30
+ <WSimpleInput v-model="debugKey" />
31
+ </label>
32
+ </div>
33
+ </div>
34
+ </template>
35
+ </WPopover>
5
36
  <WDarkModeSwitcher :show-label="false"/>
6
37
  </div>
7
38
  </template>
8
39
  <script lang="ts" setup>
9
40
  import WDarkModeSwitcher from "@witchcraft/ui/components/WDarkModeSwitcher"
41
+ import WButton from "@witchcraft/ui/components/WButton"
42
+ import WPopover from "@witchcraft/ui/components/WPopover"
43
+ import { copy } from "@witchcraft/ui/helpers"
44
+ import { ref, watch } from "vue"
45
+ import { applyFrameChanges } from "../layout/applyFrameChanges.js"
46
+ import { getFrameUncollapseInfo } from "../layout/getFrameUncollapseInfo.js"
47
+ import { settings } from "../settings.js"
48
+ import type { LayoutFrame, LayoutWindow } from "../types/index.js"
49
+ import { throwIfError } from "@alanscodelog/utils/throwIfError";
50
+ import { walk } from "@alanscodelog/utils/walk"
51
+ import type { DragActionHandler } from "../drag/DragActionHandler.js"
52
+ import { rotateLayout } from "../helpers/rotateFrames.js"
10
53
 
11
- import type { LayoutFrame } from "../types/index.js"
12
-
13
- defineProps<{
14
- frames: LayoutFrame[]
54
+ const props = defineProps<{
55
+ win: LayoutWindow
56
+ frames: LayoutFrame[],
57
+ dragActionHandler?: DragActionHandler<any>
15
58
  }>()
16
59
 
60
+ const showDevActions = ref(false)
61
+
62
+ const collapseSizeValue = ref(Math.round(settings.collapseSizeScaled.width / settings.maxInt * 100))
63
+
64
+ function handleCollapseSizeChange(value: number) {
65
+ settings.collapseSize = value
66
+ collapseSizeValue.value = value
67
+ }
68
+
69
+
70
+ function uncollapseAll() {
71
+ const win = props.win
72
+ if (!win) return
73
+ for (const frame of Object.values(win.frames)) {
74
+ if (frame.docked && typeof frame.collapsed === 'number') {
75
+ const changes = throwIfError(getFrameUncollapseInfo(win, frame.id))
76
+ applyFrameChanges(win, changes)
77
+ }
78
+ }
79
+ }
80
+
81
+ const renameFrames = ref(false)
82
+
83
+ const rotationAngle = ref(0)
84
+
85
+ function rotateLayoutAction() {
86
+ const angles = [90, 180, 270] as const
87
+ const currentIdx = angles.indexOf(rotationAngle.value as any)
88
+ const nextIdx = (currentIdx + 1) % angles.length
89
+ const nextAngle = angles[nextIdx]
90
+ rotationAngle.value = nextAngle
91
+ rotateLayout(Object.values(props.win.frames), nextAngle)
92
+ }
93
+
94
+ const alphabet = "abcdefghijklmnopqrstuvwxyz".split("")
95
+ function copyState() {
96
+ const clone = walk(props.win, undefined, { save: true })
97
+ if (renameFrames.value) {
98
+ const framesList = Object.values(clone.frames)
99
+ for (let i = 0; i < framesList.length; i++) {
100
+ const frame = framesList[i] as LayoutFrame
101
+ delete clone.frames[frame.id]
102
+ ;(frame as any)._originalId = frame.id
103
+ frame.id = alphabet[i % alphabet.length].repeat(i == 0 ? 1 : Math.ceil(i / alphabet.length))
104
+ clone.frames[frame.id] = frame
105
+ }
106
+ }
107
+ const state = JSON.stringify(clone, null, 2)
108
+ copy(state)
109
+ }
110
+
111
+ const debugKey = ref(import.meta.dev ? "state.win.frames" : "false")
112
+ watch(debugKey, () => {
113
+ if (!props.dragActionHandler) return
114
+ const value = debugKey.value === "true"
115
+ ? true
116
+ : debugKey.value === "false"
117
+ ? false
118
+ : debugKey.value
119
+
120
+ for (const plugin of Object.values(props.dragActionHandler.actions)) {
121
+ plugin.debug = value
122
+ }
123
+ })
124
+
125
+
17
126
  </script>
@@ -1,9 +1,10 @@
1
- import type { DragState, IDragAction } from "./types.js"
1
+ import { DragActionHandler } from "./DragActionHandler.js"
2
+ import type { ActionDragChangeResult, DragState, IDragAction } from "./types.js"
2
3
 
3
4
  import { dirToOrientation } from "../helpers/dirToOrientation.js"
4
5
  import { getEdgeOrientation } from "../helpers/getEdgeOrientation.js"
5
6
  import { oppositeSide } from "../helpers/oppositeSide.js"
6
- import { closeFrames } from "../layout/closeFrames.js"
7
+ import { applyFrameChanges } from "../layout/applyFrameChanges.js"
7
8
  import { findFramesTouchingEdge } from "../layout/findFramesTouchingEdge.js"
8
9
  import { getCloseFrameInfo } from "../layout/getCloseFrameInfo.js"
9
10
  import type { CloseDeco } from "../types/index.js"
@@ -19,29 +20,55 @@ export class CloseAction implements IDragAction {
19
20
  force: boolean
20
21
  res: CloseInfo
21
22
  cacheKey: string | undefined
23
+ lastReturn: ActionDragChangeResult
22
24
  } | {
23
25
  allowed: false
24
26
  force: boolean
25
27
  res: CloseInfo | undefined
26
28
  cacheKey: string | undefined
29
+ lastReturn: ActionDragChangeResult | undefined
27
30
  } = {} as any // this is initialized by this.reset()
28
31
 
29
- handleEvent: (e: PointerEvent | KeyboardEvent, state: DragState) => boolean | "force"
30
- updateCloseDecos: (decos: CloseDeco[]) => void
32
+ debug: boolean | string = false
33
+ textHints: { actions: string[], errors: string[] } = { actions: [], errors: [] }
34
+
35
+ closeHints: {
36
+ actions: string[]
37
+ transformError: (e: KnownError) => string
38
+ } = {
39
+ actions: ["Hold Shift to Close", "Hold Ctrl+Shift to Force Close"],
40
+ transformError: e => e.message
41
+ }
42
+
43
+ handleEvent: (e: PointerEvent | KeyboardEvent, state: DragState) => boolean | "force" = (e: PointerEvent | KeyboardEvent) => {
44
+ if (e.ctrlKey && e.shiftKey) {
45
+ return "force"
46
+ }
47
+ if (e.shiftKey) return true
48
+ return false
49
+ }
50
+
51
+ modifyDecos: (shapes: CloseDeco[]) => void = () => { }
31
52
  hooks: {
32
53
  onStart?: (active: boolean) => void
33
54
  onCancel?: () => void
34
55
  onError?: (e: KnownError) => void
35
- }
56
+ } = {}
36
57
 
37
58
  constructor(
38
- handleEvent: CloseAction["handleEvent"],
39
- updateCloseDecos: CloseAction["updateCloseDecos"],
40
- hooks: CloseAction["hooks"] = {}
59
+ handleEvent?: CloseAction["handleEvent"],
60
+ /** Modify the created decos before they are rendered. */
61
+ modifyDecos?: CloseAction["modifyDecos"],
62
+ hooks: CloseAction["hooks"] = {},
63
+ config?: {
64
+ debug?: boolean | string
65
+ closeHints?: Partial<CloseAction["closeHints"]>
66
+ }
41
67
  ) {
42
- this.handleEvent = handleEvent
43
- this.updateCloseDecos = updateCloseDecos
68
+ if (handleEvent !== undefined) this.handleEvent = handleEvent
69
+ if (modifyDecos !== undefined) this.modifyDecos = modifyDecos
44
70
  this.hooks = hooks
71
+ if (config?.debug) this.debug = true
45
72
  this.reset()
46
73
  }
47
74
 
@@ -50,33 +77,66 @@ export class CloseAction implements IDragAction {
50
77
  allowed: false,
51
78
  force: false,
52
79
  res: undefined,
53
- cacheKey: undefined
80
+ cacheKey: undefined,
81
+ lastReturn: undefined
54
82
  }
55
- this.updateCloseDecos([])
83
+ this.modifyDecos([])
56
84
  }
57
85
 
58
- updateDecos(state: DragState): void {
59
- const {
60
- isDragging
61
- } = state
86
+ private _getDecos(state: DragState): CloseDeco[] {
87
+ let decos: CloseDeco[] = []
88
+ const { isDragging } = state
62
89
  if (isDragging && this.state.allowed && this.state.res) {
63
90
  const { force } = this.state
64
- const decos = this.state.res.deletedFrames.map(_ => ({ id: _.id, type: "close" as const, force }))
65
- this.updateCloseDecos(decos)
91
+ decos = this.state.res.deleted.map(_ => ({
92
+ id: _.id,
93
+ type: "close" as const,
94
+ force,
95
+ shapes: [
96
+ {
97
+ type: "square",
98
+ data: { x: _.x, y: _.y, width: _.width, height: _.height },
99
+ attrs: {
100
+ class: force
101
+ ? `deco-close-force-frame bg-orange-500/50`
102
+ : `deco-close-frame bg-orange-500/20`
103
+ }
104
+ }
105
+ ]
106
+ }))
107
+ }
108
+ this.modifyDecos(decos)
109
+ return decos
110
+ }
111
+
112
+ setTextHints(result: true | CloseInfo | KnownError | undefined): void {
113
+ if (result === undefined) {
114
+ this.textHints.actions = []
115
+ this.textHints.errors = []
116
+ } else if (result instanceof Error) {
117
+ this.textHints.actions = []
118
+ this.textHints.errors = [this.closeHints.transformError(result)]
66
119
  } else {
67
- this.updateCloseDecos([])
120
+ this.textHints.actions = this.closeHints.actions
121
+ this.textHints.errors = []
68
122
  }
69
123
  }
70
124
 
125
+ getTextHints(type: "start" | "move" | "end"): {
126
+ actions: string[]
127
+ errors: string[]
128
+ } {
129
+ if (type === "end") { this.setTextHints(undefined) }
130
+ return this.textHints
131
+ }
132
+
133
+
71
134
  canHandleRequest(e: PointerEvent | KeyboardEvent, state: DragState): boolean {
72
135
  const { draggingEdges } = state
73
136
  if (draggingEdges.length !== 1) return false
74
137
  const res = this.handleEvent(e, state)
75
- if (res === "force") {
76
- this.state.force = true
77
- } else {
78
- this.state.force = false
79
- }
138
+ this.state.force = res === "force"
139
+ this.setTextHints(state.isDragging === "edge" ? true : undefined)
80
140
  if (res) {
81
141
  this.hooks.onStart?.(true)
82
142
  return true
@@ -89,7 +149,7 @@ export class CloseAction implements IDragAction {
89
149
  _type: "start" | "end" | "move",
90
150
  _e: PointerEvent | undefined,
91
151
  state: DragState
92
- ): boolean {
152
+ ): ActionDragChangeResult {
93
153
  const {
94
154
  touchingFramesArrays,
95
155
  dragDirections,
@@ -97,10 +157,16 @@ export class CloseAction implements IDragAction {
97
157
  draggingEdges,
98
158
  visualEdges,
99
159
  frames,
100
- isDraggingFromWindowEdge
160
+ isDraggingFromWindowEdge,
161
+ dragPoint
101
162
  } = state
102
163
  const oppositeOrientation = oppositeSide(getEdgeOrientation(draggingEdges[0]))
103
- let ok = false
164
+ const cacheKey = `${dragPoint?.x}-${dragPoint?.y}-${dragDirections[oppositeOrientation]!}-${this.state.force}`
165
+ if (this.state.allowed) {
166
+ if (this.state.cacheKey === cacheKey) {
167
+ return this.state.lastReturn
168
+ }
169
+ }
104
170
  if (isDragging && draggingEdges.length === 1) {
105
171
  const res = findFramesTouchingEdge(
106
172
  draggingEdges[0],
@@ -115,36 +181,32 @@ export class CloseAction implements IDragAction {
115
181
  const sizeKey = orientation === "horizontal" ? "width" : "height"
116
182
  const smallestFrameSize = Math.min(...res.map(_ => _.frame[sizeKey]))
117
183
  const frame = res.find(_ => _.frame[sizeKey] === smallestFrameSize)!.frame!
118
- const cacheKey = `${frame.id}-${dragDirections[oppositeOrientation]!}`
119
- if (this.state.allowed) {
120
- if (this.state.cacheKey === cacheKey) {
121
- this.updateDecos(state)
122
- return true
123
- }
124
- }
184
+
125
185
  const closeInfo = getCloseFrameInfo(Object.values(frames), visualEdges, frame, dragDirections[oppositeOrientation]!, "dir", this.state.force)
186
+ this.state.cacheKey = cacheKey
187
+ this.setTextHints(closeInfo)
126
188
  if (!(closeInfo instanceof Error)) {
127
- this.state.allowed = true
128
189
  this.state.res = closeInfo
129
- this.state.cacheKey = cacheKey
130
- ok = true
190
+ this.state.allowed = true
131
191
  } else {
192
+ this.state.res = undefined
193
+ this.state.allowed = false
132
194
  this.hooks.onError?.(closeInfo)
195
+ return { updateEdges: true, shapes: [] }
133
196
  }
134
197
  }
135
198
  }
136
- this.updateDecos(state)
137
- if (!ok) {
138
- this.state.allowed = false
139
- }
140
- return !isDraggingFromWindowEdge
199
+ const decos = this._getDecos(state)
200
+ this.state.lastReturn = { updateEdges: !isDraggingFromWindowEdge, shapes: decos.flatMap(_ => _.shapes) }
201
+ return this.state.lastReturn
141
202
  }
142
203
 
143
204
  onDragApply(state: DragState): boolean {
144
205
  if (this.state.res) {
145
- const { deletedFrames, modifiedFrames } = this.state.res
146
206
  const win = state.win
147
- closeFrames(win, deletedFrames, modifiedFrames)
207
+ if (this.debug) { DragActionHandler.debugState(this.name, "before", state, this.state, this.debug) }
208
+ applyFrameChanges(win, this.state.res)
209
+ if (this.debug) { DragActionHandler.debugState(this.name, "after", state, this.state, this.debug) }
148
210
  this.reset()
149
211
  return true
150
212
  }