astro-tractstack 2.0.18 → 2.0.20
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 +6 -32
- package/package.json +1 -1
- package/templates/src/components/codehooks/BunnyVideoSetup.tsx +1 -4
- package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +0 -4
- package/templates/src/components/codehooks/ListContentSetup.tsx +1 -8
- package/templates/src/components/codehooks/ProductCardSetup.tsx +0 -2
- package/templates/src/components/codehooks/ProductGridSetup.tsx +0 -2
- package/templates/src/components/compositor/Compositor.tsx +3 -6
- package/templates/src/components/compositor/Node.tsx +13 -32
- package/templates/src/components/compositor/NodeWithGuid.tsx +49 -5
- package/templates/src/components/compositor/nodes/Pane.tsx +4 -21
- package/templates/src/components/compositor/nodes/Pane_DesignLibrary.tsx +27 -7
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag.tsx +3 -1
- package/templates/src/components/compositor/preview/OgImagePreview.tsx +0 -5
- package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +5 -6
- package/templates/src/components/compositor/preview/PanesPreviewGenerator.tsx +1 -0
- package/templates/src/components/edit/PanelSwitch.tsx +3 -24
- package/templates/src/components/edit/SettingsPanel.tsx +0 -1
- package/templates/src/components/edit/ToolMode.tsx +6 -14
- package/templates/src/components/edit/pane/AddPanePanel.tsx +45 -25
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +277 -70
- package/templates/src/components/edit/pane/AddPanePanel_paste.tsx +111 -0
- package/templates/src/components/edit/pane/RestylePaneModal.tsx +7 -14
- package/templates/src/components/edit/pane/steps/AiDesignStep.tsx +0 -5
- package/templates/src/components/edit/pane/steps/DesignLibraryStep.tsx +4 -11
- package/templates/src/components/edit/panels/StyleBreakPanel.tsx +1 -3
- package/templates/src/components/edit/panels/StyleElementPanel_update.tsx +0 -6
- package/templates/src/components/edit/panels/StyleImagePanel.tsx +0 -1
- package/templates/src/components/edit/panels/StyleImagePanel_update.tsx +0 -3
- package/templates/src/components/edit/panels/StyleLiElementPanel_update.tsx +0 -4
- package/templates/src/components/edit/panels/StyleLinkPanel_config.tsx +8 -5
- package/templates/src/components/edit/panels/StyleLinkPanel_update.tsx +1 -2
- package/templates/src/components/edit/panels/StyleParentPanel.tsx +1 -3
- package/templates/src/components/edit/panels/StyleParentPanel_update.tsx +2 -5
- package/templates/src/components/edit/panels/StyleWidgetPanel_config.tsx +2 -8
- package/templates/src/components/edit/panels/StyleWidgetPanel_update.tsx +0 -4
- package/templates/src/components/edit/state/SaveToLibraryModal.tsx +27 -16
- package/templates/src/components/edit/storyfragment/StoryFragmentConfigPanel.tsx +9 -26
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_og.tsx +7 -16
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_slug.tsx +5 -6
- package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +0 -5
- package/templates/src/components/fields/BackgroundImageWrapper.tsx +1 -7
- package/templates/src/components/fields/ColorPickerCombo.tsx +8 -12
- package/templates/src/components/fields/ViewportComboBox.tsx +4 -6
- package/templates/src/constants/prompts.json +22 -1
- package/templates/src/stores/nodes.ts +297 -222
- package/templates/src/stores/storykeep.ts +3 -3
- package/templates/src/types/compositorTypes.ts +21 -1
- package/templates/src/types/tractstack.ts +1 -0
- package/templates/src/utils/compositor/TemplatePanes.ts +0 -76
- package/templates/src/utils/compositor/aiPaneParser.ts +265 -83
- package/templates/src/utils/compositor/designLibraryHelper.ts +252 -26
- package/templates/src/utils/helpers.ts +5 -4
- package/utils/inject-files.ts +6 -32
- package/templates/src/components/compositor/preview/VisualBreakPreview.tsx +0 -154
- package/templates/src/components/edit/pane/PageGen_preview.tsx +0 -511
- package/templates/src/utils/compositor/processMarkdown.ts +0 -445
- package/templates/src/utils/compositor/templateMarkdownStyles.ts +0 -1273
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import { ulid } from 'ulid';
|
|
2
2
|
import { getCtx, type NodesContext } from '@/stores/nodes';
|
|
3
3
|
import { tailwindClasses } from '@/utils/compositor/tailwindClasses';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
4
|
+
import type {
|
|
5
|
+
PaneNode,
|
|
6
|
+
FlatNode,
|
|
7
|
+
MarkdownPaneFragmentNode,
|
|
8
|
+
GridLayoutNode,
|
|
9
|
+
StoragePane,
|
|
10
|
+
StorageNode,
|
|
11
|
+
StorageMarkdown,
|
|
12
|
+
StorageBgPane,
|
|
13
|
+
StorageGridLayoutNode,
|
|
14
|
+
ArtpackImageNode,
|
|
15
|
+
BgImageNode,
|
|
16
|
+
VisualBreakNode,
|
|
17
|
+
TemplatePane,
|
|
18
|
+
TemplateNode,
|
|
17
19
|
} from '@/types/compositorTypes';
|
|
18
20
|
import type {
|
|
19
21
|
BrandConfig,
|
|
@@ -108,14 +110,14 @@ export async function savePaneToLibrary(
|
|
|
108
110
|
category: string;
|
|
109
111
|
copyMode: CopyMode;
|
|
110
112
|
}
|
|
111
|
-
): Promise<
|
|
113
|
+
): Promise<BrandConfigState | null> {
|
|
112
114
|
const ctx = getCtx();
|
|
113
115
|
const { title, category, copyMode } = formData;
|
|
114
116
|
const paneNode = ctx.allNodes.get().get(paneId) as PaneNode;
|
|
115
117
|
|
|
116
118
|
if (!paneNode) {
|
|
117
119
|
console.error('savePaneToLibrary: PaneNode not found.');
|
|
118
|
-
return
|
|
120
|
+
return null;
|
|
119
121
|
}
|
|
120
122
|
|
|
121
123
|
const childNodes = ctx
|
|
@@ -172,13 +174,14 @@ export async function savePaneToLibrary(
|
|
|
172
174
|
heightRatioDesktop: paneNode.heightRatioDesktop,
|
|
173
175
|
heightRatioMobile: paneNode.heightRatioMobile,
|
|
174
176
|
heightRatioTablet: paneNode.heightRatioTablet,
|
|
175
|
-
|
|
176
|
-
bgPane: newStorageBgPane,
|
|
177
|
+
...(newStorageMarkdown ? { markdowns: [newStorageMarkdown] } : {}),
|
|
178
|
+
...(newStorageBgPane ? { bgPane: newStorageBgPane } : {}),
|
|
177
179
|
};
|
|
178
180
|
|
|
179
181
|
const newLibraryEntry: DesignLibraryEntry = {
|
|
180
182
|
category: category,
|
|
181
183
|
title: title,
|
|
184
|
+
markdownCount: 1,
|
|
182
185
|
template: newStoragePane,
|
|
183
186
|
};
|
|
184
187
|
|
|
@@ -207,10 +210,10 @@ export async function savePaneToLibrary(
|
|
|
207
210
|
|
|
208
211
|
try {
|
|
209
212
|
await saveBrandConfig(tenantId, backendDTO);
|
|
210
|
-
return
|
|
213
|
+
return updatedState;
|
|
211
214
|
} catch (error) {
|
|
212
215
|
console.error('Failed to save design library:', error);
|
|
213
|
-
return
|
|
216
|
+
return null;
|
|
214
217
|
}
|
|
215
218
|
}
|
|
216
219
|
|
|
@@ -244,10 +247,11 @@ export function mergeCopyIntoTemplate(
|
|
|
244
247
|
copy: ExtractedCopy
|
|
245
248
|
): StoragePane {
|
|
246
249
|
const newTemplate = { ...template };
|
|
247
|
-
if (newTemplate.
|
|
248
|
-
newTemplate.
|
|
250
|
+
if (newTemplate.markdowns) {
|
|
251
|
+
newTemplate.markdowns[0].nodes = copy;
|
|
249
252
|
} else if (copy.length > 0) {
|
|
250
|
-
newTemplate.
|
|
253
|
+
if (!newTemplate.markdowns) newTemplate.markdowns = [];
|
|
254
|
+
newTemplate.markdowns[0] = {
|
|
251
255
|
nodeType: 'Markdown',
|
|
252
256
|
type: 'markdown',
|
|
253
257
|
defaultClasses: {},
|
|
@@ -289,8 +293,8 @@ export function convertStorageToLiveTemplate(
|
|
|
289
293
|
const markdownId = ulid();
|
|
290
294
|
const flatNodeList: TemplateNode[] = [];
|
|
291
295
|
|
|
292
|
-
if (storagePane.
|
|
293
|
-
for (const storageNode of storagePane.
|
|
296
|
+
if (storagePane.markdowns && storagePane.markdowns[0].nodes) {
|
|
297
|
+
for (const storageNode of storagePane.markdowns[0].nodes) {
|
|
294
298
|
const processedNodes = processStorageNode(storageNode, markdownId);
|
|
295
299
|
flatNodeList.push(...processedNodes);
|
|
296
300
|
}
|
|
@@ -307,12 +311,13 @@ export function convertStorageToLiveTemplate(
|
|
|
307
311
|
};
|
|
308
312
|
}
|
|
309
313
|
|
|
314
|
+
const { gridLayout, ...restOfStoragePane } = storagePane;
|
|
310
315
|
const liveTemplatePane: TemplatePane = {
|
|
311
|
-
...
|
|
316
|
+
...restOfStoragePane,
|
|
312
317
|
id: paneId,
|
|
313
318
|
parentId: '',
|
|
314
319
|
markdown: {
|
|
315
|
-
...(storagePane.
|
|
320
|
+
...((storagePane.markdowns && storagePane.markdowns[0]) || {
|
|
316
321
|
nodeType: 'Markdown',
|
|
317
322
|
type: 'markdown',
|
|
318
323
|
defaultClasses: {},
|
|
@@ -414,3 +419,224 @@ export function convertTemplateToAIShell(template: TemplatePane): string {
|
|
|
414
419
|
|
|
415
420
|
return JSON.stringify(shell, null, 2);
|
|
416
421
|
}
|
|
422
|
+
|
|
423
|
+
export async function copyPaneToClipboard(paneId: string): Promise<boolean> {
|
|
424
|
+
const ctx = getCtx();
|
|
425
|
+
const paneNode = ctx.allNodes.get().get(paneId) as PaneNode;
|
|
426
|
+
|
|
427
|
+
const storagePane = convertLivePaneToStoragePane(paneId, ctx, {
|
|
428
|
+
title: paneNode?.title || 'Pasted Pane',
|
|
429
|
+
copyMode: 'retain',
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
if (!storagePane) {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
try {
|
|
437
|
+
const jsonPayload = JSON.stringify(storagePane, null, 2);
|
|
438
|
+
await navigator.clipboard.writeText(jsonPayload);
|
|
439
|
+
return true;
|
|
440
|
+
} catch (error) {
|
|
441
|
+
console.error('Failed to copy pane to clipboard:', error);
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function buildIdMap(node: any, map: Map<string, string>) {
|
|
447
|
+
if (!node || typeof node !== 'object') return;
|
|
448
|
+
|
|
449
|
+
if (node.id && !map.has(node.id)) {
|
|
450
|
+
map.set(node.id, ulid());
|
|
451
|
+
}
|
|
452
|
+
// Markdown nodes have a second unique identifier
|
|
453
|
+
if (node.markdownId && !map.has(node.markdownId)) {
|
|
454
|
+
map.set(node.markdownId, ulid());
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Recursively traverse all possible child arrays/objects
|
|
458
|
+
if (node.markdowns) {
|
|
459
|
+
node.markdowns.forEach((n: any) => buildIdMap(n, map));
|
|
460
|
+
}
|
|
461
|
+
if (node.gridLayout) {
|
|
462
|
+
buildIdMap(node.gridLayout, map);
|
|
463
|
+
}
|
|
464
|
+
if (node.nodes) {
|
|
465
|
+
node.nodes.forEach((n: any) => buildIdMap(n, map));
|
|
466
|
+
}
|
|
467
|
+
if (node.bgPane) {
|
|
468
|
+
buildIdMap(node.bgPane, map);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function applyIdMap(node: any, map: Map<string, string>) {
|
|
473
|
+
if (!node || typeof node !== 'object') return;
|
|
474
|
+
|
|
475
|
+
if (node.id && map.has(node.id)) {
|
|
476
|
+
node.id = map.get(node.id);
|
|
477
|
+
}
|
|
478
|
+
if (node.parentId && map.has(node.parentId)) {
|
|
479
|
+
node.parentId = map.get(node.parentId);
|
|
480
|
+
}
|
|
481
|
+
if (node.markdownId && map.has(node.markdownId)) {
|
|
482
|
+
node.markdownId = map.get(node.markdownId);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Recursively traverse all possible child arrays/objects
|
|
486
|
+
if (node.markdowns) {
|
|
487
|
+
node.markdowns.forEach((n: any) => applyIdMap(n, map));
|
|
488
|
+
}
|
|
489
|
+
if (node.gridLayout) {
|
|
490
|
+
applyIdMap(node.gridLayout, map);
|
|
491
|
+
}
|
|
492
|
+
if (node.nodes) {
|
|
493
|
+
node.nodes.forEach((n: any) => applyIdMap(n, map));
|
|
494
|
+
}
|
|
495
|
+
if (node.bgPane) {
|
|
496
|
+
applyIdMap(node.bgPane, map);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
export function remapPaneIds(pane: StoragePane): StoragePane {
|
|
501
|
+
const idMap = new Map<string, string>();
|
|
502
|
+
// The input object may have come from JSON.parse, so we treat it as 'any' internally
|
|
503
|
+
const clone = JSON.parse(JSON.stringify(pane as any));
|
|
504
|
+
|
|
505
|
+
// First pass: Traverse the entire structure to build a complete map of old IDs to new IDs.
|
|
506
|
+
buildIdMap(clone, idMap);
|
|
507
|
+
|
|
508
|
+
// Second pass: Traverse again to apply the new IDs, ensuring parent-child relationships are correct.
|
|
509
|
+
applyIdMap(clone, idMap);
|
|
510
|
+
|
|
511
|
+
return clone as StoragePane;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function convertLivePaneToStoragePane(
|
|
515
|
+
paneId: string,
|
|
516
|
+
ctx: NodesContext,
|
|
517
|
+
options: {
|
|
518
|
+
title: string;
|
|
519
|
+
copyMode: CopyMode;
|
|
520
|
+
}
|
|
521
|
+
): StoragePane | null {
|
|
522
|
+
const paneNode = ctx.allNodes.get().get(paneId) as PaneNode;
|
|
523
|
+
if (!paneNode) {
|
|
524
|
+
console.error('convertLivePaneToStoragePane: PaneNode not found.');
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const { title, copyMode } = options;
|
|
529
|
+
const childNodes = ctx
|
|
530
|
+
.getChildNodeIDs(paneId)
|
|
531
|
+
.map((id) => ctx.allNodes.get().get(id));
|
|
532
|
+
|
|
533
|
+
const markdownNode = childNodes.find((n) => n?.nodeType === 'Markdown') as
|
|
534
|
+
| MarkdownPaneFragmentNode
|
|
535
|
+
| undefined;
|
|
536
|
+
|
|
537
|
+
const gridLayoutNode = childNodes.find(
|
|
538
|
+
(n) => n?.nodeType === 'GridLayoutNode'
|
|
539
|
+
) as GridLayoutNode | undefined;
|
|
540
|
+
|
|
541
|
+
const bgPaneNode = childNodes.find((n) => n?.nodeType === 'BgPane') as
|
|
542
|
+
| ArtpackImageNode
|
|
543
|
+
| BgImageNode
|
|
544
|
+
| VisualBreakNode
|
|
545
|
+
| undefined;
|
|
546
|
+
|
|
547
|
+
let storageMarkdown: StorageMarkdown | undefined;
|
|
548
|
+
let storageGridLayout: StorageGridLayoutNode | undefined;
|
|
549
|
+
|
|
550
|
+
if (markdownNode) {
|
|
551
|
+
storageMarkdown = {
|
|
552
|
+
nodeType: 'Markdown',
|
|
553
|
+
type: 'markdown',
|
|
554
|
+
defaultClasses: markdownNode.defaultClasses || {},
|
|
555
|
+
parentClasses: markdownNode.parentClasses || [],
|
|
556
|
+
nodes:
|
|
557
|
+
copyMode !== 'blank'
|
|
558
|
+
? ctx
|
|
559
|
+
.getChildNodeIDs(markdownNode.id)
|
|
560
|
+
.map((childId) => {
|
|
561
|
+
const childNode = ctx.allNodes.get().get(childId) as FlatNode;
|
|
562
|
+
return convertLiveNodeToStorageNode(childNode, ctx, copyMode);
|
|
563
|
+
})
|
|
564
|
+
.filter((n): n is StorageNode => n !== null)
|
|
565
|
+
: [],
|
|
566
|
+
};
|
|
567
|
+
} else if (gridLayoutNode) {
|
|
568
|
+
const { id, parentId, isChanged, ...restOfGrid } = gridLayoutNode;
|
|
569
|
+
storageGridLayout = {
|
|
570
|
+
...restOfGrid,
|
|
571
|
+
nodes: ctx
|
|
572
|
+
.getChildNodeIDs(gridLayoutNode.id)
|
|
573
|
+
.map((columnId) => {
|
|
574
|
+
const columnNode = ctx.allNodes
|
|
575
|
+
.get()
|
|
576
|
+
.get(columnId) as MarkdownPaneFragmentNode;
|
|
577
|
+
if (!columnNode) return null;
|
|
578
|
+
|
|
579
|
+
const {
|
|
580
|
+
id,
|
|
581
|
+
parentId,
|
|
582
|
+
isChanged,
|
|
583
|
+
markdownId,
|
|
584
|
+
parentCss,
|
|
585
|
+
...restOfColumn
|
|
586
|
+
} = columnNode;
|
|
587
|
+
|
|
588
|
+
const storageColumn: StorageMarkdown = {
|
|
589
|
+
...restOfColumn,
|
|
590
|
+
nodeType: 'Markdown',
|
|
591
|
+
type: 'markdown',
|
|
592
|
+
nodes:
|
|
593
|
+
copyMode !== 'blank'
|
|
594
|
+
? ctx
|
|
595
|
+
.getChildNodeIDs(columnNode.id)
|
|
596
|
+
.map((childId) => {
|
|
597
|
+
const childNode = ctx.allNodes
|
|
598
|
+
.get()
|
|
599
|
+
.get(childId) as FlatNode;
|
|
600
|
+
return convertLiveNodeToStorageNode(
|
|
601
|
+
childNode,
|
|
602
|
+
ctx,
|
|
603
|
+
copyMode
|
|
604
|
+
);
|
|
605
|
+
})
|
|
606
|
+
.filter((n): n is StorageNode => n !== null)
|
|
607
|
+
: [],
|
|
608
|
+
};
|
|
609
|
+
return storageColumn;
|
|
610
|
+
})
|
|
611
|
+
.filter((n): n is StorageMarkdown => n !== null),
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const storageBgPane: StorageBgPane | undefined = bgPaneNode
|
|
616
|
+
? { ...bgPaneNode }
|
|
617
|
+
: undefined;
|
|
618
|
+
|
|
619
|
+
if (storageBgPane) {
|
|
620
|
+
delete (storageBgPane as any).id;
|
|
621
|
+
delete (storageBgPane as any).parentId;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const storagePane: StoragePane = {
|
|
625
|
+
nodeType: 'Pane',
|
|
626
|
+
title: title,
|
|
627
|
+
slug: '',
|
|
628
|
+
bgColour: paneNode.bgColour,
|
|
629
|
+
isDecorative: paneNode.isDecorative,
|
|
630
|
+
heightOffsetDesktop: paneNode.heightOffsetDesktop,
|
|
631
|
+
heightOffsetMobile: paneNode.heightOffsetMobile,
|
|
632
|
+
heightOffsetTablet: paneNode.heightOffsetTablet,
|
|
633
|
+
heightRatioDesktop: paneNode.heightRatioDesktop,
|
|
634
|
+
heightRatioMobile: paneNode.heightRatioMobile,
|
|
635
|
+
heightRatioTablet: paneNode.heightRatioTablet,
|
|
636
|
+
...(storageMarkdown ? { markdowns: [storageMarkdown] } : {}),
|
|
637
|
+
...(storageGridLayout ? { gridLayout: storageGridLayout } : {}),
|
|
638
|
+
...(storageBgPane ? { bgPane: storageBgPane } : {}),
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
return storagePane;
|
|
642
|
+
}
|
|
@@ -291,14 +291,15 @@ export function titleToSlug(title: string, maxLength: number = 50): string {
|
|
|
291
291
|
}
|
|
292
292
|
|
|
293
293
|
export function findUniqueSlug(slug: string, existingSlugs: string[]): string {
|
|
294
|
-
|
|
295
|
-
|
|
294
|
+
const tempSlug = slug || `story`;
|
|
295
|
+
if (!existingSlugs.includes(tempSlug)) {
|
|
296
|
+
return tempSlug;
|
|
296
297
|
}
|
|
297
298
|
let counter = 1;
|
|
298
|
-
let newSlug = `${
|
|
299
|
+
let newSlug = `${tempSlug}-${counter}`;
|
|
299
300
|
while (existingSlugs.includes(newSlug)) {
|
|
300
301
|
counter++;
|
|
301
|
-
newSlug = `${
|
|
302
|
+
newSlug = `${tempSlug}-${counter}`;
|
|
302
303
|
}
|
|
303
304
|
return newSlug;
|
|
304
305
|
}
|
package/utils/inject-files.ts
CHANGED
|
@@ -453,6 +453,12 @@ export async function injectTemplateFiles(
|
|
|
453
453
|
src: resolve('../templates/src/components/edit/pane/AddPanePanel.tsx'),
|
|
454
454
|
dest: 'src/components/edit/pane/AddPanePanel.tsx',
|
|
455
455
|
},
|
|
456
|
+
{
|
|
457
|
+
src: resolve(
|
|
458
|
+
'../templates/src/components/edit/pane/AddPanePanel_paste.tsx'
|
|
459
|
+
),
|
|
460
|
+
dest: 'src/components/edit/pane/AddPanePanel_paste.tsx',
|
|
461
|
+
},
|
|
456
462
|
{
|
|
457
463
|
src: resolve(
|
|
458
464
|
'../templates/src/components/edit/pane/AddPanePanel_break.tsx'
|
|
@@ -549,22 +555,6 @@ export async function injectTemplateFiles(
|
|
|
549
555
|
),
|
|
550
556
|
dest: 'src/components/edit/context/ContextPaneConfig_slug.tsx',
|
|
551
557
|
},
|
|
552
|
-
{
|
|
553
|
-
src: resolve('../templates/src/components/edit/pane/PageGenSelector.tsx'),
|
|
554
|
-
dest: 'src/components/edit/pane/PageGenSelector.tsx',
|
|
555
|
-
},
|
|
556
|
-
{
|
|
557
|
-
src: resolve('../templates/src/components/edit/pane/PageGenSpecial.tsx'),
|
|
558
|
-
dest: 'src/components/edit/pane/PageGenSpecial.tsx',
|
|
559
|
-
},
|
|
560
|
-
{
|
|
561
|
-
src: resolve('../templates/src/components/edit/pane/PageGen.tsx'),
|
|
562
|
-
dest: 'src/components/edit/pane/PageGen.tsx',
|
|
563
|
-
},
|
|
564
|
-
{
|
|
565
|
-
src: resolve('../templates/src/components/edit/pane/PageGen_preview.tsx'),
|
|
566
|
-
dest: 'src/components/edit/pane/PageGen_preview.tsx',
|
|
567
|
-
},
|
|
568
558
|
// Compositor previews
|
|
569
559
|
{
|
|
570
560
|
src: resolve(
|
|
@@ -584,12 +574,6 @@ export async function injectTemplateFiles(
|
|
|
584
574
|
),
|
|
585
575
|
dest: 'src/components/compositor/preview/OgImagePreview.tsx',
|
|
586
576
|
},
|
|
587
|
-
{
|
|
588
|
-
src: resolve(
|
|
589
|
-
'../templates/src/components/compositor/preview/VisualBreakPreview.tsx'
|
|
590
|
-
),
|
|
591
|
-
dest: 'src/components/compositor/preview/VisualBreakPreview.tsx',
|
|
592
|
-
},
|
|
593
577
|
{
|
|
594
578
|
src: resolve(
|
|
595
579
|
'../templates/src/components/compositor/preview/ListContentPreview.tsx'
|
|
@@ -653,16 +637,6 @@ export async function injectTemplateFiles(
|
|
|
653
637
|
src: resolve('../templates/src/utils/compositor/aiPaneParser.ts'),
|
|
654
638
|
dest: 'src/utils/compositor/aiPaneParser.ts',
|
|
655
639
|
},
|
|
656
|
-
{
|
|
657
|
-
src: resolve('../templates/src/utils/compositor/processMarkdown.ts'),
|
|
658
|
-
dest: 'src/utils/compositor/processMarkdown.ts',
|
|
659
|
-
},
|
|
660
|
-
{
|
|
661
|
-
src: resolve(
|
|
662
|
-
'../templates/src/utils/compositor/templateMarkdownStyles.ts'
|
|
663
|
-
),
|
|
664
|
-
dest: 'src/utils/compositor/templateMarkdownStyles.ts',
|
|
665
|
-
},
|
|
666
640
|
{
|
|
667
641
|
src: resolve(
|
|
668
642
|
'../templates/src/utils/compositor/nodesMarkdownGenerator.ts'
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
2
|
-
import { NodesContext } from '@/stores/nodes';
|
|
3
|
-
import { createEmptyStorykeep } from '@/utils/compositor/nodesHelper';
|
|
4
|
-
import { getTemplateVisualBreakPane } from '@/utils/compositor/TemplatePanes';
|
|
5
|
-
import {
|
|
6
|
-
PanesPreviewGenerator,
|
|
7
|
-
type PanePreviewRequest,
|
|
8
|
-
type PaneFragmentResult,
|
|
9
|
-
} from '@/components/compositor/preview/PanesPreviewGenerator';
|
|
10
|
-
import {
|
|
11
|
-
PaneSnapshotGenerator,
|
|
12
|
-
type SnapshotData,
|
|
13
|
-
} from '@/components/compositor/preview/PaneSnapshotGenerator';
|
|
14
|
-
|
|
15
|
-
interface VisualBreakPreviewProps {
|
|
16
|
-
bgColour: string;
|
|
17
|
-
fillColour: string;
|
|
18
|
-
variant?: string; // Optional variant name for the visual break
|
|
19
|
-
height?: number; // Optional height for the container
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// The state is managed as a single object since this component only ever handles one preview at a time.
|
|
23
|
-
// This is slightly simpler than managing an array with a single item.
|
|
24
|
-
type PreviewState = {
|
|
25
|
-
htmlFragment?: string;
|
|
26
|
-
snapshot?: SnapshotData;
|
|
27
|
-
error?: string;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Renders a preview of a single visual break variant.
|
|
32
|
-
*
|
|
33
|
-
* This component uses a modern two-step process for efficiency:
|
|
34
|
-
* 1. It uses PanesPreviewGenerator to fetch an HTML fragment of the break.
|
|
35
|
-
* 2. It then uses PaneSnapshotGenerator to convert that HTML into an image snapshot.
|
|
36
|
-
*/
|
|
37
|
-
export const VisualBreakPreview = ({
|
|
38
|
-
bgColour,
|
|
39
|
-
fillColour,
|
|
40
|
-
variant = 'cutwide2', // Default to cutwide2 as it's a commonly used break
|
|
41
|
-
height = 120, // Default height that works well for most breaks
|
|
42
|
-
}: VisualBreakPreviewProps) => {
|
|
43
|
-
const [previewState, setPreviewState] = useState<PreviewState | null>(null);
|
|
44
|
-
const [fragmentRequest, setFragmentRequest] = useState<PanePreviewRequest[]>(
|
|
45
|
-
[]
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
useEffect(() => {
|
|
49
|
-
// Reset state whenever the props change to trigger a full regeneration
|
|
50
|
-
setPreviewState(null);
|
|
51
|
-
|
|
52
|
-
// STEP 1: Create a temporary NodesContext for the preview.
|
|
53
|
-
const ctx = new NodesContext();
|
|
54
|
-
ctx.addNode(createEmptyStorykeep('tmp')); // Add root node
|
|
55
|
-
|
|
56
|
-
// Get the template for the specified variant and apply the dynamic colours
|
|
57
|
-
const template = getTemplateVisualBreakPane(variant);
|
|
58
|
-
if (template) {
|
|
59
|
-
if (template.bgColour) template.bgColour = bgColour;
|
|
60
|
-
if (template.bgPane && template.bgPane.type === 'visual-break') {
|
|
61
|
-
if (template.bgPane.breakDesktop) {
|
|
62
|
-
template.bgPane.breakDesktop.svgFill = fillColour;
|
|
63
|
-
}
|
|
64
|
-
if (template.bgPane.breakTablet) {
|
|
65
|
-
template.bgPane.breakTablet.svgFill = fillColour;
|
|
66
|
-
}
|
|
67
|
-
if (template.bgPane.breakMobile) {
|
|
68
|
-
template.bgPane.breakMobile.svgFill = fillColour;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
ctx.addTemplatePane('tmp', template); // Add the template to the context
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Prepare a request for the PanesPreviewGenerator to get the HTML.
|
|
75
|
-
setFragmentRequest([{ id: 'visual-break-preview', ctx }]);
|
|
76
|
-
}, [variant, bgColour, fillColour]);
|
|
77
|
-
|
|
78
|
-
// Handler for when the HTML fragment has been generated
|
|
79
|
-
const handleFragmentComplete = (results: PaneFragmentResult[]) => {
|
|
80
|
-
const result = results[0];
|
|
81
|
-
if (result?.htmlString) {
|
|
82
|
-
setPreviewState({ htmlFragment: result.htmlString });
|
|
83
|
-
} else {
|
|
84
|
-
setPreviewState({
|
|
85
|
-
error: result?.error || 'Failed to generate HTML fragment.',
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
setFragmentRequest([]); // Clear the request to prevent re-fetching
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
// Handler for when the image snapshot has been generated from the HTML.
|
|
92
|
-
// The 'id' parameter is unused here as we only manage one snapshot at a time.
|
|
93
|
-
const handleSnapshotComplete = (data: SnapshotData) => {
|
|
94
|
-
setPreviewState((prev) => (prev ? { ...prev, snapshot: data } : null));
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// Display a pulsing placeholder while the process is running
|
|
98
|
-
if (!previewState) {
|
|
99
|
-
return <div className="my-4 h-12 animate-pulse bg-gray-200" />;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Display an error message if something went wrong
|
|
103
|
-
if (previewState.error) {
|
|
104
|
-
return (
|
|
105
|
-
<div className="flex items-center justify-center rounded-md border border-red-200 bg-red-50 p-4 text-sm text-red-700">
|
|
106
|
-
Preview could not be generated: {previewState.error}
|
|
107
|
-
</div>
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return (
|
|
112
|
-
<div
|
|
113
|
-
className="relative w-full overflow-hidden"
|
|
114
|
-
style={!previewState.snapshot ? { height: `${height}px` } : undefined}
|
|
115
|
-
>
|
|
116
|
-
{/* STEP 2: Render the generator to fetch the HTML fragment. This component renders nothing itself. */}
|
|
117
|
-
{fragmentRequest.length > 0 && (
|
|
118
|
-
<PanesPreviewGenerator
|
|
119
|
-
requests={fragmentRequest}
|
|
120
|
-
onComplete={handleFragmentComplete}
|
|
121
|
-
onError={(err) => setPreviewState({ error: err })}
|
|
122
|
-
/>
|
|
123
|
-
)}
|
|
124
|
-
|
|
125
|
-
{/* STEP 3: Once HTML is available, render the snapshot generator to create the image. This component also renders nothing. */}
|
|
126
|
-
{previewState.htmlFragment && !previewState.snapshot && (
|
|
127
|
-
<PaneSnapshotGenerator
|
|
128
|
-
id="visual-break-snapshot"
|
|
129
|
-
htmlString={previewState.htmlFragment}
|
|
130
|
-
outputWidth={800} // Matches the original output width
|
|
131
|
-
onComplete={(_id, data) => handleSnapshotComplete(data)}
|
|
132
|
-
onError={(_id, err) =>
|
|
133
|
-
setPreviewState((prev) =>
|
|
134
|
-
prev ? { ...prev, error: err } : { error: err }
|
|
135
|
-
)
|
|
136
|
-
}
|
|
137
|
-
/>
|
|
138
|
-
)}
|
|
139
|
-
|
|
140
|
-
{/* STEP 4: Once the snapshot is complete, display the final image. */}
|
|
141
|
-
{previewState.snapshot && (
|
|
142
|
-
<div className="w-full">
|
|
143
|
-
<img
|
|
144
|
-
src={previewState.snapshot.imageData}
|
|
145
|
-
alt={`Visual break ${variant}`}
|
|
146
|
-
className="w-full"
|
|
147
|
-
/>
|
|
148
|
-
</div>
|
|
149
|
-
)}
|
|
150
|
-
</div>
|
|
151
|
-
);
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
export default VisualBreakPreview;
|