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.
- package/dist/index.js +22 -0
- package/package.json +1 -1
- package/templates/src/client/view.js +5 -0
- package/templates/src/components/compositor/Compositor.tsx +3 -2
- package/templates/src/components/compositor/Node.tsx +4 -0
- package/templates/src/components/compositor/nodes/Pane_DesignLibrary.tsx +105 -0
- package/templates/src/components/edit/ToolMode.tsx +7 -0
- package/templates/src/components/edit/pane/AiPaneGenerator.tsx +1 -1
- package/templates/src/components/edit/pane/PageGen.tsx +1 -1
- package/templates/src/components/edit/pane/RestylePaneModal.tsx +573 -0
- package/templates/src/components/edit/state/SaveToLibraryModal.tsx +205 -0
- package/templates/src/stores/selection.ts +4 -0
- package/templates/src/types/compositorTypes.ts +51 -1
- package/templates/src/types/tractstack.ts +36 -31
- package/templates/src/utils/aai/getTitleSlug.ts +1 -1
- package/templates/src/utils/api/brandConfig.ts +8 -2
- package/templates/src/utils/api/brandHelpers.ts +4 -0
- package/templates/src/utils/compositor/designLibraryHelper.ts +331 -0
- package/templates/src/utils/compositor/processMarkdown.ts +1 -1
- package/utils/inject-files.ts +22 -0
|
@@ -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
|
+
}
|
package/utils/inject-files.ts
CHANGED
|
@@ -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',
|