astro-tractstack 2.0.20 → 2.0.21
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/package.json +1 -1
- package/templates/src/components/compositor/preview/PanesPreviewGenerator.tsx +0 -1
- package/templates/src/components/edit/pane/AddPanePanel.tsx +28 -15
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +138 -125
- package/templates/src/components/edit/pane/RestylePaneModal.tsx +225 -269
- package/templates/src/components/edit/pane/steps/DesignLibraryStep.tsx +0 -2
- package/templates/src/components/edit/state/SaveToLibraryModal.tsx +3 -1
- package/templates/src/utils/compositor/designLibraryHelper.ts +378 -281
|
@@ -9,13 +9,16 @@ import type {
|
|
|
9
9
|
StoragePane,
|
|
10
10
|
StorageNode,
|
|
11
11
|
StorageMarkdown,
|
|
12
|
-
StorageBgPane,
|
|
13
12
|
StorageGridLayoutNode,
|
|
13
|
+
StorageBgPane,
|
|
14
14
|
ArtpackImageNode,
|
|
15
15
|
BgImageNode,
|
|
16
16
|
VisualBreakNode,
|
|
17
17
|
TemplatePane,
|
|
18
18
|
TemplateNode,
|
|
19
|
+
TemplateGridLayout,
|
|
20
|
+
TemplateMarkdown,
|
|
21
|
+
ParentClassesPayload,
|
|
19
22
|
} from '@/types/compositorTypes';
|
|
20
23
|
import type {
|
|
21
24
|
BrandConfig,
|
|
@@ -31,7 +34,7 @@ import {
|
|
|
31
34
|
|
|
32
35
|
type CopyMode = 'retain' | 'lorem' | 'blank';
|
|
33
36
|
|
|
34
|
-
export type ExtractedCopy = StorageNode[];
|
|
37
|
+
export type ExtractedCopy = StorageNode[][];
|
|
35
38
|
|
|
36
39
|
const LOREM_SHORT = 'Lorem ipsum dolor sit amet.';
|
|
37
40
|
const LOREM_LONG =
|
|
@@ -101,127 +104,33 @@ function convertLiveNodeToStorageNode(
|
|
|
101
104
|
return storageNode;
|
|
102
105
|
}
|
|
103
106
|
|
|
104
|
-
export
|
|
105
|
-
paneId: string,
|
|
106
|
-
tenantId: string,
|
|
107
|
-
config: BrandConfig,
|
|
108
|
-
formData: {
|
|
109
|
-
title: string;
|
|
110
|
-
category: string;
|
|
111
|
-
copyMode: CopyMode;
|
|
112
|
-
}
|
|
113
|
-
): Promise<BrandConfigState | null> {
|
|
107
|
+
export function extractPaneCopy(paneNode: PaneNode): ExtractedCopy {
|
|
114
108
|
const ctx = getCtx();
|
|
115
|
-
const { title, category, copyMode } = formData;
|
|
116
|
-
const paneNode = ctx.allNodes.get().get(paneId) as PaneNode;
|
|
117
|
-
|
|
118
|
-
if (!paneNode) {
|
|
119
|
-
console.error('savePaneToLibrary: PaneNode not found.');
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
109
|
const childNodes = ctx
|
|
124
|
-
.getChildNodeIDs(
|
|
110
|
+
.getChildNodeIDs(paneNode.id)
|
|
125
111
|
.map((id) => ctx.allNodes.get().get(id));
|
|
126
112
|
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const bgPaneNode = childNodes.find((n) => n?.nodeType === 'BgPane') as
|
|
132
|
-
| ArtpackImageNode
|
|
133
|
-
| BgImageNode
|
|
134
|
-
| VisualBreakNode
|
|
135
|
-
| undefined;
|
|
136
|
-
|
|
137
|
-
const newStorageMarkdown: StorageMarkdown | undefined = markdownNode
|
|
138
|
-
? {
|
|
139
|
-
nodeType: 'Markdown',
|
|
140
|
-
type: 'markdown',
|
|
141
|
-
defaultClasses: markdownNode.defaultClasses || {},
|
|
142
|
-
parentClasses: markdownNode.parentClasses || [],
|
|
143
|
-
nodes:
|
|
144
|
-
copyMode !== 'blank'
|
|
145
|
-
? ctx
|
|
146
|
-
.getChildNodeIDs(markdownNode.id)
|
|
147
|
-
.map((childId) => {
|
|
148
|
-
const childNode = ctx.allNodes.get().get(childId) as FlatNode;
|
|
149
|
-
return convertLiveNodeToStorageNode(childNode, ctx, copyMode);
|
|
150
|
-
})
|
|
151
|
-
.filter((n): n is StorageNode => n !== null)
|
|
152
|
-
: [],
|
|
153
|
-
}
|
|
154
|
-
: undefined;
|
|
155
|
-
|
|
156
|
-
const newStorageBgPane: StorageBgPane | undefined = bgPaneNode
|
|
157
|
-
? { ...bgPaneNode }
|
|
158
|
-
: undefined;
|
|
159
|
-
|
|
160
|
-
if (newStorageBgPane) {
|
|
161
|
-
delete (newStorageBgPane as any).id;
|
|
162
|
-
delete (newStorageBgPane as any).parentId;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const newStoragePane: StoragePane = {
|
|
166
|
-
nodeType: 'Pane',
|
|
167
|
-
title: title,
|
|
168
|
-
slug: '',
|
|
169
|
-
bgColour: paneNode.bgColour,
|
|
170
|
-
isDecorative: paneNode.isDecorative,
|
|
171
|
-
heightOffsetDesktop: paneNode.heightOffsetDesktop,
|
|
172
|
-
heightOffsetMobile: paneNode.heightOffsetMobile,
|
|
173
|
-
heightOffsetTablet: paneNode.heightOffsetTablet,
|
|
174
|
-
heightRatioDesktop: paneNode.heightRatioDesktop,
|
|
175
|
-
heightRatioMobile: paneNode.heightRatioMobile,
|
|
176
|
-
heightRatioTablet: paneNode.heightRatioTablet,
|
|
177
|
-
...(newStorageMarkdown ? { markdowns: [newStorageMarkdown] } : {}),
|
|
178
|
-
...(newStorageBgPane ? { bgPane: newStorageBgPane } : {}),
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
const newLibraryEntry: DesignLibraryEntry = {
|
|
182
|
-
category: category,
|
|
183
|
-
title: title,
|
|
184
|
-
markdownCount: 1,
|
|
185
|
-
template: newStoragePane,
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
const currentState: BrandConfigState = convertToLocalState(config);
|
|
189
|
-
const currentLibrary =
|
|
190
|
-
(currentState.designLibrary as DesignLibraryConfig) || [];
|
|
191
|
-
|
|
192
|
-
const existingEntryIndex = currentLibrary.findIndex(
|
|
193
|
-
(entry) => entry.title === title && entry.category === category
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
let newLibrary: DesignLibraryConfig;
|
|
197
|
-
if (existingEntryIndex !== -1) {
|
|
198
|
-
newLibrary = [...currentLibrary];
|
|
199
|
-
newLibrary[existingEntryIndex] = newLibraryEntry;
|
|
200
|
-
} else {
|
|
201
|
-
newLibrary = [...currentLibrary, newLibraryEntry];
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const updatedState: BrandConfigState = {
|
|
205
|
-
...currentState,
|
|
206
|
-
designLibrary: newLibrary,
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
const backendDTO: BrandConfig = convertToBackendFormat(updatedState);
|
|
113
|
+
const gridLayoutNode = childNodes.find(
|
|
114
|
+
(n) => n?.nodeType === 'GridLayoutNode'
|
|
115
|
+
) as GridLayoutNode | undefined;
|
|
210
116
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
return
|
|
117
|
+
if (gridLayoutNode) {
|
|
118
|
+
const columns = ctx
|
|
119
|
+
.getChildNodeIDs(gridLayoutNode.id)
|
|
120
|
+
.map((id) => ctx.allNodes.get().get(id) as MarkdownPaneFragmentNode);
|
|
121
|
+
|
|
122
|
+
return columns.map((col) => {
|
|
123
|
+
return (
|
|
124
|
+
ctx
|
|
125
|
+
.getChildNodeIDs(col.id)
|
|
126
|
+
.map((childId) => {
|
|
127
|
+
const childNode = ctx.allNodes.get().get(childId) as FlatNode;
|
|
128
|
+
return convertLiveNodeToStorageNode(childNode, ctx, 'retain');
|
|
129
|
+
})
|
|
130
|
+
.filter((n): n is StorageNode => n !== null) || []
|
|
131
|
+
);
|
|
132
|
+
});
|
|
217
133
|
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
export function extractPaneCopy(paneNode: PaneNode): ExtractedCopy {
|
|
221
|
-
const ctx = getCtx();
|
|
222
|
-
const childNodes = ctx
|
|
223
|
-
.getChildNodeIDs(paneNode.id)
|
|
224
|
-
.map((id) => ctx.allNodes.get().get(id));
|
|
225
134
|
|
|
226
135
|
const markdownNode = childNodes.find((n) => n?.nodeType === 'Markdown') as
|
|
227
136
|
| MarkdownPaneFragmentNode
|
|
@@ -231,34 +140,37 @@ export function extractPaneCopy(paneNode: PaneNode): ExtractedCopy {
|
|
|
231
140
|
return [];
|
|
232
141
|
}
|
|
233
142
|
|
|
234
|
-
|
|
143
|
+
const nodes =
|
|
235
144
|
ctx
|
|
236
145
|
.getChildNodeIDs(markdownNode.id)
|
|
237
146
|
.map((childId) => {
|
|
238
147
|
const childNode = ctx.allNodes.get().get(childId) as FlatNode;
|
|
239
148
|
return convertLiveNodeToStorageNode(childNode, ctx, 'retain');
|
|
240
149
|
})
|
|
241
|
-
.filter((n): n is StorageNode => n !== null) || []
|
|
242
|
-
|
|
150
|
+
.filter((n): n is StorageNode => n !== null) || [];
|
|
151
|
+
|
|
152
|
+
return [nodes];
|
|
243
153
|
}
|
|
244
154
|
|
|
245
155
|
export function mergeCopyIntoTemplate(
|
|
246
156
|
template: StoragePane,
|
|
247
157
|
copy: ExtractedCopy
|
|
248
158
|
): StoragePane {
|
|
249
|
-
const newTemplate =
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
159
|
+
const newTemplate = JSON.parse(JSON.stringify(template)) as StoragePane;
|
|
160
|
+
|
|
161
|
+
if (newTemplate.gridLayout && newTemplate.gridLayout.nodes) {
|
|
162
|
+
newTemplate.gridLayout.nodes.forEach((column, index) => {
|
|
163
|
+
if (copy[index] && copy[index].length > 0) {
|
|
164
|
+
column.nodes = copy[index];
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
} else if (newTemplate.markdowns && newTemplate.markdowns[0]) {
|
|
168
|
+
const flatCopy = copy.flat();
|
|
169
|
+
if (flatCopy.length > 0) {
|
|
170
|
+
newTemplate.markdowns[0].nodes = flatCopy;
|
|
171
|
+
}
|
|
261
172
|
}
|
|
173
|
+
|
|
262
174
|
return newTemplate;
|
|
263
175
|
}
|
|
264
176
|
|
|
@@ -290,18 +202,63 @@ export function convertStorageToLiveTemplate(
|
|
|
290
202
|
storagePane: StoragePane
|
|
291
203
|
): TemplatePane {
|
|
292
204
|
const paneId = ulid();
|
|
293
|
-
|
|
294
|
-
|
|
205
|
+
let liveMarkdown: TemplateMarkdown | undefined = undefined;
|
|
206
|
+
let liveGridLayout: TemplateGridLayout | undefined = undefined;
|
|
207
|
+
let liveBgPane: ArtpackImageNode | BgImageNode | VisualBreakNode | undefined =
|
|
208
|
+
undefined;
|
|
295
209
|
|
|
296
|
-
if (storagePane.
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
210
|
+
if (storagePane.gridLayout) {
|
|
211
|
+
const gridId = ulid();
|
|
212
|
+
const storageGrid = storagePane.gridLayout;
|
|
213
|
+
|
|
214
|
+
liveGridLayout = {
|
|
215
|
+
id: gridId,
|
|
216
|
+
parentId: paneId,
|
|
217
|
+
nodeType: 'GridLayoutNode',
|
|
218
|
+
type: 'grid-layout',
|
|
219
|
+
gridColumns: storageGrid.gridColumns,
|
|
220
|
+
parentClasses: storageGrid.parentClasses as ParentClassesPayload,
|
|
221
|
+
defaultClasses: storageGrid.defaultClasses,
|
|
222
|
+
nodes:
|
|
223
|
+
storageGrid.nodes?.map((storageColumn) => {
|
|
224
|
+
const columnId = ulid();
|
|
225
|
+
const columnNodes =
|
|
226
|
+
storageColumn.nodes?.flatMap((storageNode) =>
|
|
227
|
+
processStorageNode(storageNode, columnId)
|
|
228
|
+
) || [];
|
|
229
|
+
return {
|
|
230
|
+
id: columnId,
|
|
231
|
+
parentId: gridId,
|
|
232
|
+
nodeType: 'Markdown',
|
|
233
|
+
type: 'markdown',
|
|
234
|
+
markdownId: columnId,
|
|
235
|
+
defaultClasses: storageColumn.defaultClasses,
|
|
236
|
+
parentClasses: storageColumn.parentClasses,
|
|
237
|
+
gridClasses: storageColumn.gridClasses,
|
|
238
|
+
nodes: columnNodes,
|
|
239
|
+
};
|
|
240
|
+
}) || [],
|
|
241
|
+
};
|
|
242
|
+
} else if (storagePane.markdowns && storagePane.markdowns[0]) {
|
|
243
|
+
const markdownId = ulid();
|
|
244
|
+
const storageMarkdown = storagePane.markdowns[0];
|
|
245
|
+
const flatNodeList =
|
|
246
|
+
storageMarkdown.nodes?.flatMap((storageNode) =>
|
|
247
|
+
processStorageNode(storageNode, markdownId)
|
|
248
|
+
) || [];
|
|
249
|
+
|
|
250
|
+
liveMarkdown = {
|
|
251
|
+
id: markdownId,
|
|
252
|
+
parentId: paneId,
|
|
253
|
+
nodeType: 'Markdown',
|
|
254
|
+
type: 'markdown',
|
|
255
|
+
markdownId: markdownId,
|
|
256
|
+
defaultClasses: storageMarkdown.defaultClasses,
|
|
257
|
+
parentClasses: storageMarkdown.parentClasses,
|
|
258
|
+
nodes: flatNodeList,
|
|
259
|
+
};
|
|
301
260
|
}
|
|
302
261
|
|
|
303
|
-
let liveBgPane: ArtpackImageNode | BgImageNode | VisualBreakNode | undefined =
|
|
304
|
-
undefined;
|
|
305
262
|
if (storagePane.bgPane) {
|
|
306
263
|
const bgPaneId = ulid();
|
|
307
264
|
liveBgPane = {
|
|
@@ -311,23 +268,14 @@ export function convertStorageToLiveTemplate(
|
|
|
311
268
|
};
|
|
312
269
|
}
|
|
313
270
|
|
|
314
|
-
const { gridLayout, ...restOfStoragePane } = storagePane;
|
|
271
|
+
const { markdowns, gridLayout, bgPane, ...restOfStoragePane } = storagePane;
|
|
272
|
+
|
|
315
273
|
const liveTemplatePane: TemplatePane = {
|
|
316
274
|
...restOfStoragePane,
|
|
317
275
|
id: paneId,
|
|
318
276
|
parentId: '',
|
|
319
|
-
markdown:
|
|
320
|
-
|
|
321
|
-
nodeType: 'Markdown',
|
|
322
|
-
type: 'markdown',
|
|
323
|
-
defaultClasses: {},
|
|
324
|
-
parentClasses: [],
|
|
325
|
-
}),
|
|
326
|
-
id: markdownId,
|
|
327
|
-
markdownId: markdownId,
|
|
328
|
-
parentId: paneId,
|
|
329
|
-
nodes: flatNodeList,
|
|
330
|
-
},
|
|
277
|
+
markdown: liveMarkdown,
|
|
278
|
+
gridLayout: liveGridLayout,
|
|
331
279
|
bgPane: liveBgPane,
|
|
332
280
|
};
|
|
333
281
|
|
|
@@ -373,142 +321,129 @@ export function convertTemplateToAIShell(template: TemplatePane): string {
|
|
|
373
321
|
defaultClasses: {},
|
|
374
322
|
};
|
|
375
323
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
{}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
// 2. Process defaultClasses (typography, etc.)
|
|
395
|
-
if (template.markdown?.defaultClasses) {
|
|
396
|
-
for (const tag in template.markdown.defaultClasses) {
|
|
397
|
-
const styles = template.markdown.defaultClasses[tag];
|
|
398
|
-
const newTagStyles: {
|
|
399
|
-
mobile?: string;
|
|
400
|
-
tablet?: string;
|
|
401
|
-
desktop?: string;
|
|
402
|
-
} = {};
|
|
324
|
+
if (template.gridLayout) {
|
|
325
|
+
// 1. Process parentClasses (layout)
|
|
326
|
+
if (template.gridLayout.parentClasses) {
|
|
327
|
+
shell.parentClasses = template.gridLayout.parentClasses.map((layer) => {
|
|
328
|
+
const newLayer: { mobile?: string; tablet?: string; desktop?: string } =
|
|
329
|
+
{};
|
|
330
|
+
if (layer.mobile && Object.keys(layer.mobile).length > 0) {
|
|
331
|
+
newLayer.mobile = classObjectToString(layer.mobile);
|
|
332
|
+
}
|
|
333
|
+
if (layer.tablet && Object.keys(layer.tablet).length > 0) {
|
|
334
|
+
newLayer.tablet = classObjectToString(layer.tablet);
|
|
335
|
+
}
|
|
336
|
+
if (layer.desktop && Object.keys(layer.desktop).length > 0) {
|
|
337
|
+
newLayer.desktop = classObjectToString(layer.desktop);
|
|
338
|
+
}
|
|
339
|
+
return newLayer;
|
|
340
|
+
});
|
|
341
|
+
}
|
|
403
342
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
newTagStyles
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
343
|
+
// 2. Process defaultClasses (typography, etc.)
|
|
344
|
+
if (template.gridLayout.defaultClasses) {
|
|
345
|
+
for (const tag in template.gridLayout.defaultClasses) {
|
|
346
|
+
const styles = template.gridLayout.defaultClasses[tag];
|
|
347
|
+
const newTagStyles: {
|
|
348
|
+
mobile?: string;
|
|
349
|
+
tablet?: string;
|
|
350
|
+
desktop?: string;
|
|
351
|
+
} = {};
|
|
352
|
+
|
|
353
|
+
if (styles.mobile && Object.keys(styles.mobile).length > 0) {
|
|
354
|
+
newTagStyles.mobile = classObjectToString(styles.mobile);
|
|
355
|
+
}
|
|
356
|
+
if (styles.tablet && Object.keys(styles.tablet).length > 0) {
|
|
357
|
+
newTagStyles.tablet = classObjectToString(styles.tablet);
|
|
358
|
+
}
|
|
359
|
+
if (styles.desktop && Object.keys(styles.desktop).length > 0) {
|
|
360
|
+
newTagStyles.desktop = classObjectToString(styles.desktop);
|
|
361
|
+
}
|
|
413
362
|
|
|
414
|
-
|
|
415
|
-
|
|
363
|
+
if (Object.keys(newTagStyles).length > 0) {
|
|
364
|
+
shell.defaultClasses[tag] = newTagStyles;
|
|
365
|
+
}
|
|
416
366
|
}
|
|
417
367
|
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
return JSON.stringify(shell, null, 2);
|
|
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
368
|
|
|
472
|
-
|
|
473
|
-
|
|
369
|
+
// 3. Process columns
|
|
370
|
+
if (template.gridLayout.nodes) {
|
|
371
|
+
shell.columns = template.gridLayout.nodes.map((column) => {
|
|
372
|
+
const newColumn: {
|
|
373
|
+
gridClasses: { mobile?: string; tablet?: string; desktop?: string };
|
|
374
|
+
} = {
|
|
375
|
+
gridClasses: {},
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
if (column.gridClasses) {
|
|
379
|
+
const styles = column.gridClasses;
|
|
380
|
+
const newGridClasses: {
|
|
381
|
+
mobile?: string;
|
|
382
|
+
tablet?: string;
|
|
383
|
+
desktop?: string;
|
|
384
|
+
} = {};
|
|
385
|
+
|
|
386
|
+
if (styles.mobile && Object.keys(styles.mobile).length > 0) {
|
|
387
|
+
newGridClasses.mobile = classObjectToString(styles.mobile);
|
|
388
|
+
}
|
|
389
|
+
if (styles.tablet && Object.keys(styles.tablet).length > 0) {
|
|
390
|
+
newGridClasses.tablet = classObjectToString(styles.tablet);
|
|
391
|
+
}
|
|
392
|
+
if (styles.desktop && Object.keys(styles.desktop).length > 0) {
|
|
393
|
+
newGridClasses.desktop = classObjectToString(styles.desktop);
|
|
394
|
+
}
|
|
395
|
+
newColumn.gridClasses = newGridClasses;
|
|
396
|
+
}
|
|
397
|
+
return newColumn;
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
} else if (template.markdown) {
|
|
401
|
+
// 1. Process parentClasses (layout)
|
|
402
|
+
if (template.markdown.parentClasses) {
|
|
403
|
+
shell.parentClasses = template.markdown.parentClasses.map((layer) => {
|
|
404
|
+
const newLayer: { mobile?: string; tablet?: string; desktop?: string } =
|
|
405
|
+
{};
|
|
406
|
+
if (layer.mobile && Object.keys(layer.mobile).length > 0) {
|
|
407
|
+
newLayer.mobile = classObjectToString(layer.mobile);
|
|
408
|
+
}
|
|
409
|
+
if (layer.tablet && Object.keys(layer.tablet).length > 0) {
|
|
410
|
+
newLayer.tablet = classObjectToString(layer.tablet);
|
|
411
|
+
}
|
|
412
|
+
if (layer.desktop && Object.keys(layer.desktop).length > 0) {
|
|
413
|
+
newLayer.desktop = classObjectToString(layer.desktop);
|
|
414
|
+
}
|
|
415
|
+
return newLayer;
|
|
416
|
+
});
|
|
417
|
+
}
|
|
474
418
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
419
|
+
// 2. Process defaultClasses (typography, etc.)
|
|
420
|
+
if (template.markdown.defaultClasses) {
|
|
421
|
+
for (const tag in template.markdown.defaultClasses) {
|
|
422
|
+
const styles = template.markdown.defaultClasses[tag];
|
|
423
|
+
const newTagStyles: {
|
|
424
|
+
mobile?: string;
|
|
425
|
+
tablet?: string;
|
|
426
|
+
desktop?: string;
|
|
427
|
+
} = {};
|
|
428
|
+
|
|
429
|
+
if (styles.mobile && Object.keys(styles.mobile).length > 0) {
|
|
430
|
+
newTagStyles.mobile = classObjectToString(styles.mobile);
|
|
431
|
+
}
|
|
432
|
+
if (styles.tablet && Object.keys(styles.tablet).length > 0) {
|
|
433
|
+
newTagStyles.tablet = classObjectToString(styles.tablet);
|
|
434
|
+
}
|
|
435
|
+
if (styles.desktop && Object.keys(styles.desktop).length > 0) {
|
|
436
|
+
newTagStyles.desktop = classObjectToString(styles.desktop);
|
|
437
|
+
}
|
|
484
438
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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);
|
|
439
|
+
if (Object.keys(newTagStyles).length > 0) {
|
|
440
|
+
shell.defaultClasses[tag] = newTagStyles;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
497
444
|
}
|
|
498
|
-
}
|
|
499
445
|
|
|
500
|
-
|
|
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;
|
|
446
|
+
return JSON.stringify(shell, null, 2);
|
|
512
447
|
}
|
|
513
448
|
|
|
514
449
|
function convertLivePaneToStoragePane(
|
|
@@ -640,3 +575,165 @@ function convertLivePaneToStoragePane(
|
|
|
640
575
|
|
|
641
576
|
return storagePane;
|
|
642
577
|
}
|
|
578
|
+
|
|
579
|
+
export async function savePaneToLibrary(
|
|
580
|
+
paneId: string,
|
|
581
|
+
tenantId: string,
|
|
582
|
+
config: BrandConfig,
|
|
583
|
+
formData: {
|
|
584
|
+
title: string;
|
|
585
|
+
category: string;
|
|
586
|
+
copyMode: CopyMode;
|
|
587
|
+
}
|
|
588
|
+
): Promise<BrandConfigState | null> {
|
|
589
|
+
const ctx = getCtx();
|
|
590
|
+
const { title, category, copyMode } = formData;
|
|
591
|
+
|
|
592
|
+
const newStoragePane = convertLivePaneToStoragePane(paneId, ctx, {
|
|
593
|
+
title,
|
|
594
|
+
copyMode,
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
if (!newStoragePane) {
|
|
598
|
+
console.error(
|
|
599
|
+
'savePaneToLibrary: Failed to convert pane to storage format.'
|
|
600
|
+
);
|
|
601
|
+
return null;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
let actualMarkdownCount = 0;
|
|
605
|
+
if (newStoragePane.gridLayout && newStoragePane.gridLayout.nodes) {
|
|
606
|
+
actualMarkdownCount = newStoragePane.gridLayout.nodes.length;
|
|
607
|
+
} else if (newStoragePane.markdowns) {
|
|
608
|
+
actualMarkdownCount = newStoragePane.markdowns.length;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const newLibraryEntry: DesignLibraryEntry = {
|
|
612
|
+
category: category,
|
|
613
|
+
title: title,
|
|
614
|
+
markdownCount: actualMarkdownCount,
|
|
615
|
+
template: newStoragePane,
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
const currentState: BrandConfigState = convertToLocalState(config);
|
|
619
|
+
const currentLibrary =
|
|
620
|
+
(currentState.designLibrary as DesignLibraryConfig) || [];
|
|
621
|
+
|
|
622
|
+
const existingEntryIndex = currentLibrary.findIndex(
|
|
623
|
+
(entry) => entry.title === title && entry.category === category
|
|
624
|
+
);
|
|
625
|
+
|
|
626
|
+
let newLibrary: DesignLibraryConfig;
|
|
627
|
+
if (existingEntryIndex !== -1) {
|
|
628
|
+
newLibrary = [...currentLibrary];
|
|
629
|
+
newLibrary[existingEntryIndex] = newLibraryEntry;
|
|
630
|
+
} else {
|
|
631
|
+
newLibrary = [...currentLibrary, newLibraryEntry];
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const updatedState: BrandConfigState = {
|
|
635
|
+
...currentState,
|
|
636
|
+
designLibrary: newLibrary,
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
const backendDTO: BrandConfig = convertToBackendFormat(updatedState);
|
|
640
|
+
|
|
641
|
+
try {
|
|
642
|
+
await saveBrandConfig(tenantId, backendDTO);
|
|
643
|
+
return updatedState;
|
|
644
|
+
} catch (error) {
|
|
645
|
+
console.error('Failed to save design library:', error);
|
|
646
|
+
return null;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
export async function copyPaneToClipboard(paneId: string): Promise<boolean> {
|
|
651
|
+
const ctx = getCtx();
|
|
652
|
+
const paneNode = ctx.allNodes.get().get(paneId) as PaneNode;
|
|
653
|
+
|
|
654
|
+
const storagePane = convertLivePaneToStoragePane(paneId, ctx, {
|
|
655
|
+
title: paneNode?.title || 'Pasted Pane',
|
|
656
|
+
copyMode: 'retain',
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
if (!storagePane) {
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
try {
|
|
664
|
+
const jsonPayload = JSON.stringify(storagePane, null, 2);
|
|
665
|
+
await navigator.clipboard.writeText(jsonPayload);
|
|
666
|
+
return true;
|
|
667
|
+
} catch (error) {
|
|
668
|
+
console.error('Failed to copy pane to clipboard:', error);
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function buildIdMap(node: any, map: Map<string, string>) {
|
|
674
|
+
if (!node || typeof node !== 'object') return;
|
|
675
|
+
|
|
676
|
+
if (node.id && !map.has(node.id)) {
|
|
677
|
+
map.set(node.id, ulid());
|
|
678
|
+
}
|
|
679
|
+
// Markdown nodes have a second unique identifier
|
|
680
|
+
if (node.markdownId && !map.has(node.markdownId)) {
|
|
681
|
+
map.set(node.markdownId, ulid());
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Recursively traverse all possible child arrays/objects
|
|
685
|
+
if (node.markdowns) {
|
|
686
|
+
node.markdowns.forEach((n: any) => buildIdMap(n, map));
|
|
687
|
+
}
|
|
688
|
+
if (node.gridLayout) {
|
|
689
|
+
buildIdMap(node.gridLayout, map);
|
|
690
|
+
}
|
|
691
|
+
if (node.nodes) {
|
|
692
|
+
node.nodes.forEach((n: any) => buildIdMap(n, map));
|
|
693
|
+
}
|
|
694
|
+
if (node.bgPane) {
|
|
695
|
+
buildIdMap(node.bgPane, map);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function applyIdMap(node: any, map: Map<string, string>) {
|
|
700
|
+
if (!node || typeof node !== 'object') return;
|
|
701
|
+
|
|
702
|
+
if (node.id && map.has(node.id)) {
|
|
703
|
+
node.id = map.get(node.id);
|
|
704
|
+
}
|
|
705
|
+
if (node.parentId && map.has(node.parentId)) {
|
|
706
|
+
node.parentId = map.get(node.parentId);
|
|
707
|
+
}
|
|
708
|
+
if (node.markdownId && map.has(node.markdownId)) {
|
|
709
|
+
node.markdownId = map.get(node.markdownId);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Recursively traverse all possible child arrays/objects
|
|
713
|
+
if (node.markdowns) {
|
|
714
|
+
node.markdowns.forEach((n: any) => applyIdMap(n, map));
|
|
715
|
+
}
|
|
716
|
+
if (node.gridLayout) {
|
|
717
|
+
applyIdMap(node.gridLayout, map);
|
|
718
|
+
}
|
|
719
|
+
if (node.nodes) {
|
|
720
|
+
node.nodes.forEach((n: any) => applyIdMap(n, map));
|
|
721
|
+
}
|
|
722
|
+
if (node.bgPane) {
|
|
723
|
+
applyIdMap(node.bgPane, map);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
export function remapPaneIds(pane: StoragePane): StoragePane {
|
|
728
|
+
const idMap = new Map<string, string>();
|
|
729
|
+
// The input object may have come from JSON.parse, so we treat it as 'any' internally
|
|
730
|
+
const clone = JSON.parse(JSON.stringify(pane as any));
|
|
731
|
+
|
|
732
|
+
// First pass: Traverse the entire structure to build a complete map of old IDs to new IDs.
|
|
733
|
+
buildIdMap(clone, idMap);
|
|
734
|
+
|
|
735
|
+
// Second pass: Traverse again to apply the new IDs, ensuring parent-child relationships are correct.
|
|
736
|
+
applyIdMap(clone, idMap);
|
|
737
|
+
|
|
738
|
+
return clone as StoragePane;
|
|
739
|
+
}
|