astro-tractstack 2.0.11 → 2.0.13
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 +8 -14
- package/package.json +1 -1
- package/templates/css/storykeep.css +1 -92872
- package/templates/src/components/compositor/Compositor.tsx +14 -6
- package/templates/src/components/compositor/Node.tsx +42 -2
- package/templates/src/components/compositor/nodes/Pane.tsx +0 -5
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag.tsx +2 -1
- package/templates/src/components/edit/pane/AddPanePanel.tsx +5 -1
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +140 -116
- package/templates/src/components/edit/pane/AiPaneGenerator.tsx +575 -0
- package/templates/src/components/edit/pane/AiPanePreview.tsx +107 -0
- package/templates/src/components/edit/pane/PageGenSelector.tsx +10 -3
- package/templates/src/constants/prompts.json +35 -40
- package/templates/src/stores/nodes.ts +18 -15
- package/templates/src/types/compositorTypes.ts +27 -13
- package/templates/src/utils/compositor/aiPaneParser.ts +613 -0
- package/templates/src/utils/compositor/allowInsert.ts +1 -3
- package/templates/src/utils/compositor/nodesHelper.ts +61 -42
- package/templates/src/utils/compositor/tailwindClasses.ts +200 -70
- package/utils/inject-files.ts +8 -14
- package/templates/src/components/edit/pane/AddPanePanel_newAICopy.tsx +0 -107
- package/templates/src/components/edit/pane/AddPanePanel_newAICopy_modal.tsx +0 -217
- package/templates/src/components/edit/pane/AddPanePanel_newCopyMode.tsx +0 -109
|
@@ -387,12 +387,6 @@ export const Compositor = (props: CompositorProps) => {
|
|
|
387
387
|
const ctx = getCtx(props);
|
|
388
388
|
const range = $selection;
|
|
389
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
390
|
if ($selection.pendingAction === 'link') {
|
|
397
391
|
if (VERBOSE) console.log(LOG_PREFIX + 'useEffect acting on: link');
|
|
398
392
|
const newAnchorNodeId = await ctx.wrapRangeInAnchor(
|
|
@@ -402,6 +396,20 @@ export const Compositor = (props: CompositorProps) => {
|
|
|
402
396
|
ctx.handleInsertSignal('a', newAnchorNodeId);
|
|
403
397
|
}
|
|
404
398
|
resetSelectionStore();
|
|
399
|
+
} else if ($selection.pendingAction === 'style') {
|
|
400
|
+
if (VERBOSE) console.log(LOG_PREFIX + 'useEffect acting on: style');
|
|
401
|
+
const newSpanNodeId = await ctx.wrapRangeInSpan(
|
|
402
|
+
range as SelectionStoreState,
|
|
403
|
+
'span'
|
|
404
|
+
);
|
|
405
|
+
if (newSpanNodeId) {
|
|
406
|
+
settingsPanelStore.set({
|
|
407
|
+
action: 'style-element',
|
|
408
|
+
nodeId: newSpanNodeId,
|
|
409
|
+
expanded: true,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
resetSelectionStore();
|
|
405
413
|
}
|
|
406
414
|
ctx.notifyNode('root');
|
|
407
415
|
};
|
|
@@ -45,6 +45,8 @@ import type {
|
|
|
45
45
|
BaseNode,
|
|
46
46
|
FlatNode,
|
|
47
47
|
} from '@/types/compositorTypes';
|
|
48
|
+
import { handleClickEventDefault } from '@/utils/compositor/handleClickEvent';
|
|
49
|
+
import { selectionStore } from '@/stores/selection';
|
|
48
50
|
import type { NodeProps, SelectionOrigin } from '@/types/nodeProps';
|
|
49
51
|
|
|
50
52
|
const VERBOSE = false;
|
|
@@ -153,6 +155,7 @@ const getElement = (
|
|
|
153
155
|
nodeId={props.nodeId}
|
|
154
156
|
ctx={getCtx(props)}
|
|
155
157
|
isTemplate={isTemplate}
|
|
158
|
+
config={props.config!}
|
|
156
159
|
/>
|
|
157
160
|
) : (
|
|
158
161
|
<>
|
|
@@ -213,6 +216,7 @@ const getElement = (
|
|
|
213
216
|
nodeId={node.id}
|
|
214
217
|
first={true}
|
|
215
218
|
ctx={getCtx(props)}
|
|
219
|
+
config={props.config!}
|
|
216
220
|
isContextPane={true}
|
|
217
221
|
/>
|
|
218
222
|
</PanelVisibilityWrapper>
|
|
@@ -240,7 +244,12 @@ const getElement = (
|
|
|
240
244
|
panelType="add"
|
|
241
245
|
ctx={getCtx(props)}
|
|
242
246
|
>
|
|
243
|
-
<AddPanePanel
|
|
247
|
+
<AddPanePanel
|
|
248
|
+
nodeId={node.id}
|
|
249
|
+
first={true}
|
|
250
|
+
ctx={getCtx(props)}
|
|
251
|
+
config={props.config!}
|
|
252
|
+
/>
|
|
244
253
|
</PanelVisibilityWrapper>
|
|
245
254
|
)}
|
|
246
255
|
<div className="py-0.5">
|
|
@@ -264,7 +273,12 @@ const getElement = (
|
|
|
264
273
|
panelType="add"
|
|
265
274
|
ctx={getCtx(props)}
|
|
266
275
|
>
|
|
267
|
-
<AddPanePanel
|
|
276
|
+
<AddPanePanel
|
|
277
|
+
nodeId={node.id}
|
|
278
|
+
first={false}
|
|
279
|
+
ctx={getCtx(props)}
|
|
280
|
+
config={props.config!}
|
|
281
|
+
/>
|
|
268
282
|
</PanelVisibilityWrapper>
|
|
269
283
|
</>
|
|
270
284
|
);
|
|
@@ -291,6 +305,31 @@ const getElement = (
|
|
|
291
305
|
viewportKeyStore.get().value
|
|
292
306
|
);
|
|
293
307
|
|
|
308
|
+
const handleElementClick = (e: MouseEvent<HTMLElement>) => {
|
|
309
|
+
// 1. ALWAYS stop the event from bubbling up to the Pane.
|
|
310
|
+
e.stopPropagation();
|
|
311
|
+
|
|
312
|
+
// 2. Check the selection store. The handleMouseUp in Compositor.tsx
|
|
313
|
+
// has already run by the time this 'click' event fires.
|
|
314
|
+
//
|
|
315
|
+
// - If it was a drag, Compositor.tsx set 'isActive: true' (line 267).
|
|
316
|
+
// - If it was a click, Compositor.tsx called 'resetSelectionStore'
|
|
317
|
+
// (line 242), so 'isActive: false'.
|
|
318
|
+
//
|
|
319
|
+
const { isActive } = selectionStore.get();
|
|
320
|
+
|
|
321
|
+
if (isActive) {
|
|
322
|
+
// A drag just finished. The user's intent was to select text.
|
|
323
|
+
// Do NOT open the panel.
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// 3. 'isActive' was false. This was a genuine click.
|
|
328
|
+
// We can safely open the settings panel.
|
|
329
|
+
// 'node' is already in scope from the getElement function.
|
|
330
|
+
handleClickEventDefault(node as FlatNode, true);
|
|
331
|
+
};
|
|
332
|
+
|
|
294
333
|
const handleMouseDown = (e: MouseEvent<HTMLElement>) => {
|
|
295
334
|
if (VERBOSE)
|
|
296
335
|
console.log('[Node.tsx] handleMouseDown FIRED', { event: e });
|
|
@@ -346,6 +385,7 @@ const getElement = (
|
|
|
346
385
|
{
|
|
347
386
|
className: className,
|
|
348
387
|
onMouseDown: handleMouseDown,
|
|
388
|
+
onClick: handleElementClick,
|
|
349
389
|
'data-node-id': node.id,
|
|
350
390
|
style: { userSelect: 'none' },
|
|
351
391
|
},
|
|
@@ -294,11 +294,6 @@ const Pane = memo(
|
|
|
294
294
|
(prevProps: NodeProps, nextProps: NodeProps) => {
|
|
295
295
|
const isEqual =
|
|
296
296
|
prevProps.nodeId === nextProps.nodeId && prevProps.ctx === nextProps.ctx;
|
|
297
|
-
if (!isEqual) {
|
|
298
|
-
console.log(
|
|
299
|
-
` !! Pane rerender triggered by props change: ${prevProps.nodeId} -> ${nextProps.nodeId}`
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
297
|
return isEqual;
|
|
303
298
|
}
|
|
304
299
|
);
|
|
@@ -379,8 +379,9 @@ export const NodeBasicTag = (props: NodeTagProps) => {
|
|
|
379
379
|
.filter(
|
|
380
380
|
(childNode): childNode is FlatNode =>
|
|
381
381
|
'tagName' in childNode &&
|
|
382
|
-
['a', 'button'].includes(childNode.tagName as string)
|
|
382
|
+
['a', 'button', 'span'].includes(childNode.tagName as string)
|
|
383
383
|
) as FlatNode[];
|
|
384
|
+
if (VERBOSE) console.log('originalNodes to save:', originalNodes);
|
|
384
385
|
|
|
385
386
|
const parsedNodes = processRichTextToNodes(
|
|
386
387
|
currentContent,
|
|
@@ -5,8 +5,9 @@ import AddPaneNewPanel from './AddPanePanel_new';
|
|
|
5
5
|
import AddPaneBreakPanel from './AddPanePanel_break';
|
|
6
6
|
import AddPaneReUsePanel from './AddPanePanel_reuse';
|
|
7
7
|
import AddPaneCodeHookPanel from './AddPanePanel_codehook';
|
|
8
|
-
import { NodesContext, ROOT_NODE_NAME, getCtx } from '@/stores/nodes';
|
|
8
|
+
import { NodesContext, ROOT_NODE_NAME, getCtx } from '@/stores/nodes';
|
|
9
9
|
import { PaneAddMode } from '@/types/compositorTypes';
|
|
10
|
+
import type { BrandConfig } from '@/types/tractstack';
|
|
10
11
|
|
|
11
12
|
interface AddPanePanelProps {
|
|
12
13
|
nodeId: string;
|
|
@@ -14,6 +15,7 @@ interface AddPanePanelProps {
|
|
|
14
15
|
ctx?: NodesContext;
|
|
15
16
|
isStoryFragment?: boolean;
|
|
16
17
|
isContextPane?: boolean;
|
|
18
|
+
config?: BrandConfig;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
const AddPanePanel = ({
|
|
@@ -22,6 +24,7 @@ const AddPanePanel = ({
|
|
|
22
24
|
ctx,
|
|
23
25
|
isStoryFragment = false,
|
|
24
26
|
isContextPane = false,
|
|
27
|
+
config,
|
|
25
28
|
}: AddPanePanelProps) => {
|
|
26
29
|
const [reset, setReset] = useState(false);
|
|
27
30
|
const lookup = first ? `${nodeId}-0` : nodeId;
|
|
@@ -62,6 +65,7 @@ const AddPanePanel = ({
|
|
|
62
65
|
ctx={nodesCtx}
|
|
63
66
|
isStoryFragment={isStoryFragment}
|
|
64
67
|
isContextPane={isContextPane}
|
|
68
|
+
config={config!}
|
|
65
69
|
/>
|
|
66
70
|
) : mode === PaneAddMode.BREAK && !isContextPane ? (
|
|
67
71
|
<AddPaneBreakPanel
|
|
@@ -24,13 +24,11 @@ import {
|
|
|
24
24
|
hasAssemblyAIStore,
|
|
25
25
|
} from '@/stores/storykeep';
|
|
26
26
|
import { templateCategories } from '@/utils/compositor/templateMarkdownStyles';
|
|
27
|
-
import {
|
|
28
|
-
import { AddPaneNewCopyMode, type CopyMode } from './AddPanePanel_newCopyMode';
|
|
27
|
+
import { AiPaneGenerator } from './AiPaneGenerator';
|
|
29
28
|
import { AddPaneNewCustomCopy } from './AddPanePanel_newCustomCopy';
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import { PaneAddMode } from '@/types/compositorTypes';
|
|
29
|
+
import { themes, type Theme, type BrandConfig } from '@/types/tractstack';
|
|
30
|
+
import { PaneAddMode, type TemplatePane } from '@/types/compositorTypes';
|
|
31
|
+
import { useStore } from '@nanostores/react';
|
|
34
32
|
|
|
35
33
|
interface AddPaneNewPanelProps {
|
|
36
34
|
nodeId: string;
|
|
@@ -39,6 +37,7 @@ interface AddPaneNewPanelProps {
|
|
|
39
37
|
ctx?: NodesContext;
|
|
40
38
|
isStoryFragment?: boolean;
|
|
41
39
|
isContextPane?: boolean;
|
|
40
|
+
config?: BrandConfig;
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
interface PreviewPane {
|
|
@@ -58,47 +57,47 @@ interface TemplateCategory {
|
|
|
58
57
|
|
|
59
58
|
const ITEMS_PER_PAGE = 8;
|
|
60
59
|
|
|
60
|
+
type Mode = 'template' | 'custom' | 'ai';
|
|
61
|
+
|
|
61
62
|
const AddPaneNewPanel = ({
|
|
62
63
|
nodeId,
|
|
63
64
|
first,
|
|
64
|
-
setMode,
|
|
65
|
+
setMode: setParentMode, // Renamed prop to avoid conflict with local state
|
|
65
66
|
ctx,
|
|
66
67
|
isStoryFragment = false,
|
|
67
68
|
isContextPane = false,
|
|
69
|
+
config,
|
|
68
70
|
}: AddPaneNewPanelProps) => {
|
|
69
|
-
const brand = brandColourStore
|
|
70
|
-
const hasAssemblyAI = hasAssemblyAIStore
|
|
71
|
-
const [
|
|
71
|
+
const brand = useStore(brandColourStore);
|
|
72
|
+
const hasAssemblyAI = useStore(hasAssemblyAIStore);
|
|
73
|
+
const [mode, setMode] = useState<Mode>('template'); // Local mode state
|
|
72
74
|
const [customMarkdown, setCustomMarkdown] = useState<string>(`...`);
|
|
73
75
|
const [previews, setPreviews] = useState<PreviewPane[]>([]);
|
|
74
76
|
const [currentPage, setCurrentPage] = useState(0);
|
|
75
77
|
const [renderedPages, setRenderedPages] = useState<Set<number>>(new Set());
|
|
76
78
|
const [selectedTheme, setSelectedTheme] = useState<Theme>(
|
|
77
|
-
preferredThemeStore
|
|
79
|
+
useStore(preferredThemeStore)
|
|
78
80
|
);
|
|
79
81
|
const [useOddVariant, setUseOddVariant] = useState(false);
|
|
80
82
|
const [selectedCategory, setSelectedCategory] = useState<TemplateCategory>(
|
|
81
83
|
templateCategories[isContextPane ? 1 : first ? 4 : 0]
|
|
82
84
|
);
|
|
83
85
|
const [isInserting, setIsInserting] = useState(false);
|
|
84
|
-
const [aiContentGenerated, setAiContentGenerated] = useState(false);
|
|
85
86
|
const [fragmentsToGenerate, setFragmentsToGenerate] = useState<
|
|
86
87
|
PanePreviewRequest[]
|
|
87
88
|
>([]);
|
|
88
|
-
const shouldShowDesigns = copyMode !== 'ai' || aiContentGenerated;
|
|
89
89
|
|
|
90
90
|
const categoryCollection = useMemo(() => {
|
|
91
|
-
const categories =
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
: templateCategories;
|
|
91
|
+
const categories = isContextPane
|
|
92
|
+
? [templateCategories[1]]
|
|
93
|
+
: templateCategories;
|
|
95
94
|
|
|
96
95
|
return createListCollection({
|
|
97
96
|
items: categories,
|
|
98
97
|
itemToValue: (item) => item.id,
|
|
99
98
|
itemToString: (item) => item.title,
|
|
100
99
|
});
|
|
101
|
-
}, [
|
|
100
|
+
}, [isContextPane]);
|
|
102
101
|
|
|
103
102
|
const themesCollection = useMemo(() => {
|
|
104
103
|
return createListCollection({
|
|
@@ -109,38 +108,25 @@ const AddPaneNewPanel = ({
|
|
|
109
108
|
}, []);
|
|
110
109
|
|
|
111
110
|
const filteredTemplates = useMemo(() => {
|
|
112
|
-
if (copyMode === `ai` || isContextPane)
|
|
113
|
-
return templateCategories[1].getTemplates(
|
|
114
|
-
selectedTheme,
|
|
115
|
-
brand,
|
|
116
|
-
useOddVariant
|
|
117
|
-
);
|
|
118
|
-
if (isContextPane)
|
|
119
|
-
return templateCategories[1].getTemplates(
|
|
120
|
-
selectedTheme,
|
|
121
|
-
brand,
|
|
122
|
-
useOddVariant
|
|
123
|
-
);
|
|
124
111
|
return selectedCategory.getTemplates(selectedTheme, brand, useOddVariant);
|
|
125
|
-
}, [selectedTheme, useOddVariant, selectedCategory,
|
|
112
|
+
}, [selectedTheme, useOddVariant, selectedCategory, brand]);
|
|
126
113
|
|
|
127
114
|
useEffect(() => {
|
|
128
|
-
if (
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
};
|
|
115
|
+
if (isContextPane) {
|
|
116
|
+
setSelectedCategory(templateCategories[1]);
|
|
117
|
+
} else if (first) {
|
|
118
|
+
setSelectedCategory(templateCategories[4]);
|
|
119
|
+
} else {
|
|
120
|
+
setSelectedCategory(templateCategories[0]);
|
|
121
|
+
}
|
|
122
|
+
}, [isContextPane, first]);
|
|
137
123
|
|
|
138
124
|
useEffect(() => {
|
|
139
125
|
const newPreviews = filteredTemplates.map((template, index: number) => {
|
|
140
|
-
const
|
|
141
|
-
|
|
126
|
+
const previewCtx = new NodesContext();
|
|
127
|
+
previewCtx.addNode(createEmptyStorykeep('tmp'));
|
|
142
128
|
const thisTemplate =
|
|
143
|
-
|
|
129
|
+
mode === 'custom'
|
|
144
130
|
? {
|
|
145
131
|
...template,
|
|
146
132
|
markdown: template.markdown && {
|
|
@@ -149,13 +135,13 @@ const AddPaneNewPanel = ({
|
|
|
149
135
|
},
|
|
150
136
|
}
|
|
151
137
|
: template;
|
|
152
|
-
|
|
153
|
-
return { ctx, template: thisTemplate, index };
|
|
138
|
+
previewCtx.addTemplatePane('tmp', thisTemplate);
|
|
139
|
+
return { ctx: previewCtx, template: thisTemplate, index };
|
|
154
140
|
});
|
|
155
141
|
setPreviews(newPreviews);
|
|
156
142
|
setCurrentPage(0);
|
|
157
143
|
setRenderedPages(new Set());
|
|
158
|
-
}, [filteredTemplates, customMarkdown,
|
|
144
|
+
}, [filteredTemplates, customMarkdown, mode]);
|
|
159
145
|
|
|
160
146
|
const totalPages = Math.ceil(previews.length / ITEMS_PER_PAGE);
|
|
161
147
|
|
|
@@ -223,81 +209,47 @@ const AddPaneNewPanel = ({
|
|
|
223
209
|
});
|
|
224
210
|
};
|
|
225
211
|
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
nodeId: string,
|
|
229
|
-
first: boolean
|
|
230
|
-
) => {
|
|
231
|
-
if (isInserting) return;
|
|
212
|
+
const handleApplyTemplate = async (template: any) => {
|
|
213
|
+
if (isInserting || !ctx) return;
|
|
232
214
|
setIsInserting(true);
|
|
233
215
|
|
|
234
216
|
try {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
template?.markdown?.markdownBody &&
|
|
238
|
-
template.markdown.markdownBody.trim() !== '...' &&
|
|
239
|
-
template.markdown.markdownBody.trim().length > 0;
|
|
240
|
-
|
|
241
|
-
const insertTemplate = [`blank`, `custom`].includes(copyMode)
|
|
217
|
+
const insertTemplate =
|
|
218
|
+
mode === 'custom'
|
|
242
219
|
? {
|
|
243
220
|
...cloneDeep(template),
|
|
244
221
|
markdown: template.markdown && {
|
|
245
222
|
...template.markdown,
|
|
246
|
-
markdownBody:
|
|
223
|
+
markdownBody: customMarkdown,
|
|
247
224
|
},
|
|
248
225
|
}
|
|
249
226
|
: cloneDeep(template);
|
|
250
227
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
) {
|
|
266
|
-
|
|
267
|
-
.get()
|
|
268
|
-
.filter((item) => ['Pane', 'StoryFragment'].includes(item.type))
|
|
269
|
-
.map((item) => item.slug);
|
|
270
|
-
|
|
271
|
-
const titleSlugResult = await getTitleSlug(
|
|
272
|
-
markdownContent,
|
|
273
|
-
existingSlugs
|
|
274
|
-
);
|
|
275
|
-
|
|
276
|
-
if (titleSlugResult) {
|
|
277
|
-
insertTemplate.title = titleSlugResult.title;
|
|
278
|
-
insertTemplate.slug = titleSlugResult.slug;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const ownerId =
|
|
283
|
-
isStoryFragment || isContextPane
|
|
284
|
-
? nodeId
|
|
285
|
-
: ctx.getClosestNodeTypeFromId(nodeId, 'StoryFragment');
|
|
286
|
-
|
|
287
|
-
if (isContextPane) {
|
|
288
|
-
insertTemplate.isContextPane = true;
|
|
289
|
-
ctx.addContextTemplatePane(ownerId, insertTemplate);
|
|
290
|
-
} else {
|
|
291
|
-
const newPaneId = ctx.addTemplatePane(
|
|
228
|
+
insertTemplate.title = '';
|
|
229
|
+
insertTemplate.slug = '';
|
|
230
|
+
|
|
231
|
+
const ownerId =
|
|
232
|
+
isStoryFragment || isContextPane
|
|
233
|
+
? nodeId
|
|
234
|
+
: ctx.getClosestNodeTypeFromId(nodeId, 'StoryFragment');
|
|
235
|
+
|
|
236
|
+
if (isContextPane) {
|
|
237
|
+
insertTemplate.isContextPane = true;
|
|
238
|
+
await ctx.applyAtomicUpdate(async (tmpCtx) => {
|
|
239
|
+
tmpCtx.addContextTemplatePane(ownerId, insertTemplate);
|
|
240
|
+
});
|
|
241
|
+
} else {
|
|
242
|
+
await ctx.applyAtomicUpdate(async (tmpCtx) => {
|
|
243
|
+
tmpCtx.addTemplatePane(
|
|
292
244
|
ownerId,
|
|
293
245
|
insertTemplate,
|
|
294
246
|
nodeId,
|
|
295
247
|
first ? 'before' : 'after'
|
|
296
248
|
);
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
setMode(PaneAddMode.DEFAULT, false);
|
|
249
|
+
});
|
|
250
|
+
ctx.notifyNode(`root`);
|
|
300
251
|
}
|
|
252
|
+
setParentMode(PaneAddMode.DEFAULT, false);
|
|
301
253
|
} catch (error) {
|
|
302
254
|
console.error('Error inserting template:', error);
|
|
303
255
|
} finally {
|
|
@@ -305,6 +257,39 @@ const AddPaneNewPanel = ({
|
|
|
305
257
|
}
|
|
306
258
|
};
|
|
307
259
|
|
|
260
|
+
const handleApplyGeneratedPane = async (pane: TemplatePane) => {
|
|
261
|
+
if (isInserting || !ctx) return;
|
|
262
|
+
setIsInserting(true);
|
|
263
|
+
try {
|
|
264
|
+
const ownerId =
|
|
265
|
+
isStoryFragment || isContextPane
|
|
266
|
+
? nodeId
|
|
267
|
+
: ctx.getClosestNodeTypeFromId(nodeId, 'StoryFragment');
|
|
268
|
+
|
|
269
|
+
if (isContextPane) {
|
|
270
|
+
pane.isContextPane = true;
|
|
271
|
+
await ctx.applyAtomicUpdate(async (tmpCtx) => {
|
|
272
|
+
tmpCtx.addContextTemplatePane(ownerId, pane);
|
|
273
|
+
});
|
|
274
|
+
} else {
|
|
275
|
+
await ctx.applyAtomicUpdate(async (tmpCtx) => {
|
|
276
|
+
tmpCtx.addTemplatePane(
|
|
277
|
+
ownerId,
|
|
278
|
+
pane,
|
|
279
|
+
nodeId,
|
|
280
|
+
first ? 'before' : 'after'
|
|
281
|
+
);
|
|
282
|
+
});
|
|
283
|
+
ctx.notifyNode(`root`);
|
|
284
|
+
}
|
|
285
|
+
setParentMode(PaneAddMode.DEFAULT, false);
|
|
286
|
+
} catch (error) {
|
|
287
|
+
console.error('Error applying generated pane:', error);
|
|
288
|
+
} finally {
|
|
289
|
+
setIsInserting(false);
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
|
|
308
293
|
const handleThemeChange = (details: { value: string[] }) => {
|
|
309
294
|
const newTheme = details.value[0] as Theme;
|
|
310
295
|
if (newTheme) {
|
|
@@ -338,8 +323,9 @@ const AddPaneNewPanel = ({
|
|
|
338
323
|
<style>{customStyles}</style>
|
|
339
324
|
<div className="group flex w-full gap-1 rounded-md bg-white p-1.5">
|
|
340
325
|
<button
|
|
341
|
-
onClick={() =>
|
|
326
|
+
onClick={() => setParentMode(PaneAddMode.DEFAULT, first)}
|
|
342
327
|
className="w-fit rounded bg-gray-100 px-3 py-1 text-sm text-gray-700 transition-colors hover:bg-gray-200 focus:bg-gray-200"
|
|
328
|
+
type="button"
|
|
343
329
|
>
|
|
344
330
|
← Go Back
|
|
345
331
|
</button>
|
|
@@ -348,11 +334,45 @@ const AddPaneNewPanel = ({
|
|
|
348
334
|
<div className="font-action flex-none rounded px-2 py-2.5 text-sm font-bold text-cyan-700 shadow-sm">
|
|
349
335
|
+ Design New Pane
|
|
350
336
|
</div>
|
|
337
|
+
<div className="flex items-center space-x-2 rounded-lg bg-gray-100 p-1">
|
|
338
|
+
<button
|
|
339
|
+
onClick={() => setMode('template')}
|
|
340
|
+
className={`rounded-md px-3 py-1 text-sm font-medium transition-colors ${
|
|
341
|
+
mode === 'template'
|
|
342
|
+
? 'bg-white text-cyan-700 shadow'
|
|
343
|
+
: 'text-gray-600 hover:text-gray-800'
|
|
344
|
+
}`}
|
|
345
|
+
type="button"
|
|
346
|
+
>
|
|
347
|
+
Use Template
|
|
348
|
+
</button>
|
|
349
|
+
<button
|
|
350
|
+
onClick={() => setMode('custom')}
|
|
351
|
+
className={`rounded-md px-3 py-1 text-sm font-medium transition-colors ${
|
|
352
|
+
mode === 'custom'
|
|
353
|
+
? 'bg-white text-cyan-700 shadow'
|
|
354
|
+
: 'text-gray-600 hover:text-gray-800'
|
|
355
|
+
}`}
|
|
356
|
+
type="button"
|
|
357
|
+
>
|
|
358
|
+
Paste Markdown
|
|
359
|
+
</button>
|
|
360
|
+
{hasAssemblyAI && (
|
|
361
|
+
<button
|
|
362
|
+
onClick={() => setMode('ai')}
|
|
363
|
+
className={`rounded-md px-3 py-1 text-sm font-medium transition-colors ${
|
|
364
|
+
mode === 'ai'
|
|
365
|
+
? 'rounded-md border border-transparent bg-cyan-600 px-3 py-1.5 text-sm font-bold text-white shadow-sm hover:bg-cyan-700 focus:outline-none focus:ring-2 focus:ring-cyan-500 focus:ring-offset-2'
|
|
366
|
+
: 'text-gray-600 hover:text-gray-800'
|
|
367
|
+
}`}
|
|
368
|
+
type="button"
|
|
369
|
+
>
|
|
370
|
+
✨ Generate with AI
|
|
371
|
+
</button>
|
|
372
|
+
)}
|
|
373
|
+
</div>
|
|
351
374
|
|
|
352
|
-
{
|
|
353
|
-
<AddPaneNewCopyMode selected={copyMode} onChange={setCopyMode} />
|
|
354
|
-
)}
|
|
355
|
-
{copyMode === 'custom' && (
|
|
375
|
+
{mode === 'custom' && (
|
|
356
376
|
<div className="mt-4 w-full">
|
|
357
377
|
<AddPaneNewCustomCopy
|
|
358
378
|
value={customMarkdown}
|
|
@@ -360,18 +380,20 @@ const AddPaneNewPanel = ({
|
|
|
360
380
|
/>
|
|
361
381
|
</div>
|
|
362
382
|
)}
|
|
363
|
-
{
|
|
383
|
+
{mode === 'ai' && (
|
|
364
384
|
<div className="mt-4 w-full">
|
|
365
|
-
<
|
|
366
|
-
|
|
367
|
-
|
|
385
|
+
<AiPaneGenerator
|
|
386
|
+
ownerId={nodeId}
|
|
387
|
+
onComplete={handleApplyGeneratedPane}
|
|
388
|
+
onCancel={() => setMode('template')}
|
|
389
|
+
config={config!}
|
|
368
390
|
/>
|
|
369
391
|
</div>
|
|
370
392
|
)}
|
|
371
393
|
</div>
|
|
372
394
|
</div>
|
|
373
395
|
|
|
374
|
-
{
|
|
396
|
+
{mode !== 'ai' && (
|
|
375
397
|
<>
|
|
376
398
|
<h3 className="font-action px-3.5 pb-1.5 pt-4 text-xl font-bold text-black">
|
|
377
399
|
1. Template design settings
|
|
@@ -521,8 +543,7 @@ const AddPaneNewPanel = ({
|
|
|
521
543
|
onClick={
|
|
522
544
|
isInserting
|
|
523
545
|
? undefined
|
|
524
|
-
: () =>
|
|
525
|
-
handleTemplateInsert(preview.template, nodeId, first)
|
|
546
|
+
: () => handleApplyTemplate(preview.template)
|
|
526
547
|
}
|
|
527
548
|
className={`bg-mywhite group relative w-full rounded-sm shadow-inner ${
|
|
528
549
|
isInserting
|
|
@@ -588,6 +609,7 @@ const AddPaneNewPanel = ({
|
|
|
588
609
|
onClick={() => handlePageChange(currentPage - 1)}
|
|
589
610
|
disabled={currentPage === 0}
|
|
590
611
|
className="rounded bg-gray-100 px-3 py-1 text-sm text-gray-700 transition-colors hover:bg-gray-200 focus:bg-gray-200 disabled:cursor-not-allowed disabled:opacity-50"
|
|
612
|
+
type="button"
|
|
591
613
|
>
|
|
592
614
|
Previous
|
|
593
615
|
</button>
|
|
@@ -601,6 +623,7 @@ const AddPaneNewPanel = ({
|
|
|
601
623
|
? 'bg-cyan-700 text-white'
|
|
602
624
|
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
|
603
625
|
}`}
|
|
626
|
+
type="button"
|
|
604
627
|
>
|
|
605
628
|
{index + 1}
|
|
606
629
|
</button>
|
|
@@ -610,6 +633,7 @@ const AddPaneNewPanel = ({
|
|
|
610
633
|
onClick={() => handlePageChange(currentPage + 1)}
|
|
611
634
|
disabled={currentPage === totalPages - 1}
|
|
612
635
|
className="rounded bg-gray-100 px-3 py-1 text-sm text-gray-700 transition-colors hover:bg-gray-200 focus:bg-gray-200 disabled:cursor-not-allowed disabled:opacity-50"
|
|
636
|
+
type="button"
|
|
613
637
|
>
|
|
614
638
|
Next
|
|
615
639
|
</button>
|