@witchcraft/layout 0.1.3 → 0.2.1

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