astro-tractstack 2.0.9 → 2.0.11
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/dist/index.js +4 -6
- package/package.json +1 -1
- package/templates/css/custom.css +0 -6
- package/templates/src/components/codehooks/EpinetDurationSelector.tsx +1 -1
- package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +2 -1
- package/templates/src/components/codehooks/ProductGridSetup.tsx +4 -4
- package/templates/src/components/compositor/Compositor.tsx +335 -16
- package/templates/src/components/compositor/Node.tsx +86 -6
- package/templates/src/components/compositor/nodes/RenderChildren.tsx +3 -6
- package/templates/src/components/compositor/nodes/tagElements/NodeA.tsx +2 -1
- package/templates/src/components/compositor/nodes/tagElements/NodeAnchorComponent.tsx +11 -19
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag.tsx +70 -17
- package/templates/src/components/compositor/nodes/tagElements/NodeButton.tsx +1 -1
- package/templates/src/components/compositor/nodes/tagElements/NodeText.tsx +78 -8
- package/templates/src/components/edit/SettingsPanel.tsx +1 -1
- package/templates/src/components/edit/ToolMode.tsx +93 -22
- package/templates/src/components/edit/pane/AddPanePanel_break.tsx +2 -1
- package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +2 -1
- package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +1 -1
- package/templates/src/components/edit/pane/PageGen_preview.tsx +2 -1
- package/templates/src/components/edit/panels/StyleElementPanel_update.tsx +9 -5
- package/templates/src/components/edit/state/SaveModal.tsx +84 -14
- package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +2 -2
- package/templates/src/components/search/SearchModal.tsx +2 -1
- package/templates/src/components/search/SearchResults.tsx +2 -1
- package/templates/src/components/search/SearchWrapper.tsx +1 -1
- package/templates/src/components/storykeep/Dashboard_Analytics.tsx +1 -1
- package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +3 -5
- package/templates/src/components/storykeep/controls/content/BeliefTable.tsx +1 -1
- package/templates/src/components/storykeep/controls/content/MenuTable.tsx +1 -1
- package/templates/src/components/storykeep/controls/content/StoryFragmentTable.tsx +1 -1
- package/templates/src/components/widgets/ImpressionWrapper.tsx +1 -1
- package/templates/src/hooks/useFormState.ts +3 -4
- package/templates/src/pages/sitemap.xml.ts +38 -8
- package/templates/src/stores/nodes.ts +627 -21
- package/templates/src/stores/selection.ts +41 -0
- package/templates/src/types/compositorTypes.ts +1 -0
- package/templates/src/types/nodeProps.ts +12 -0
- package/templates/src/utils/compositor/nodesHelper.ts +2 -2
- package/utils/inject-files.ts +4 -6
- package/templates/src/components/compositor/elements/PlayButton.tsx +0 -19
package/dist/index.js
CHANGED
|
@@ -381,12 +381,6 @@ async function w(t, e, c) {
|
|
|
381
381
|
src: t("../templates/src/components/compositor/elements/Svg.tsx"),
|
|
382
382
|
dest: "src/components/compositor/elements/Svg.tsx"
|
|
383
383
|
},
|
|
384
|
-
{
|
|
385
|
-
src: t(
|
|
386
|
-
"../templates/src/components/compositor/elements/PlayButton.tsx"
|
|
387
|
-
),
|
|
388
|
-
dest: "src/components/compositor/elements/PlayButton.tsx"
|
|
389
|
-
},
|
|
390
384
|
// Compositor panels
|
|
391
385
|
{
|
|
392
386
|
src: t(
|
|
@@ -581,6 +575,10 @@ async function w(t, e, c) {
|
|
|
581
575
|
src: t("../templates/src/stores/nodesHistory.ts"),
|
|
582
576
|
dest: "src/stores/nodesHistory.ts"
|
|
583
577
|
},
|
|
578
|
+
{
|
|
579
|
+
src: t("../templates/src/stores/selection.ts"),
|
|
580
|
+
dest: "src/stores/selection.ts"
|
|
581
|
+
},
|
|
584
582
|
// AAI utils
|
|
585
583
|
{
|
|
586
584
|
src: t("../templates/src/utils/aai/getTitleSlug.ts"),
|
package/package.json
CHANGED
package/templates/css/custom.css
CHANGED
|
@@ -512,7 +512,7 @@ const EpinetDurationSelector = ({
|
|
|
512
512
|
onBeliefFilterChange(filter.beliefSlug, value)
|
|
513
513
|
}
|
|
514
514
|
type="button"
|
|
515
|
-
className={`flex items-center gap-x-1.5 rounded-full px-3 py-1 text-sm font-
|
|
515
|
+
className={`flex items-center gap-x-1.5 rounded-full px-3 py-1 text-sm font-bold transition-all duration-150 focus:outline-none focus:ring-2 focus:ring-cyan-500 focus:ring-offset-2 ${
|
|
516
516
|
selectedValue === value
|
|
517
517
|
? 'bg-cyan-600 text-white shadow-sm'
|
|
518
518
|
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
|
@@ -2,7 +2,8 @@ import { useState, useEffect, useMemo, useRef } from 'react';
|
|
|
2
2
|
import { useStore } from '@nanostores/react';
|
|
3
3
|
import { Combobox } from '@ark-ui/react';
|
|
4
4
|
import { createListCollection } from '@ark-ui/react/collection';
|
|
5
|
-
import
|
|
5
|
+
import CheckIcon from '@heroicons/react/20/solid/CheckIcon';
|
|
6
|
+
import ChevronUpDownIcon from '@heroicons/react/20/solid/ChevronUpDownIcon';
|
|
6
7
|
import { fullContentMapStore, viewportKeyStore } from '@/stores/storykeep';
|
|
7
8
|
import { getCtx } from '@/stores/nodes';
|
|
8
9
|
import { cloneDeep } from '@/utils/helpers';
|
|
@@ -190,7 +190,7 @@ export const ProductGridSetup = (props: ProductGridSetupProps) => {
|
|
|
190
190
|
<div className="space-y-2 rounded-md border p-3">
|
|
191
191
|
<label
|
|
192
192
|
htmlFor="productType"
|
|
193
|
-
className="text-sm font-
|
|
193
|
+
className="text-sm font-bold text-gray-700"
|
|
194
194
|
>
|
|
195
195
|
Product Type
|
|
196
196
|
</label>
|
|
@@ -209,7 +209,7 @@ export const ProductGridSetup = (props: ProductGridSetupProps) => {
|
|
|
209
209
|
<div className="rounded-md border bg-gray-50 p-3">
|
|
210
210
|
<div className="flex items-center justify-between">
|
|
211
211
|
<div>
|
|
212
|
-
<p className="text-sm font-
|
|
212
|
+
<p className="text-sm font-bold text-gray-600">
|
|
213
213
|
Selected Products
|
|
214
214
|
</p>
|
|
215
215
|
<p className="font-bold text-gray-900">
|
|
@@ -219,7 +219,7 @@ export const ProductGridSetup = (props: ProductGridSetupProps) => {
|
|
|
219
219
|
<button
|
|
220
220
|
type="button"
|
|
221
221
|
onClick={() => setShowSelector(!showSelector)}
|
|
222
|
-
className="rounded bg-white px-3 py-1 text-sm font-
|
|
222
|
+
className="rounded bg-white px-3 py-1 text-sm font-bold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
|
|
223
223
|
>
|
|
224
224
|
{showSelector ? 'Close' : 'Change Selection'}
|
|
225
225
|
</button>
|
|
@@ -235,7 +235,7 @@ export const ProductGridSetup = (props: ProductGridSetupProps) => {
|
|
|
235
235
|
lazyMount
|
|
236
236
|
unmountOnExit
|
|
237
237
|
>
|
|
238
|
-
<Combobox.Label className="text-sm font-
|
|
238
|
+
<Combobox.Label className="text-sm font-bold text-gray-700">
|
|
239
239
|
Find products to include
|
|
240
240
|
</Combobox.Label>
|
|
241
241
|
<Combobox.Control>
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
useEffect,
|
|
3
|
+
useState,
|
|
4
|
+
useRef,
|
|
5
|
+
type MouseEvent as ReactMouseEvent, // Alias React's MouseEvent
|
|
6
|
+
} from 'react';
|
|
2
7
|
import { useStore } from '@nanostores/react';
|
|
3
8
|
import {
|
|
4
9
|
viewportKeyStore,
|
|
@@ -10,17 +15,31 @@ import {
|
|
|
10
15
|
codehookMapStore,
|
|
11
16
|
brandColourStore,
|
|
12
17
|
hasArtpacksStore,
|
|
18
|
+
settingsPanelStore,
|
|
13
19
|
} from '@/stores/storykeep';
|
|
14
20
|
import { getCtx, ROOT_NODE_NAME, type NodesContext } from '@/stores/nodes';
|
|
15
21
|
import { stopLoadingAnimation } from '@/utils/helpers';
|
|
16
22
|
import Node from './Node';
|
|
17
23
|
import { ARTPACKS } from '@/constants/brandThemes';
|
|
24
|
+
import {
|
|
25
|
+
selectionStore,
|
|
26
|
+
resetSelectionStore,
|
|
27
|
+
type SelectionStoreState,
|
|
28
|
+
} from '@/stores/selection';
|
|
18
29
|
import type { LoadData } from '@/types/compositorTypes';
|
|
19
30
|
import type {
|
|
20
31
|
Theme,
|
|
21
32
|
BrandConfig,
|
|
22
33
|
FullContentMapItem,
|
|
23
34
|
} from '@/types/tractstack';
|
|
35
|
+
import type { SelectionOrigin } from '@/types/nodeProps';
|
|
36
|
+
|
|
37
|
+
type SelectionRect = {
|
|
38
|
+
top: number;
|
|
39
|
+
left: number;
|
|
40
|
+
width: number;
|
|
41
|
+
height: number;
|
|
42
|
+
};
|
|
24
43
|
|
|
25
44
|
export type CompositorProps = {
|
|
26
45
|
nodes: LoadData | null;
|
|
@@ -33,10 +52,235 @@ export type CompositorProps = {
|
|
|
33
52
|
fullCanonicalURL: string;
|
|
34
53
|
};
|
|
35
54
|
|
|
55
|
+
const VERBOSE = false;
|
|
56
|
+
const LOG_PREFIX = '[Compositor] ';
|
|
57
|
+
|
|
36
58
|
export const Compositor = (props: CompositorProps) => {
|
|
37
59
|
const [initialized, setInitialized] = useState(false);
|
|
38
60
|
const [updateCounter, setUpdateCounter] = useState(0);
|
|
39
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
61
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
62
|
+
|
|
63
|
+
const [selectionRect, setSelectionRect] = useState<SelectionRect | null>(
|
|
64
|
+
null
|
|
65
|
+
);
|
|
66
|
+
const isDragging = useRef(false);
|
|
67
|
+
const selectionOrigin = useRef<SelectionOrigin | null>(null);
|
|
68
|
+
const dragStartCoords = useRef<{ x: number; y: number } | null>(null);
|
|
69
|
+
|
|
70
|
+
const $viewportKey = useStore(viewportKeyStore);
|
|
71
|
+
const $selection = useStore(selectionStore);
|
|
72
|
+
const viewportMaxWidth =
|
|
73
|
+
$viewportKey.value === `mobile`
|
|
74
|
+
? 600
|
|
75
|
+
: $viewportKey.value === `tablet`
|
|
76
|
+
? 1000
|
|
77
|
+
: 1500;
|
|
78
|
+
const viewportMinWidth =
|
|
79
|
+
$viewportKey.value === `mobile`
|
|
80
|
+
? null
|
|
81
|
+
: $viewportKey.value === `tablet`
|
|
82
|
+
? 801
|
|
83
|
+
: 1368;
|
|
84
|
+
|
|
85
|
+
const handleDragStart = (
|
|
86
|
+
origin: SelectionOrigin,
|
|
87
|
+
e: ReactMouseEvent<HTMLElement> // Use aliased React MouseEvent
|
|
88
|
+
) => {
|
|
89
|
+
if (VERBOSE)
|
|
90
|
+
console.log(LOG_PREFIX + 'handleDragStart FIRED', { origin, event: e });
|
|
91
|
+
if (isDragging.current) {
|
|
92
|
+
if (VERBOSE)
|
|
93
|
+
console.log(LOG_PREFIX + 'handleDragStart aborted: already dragging.');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
isDragging.current = true;
|
|
97
|
+
selectionOrigin.current = origin;
|
|
98
|
+
dragStartCoords.current = { x: e.clientX, y: e.clientY };
|
|
99
|
+
|
|
100
|
+
resetSelectionStore();
|
|
101
|
+
if (VERBOSE) console.log(LOG_PREFIX + 'Selection store reset.');
|
|
102
|
+
selectionStore.setKey('isDragging', true);
|
|
103
|
+
selectionStore.setKey('blockNodeId', origin.blockNodeId);
|
|
104
|
+
selectionStore.setKey('lcaNodeId', origin.lcaNodeId);
|
|
105
|
+
selectionStore.setKey('startNodeId', origin.startNodeId);
|
|
106
|
+
selectionStore.setKey('startCharOffset', origin.startCharOffset);
|
|
107
|
+
selectionStore.setKey('endNodeId', origin.endNodeId);
|
|
108
|
+
selectionStore.setKey('endCharOffset', origin.endCharOffset);
|
|
109
|
+
if (VERBOSE)
|
|
110
|
+
console.log(
|
|
111
|
+
LOG_PREFIX + 'Selection store updated with origin:',
|
|
112
|
+
selectionStore.get()
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const initialRect = {
|
|
116
|
+
left: e.clientX,
|
|
117
|
+
top: e.clientY,
|
|
118
|
+
width: 0,
|
|
119
|
+
height: 0,
|
|
120
|
+
};
|
|
121
|
+
setSelectionRect(initialRect);
|
|
122
|
+
if (VERBOSE)
|
|
123
|
+
console.log(LOG_PREFIX + 'Initial selectionRect set:', initialRect);
|
|
124
|
+
|
|
125
|
+
if (VERBOSE) console.log(LOG_PREFIX + 'Adding window event listeners...');
|
|
126
|
+
try {
|
|
127
|
+
window.addEventListener('mousemove', handleDragMove);
|
|
128
|
+
window.addEventListener('mouseup', handleMouseUp);
|
|
129
|
+
if (VERBOSE)
|
|
130
|
+
console.log(LOG_PREFIX + 'Window event listeners successfully added.');
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error(LOG_PREFIX + 'Error adding window event listeners:', error);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const handleDragMove = (e: globalThis.MouseEvent) => {
|
|
137
|
+
if (
|
|
138
|
+
!isDragging.current ||
|
|
139
|
+
!selectionOrigin.current ||
|
|
140
|
+
!dragStartCoords.current
|
|
141
|
+
) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const startX = dragStartCoords.current.x;
|
|
146
|
+
const startY = dragStartCoords.current.y;
|
|
147
|
+
const currentX = e.clientX;
|
|
148
|
+
const currentY = e.clientY;
|
|
149
|
+
|
|
150
|
+
const newRect = {
|
|
151
|
+
left: Math.min(startX, currentX),
|
|
152
|
+
top: Math.min(startY, currentY),
|
|
153
|
+
width: Math.abs(currentX - startX),
|
|
154
|
+
height: Math.abs(currentY - startY),
|
|
155
|
+
};
|
|
156
|
+
setSelectionRect(newRect);
|
|
157
|
+
|
|
158
|
+
const elementAtPoint = document.elementFromPoint(currentX, currentY);
|
|
159
|
+
if (!elementAtPoint) return;
|
|
160
|
+
|
|
161
|
+
const textNodeElement = elementAtPoint.closest(
|
|
162
|
+
'[data-parent-text-node-id]'
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
if (textNodeElement) {
|
|
166
|
+
const parentBlockNodeId =
|
|
167
|
+
textNodeElement
|
|
168
|
+
.closest('[data-node-id]')
|
|
169
|
+
?.getAttribute('data-node-id') || null;
|
|
170
|
+
|
|
171
|
+
if (parentBlockNodeId !== selectionOrigin.current.blockNodeId) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const endNodeId = textNodeElement.getAttribute(
|
|
176
|
+
'data-parent-text-node-id'
|
|
177
|
+
);
|
|
178
|
+
const endCharOffset = parseInt(
|
|
179
|
+
textNodeElement.getAttribute('data-end-char-offset') || '0',
|
|
180
|
+
10
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
if (endNodeId) {
|
|
184
|
+
selectionStore.setKey('endNodeId', endNodeId);
|
|
185
|
+
selectionStore.setKey('endCharOffset', endCharOffset);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const handleMouseUp = async (e: globalThis.MouseEvent) => {
|
|
191
|
+
if (VERBOSE) console.log(LOG_PREFIX + 'handleMouseUp FIRED', { event: e });
|
|
192
|
+
if (!isDragging.current) {
|
|
193
|
+
if (VERBOSE)
|
|
194
|
+
console.log(LOG_PREFIX + 'handleMouseUp aborted: was not dragging.');
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (VERBOSE) console.log(LOG_PREFIX + 'Removing window event listeners...');
|
|
199
|
+
try {
|
|
200
|
+
window.removeEventListener('mousemove', handleDragMove);
|
|
201
|
+
window.removeEventListener('mouseup', handleMouseUp);
|
|
202
|
+
if (VERBOSE)
|
|
203
|
+
console.log(
|
|
204
|
+
LOG_PREFIX + 'Window event listeners successfully removed.'
|
|
205
|
+
);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.error(
|
|
208
|
+
LOG_PREFIX + 'Error removing window event listeners:',
|
|
209
|
+
error
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
isDragging.current = false;
|
|
214
|
+
dragStartCoords.current = null;
|
|
215
|
+
setSelectionRect(null);
|
|
216
|
+
if (VERBOSE)
|
|
217
|
+
console.log(LOG_PREFIX + 'Drag state reset, selectionRect cleared.');
|
|
218
|
+
|
|
219
|
+
const selectionRange = selectionStore.get();
|
|
220
|
+
if (VERBOSE)
|
|
221
|
+
console.log(
|
|
222
|
+
LOG_PREFIX + 'Final selection range from store:',
|
|
223
|
+
selectionRange
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
if (
|
|
227
|
+
!selectionRange.startNodeId ||
|
|
228
|
+
!selectionRange.endNodeId ||
|
|
229
|
+
(selectionRange.startNodeId === selectionRange.endNodeId &&
|
|
230
|
+
selectionRange.startCharOffset === selectionRange.endCharOffset)
|
|
231
|
+
) {
|
|
232
|
+
if (VERBOSE)
|
|
233
|
+
console.log(
|
|
234
|
+
LOG_PREFIX +
|
|
235
|
+
'handleMouseUp aborted: invalid or zero-length selection.'
|
|
236
|
+
);
|
|
237
|
+
resetSelectionStore();
|
|
238
|
+
selectionOrigin.current = null;
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (VERBOSE)
|
|
243
|
+
console.log(LOG_PREFIX + 'Calculating selection bounding box.');
|
|
244
|
+
|
|
245
|
+
const startElement = document.querySelector(
|
|
246
|
+
`[data-parent-text-node-id="${selectionRange.startNodeId}"][data-start-char-offset="${selectionRange.startCharOffset}"]`
|
|
247
|
+
);
|
|
248
|
+
const endElement = document.querySelector(
|
|
249
|
+
`[data-parent-text-node-id="${selectionRange.endNodeId}"]`
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
let selectionBox = null;
|
|
253
|
+
if (startElement && endElement) {
|
|
254
|
+
const startRect = startElement.getBoundingClientRect();
|
|
255
|
+
const endRect = endElement.getBoundingClientRect();
|
|
256
|
+
const contentRect = document
|
|
257
|
+
.getElementById('content')
|
|
258
|
+
?.getBoundingClientRect();
|
|
259
|
+
|
|
260
|
+
if (contentRect) {
|
|
261
|
+
selectionBox = {
|
|
262
|
+
top: Math.min(startRect.top, endRect.top) - contentRect.top,
|
|
263
|
+
left: Math.min(startRect.left, endRect.left) - contentRect.left,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (selectionBox) {
|
|
269
|
+
if (VERBOSE)
|
|
270
|
+
console.log(LOG_PREFIX + 'Selection complete, setting isActive: true.');
|
|
271
|
+
selectionStore.setKey('selectionBox', selectionBox);
|
|
272
|
+
selectionStore.setKey('isActive', true);
|
|
273
|
+
} else {
|
|
274
|
+
if (VERBOSE)
|
|
275
|
+
console.log(
|
|
276
|
+
LOG_PREFIX + 'Could not calculate bounding box, resetting selection.'
|
|
277
|
+
);
|
|
278
|
+
resetSelectionStore();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
selectionStore.setKey('isDragging', false);
|
|
282
|
+
selectionOrigin.current = null;
|
|
283
|
+
};
|
|
40
284
|
|
|
41
285
|
useEffect(() => {
|
|
42
286
|
fullContentMapStore.set(props.fullContentMap);
|
|
@@ -56,35 +300,28 @@ export const Compositor = (props: CompositorProps) => {
|
|
|
56
300
|
props.availableCodeHooks,
|
|
57
301
|
]);
|
|
58
302
|
|
|
59
|
-
const $viewportKey = useStore(viewportKeyStore);
|
|
60
|
-
const viewportMaxWidth =
|
|
61
|
-
$viewportKey.value === `mobile`
|
|
62
|
-
? 600
|
|
63
|
-
: $viewportKey.value === `tablet`
|
|
64
|
-
? 1000
|
|
65
|
-
: 1500;
|
|
66
|
-
const viewportMinWidth =
|
|
67
|
-
$viewportKey.value === `mobile`
|
|
68
|
-
? null
|
|
69
|
-
: $viewportKey.value === `tablet`
|
|
70
|
-
? 801
|
|
71
|
-
: 1368;
|
|
72
|
-
|
|
73
303
|
// Initialize nodes tree and set up subscriptions
|
|
74
304
|
useEffect(() => {
|
|
305
|
+
if (VERBOSE) console.log(LOG_PREFIX + 'Compositor initializing...');
|
|
75
306
|
getCtx(props).buildNodesTreeFromRowDataMadeNodes(props.nodes);
|
|
76
307
|
hasArtpacksStore.set(ARTPACKS);
|
|
77
308
|
setInitialized(true);
|
|
309
|
+
if (VERBOSE)
|
|
310
|
+
console.log(LOG_PREFIX + 'Nodes tree built, initialized set to true.');
|
|
78
311
|
|
|
79
312
|
// Stop initial loading after initialization
|
|
80
313
|
setTimeout(() => {
|
|
81
314
|
setIsLoading(false);
|
|
82
315
|
stopLoadingAnimation();
|
|
316
|
+
if (VERBOSE)
|
|
317
|
+
console.log(LOG_PREFIX + 'Initial loading animation stopped.');
|
|
83
318
|
}, 300);
|
|
84
319
|
|
|
85
320
|
const unsubscribe = getCtx(props).notifications.subscribe(
|
|
86
321
|
ROOT_NODE_NAME,
|
|
87
322
|
() => {
|
|
323
|
+
if (VERBOSE)
|
|
324
|
+
console.log(LOG_PREFIX + 'Received root notification, updating...');
|
|
88
325
|
// Start loading state
|
|
89
326
|
setIsLoading(true);
|
|
90
327
|
|
|
@@ -95,16 +332,83 @@ export const Compositor = (props: CompositorProps) => {
|
|
|
95
332
|
setTimeout(() => {
|
|
96
333
|
setIsLoading(false);
|
|
97
334
|
stopLoadingAnimation();
|
|
335
|
+
if (VERBOSE)
|
|
336
|
+
console.log(LOG_PREFIX + 'Update loading animation stopped.');
|
|
98
337
|
}, 300);
|
|
99
338
|
}
|
|
100
339
|
);
|
|
101
340
|
|
|
341
|
+
const unsubscribeToolMode = getCtx(props).toolModeValStore.subscribe(
|
|
342
|
+
(mode) => {
|
|
343
|
+
if (VERBOSE) console.log(LOG_PREFIX + 'Tool mode changed:', mode.value);
|
|
344
|
+
if (mode.value !== 'styles') {
|
|
345
|
+
if (VERBOSE)
|
|
346
|
+
console.log(
|
|
347
|
+
LOG_PREFIX + 'Exited styles mode, resetting selection store.'
|
|
348
|
+
);
|
|
349
|
+
resetSelectionStore();
|
|
350
|
+
// Ensure drag state is also reset if mode changes mid-drag
|
|
351
|
+
if (isDragging.current) {
|
|
352
|
+
if (VERBOSE)
|
|
353
|
+
console.log(
|
|
354
|
+
LOG_PREFIX + 'Mode changed mid-drag, cleaning up listeners.'
|
|
355
|
+
);
|
|
356
|
+
window.removeEventListener('mousemove', handleDragMove);
|
|
357
|
+
window.removeEventListener('mouseup', handleMouseUp);
|
|
358
|
+
isDragging.current = false;
|
|
359
|
+
dragStartCoords.current = null;
|
|
360
|
+
setSelectionRect(null);
|
|
361
|
+
selectionOrigin.current = null;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
// Cleanup function
|
|
102
368
|
return () => {
|
|
369
|
+
if (VERBOSE)
|
|
370
|
+
console.log(LOG_PREFIX + 'Compositor unmounting, cleaning up...');
|
|
103
371
|
unsubscribe();
|
|
372
|
+
unsubscribeToolMode();
|
|
104
373
|
stopLoadingAnimation();
|
|
374
|
+
// Ensure listeners are removed on unmount
|
|
375
|
+
window.removeEventListener('mousemove', handleDragMove);
|
|
376
|
+
window.removeEventListener('mouseup', handleMouseUp);
|
|
377
|
+
if (VERBOSE) console.log(LOG_PREFIX + 'Cleanup complete.');
|
|
105
378
|
};
|
|
106
379
|
}, []);
|
|
107
380
|
|
|
381
|
+
useEffect(() => {
|
|
382
|
+
const handleAction = async () => {
|
|
383
|
+
if (!$selection.isActive || !$selection.pendingAction) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const ctx = getCtx(props);
|
|
388
|
+
const range = $selection;
|
|
389
|
+
|
|
390
|
+
if ($selection.pendingAction === 'style') {
|
|
391
|
+
if (VERBOSE) console.log(LOG_PREFIX + 'useEffect acting on: style');
|
|
392
|
+
await ctx.wrapRangeInSpan(range as SelectionStoreState, 'span');
|
|
393
|
+
resetSelectionStore();
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if ($selection.pendingAction === 'link') {
|
|
397
|
+
if (VERBOSE) console.log(LOG_PREFIX + 'useEffect acting on: link');
|
|
398
|
+
const newAnchorNodeId = await ctx.wrapRangeInAnchor(
|
|
399
|
+
range as SelectionStoreState
|
|
400
|
+
);
|
|
401
|
+
if (newAnchorNodeId) {
|
|
402
|
+
ctx.handleInsertSignal('a', newAnchorNodeId);
|
|
403
|
+
}
|
|
404
|
+
resetSelectionStore();
|
|
405
|
+
}
|
|
406
|
+
ctx.notifyNode('root');
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
handleAction();
|
|
410
|
+
}, [$selection.pendingAction, $selection.isActive]);
|
|
411
|
+
|
|
108
412
|
return (
|
|
109
413
|
<div
|
|
110
414
|
id="content" // This ID is used by startLoadingAnimation
|
|
@@ -130,6 +434,20 @@ export const Compositor = (props: CompositorProps) => {
|
|
|
130
434
|
</div>
|
|
131
435
|
)}
|
|
132
436
|
|
|
437
|
+
{/* Selection drag box */}
|
|
438
|
+
{selectionRect && (
|
|
439
|
+
<div
|
|
440
|
+
className="bg-mygreen/20 fixed z-50 border border-blue-600"
|
|
441
|
+
style={{
|
|
442
|
+
left: `${selectionRect.left}px`,
|
|
443
|
+
top: `${selectionRect.top}px`,
|
|
444
|
+
width: `${selectionRect.width}px`,
|
|
445
|
+
height: `${selectionRect.height}px`,
|
|
446
|
+
pointerEvents: 'none',
|
|
447
|
+
}}
|
|
448
|
+
/>
|
|
449
|
+
)}
|
|
450
|
+
|
|
133
451
|
{/* Main content */}
|
|
134
452
|
{initialized && (
|
|
135
453
|
<Node
|
|
@@ -137,6 +455,7 @@ export const Compositor = (props: CompositorProps) => {
|
|
|
137
455
|
key={`${props.id}-${updateCounter}`}
|
|
138
456
|
ctx={props.ctx}
|
|
139
457
|
config={props.config}
|
|
458
|
+
onDragStart={handleDragStart}
|
|
140
459
|
/>
|
|
141
460
|
)}
|
|
142
461
|
</div>
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
memo,
|
|
3
|
+
type ReactElement,
|
|
4
|
+
useEffect,
|
|
5
|
+
useState,
|
|
6
|
+
createElement,
|
|
7
|
+
type MouseEvent,
|
|
8
|
+
} from 'react';
|
|
2
9
|
import { useStore } from '@nanostores/react';
|
|
3
10
|
import { getCtx } from '@/stores/nodes';
|
|
4
|
-
import { styleElementInfoStore } from '@/stores/storykeep';
|
|
11
|
+
import { styleElementInfoStore, viewportKeyStore } from '@/stores/storykeep';
|
|
5
12
|
import { getType } from '@/utils/compositor/typeGuards';
|
|
6
13
|
import { NodeWithGuid } from './NodeWithGuid';
|
|
7
14
|
import PanelVisibilityWrapper from './PanelVisibilityWrapper';
|
|
@@ -31,13 +38,16 @@ import StoryFragmentTitlePanel from '@/components/edit/storyfragment/StoryFragme
|
|
|
31
38
|
import ContextPanePanel from '@/components/edit/context/ContextPaneConfig';
|
|
32
39
|
import ContextPaneTitlePanel from '@/components/edit/context/ContextPaneConfig_title';
|
|
33
40
|
import { regexpHook } from '@/constants';
|
|
41
|
+
import { RenderChildren } from './nodes/RenderChildren';
|
|
34
42
|
import type {
|
|
35
43
|
StoryFragmentNode,
|
|
36
44
|
PaneNode,
|
|
37
45
|
BaseNode,
|
|
38
46
|
FlatNode,
|
|
39
47
|
} from '@/types/compositorTypes';
|
|
40
|
-
import type { NodeProps } from '@/types/nodeProps';
|
|
48
|
+
import type { NodeProps, SelectionOrigin } from '@/types/nodeProps';
|
|
49
|
+
|
|
50
|
+
const VERBOSE = false;
|
|
41
51
|
|
|
42
52
|
function parseCodeHook(node: BaseNode | FlatNode) {
|
|
43
53
|
if ('codeHookParams' in node && Array.isArray(node.codeHookParams)) {
|
|
@@ -84,7 +94,7 @@ const getElement = (
|
|
|
84
94
|
const isPreview = getCtx(props).rootNodeId.get() === `tmp`;
|
|
85
95
|
const hasPanes = useStore(getCtx(props).hasPanes);
|
|
86
96
|
const isTemplate = useStore(getCtx(props).isTemplate);
|
|
87
|
-
const sharedProps = { nodeId: node.id
|
|
97
|
+
const sharedProps = { ...props, nodeId: node.id };
|
|
88
98
|
const type = getType(node);
|
|
89
99
|
|
|
90
100
|
switch (type) {
|
|
@@ -274,6 +284,75 @@ const getElement = (
|
|
|
274
284
|
case 'aside':
|
|
275
285
|
case 'p': {
|
|
276
286
|
const toolModeVal = getCtx(props).toolModeValStore.get().value;
|
|
287
|
+
|
|
288
|
+
if (toolModeVal === 'styles') {
|
|
289
|
+
const className = getCtx(props).getNodeClasses(
|
|
290
|
+
node.id,
|
|
291
|
+
viewportKeyStore.get().value
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
const handleMouseDown = (e: MouseEvent<HTMLElement>) => {
|
|
295
|
+
if (VERBOSE)
|
|
296
|
+
console.log('[Node.tsx] handleMouseDown FIRED', { event: e });
|
|
297
|
+
if (!props.onDragStart) {
|
|
298
|
+
if (VERBOSE)
|
|
299
|
+
console.log(
|
|
300
|
+
'[Node.tsx] handleMouseDown ABORTED: no onDragStart prop'
|
|
301
|
+
);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
e.preventDefault();
|
|
305
|
+
if (VERBOSE) console.log('[Node.tsx] preventDefault called');
|
|
306
|
+
|
|
307
|
+
const target = e.target as HTMLElement;
|
|
308
|
+
if (VERBOSE) console.log('[Node.tsx] mousedown target:', target);
|
|
309
|
+
const textNodeElement = target.closest('[data-parent-text-node-id]');
|
|
310
|
+
|
|
311
|
+
if (textNodeElement) {
|
|
312
|
+
const parentTextNodeId = textNodeElement.getAttribute(
|
|
313
|
+
'data-parent-text-node-id'
|
|
314
|
+
);
|
|
315
|
+
const startCharOffset = parseInt(
|
|
316
|
+
textNodeElement.getAttribute('data-start-char-offset') || '0',
|
|
317
|
+
10
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
if (parentTextNodeId) {
|
|
321
|
+
const origin: SelectionOrigin = {
|
|
322
|
+
blockNodeId: node.id,
|
|
323
|
+
lcaNodeId: node.id,
|
|
324
|
+
startNodeId: parentTextNodeId,
|
|
325
|
+
startCharOffset: startCharOffset,
|
|
326
|
+
endNodeId: parentTextNodeId,
|
|
327
|
+
endCharOffset: startCharOffset,
|
|
328
|
+
};
|
|
329
|
+
props.onDragStart(origin, e);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const children = getCtx(props).getChildNodeIDs(node.id);
|
|
335
|
+
|
|
336
|
+
// Propagate props to children, but explicitly enable text selection
|
|
337
|
+
// and remove the onDragStart handler to prevent it from firing on child elements.
|
|
338
|
+
const childProps: NodeProps = {
|
|
339
|
+
...props,
|
|
340
|
+
onDragStart: undefined,
|
|
341
|
+
isSelectableText: true,
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
return createElement(
|
|
345
|
+
type,
|
|
346
|
+
{
|
|
347
|
+
className: className,
|
|
348
|
+
onMouseDown: handleMouseDown,
|
|
349
|
+
'data-node-id': node.id,
|
|
350
|
+
style: { userSelect: 'none' },
|
|
351
|
+
},
|
|
352
|
+
<RenderChildren children={children} nodeProps={childProps} />
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
277
356
|
if (toolModeVal === `insert`)
|
|
278
357
|
return <NodeBasicTagInsert {...sharedProps} tagName={type} />;
|
|
279
358
|
else if (toolModeVal === `eraser`)
|
|
@@ -283,6 +362,7 @@ const getElement = (
|
|
|
283
362
|
return <NodeBasicTag {...sharedProps} tagName={type} />;
|
|
284
363
|
}
|
|
285
364
|
|
|
365
|
+
case 'span':
|
|
286
366
|
case 'strong':
|
|
287
367
|
case 'em':
|
|
288
368
|
return <NodeBasicTag {...sharedProps} tagName={type} />;
|
|
@@ -293,12 +373,12 @@ const getElement = (
|
|
|
293
373
|
const toolModeVal = getCtx(props).toolModeValStore.get().value;
|
|
294
374
|
if (toolModeVal === `eraser`)
|
|
295
375
|
return <NodeButtonEraser {...sharedProps} />;
|
|
296
|
-
return <NodeButton {...sharedProps} />;
|
|
376
|
+
return <NodeButton {...sharedProps} isSelectableText={false} />;
|
|
297
377
|
}
|
|
298
378
|
case 'a': {
|
|
299
379
|
const toolModeVal = getCtx(props).toolModeValStore.get().value;
|
|
300
380
|
if (toolModeVal === `eraser`) return <NodeAEraser {...sharedProps} />;
|
|
301
|
-
return <NodeA {...sharedProps} />;
|
|
381
|
+
return <NodeA {...sharedProps} isSelectableText={false} />;
|
|
302
382
|
}
|
|
303
383
|
case 'img':
|
|
304
384
|
return <NodeImg {...sharedProps} />;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// templates/src/components/compositor/nodes/RenderChildren.tsx
|
|
2
|
+
|
|
1
3
|
import Node from '../Node';
|
|
2
4
|
import type { NodeProps } from '@/types/nodeProps';
|
|
3
5
|
import type { CompositorProps } from '../Compositor';
|
|
@@ -12,12 +14,7 @@ export const RenderChildren = (props: RenderChildrenProps) => {
|
|
|
12
14
|
return (
|
|
13
15
|
<>
|
|
14
16
|
{children.map((id: string) => (
|
|
15
|
-
<Node
|
|
16
|
-
nodeId={id}
|
|
17
|
-
key={id}
|
|
18
|
-
ctx={nodeProps.ctx}
|
|
19
|
-
config={nodeProps.config}
|
|
20
|
-
/>
|
|
17
|
+
<Node {...nodeProps} nodeId={id} key={id} />
|
|
21
18
|
))}
|
|
22
19
|
</>
|
|
23
20
|
);
|