astro-tractstack 2.0.13 → 2.0.14

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.
@@ -0,0 +1,331 @@
1
+ import { ulid } from 'ulid';
2
+ import { getCtx, type NodesContext } from '@/stores/nodes';
3
+ import {
4
+ type PaneNode,
5
+ type FlatNode,
6
+ type MarkdownPaneFragmentNode,
7
+ type StoragePane,
8
+ type StorageNode,
9
+ type StorageMarkdown,
10
+ type StorageBgPane,
11
+ type ArtpackImageNode,
12
+ type BgImageNode,
13
+ type VisualBreakNode,
14
+ type TemplatePane,
15
+ type TemplateNode,
16
+ type BaseNode,
17
+ type TemplateMarkdown,
18
+ } from '@/types/compositorTypes';
19
+ import type {
20
+ BrandConfig,
21
+ BrandConfigState,
22
+ DesignLibraryConfig,
23
+ DesignLibraryEntry,
24
+ } from '@/types/tractstack';
25
+ import { saveBrandConfig } from '@/utils/api/brandConfig';
26
+ import {
27
+ convertToLocalState,
28
+ convertToBackendFormat,
29
+ } from '@/utils/api/brandHelpers';
30
+
31
+ type CopyMode = 'retain' | 'lorem' | 'blank';
32
+
33
+ export type ExtractedCopy = StorageNode[];
34
+
35
+ const LOREM_SHORT = 'Lorem ipsum dolor sit amet.';
36
+ const LOREM_LONG =
37
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.';
38
+
39
+ function convertLiveNodeToStorageNode(
40
+ node: FlatNode,
41
+ ctx: NodesContext,
42
+ copyMode: CopyMode
43
+ ): StorageNode | null {
44
+ if (copyMode === 'lorem') {
45
+ if (!node.tagName || !['h2', 'h3', 'h4', 'p'].includes(node.tagName)) {
46
+ return null;
47
+ }
48
+ }
49
+
50
+ const storageNode: StorageNode = {
51
+ nodeType: node.nodeType,
52
+ tagName: node.tagName,
53
+ tagNameCustom: node.tagNameCustom,
54
+ overrideClasses: copyMode === 'retain' ? node.overrideClasses : undefined,
55
+ href: copyMode === 'retain' ? node.href : undefined,
56
+ src: copyMode === 'retain' ? node.src : undefined,
57
+ alt: copyMode === 'retain' ? node.alt : undefined,
58
+ fileId: copyMode === 'retain' ? node.fileId : undefined,
59
+ buttonPayload: copyMode === 'retain' ? node.buttonPayload : undefined,
60
+ codeHookParams: copyMode === 'retain' ? node.codeHookParams : undefined,
61
+ elementCss: copyMode === 'retain' ? node.elementCss : undefined,
62
+ };
63
+
64
+ const childIds = ctx.getChildNodeIDs(node.id);
65
+
66
+ if (childIds.length > 0) {
67
+ const childNodes = childIds
68
+ .map((id) => {
69
+ const childNode = ctx.allNodes.get().get(id) as FlatNode;
70
+ if (!childNode) return null;
71
+
72
+ if (childNode.tagName === 'text' && childNode.copy) {
73
+ if (copyMode === 'lorem') {
74
+ const isHeadingParent =
75
+ node.tagName && node.tagName.startsWith('h');
76
+ return {
77
+ nodeType: 'TagElement',
78
+ tagName: 'text',
79
+ copy: isHeadingParent ? LOREM_SHORT : LOREM_LONG,
80
+ };
81
+ }
82
+ if (copyMode === 'retain') {
83
+ return {
84
+ nodeType: 'TagElement',
85
+ tagName: 'text',
86
+ copy: childNode.copy,
87
+ };
88
+ }
89
+ }
90
+
91
+ return convertLiveNodeToStorageNode(childNode, ctx, copyMode);
92
+ })
93
+ .filter((n): n is StorageNode => n !== null);
94
+
95
+ if (childNodes.length > 0) {
96
+ storageNode.nodes = childNodes;
97
+ }
98
+ }
99
+
100
+ return storageNode;
101
+ }
102
+
103
+ export async function savePaneToLibrary(
104
+ paneId: string,
105
+ tenantId: string,
106
+ config: BrandConfig,
107
+ formData: {
108
+ title: string;
109
+ category: string;
110
+ copyMode: CopyMode;
111
+ }
112
+ ): Promise<boolean> {
113
+ const ctx = getCtx();
114
+ const { title, category, copyMode } = formData;
115
+ const paneNode = ctx.allNodes.get().get(paneId) as PaneNode;
116
+
117
+ if (!paneNode) {
118
+ console.error('savePaneToLibrary: PaneNode not found.');
119
+ return false;
120
+ }
121
+
122
+ const childNodes = ctx
123
+ .getChildNodeIDs(paneId)
124
+ .map((id) => ctx.allNodes.get().get(id));
125
+
126
+ const markdownNode = childNodes.find((n) => n?.nodeType === 'Markdown') as
127
+ | MarkdownPaneFragmentNode
128
+ | undefined;
129
+
130
+ const bgPaneNode = childNodes.find((n) => n?.nodeType === 'BgPane') as
131
+ | ArtpackImageNode
132
+ | BgImageNode
133
+ | VisualBreakNode
134
+ | undefined;
135
+
136
+ const newStorageMarkdown: StorageMarkdown | undefined = markdownNode
137
+ ? {
138
+ nodeType: 'Markdown',
139
+ type: 'markdown',
140
+ defaultClasses: markdownNode.defaultClasses || {},
141
+ parentClasses: markdownNode.parentClasses || [],
142
+ nodes:
143
+ copyMode !== 'blank'
144
+ ? ctx
145
+ .getChildNodeIDs(markdownNode.id)
146
+ .map((childId) => {
147
+ const childNode = ctx.allNodes.get().get(childId) as FlatNode;
148
+ return convertLiveNodeToStorageNode(childNode, ctx, copyMode);
149
+ })
150
+ .filter((n): n is StorageNode => n !== null)
151
+ : [],
152
+ }
153
+ : undefined;
154
+
155
+ const newStorageBgPane: StorageBgPane | undefined = bgPaneNode
156
+ ? { ...bgPaneNode }
157
+ : undefined;
158
+
159
+ if (newStorageBgPane) {
160
+ delete (newStorageBgPane as any).id;
161
+ delete (newStorageBgPane as any).parentId;
162
+ }
163
+
164
+ const newStoragePane: StoragePane = {
165
+ nodeType: 'Pane',
166
+ title: title,
167
+ slug: '',
168
+ bgColour: paneNode.bgColour,
169
+ isDecorative: paneNode.isDecorative,
170
+ heightOffsetDesktop: paneNode.heightOffsetDesktop,
171
+ heightOffsetMobile: paneNode.heightOffsetMobile,
172
+ heightOffsetTablet: paneNode.heightOffsetTablet,
173
+ heightRatioDesktop: paneNode.heightRatioDesktop,
174
+ heightRatioMobile: paneNode.heightRatioMobile,
175
+ heightRatioTablet: paneNode.heightRatioTablet,
176
+ markdown: newStorageMarkdown,
177
+ bgPane: newStorageBgPane,
178
+ };
179
+
180
+ const newLibraryEntry: DesignLibraryEntry = {
181
+ category: category,
182
+ title: title,
183
+ template: newStoragePane,
184
+ };
185
+
186
+ const currentState: BrandConfigState = convertToLocalState(config);
187
+ const currentLibrary =
188
+ (currentState.designLibrary as DesignLibraryConfig) || [];
189
+
190
+ const existingEntryIndex = currentLibrary.findIndex(
191
+ (entry) => entry.title === title && entry.category === category
192
+ );
193
+
194
+ let newLibrary: DesignLibraryConfig;
195
+ if (existingEntryIndex !== -1) {
196
+ newLibrary = [...currentLibrary];
197
+ newLibrary[existingEntryIndex] = newLibraryEntry;
198
+ } else {
199
+ newLibrary = [...currentLibrary, newLibraryEntry];
200
+ }
201
+
202
+ const updatedState: BrandConfigState = {
203
+ ...currentState,
204
+ designLibrary: newLibrary,
205
+ };
206
+
207
+ const backendDTO: BrandConfig = convertToBackendFormat(updatedState);
208
+
209
+ try {
210
+ await saveBrandConfig(tenantId, backendDTO);
211
+ return true;
212
+ } catch (error) {
213
+ console.error('Failed to save design library:', error);
214
+ return false;
215
+ }
216
+ }
217
+
218
+ export function extractPaneCopy(paneNode: PaneNode): ExtractedCopy {
219
+ const ctx = getCtx();
220
+ const childNodes = ctx
221
+ .getChildNodeIDs(paneNode.id)
222
+ .map((id) => ctx.allNodes.get().get(id));
223
+
224
+ const markdownNode = childNodes.find((n) => n?.nodeType === 'Markdown') as
225
+ | MarkdownPaneFragmentNode
226
+ | undefined;
227
+
228
+ if (!markdownNode) {
229
+ return [];
230
+ }
231
+
232
+ return (
233
+ ctx
234
+ .getChildNodeIDs(markdownNode.id)
235
+ .map((childId) => {
236
+ const childNode = ctx.allNodes.get().get(childId) as FlatNode;
237
+ return convertLiveNodeToStorageNode(childNode, ctx, 'retain');
238
+ })
239
+ .filter((n): n is StorageNode => n !== null) || []
240
+ );
241
+ }
242
+
243
+ export function mergeCopyIntoTemplate(
244
+ template: StoragePane,
245
+ copy: ExtractedCopy
246
+ ): StoragePane {
247
+ const newTemplate = { ...template };
248
+ if (newTemplate.markdown) {
249
+ newTemplate.markdown.nodes = copy;
250
+ } else if (copy.length > 0) {
251
+ newTemplate.markdown = {
252
+ nodeType: 'Markdown',
253
+ type: 'markdown',
254
+ defaultClasses: {},
255
+ parentClasses: [],
256
+ nodes: copy,
257
+ };
258
+ }
259
+ return newTemplate;
260
+ }
261
+
262
+ function processStorageNode(
263
+ node: StorageNode,
264
+ parentId: string
265
+ ): TemplateNode[] {
266
+ const newId = ulid();
267
+ const { nodes, ...rest } = node;
268
+ const liveNode: TemplateNode = {
269
+ ...rest,
270
+ id: newId,
271
+ parentId: parentId,
272
+ };
273
+
274
+ const flatList: TemplateNode[] = [liveNode];
275
+
276
+ if (nodes) {
277
+ for (const child of nodes) {
278
+ const processedChildren = processStorageNode(child, newId);
279
+ flatList.push(...processedChildren);
280
+ }
281
+ }
282
+
283
+ return flatList;
284
+ }
285
+
286
+ export function convertStorageToLiveTemplate(
287
+ storagePane: StoragePane
288
+ ): TemplatePane {
289
+ const paneId = ulid();
290
+ const markdownId = ulid();
291
+ const flatNodeList: TemplateNode[] = [];
292
+
293
+ if (storagePane.markdown && storagePane.markdown.nodes) {
294
+ for (const storageNode of storagePane.markdown.nodes) {
295
+ const processedNodes = processStorageNode(storageNode, markdownId);
296
+ flatNodeList.push(...processedNodes);
297
+ }
298
+ }
299
+
300
+ let liveBgPane: ArtpackImageNode | BgImageNode | VisualBreakNode | undefined =
301
+ undefined;
302
+ if (storagePane.bgPane) {
303
+ const bgPaneId = ulid();
304
+ liveBgPane = {
305
+ ...storagePane.bgPane,
306
+ id: bgPaneId,
307
+ parentId: paneId,
308
+ };
309
+ }
310
+
311
+ const liveTemplatePane: TemplatePane = {
312
+ ...storagePane,
313
+ id: paneId,
314
+ parentId: '',
315
+ markdown: {
316
+ ...(storagePane.markdown || {
317
+ nodeType: 'Markdown',
318
+ type: 'markdown',
319
+ defaultClasses: {},
320
+ parentClasses: [],
321
+ }),
322
+ id: markdownId,
323
+ markdownId: markdownId,
324
+ parentId: paneId,
325
+ nodes: flatNodeList,
326
+ },
327
+ bgPane: liveBgPane,
328
+ };
329
+
330
+ return liveTemplatePane;
331
+ }
@@ -63,7 +63,7 @@ Example response format:
63
63
  "slug": "short-descriptive-title"
64
64
  }`,
65
65
  input_text: markdownContent,
66
- final_model: 'anthropic/claude-3-5-sonnet',
66
+ final_model: '',
67
67
  temperature: 0.3,
68
68
  max_tokens: 200,
69
69
  }),
@@ -127,6 +127,12 @@ export async function injectTemplateFiles(
127
127
  ),
128
128
  dest: 'src/components/compositor/nodes/Pane_eraser.tsx',
129
129
  },
130
+ {
131
+ src: resolve(
132
+ '../templates/src/components/compositor/nodes/Pane_DesignLibrary.tsx'
133
+ ),
134
+ dest: 'src/components/compositor/nodes/Pane_DesignLibrary.tsx',
135
+ },
130
136
  {
131
137
  src: resolve(
132
138
  '../templates/src/components/compositor/nodes/Pane_layout.tsx'
@@ -429,6 +435,12 @@ export async function injectTemplateFiles(
429
435
  ),
430
436
  dest: 'src/components/edit/pane/AddPanePanel_codehook.tsx',
431
437
  },
438
+ {
439
+ src: resolve(
440
+ '../templates/src/components/edit/pane/RestylePaneModal.tsx'
441
+ ),
442
+ dest: 'src/components/edit/pane/RestylePaneModal.tsx',
443
+ },
432
444
  {
433
445
  src: resolve('../templates/src/components/edit/pane/AiPaneGenerator.tsx'),
434
446
  dest: 'src/components/edit/pane/AiPaneGenerator.tsx',
@@ -629,6 +641,10 @@ export async function injectTemplateFiles(
629
641
  src: resolve('../templates/src/utils/compositor/typeGuards.ts'),
630
642
  dest: 'src/utils/compositor/typeGuards.ts',
631
643
  },
644
+ {
645
+ src: resolve('../templates/src/utils/compositor/designLibraryHelper.ts'),
646
+ dest: 'src/utils/compositor/designLibraryHelper.ts',
647
+ },
632
648
  {
633
649
  src: resolve('../templates/src/utils/compositor/domHelpers.ts'),
634
650
  dest: 'src/utils/compositor/domHelpers.ts',
@@ -1372,6 +1388,12 @@ export async function injectTemplateFiles(
1372
1388
  src: resolve('../templates/src/components/edit/state/SaveModal.tsx'),
1373
1389
  dest: 'src/components/edit/state/SaveModal.tsx',
1374
1390
  },
1391
+ {
1392
+ src: resolve(
1393
+ '../templates/src/components/edit/state/SaveToLibraryModal.tsx'
1394
+ ),
1395
+ dest: 'src/components/edit/state/SaveToLibraryModal.tsx',
1396
+ },
1375
1397
  {
1376
1398
  src: resolve('../templates/src/components/edit/state/StylesMemory.tsx'),
1377
1399
  dest: 'src/components/edit/state/StylesMemory.tsx',