astro-tractstack 2.0.19 → 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/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/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 +58 -25
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +140 -133
- package/templates/src/components/edit/pane/AddPanePanel_paste.tsx +111 -0
- package/templates/src/components/edit/pane/RestylePaneModal.tsx +231 -282
- package/templates/src/components/edit/pane/steps/AiDesignStep.tsx +0 -5
- package/templates/src/components/edit/pane/steps/DesignLibraryStep.tsx +4 -13
- 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_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 +29 -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/stores/nodes.ts +14 -6
- package/templates/src/stores/storykeep.ts +3 -3
- package/templates/src/types/compositorTypes.ts +2 -0
- package/templates/src/utils/compositor/TemplatePanes.ts +0 -76
- package/templates/src/utils/compositor/aiPaneParser.ts +3 -1
- package/templates/src/utils/compositor/designLibraryHelper.ts +523 -203
- 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,24 @@
|
|
|
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
|
+
StorageGridLayoutNode,
|
|
13
|
+
StorageBgPane,
|
|
14
|
+
ArtpackImageNode,
|
|
15
|
+
BgImageNode,
|
|
16
|
+
VisualBreakNode,
|
|
17
|
+
TemplatePane,
|
|
18
|
+
TemplateNode,
|
|
19
|
+
TemplateGridLayout,
|
|
20
|
+
TemplateMarkdown,
|
|
21
|
+
ParentClassesPayload,
|
|
17
22
|
} from '@/types/compositorTypes';
|
|
18
23
|
import type {
|
|
19
24
|
BrandConfig,
|
|
@@ -29,7 +34,7 @@ import {
|
|
|
29
34
|
|
|
30
35
|
type CopyMode = 'retain' | 'lorem' | 'blank';
|
|
31
36
|
|
|
32
|
-
export type ExtractedCopy = StorageNode[];
|
|
37
|
+
export type ExtractedCopy = StorageNode[][];
|
|
33
38
|
|
|
34
39
|
const LOREM_SHORT = 'Lorem ipsum dolor sit amet.';
|
|
35
40
|
const LOREM_LONG =
|
|
@@ -99,128 +104,34 @@ function convertLiveNodeToStorageNode(
|
|
|
99
104
|
return storageNode;
|
|
100
105
|
}
|
|
101
106
|
|
|
102
|
-
export async function savePaneToLibrary(
|
|
103
|
-
paneId: string,
|
|
104
|
-
tenantId: string,
|
|
105
|
-
config: BrandConfig,
|
|
106
|
-
formData: {
|
|
107
|
-
title: string;
|
|
108
|
-
category: string;
|
|
109
|
-
copyMode: CopyMode;
|
|
110
|
-
}
|
|
111
|
-
): Promise<boolean> {
|
|
112
|
-
const ctx = getCtx();
|
|
113
|
-
const { title, category, copyMode } = formData;
|
|
114
|
-
const paneNode = ctx.allNodes.get().get(paneId) as PaneNode;
|
|
115
|
-
|
|
116
|
-
if (!paneNode) {
|
|
117
|
-
console.error('savePaneToLibrary: PaneNode not found.');
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const childNodes = ctx
|
|
122
|
-
.getChildNodeIDs(paneId)
|
|
123
|
-
.map((id) => ctx.allNodes.get().get(id));
|
|
124
|
-
|
|
125
|
-
const markdownNode = childNodes.find((n) => n?.nodeType === 'Markdown') as
|
|
126
|
-
| MarkdownPaneFragmentNode
|
|
127
|
-
| undefined;
|
|
128
|
-
|
|
129
|
-
const bgPaneNode = childNodes.find((n) => n?.nodeType === 'BgPane') as
|
|
130
|
-
| ArtpackImageNode
|
|
131
|
-
| BgImageNode
|
|
132
|
-
| VisualBreakNode
|
|
133
|
-
| undefined;
|
|
134
|
-
|
|
135
|
-
const newStorageMarkdown: StorageMarkdown | undefined = markdownNode
|
|
136
|
-
? {
|
|
137
|
-
nodeType: 'Markdown',
|
|
138
|
-
type: 'markdown',
|
|
139
|
-
defaultClasses: markdownNode.defaultClasses || {},
|
|
140
|
-
parentClasses: markdownNode.parentClasses || [],
|
|
141
|
-
nodes:
|
|
142
|
-
copyMode !== 'blank'
|
|
143
|
-
? ctx
|
|
144
|
-
.getChildNodeIDs(markdownNode.id)
|
|
145
|
-
.map((childId) => {
|
|
146
|
-
const childNode = ctx.allNodes.get().get(childId) as FlatNode;
|
|
147
|
-
return convertLiveNodeToStorageNode(childNode, ctx, copyMode);
|
|
148
|
-
})
|
|
149
|
-
.filter((n): n is StorageNode => n !== null)
|
|
150
|
-
: [],
|
|
151
|
-
}
|
|
152
|
-
: undefined;
|
|
153
|
-
|
|
154
|
-
const newStorageBgPane: StorageBgPane | undefined = bgPaneNode
|
|
155
|
-
? { ...bgPaneNode }
|
|
156
|
-
: undefined;
|
|
157
|
-
|
|
158
|
-
if (newStorageBgPane) {
|
|
159
|
-
delete (newStorageBgPane as any).id;
|
|
160
|
-
delete (newStorageBgPane as any).parentId;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const newStoragePane: StoragePane = {
|
|
164
|
-
nodeType: 'Pane',
|
|
165
|
-
title: title,
|
|
166
|
-
slug: '',
|
|
167
|
-
bgColour: paneNode.bgColour,
|
|
168
|
-
isDecorative: paneNode.isDecorative,
|
|
169
|
-
heightOffsetDesktop: paneNode.heightOffsetDesktop,
|
|
170
|
-
heightOffsetMobile: paneNode.heightOffsetMobile,
|
|
171
|
-
heightOffsetTablet: paneNode.heightOffsetTablet,
|
|
172
|
-
heightRatioDesktop: paneNode.heightRatioDesktop,
|
|
173
|
-
heightRatioMobile: paneNode.heightRatioMobile,
|
|
174
|
-
heightRatioTablet: paneNode.heightRatioTablet,
|
|
175
|
-
...(newStorageMarkdown ? { markdowns: [newStorageMarkdown] } : {}),
|
|
176
|
-
...(newStorageBgPane ? { bgPane: newStorageBgPane } : {}),
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
const newLibraryEntry: DesignLibraryEntry = {
|
|
180
|
-
category: category,
|
|
181
|
-
title: title,
|
|
182
|
-
markdownCount: 1,
|
|
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
107
|
export function extractPaneCopy(paneNode: PaneNode): ExtractedCopy {
|
|
219
108
|
const ctx = getCtx();
|
|
220
109
|
const childNodes = ctx
|
|
221
110
|
.getChildNodeIDs(paneNode.id)
|
|
222
111
|
.map((id) => ctx.allNodes.get().get(id));
|
|
223
112
|
|
|
113
|
+
const gridLayoutNode = childNodes.find(
|
|
114
|
+
(n) => n?.nodeType === 'GridLayoutNode'
|
|
115
|
+
) as GridLayoutNode | undefined;
|
|
116
|
+
|
|
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
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
224
135
|
const markdownNode = childNodes.find((n) => n?.nodeType === 'Markdown') as
|
|
225
136
|
| MarkdownPaneFragmentNode
|
|
226
137
|
| undefined;
|
|
@@ -229,34 +140,37 @@ export function extractPaneCopy(paneNode: PaneNode): ExtractedCopy {
|
|
|
229
140
|
return [];
|
|
230
141
|
}
|
|
231
142
|
|
|
232
|
-
|
|
143
|
+
const nodes =
|
|
233
144
|
ctx
|
|
234
145
|
.getChildNodeIDs(markdownNode.id)
|
|
235
146
|
.map((childId) => {
|
|
236
147
|
const childNode = ctx.allNodes.get().get(childId) as FlatNode;
|
|
237
148
|
return convertLiveNodeToStorageNode(childNode, ctx, 'retain');
|
|
238
149
|
})
|
|
239
|
-
.filter((n): n is StorageNode => n !== null) || []
|
|
240
|
-
|
|
150
|
+
.filter((n): n is StorageNode => n !== null) || [];
|
|
151
|
+
|
|
152
|
+
return [nodes];
|
|
241
153
|
}
|
|
242
154
|
|
|
243
155
|
export function mergeCopyIntoTemplate(
|
|
244
156
|
template: StoragePane,
|
|
245
157
|
copy: ExtractedCopy
|
|
246
158
|
): StoragePane {
|
|
247
|
-
const newTemplate =
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
+
}
|
|
259
172
|
}
|
|
173
|
+
|
|
260
174
|
return newTemplate;
|
|
261
175
|
}
|
|
262
176
|
|
|
@@ -288,18 +202,63 @@ export function convertStorageToLiveTemplate(
|
|
|
288
202
|
storagePane: StoragePane
|
|
289
203
|
): TemplatePane {
|
|
290
204
|
const paneId = ulid();
|
|
291
|
-
|
|
292
|
-
|
|
205
|
+
let liveMarkdown: TemplateMarkdown | undefined = undefined;
|
|
206
|
+
let liveGridLayout: TemplateGridLayout | undefined = undefined;
|
|
207
|
+
let liveBgPane: ArtpackImageNode | BgImageNode | VisualBreakNode | undefined =
|
|
208
|
+
undefined;
|
|
293
209
|
|
|
294
|
-
if (storagePane.
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
+
};
|
|
299
260
|
}
|
|
300
261
|
|
|
301
|
-
let liveBgPane: ArtpackImageNode | BgImageNode | VisualBreakNode | undefined =
|
|
302
|
-
undefined;
|
|
303
262
|
if (storagePane.bgPane) {
|
|
304
263
|
const bgPaneId = ulid();
|
|
305
264
|
liveBgPane = {
|
|
@@ -309,23 +268,14 @@ export function convertStorageToLiveTemplate(
|
|
|
309
268
|
};
|
|
310
269
|
}
|
|
311
270
|
|
|
312
|
-
const { gridLayout, ...restOfStoragePane } = storagePane;
|
|
271
|
+
const { markdowns, gridLayout, bgPane, ...restOfStoragePane } = storagePane;
|
|
272
|
+
|
|
313
273
|
const liveTemplatePane: TemplatePane = {
|
|
314
274
|
...restOfStoragePane,
|
|
315
275
|
id: paneId,
|
|
316
276
|
parentId: '',
|
|
317
|
-
markdown:
|
|
318
|
-
|
|
319
|
-
nodeType: 'Markdown',
|
|
320
|
-
type: 'markdown',
|
|
321
|
-
defaultClasses: {},
|
|
322
|
-
parentClasses: [],
|
|
323
|
-
}),
|
|
324
|
-
id: markdownId,
|
|
325
|
-
markdownId: markdownId,
|
|
326
|
-
parentId: paneId,
|
|
327
|
-
nodes: flatNodeList,
|
|
328
|
-
},
|
|
277
|
+
markdown: liveMarkdown,
|
|
278
|
+
gridLayout: liveGridLayout,
|
|
329
279
|
bgPane: liveBgPane,
|
|
330
280
|
};
|
|
331
281
|
|
|
@@ -371,49 +321,419 @@ export function convertTemplateToAIShell(template: TemplatePane): string {
|
|
|
371
321
|
defaultClasses: {},
|
|
372
322
|
};
|
|
373
323
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
{}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
+
}
|
|
391
342
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
+
}
|
|
401
362
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
if (styles.tablet && Object.keys(styles.tablet).length > 0) {
|
|
406
|
-
newTagStyles.tablet = classObjectToString(styles.tablet);
|
|
407
|
-
}
|
|
408
|
-
if (styles.desktop && Object.keys(styles.desktop).length > 0) {
|
|
409
|
-
newTagStyles.desktop = classObjectToString(styles.desktop);
|
|
363
|
+
if (Object.keys(newTagStyles).length > 0) {
|
|
364
|
+
shell.defaultClasses[tag] = newTagStyles;
|
|
365
|
+
}
|
|
410
366
|
}
|
|
367
|
+
}
|
|
368
|
+
|
|
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
|
+
}
|
|
418
|
+
|
|
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
|
+
}
|
|
411
438
|
|
|
412
|
-
|
|
413
|
-
|
|
439
|
+
if (Object.keys(newTagStyles).length > 0) {
|
|
440
|
+
shell.defaultClasses[tag] = newTagStyles;
|
|
441
|
+
}
|
|
414
442
|
}
|
|
415
443
|
}
|
|
416
444
|
}
|
|
417
445
|
|
|
418
446
|
return JSON.stringify(shell, null, 2);
|
|
419
447
|
}
|
|
448
|
+
|
|
449
|
+
function convertLivePaneToStoragePane(
|
|
450
|
+
paneId: string,
|
|
451
|
+
ctx: NodesContext,
|
|
452
|
+
options: {
|
|
453
|
+
title: string;
|
|
454
|
+
copyMode: CopyMode;
|
|
455
|
+
}
|
|
456
|
+
): StoragePane | null {
|
|
457
|
+
const paneNode = ctx.allNodes.get().get(paneId) as PaneNode;
|
|
458
|
+
if (!paneNode) {
|
|
459
|
+
console.error('convertLivePaneToStoragePane: PaneNode not found.');
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const { title, copyMode } = options;
|
|
464
|
+
const childNodes = ctx
|
|
465
|
+
.getChildNodeIDs(paneId)
|
|
466
|
+
.map((id) => ctx.allNodes.get().get(id));
|
|
467
|
+
|
|
468
|
+
const markdownNode = childNodes.find((n) => n?.nodeType === 'Markdown') as
|
|
469
|
+
| MarkdownPaneFragmentNode
|
|
470
|
+
| undefined;
|
|
471
|
+
|
|
472
|
+
const gridLayoutNode = childNodes.find(
|
|
473
|
+
(n) => n?.nodeType === 'GridLayoutNode'
|
|
474
|
+
) as GridLayoutNode | undefined;
|
|
475
|
+
|
|
476
|
+
const bgPaneNode = childNodes.find((n) => n?.nodeType === 'BgPane') as
|
|
477
|
+
| ArtpackImageNode
|
|
478
|
+
| BgImageNode
|
|
479
|
+
| VisualBreakNode
|
|
480
|
+
| undefined;
|
|
481
|
+
|
|
482
|
+
let storageMarkdown: StorageMarkdown | undefined;
|
|
483
|
+
let storageGridLayout: StorageGridLayoutNode | undefined;
|
|
484
|
+
|
|
485
|
+
if (markdownNode) {
|
|
486
|
+
storageMarkdown = {
|
|
487
|
+
nodeType: 'Markdown',
|
|
488
|
+
type: 'markdown',
|
|
489
|
+
defaultClasses: markdownNode.defaultClasses || {},
|
|
490
|
+
parentClasses: markdownNode.parentClasses || [],
|
|
491
|
+
nodes:
|
|
492
|
+
copyMode !== 'blank'
|
|
493
|
+
? ctx
|
|
494
|
+
.getChildNodeIDs(markdownNode.id)
|
|
495
|
+
.map((childId) => {
|
|
496
|
+
const childNode = ctx.allNodes.get().get(childId) as FlatNode;
|
|
497
|
+
return convertLiveNodeToStorageNode(childNode, ctx, copyMode);
|
|
498
|
+
})
|
|
499
|
+
.filter((n): n is StorageNode => n !== null)
|
|
500
|
+
: [],
|
|
501
|
+
};
|
|
502
|
+
} else if (gridLayoutNode) {
|
|
503
|
+
const { id, parentId, isChanged, ...restOfGrid } = gridLayoutNode;
|
|
504
|
+
storageGridLayout = {
|
|
505
|
+
...restOfGrid,
|
|
506
|
+
nodes: ctx
|
|
507
|
+
.getChildNodeIDs(gridLayoutNode.id)
|
|
508
|
+
.map((columnId) => {
|
|
509
|
+
const columnNode = ctx.allNodes
|
|
510
|
+
.get()
|
|
511
|
+
.get(columnId) as MarkdownPaneFragmentNode;
|
|
512
|
+
if (!columnNode) return null;
|
|
513
|
+
|
|
514
|
+
const {
|
|
515
|
+
id,
|
|
516
|
+
parentId,
|
|
517
|
+
isChanged,
|
|
518
|
+
markdownId,
|
|
519
|
+
parentCss,
|
|
520
|
+
...restOfColumn
|
|
521
|
+
} = columnNode;
|
|
522
|
+
|
|
523
|
+
const storageColumn: StorageMarkdown = {
|
|
524
|
+
...restOfColumn,
|
|
525
|
+
nodeType: 'Markdown',
|
|
526
|
+
type: 'markdown',
|
|
527
|
+
nodes:
|
|
528
|
+
copyMode !== 'blank'
|
|
529
|
+
? ctx
|
|
530
|
+
.getChildNodeIDs(columnNode.id)
|
|
531
|
+
.map((childId) => {
|
|
532
|
+
const childNode = ctx.allNodes
|
|
533
|
+
.get()
|
|
534
|
+
.get(childId) as FlatNode;
|
|
535
|
+
return convertLiveNodeToStorageNode(
|
|
536
|
+
childNode,
|
|
537
|
+
ctx,
|
|
538
|
+
copyMode
|
|
539
|
+
);
|
|
540
|
+
})
|
|
541
|
+
.filter((n): n is StorageNode => n !== null)
|
|
542
|
+
: [],
|
|
543
|
+
};
|
|
544
|
+
return storageColumn;
|
|
545
|
+
})
|
|
546
|
+
.filter((n): n is StorageMarkdown => n !== null),
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const storageBgPane: StorageBgPane | undefined = bgPaneNode
|
|
551
|
+
? { ...bgPaneNode }
|
|
552
|
+
: undefined;
|
|
553
|
+
|
|
554
|
+
if (storageBgPane) {
|
|
555
|
+
delete (storageBgPane as any).id;
|
|
556
|
+
delete (storageBgPane as any).parentId;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const storagePane: StoragePane = {
|
|
560
|
+
nodeType: 'Pane',
|
|
561
|
+
title: title,
|
|
562
|
+
slug: '',
|
|
563
|
+
bgColour: paneNode.bgColour,
|
|
564
|
+
isDecorative: paneNode.isDecorative,
|
|
565
|
+
heightOffsetDesktop: paneNode.heightOffsetDesktop,
|
|
566
|
+
heightOffsetMobile: paneNode.heightOffsetMobile,
|
|
567
|
+
heightOffsetTablet: paneNode.heightOffsetTablet,
|
|
568
|
+
heightRatioDesktop: paneNode.heightRatioDesktop,
|
|
569
|
+
heightRatioMobile: paneNode.heightRatioMobile,
|
|
570
|
+
heightRatioTablet: paneNode.heightRatioTablet,
|
|
571
|
+
...(storageMarkdown ? { markdowns: [storageMarkdown] } : {}),
|
|
572
|
+
...(storageGridLayout ? { gridLayout: storageGridLayout } : {}),
|
|
573
|
+
...(storageBgPane ? { bgPane: storageBgPane } : {}),
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
return storagePane;
|
|
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
|
+
}
|