astro-tractstack 2.0.16 → 2.0.18
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 +24 -0
- package/package.json +1 -1
- package/templates/custom/with-examples/SandboxLauncher.tsx +11 -9
- package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +1 -1
- package/templates/src/components/codehooks/ListContentSetup.tsx +1 -1
- package/templates/src/components/compositor/Compositor.tsx +1 -0
- package/templates/src/components/compositor/Node.tsx +41 -17
- package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +9 -6
- package/templates/src/components/compositor/nodes/GridLayout.tsx +124 -0
- package/templates/src/components/compositor/nodes/GridLayout_eraser.tsx +33 -0
- package/templates/src/components/compositor/nodes/Markdown.tsx +67 -37
- package/templates/src/components/compositor/nodes/Markdown_eraser.tsx +56 -0
- package/templates/src/components/compositor/nodes/Pane_DesignLibrary.tsx +1 -1
- package/templates/src/components/compositor/preview/FeaturedArticlePreview.tsx +8 -2
- package/templates/src/components/edit/PanelSwitch.tsx +232 -75
- package/templates/src/components/edit/SettingsPanel.tsx +0 -1
- package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +3 -3
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +184 -151
- package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +2 -2
- package/templates/src/components/edit/pane/ConfigPanePanel.tsx +1 -7
- package/templates/src/components/edit/pane/PanePanel_impression.tsx +1 -1
- package/templates/src/components/edit/pane/RestylePaneModal.tsx +8 -5
- package/templates/src/components/edit/pane/steps/AiDesignStep.tsx +6 -6
- package/templates/src/components/edit/pane/steps/CopyInputStep.tsx +3 -3
- package/templates/src/components/edit/pane/steps/DesignLibraryStep.tsx +4 -4
- package/templates/src/components/edit/pane/steps/DirectInjectStep.tsx +96 -0
- package/templates/src/components/edit/panels/StyleElementPanel.tsx +11 -4
- package/templates/src/components/edit/panels/StyleElementPanel_add.tsx +8 -8
- package/templates/src/components/edit/panels/StyleElementPanel_remove.tsx +14 -4
- package/templates/src/components/edit/panels/StyleElementPanel_update.tsx +16 -4
- package/templates/src/components/edit/panels/StyleImagePanel.tsx +8 -3
- package/templates/src/components/edit/panels/StyleImagePanel_add.tsx +9 -2
- package/templates/src/components/edit/panels/StyleImagePanel_remove.tsx +5 -2
- package/templates/src/components/edit/panels/StyleImagePanel_update.tsx +5 -2
- package/templates/src/components/edit/panels/StyleLiElementPanel.tsx +7 -3
- package/templates/src/components/edit/panels/StyleLiElementPanel_add.tsx +9 -2
- package/templates/src/components/edit/panels/StyleLiElementPanel_remove.tsx +5 -2
- package/templates/src/components/edit/panels/StyleLiElementPanel_update.tsx +5 -2
- package/templates/src/components/edit/panels/StyleParentPanel.tsx +530 -171
- package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +77 -42
- package/templates/src/components/edit/panels/StyleParentPanel_deleteLayer.tsx +38 -22
- package/templates/src/components/edit/panels/StyleParentPanel_remove.tsx +171 -66
- package/templates/src/components/edit/panels/StyleParentPanel_update.tsx +166 -98
- package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +7 -3
- package/templates/src/components/edit/panels/StyleWidgetPanel_add.tsx +9 -2
- package/templates/src/components/edit/panels/StyleWidgetPanel_remove.tsx +5 -2
- package/templates/src/components/edit/panels/StyleWidgetPanel_update.tsx +6 -2
- package/templates/src/components/edit/state/SaveModal.tsx +10 -2
- package/templates/src/components/edit/state/SaveToLibraryModal.tsx +6 -6
- package/templates/src/components/fields/PaneBreakShapeSelector.tsx +1 -1
- package/templates/src/components/widgets/ImpressionWrapper.tsx +4 -1
- package/templates/src/constants/prompts.json +1 -1
- package/templates/src/constants.ts +1 -0
- package/templates/src/stores/nodes.ts +110 -33
- package/templates/src/stores/storykeep.ts +3 -1
- package/templates/src/types/compositorTypes.ts +37 -2
- package/templates/src/utils/compositor/TemplateNodes.ts +8 -0
- package/templates/src/utils/compositor/aiPaneParser.ts +8 -2
- package/templates/src/utils/compositor/nodesHelper.ts +229 -0
- package/templates/src/utils/compositor/reduceNodesClassNames.ts +40 -1
- package/templates/src/utils/compositor/typeGuards.ts +7 -0
- package/templates/src/utils/etl/extractor.ts +1 -5
- package/templates/src/utils/etl/index.ts +1 -0
- package/templates/src/utils/etl/transformer.ts +70 -25
- package/utils/inject-files.ts +24 -0
|
@@ -1,85 +1,149 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react';
|
|
2
2
|
import PlusIcon from '@heroicons/react/24/outline/PlusIcon';
|
|
3
|
-
import
|
|
3
|
+
import ArrowUturnLeftIcon from '@heroicons/react/24/outline/ArrowUturnLeftIcon';
|
|
4
|
+
import ChevronLeftIcon from '@heroicons/react/24/outline/ChevronLeftIcon';
|
|
5
|
+
import ChevronRightIcon from '@heroicons/react/24/outline/ChevronRightIcon';
|
|
6
|
+
import {
|
|
7
|
+
settingsPanelStore,
|
|
8
|
+
stylePanelTargetMemoryStore,
|
|
9
|
+
} from '@/stores/storykeep';
|
|
4
10
|
import { getCtx } from '@/stores/nodes';
|
|
5
11
|
import {
|
|
6
12
|
isMarkdownPaneFragmentNode,
|
|
13
|
+
isArtpackImageNode,
|
|
14
|
+
isBgImageNode,
|
|
15
|
+
isGridLayoutNode,
|
|
7
16
|
isPaneNode,
|
|
8
17
|
} from '@/utils/compositor/typeGuards';
|
|
9
|
-
import { StylesMemory } from '@/components/edit/state/StylesMemory';
|
|
10
18
|
import SelectedTailwindClass from '@/components/fields/SelectedTailwindClass';
|
|
11
19
|
import BackgroundImageWrapper from '@/components/fields/BackgroundImageWrapper';
|
|
20
|
+
import ColorPickerCombo from '@/components/fields/ColorPickerCombo';
|
|
12
21
|
import { cloneDeep } from '@/utils/helpers';
|
|
22
|
+
import {
|
|
23
|
+
convertToGrid,
|
|
24
|
+
revertFromGrid,
|
|
25
|
+
addColumn,
|
|
26
|
+
} from '@/utils/compositor/nodesHelper';
|
|
13
27
|
import type {
|
|
14
28
|
MarkdownPaneFragmentNode,
|
|
15
|
-
|
|
29
|
+
ParentBasePanelProps,
|
|
30
|
+
ArtpackImageNode,
|
|
31
|
+
BgImageNode,
|
|
32
|
+
GridLayoutNode,
|
|
33
|
+
BaseNode,
|
|
34
|
+
DefaultClassValue,
|
|
16
35
|
} from '@/types/compositorTypes';
|
|
17
36
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
37
|
+
type VisibilityKey =
|
|
38
|
+
| 'hiddenViewportMobile'
|
|
39
|
+
| 'hiddenViewportTablet'
|
|
40
|
+
| 'hiddenViewportDesktop';
|
|
41
|
+
type PanelView = 'summary' | 'wrapperStyles' | 'backgroundImage';
|
|
42
|
+
|
|
43
|
+
type StyleableNode = MarkdownPaneFragmentNode | GridLayoutNode;
|
|
44
|
+
|
|
45
|
+
type StyleableTarget = {
|
|
46
|
+
id: string;
|
|
47
|
+
name: string;
|
|
48
|
+
node: StyleableNode;
|
|
49
|
+
targetProperty: 'parentClasses' | 'gridClasses';
|
|
50
|
+
};
|
|
26
51
|
|
|
27
52
|
const StyleParentPanel = ({
|
|
28
|
-
node,
|
|
29
|
-
parentNode,
|
|
53
|
+
node: initialNode,
|
|
54
|
+
parentNode: paneNode,
|
|
30
55
|
layer,
|
|
31
56
|
config,
|
|
32
|
-
}:
|
|
33
|
-
|
|
34
|
-
!parentNode ||
|
|
35
|
-
!node ||
|
|
36
|
-
!isMarkdownPaneFragmentNode(node) ||
|
|
37
|
-
!isPaneNode(parentNode) ||
|
|
38
|
-
!isMarkdownPaneFragmentNode(node)
|
|
39
|
-
) {
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const [layerCount, setLayerCount] = useState(node.parentClasses?.length || 0);
|
|
57
|
+
}: ParentBasePanelProps) => {
|
|
58
|
+
const [currentView, setCurrentView] = useState<PanelView>('summary');
|
|
44
59
|
const [currentLayer, setCurrentLayer] = useState<number>(layer || 1);
|
|
45
|
-
const [
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
60
|
+
const [styleTargets, setStyleTargets] = useState<StyleableTarget[]>([]);
|
|
61
|
+
const [selectedTargetIndex, setSelectedTargetIndex] = useState(0);
|
|
62
|
+
|
|
63
|
+
const ctx = getCtx();
|
|
49
64
|
|
|
50
|
-
// Update state when node changes
|
|
51
65
|
useEffect(() => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
66
|
+
if (
|
|
67
|
+
!initialNode ||
|
|
68
|
+
!(
|
|
69
|
+
isMarkdownPaneFragmentNode(initialNode) || isGridLayoutNode(initialNode)
|
|
70
|
+
) ||
|
|
71
|
+
!paneNode ||
|
|
72
|
+
!isPaneNode(paneNode)
|
|
73
|
+
) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const targets: StyleableTarget[] = [];
|
|
78
|
+
const isGrid = isGridLayoutNode(initialNode);
|
|
79
|
+
|
|
80
|
+
targets.push({
|
|
81
|
+
id: initialNode.id,
|
|
82
|
+
name: isGrid ? 'Outer Container' : 'Pane Styles',
|
|
83
|
+
node: initialNode,
|
|
84
|
+
targetProperty: 'parentClasses',
|
|
56
85
|
});
|
|
57
|
-
|
|
86
|
+
|
|
87
|
+
if (isGrid) {
|
|
88
|
+
const columnNodes = ctx
|
|
89
|
+
.getChildNodeIDs(initialNode.id)
|
|
90
|
+
.map((id) => ctx.allNodes.get().get(id) as BaseNode)
|
|
91
|
+
.filter(isMarkdownPaneFragmentNode);
|
|
92
|
+
|
|
93
|
+
columnNodes.forEach((colNode, index) => {
|
|
94
|
+
targets.push({
|
|
95
|
+
id: colNode.id,
|
|
96
|
+
name: `Column ${index + 1}`,
|
|
97
|
+
node: colNode,
|
|
98
|
+
targetProperty: 'gridClasses',
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
setStyleTargets(targets);
|
|
104
|
+
|
|
105
|
+
const rememberedIndex = stylePanelTargetMemoryStore.get().get(paneNode.id);
|
|
106
|
+
|
|
107
|
+
if (rememberedIndex != null && rememberedIndex < targets.length) {
|
|
108
|
+
setSelectedTargetIndex(rememberedIndex);
|
|
109
|
+
} else {
|
|
110
|
+
setSelectedTargetIndex(0);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
setCurrentView('summary');
|
|
114
|
+
}, [initialNode, ctx, paneNode]);
|
|
58
115
|
|
|
59
116
|
useEffect(() => {
|
|
60
117
|
setCurrentLayer(layer || 1);
|
|
61
118
|
}, [layer]);
|
|
62
119
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (paneNode?.id) {
|
|
122
|
+
const newMemory = new Map(stylePanelTargetMemoryStore.get());
|
|
123
|
+
newMemory.set(paneNode.id, selectedTargetIndex);
|
|
124
|
+
stylePanelTargetMemoryStore.set(newMemory);
|
|
125
|
+
}
|
|
126
|
+
}, [selectedTargetIndex, paneNode?.id]);
|
|
68
127
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
desktop: {},
|
|
74
|
-
};
|
|
128
|
+
const selectedTarget = styleTargets[selectedTargetIndex];
|
|
129
|
+
if (!selectedTarget || !paneNode || !isPaneNode(paneNode)) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
75
132
|
|
|
76
|
-
|
|
77
|
-
|
|
133
|
+
const {
|
|
134
|
+
id: selectedTargetId,
|
|
135
|
+
name: selectedTargetName,
|
|
136
|
+
node: selectedTargetNode,
|
|
137
|
+
targetProperty,
|
|
138
|
+
} = selectedTarget;
|
|
78
139
|
|
|
79
|
-
|
|
140
|
+
const handleLayerAdd = (position: 'before' | 'after', layerNum: number) => {
|
|
141
|
+
const targetNode = cloneDeep(selectedTargetNode);
|
|
142
|
+
|
|
143
|
+
const emptyLayer = { mobile: {}, tablet: {}, desktop: {} };
|
|
144
|
+
let newParentClasses = [...(targetNode.parentClasses || [])];
|
|
80
145
|
const insertIndex = position === 'before' ? layerNum - 1 : layerNum;
|
|
81
146
|
|
|
82
|
-
// Insert the empty layer at the calculated index
|
|
83
147
|
newParentClasses = [
|
|
84
148
|
...newParentClasses.slice(0, insertIndex),
|
|
85
149
|
emptyLayer,
|
|
@@ -88,176 +152,471 @@ const StyleParentPanel = ({
|
|
|
88
152
|
|
|
89
153
|
ctx.modifyNodes([
|
|
90
154
|
{
|
|
91
|
-
...
|
|
155
|
+
...targetNode,
|
|
92
156
|
parentClasses: newParentClasses,
|
|
93
157
|
isChanged: true,
|
|
94
|
-
} as
|
|
158
|
+
} as StyleableNode,
|
|
95
159
|
]);
|
|
96
160
|
|
|
97
|
-
// Update local state
|
|
98
|
-
setSettings((prev) => ({
|
|
99
|
-
...prev,
|
|
100
|
-
parentClasses: newParentClasses,
|
|
101
|
-
}));
|
|
102
|
-
setLayerCount(newParentClasses.length);
|
|
103
|
-
|
|
104
|
-
// Set the current layer to the newly added layer
|
|
105
161
|
const newLayer = position === 'before' ? layerNum : layerNum + 1;
|
|
106
162
|
setCurrentLayer(newLayer);
|
|
107
163
|
};
|
|
108
164
|
|
|
109
|
-
const
|
|
165
|
+
const dispatchToSubPanel = (
|
|
166
|
+
action: string,
|
|
167
|
+
extraProps: Record<string, any> = {}
|
|
168
|
+
) => {
|
|
110
169
|
settingsPanelStore.set({
|
|
111
|
-
nodeId:
|
|
112
|
-
|
|
113
|
-
|
|
170
|
+
nodeId: selectedTargetId,
|
|
171
|
+
action,
|
|
172
|
+
...extraProps,
|
|
173
|
+
targetProperty: targetProperty,
|
|
114
174
|
expanded: true,
|
|
115
175
|
});
|
|
116
176
|
};
|
|
117
177
|
|
|
178
|
+
const handleGridColumnChange = (
|
|
179
|
+
viewport: 'mobile' | 'tablet' | 'desktop',
|
|
180
|
+
value: string
|
|
181
|
+
) => {
|
|
182
|
+
const count = parseInt(value, 10);
|
|
183
|
+
if (isNaN(count) || !isGridLayoutNode(selectedTargetNode)) return;
|
|
184
|
+
|
|
185
|
+
const updatedNode = cloneDeep(selectedTargetNode);
|
|
186
|
+
updatedNode.gridColumns[viewport] = count;
|
|
187
|
+
updatedNode.isChanged = true;
|
|
188
|
+
ctx.modifyNodes([updatedNode]);
|
|
189
|
+
ctx.notifyNode('root');
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const handleClickDeleteLayer = () => {
|
|
193
|
+
dispatchToSubPanel('style-parent-delete-layer', { layer: currentLayer });
|
|
194
|
+
};
|
|
118
195
|
const handleClickRemove = (name: string) => {
|
|
119
|
-
|
|
120
|
-
nodeId: node.id,
|
|
196
|
+
dispatchToSubPanel('style-parent-remove', {
|
|
121
197
|
layer: currentLayer,
|
|
122
198
|
className: name,
|
|
123
|
-
action: `style-parent-remove`,
|
|
124
|
-
expanded: true,
|
|
125
199
|
});
|
|
126
200
|
};
|
|
127
|
-
|
|
128
201
|
const handleClickUpdate = (name: string) => {
|
|
129
|
-
|
|
130
|
-
nodeId: node.id,
|
|
202
|
+
dispatchToSubPanel('style-parent-update', {
|
|
131
203
|
layer: currentLayer,
|
|
132
204
|
className: name,
|
|
133
|
-
action: `style-parent-update`,
|
|
134
|
-
expanded: true,
|
|
135
205
|
});
|
|
136
206
|
};
|
|
137
|
-
|
|
138
207
|
const handleClickAdd = () => {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
208
|
+
dispatchToSubPanel('style-parent-add', { layer: currentLayer });
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const handleColorChange = (color: string) => {
|
|
212
|
+
const updatedPaneNode = cloneDeep(paneNode);
|
|
213
|
+
if (color) {
|
|
214
|
+
updatedPaneNode.bgColour = color;
|
|
215
|
+
} else if (typeof updatedPaneNode.bgColour === 'string' && !color) {
|
|
216
|
+
delete updatedPaneNode.bgColour;
|
|
217
|
+
}
|
|
218
|
+
updatedPaneNode.isChanged = true;
|
|
219
|
+
ctx.modifyNodes([updatedPaneNode]);
|
|
220
|
+
ctx.notifyNode('root');
|
|
145
221
|
};
|
|
146
222
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
223
|
+
const handleVisibilityChange = (
|
|
224
|
+
viewport: 'mobile' | 'tablet' | 'desktop'
|
|
225
|
+
) => {
|
|
226
|
+
const updatedNode = cloneDeep(selectedTargetNode);
|
|
227
|
+
const key: VisibilityKey = `hiddenViewport${
|
|
228
|
+
viewport.charAt(0).toUpperCase() + viewport.slice(1)
|
|
229
|
+
}` as VisibilityKey;
|
|
230
|
+
updatedNode[key] = !updatedNode[key];
|
|
231
|
+
updatedNode.isChanged = true;
|
|
232
|
+
ctx.modifyNodes([updatedNode]);
|
|
233
|
+
ctx.notifyNode('root');
|
|
152
234
|
};
|
|
153
|
-
|
|
154
|
-
|
|
235
|
+
|
|
236
|
+
const BackButton = () => (
|
|
237
|
+
<button
|
|
238
|
+
onClick={() => setCurrentView('summary')}
|
|
239
|
+
className="mb-4 flex items-center gap-2 text-sm font-bold text-gray-600 hover:text-black"
|
|
240
|
+
>
|
|
241
|
+
<ArrowUturnLeftIcon className="h-4 w-4" />
|
|
242
|
+
Back to Summary
|
|
243
|
+
</button>
|
|
155
244
|
);
|
|
156
245
|
|
|
157
|
-
|
|
158
|
-
<div className="
|
|
246
|
+
const TargetNavigator = () => (
|
|
247
|
+
<div className="mb-4 flex items-center justify-between rounded-md bg-slate-100 p-2">
|
|
248
|
+
<button
|
|
249
|
+
onClick={() =>
|
|
250
|
+
setSelectedTargetIndex(
|
|
251
|
+
(prev) => (prev - 1 + styleTargets.length) % styleTargets.length
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
className="rounded-full p-1 text-gray-500 hover:bg-gray-200 hover:text-black"
|
|
255
|
+
disabled={styleTargets.length < 2}
|
|
256
|
+
>
|
|
257
|
+
<ChevronLeftIcon className="h-5 w-5" />
|
|
258
|
+
</button>
|
|
259
|
+
<span className="text-sm font-bold uppercase tracking-wider text-gray-700">
|
|
260
|
+
{selectedTargetName}
|
|
261
|
+
</span>
|
|
262
|
+
<button
|
|
263
|
+
onClick={() =>
|
|
264
|
+
setSelectedTargetIndex((prev) => (prev + 1) % styleTargets.length)
|
|
265
|
+
}
|
|
266
|
+
className="rounded-full p-1 text-gray-500 hover:bg-gray-200 hover:text-black"
|
|
267
|
+
disabled={styleTargets.length < 2}
|
|
268
|
+
>
|
|
269
|
+
<ChevronRightIcon className="h-5 w-5" />
|
|
270
|
+
</button>
|
|
271
|
+
</div>
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
const renderSummaryView = () => {
|
|
275
|
+
if (!initialNode) return null;
|
|
276
|
+
const isGrid = isGridLayoutNode(initialNode);
|
|
277
|
+
const childNodeIds = ctx.getChildNodeIDs(paneNode.id);
|
|
278
|
+
const bgNode = childNodeIds
|
|
279
|
+
.map((id) => ctx.allNodes.get().get(id))
|
|
280
|
+
.find(
|
|
281
|
+
(n) =>
|
|
282
|
+
n?.nodeType === 'BgPane' &&
|
|
283
|
+
'type' in n &&
|
|
284
|
+
(n.type === 'background-image' || n.type === 'artpack-image')
|
|
285
|
+
) as (BgImageNode | ArtpackImageNode) | undefined;
|
|
286
|
+
let bgSummary = 'None';
|
|
287
|
+
if (bgNode) {
|
|
288
|
+
if (isArtpackImageNode(bgNode)) bgSummary = `Artpack: ${bgNode.image}`;
|
|
289
|
+
else if (isBgImageNode(bgNode)) bgSummary = `Custom Image`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const wrapperSummary = `${selectedTargetNode.parentClasses?.length || 0} layers`;
|
|
293
|
+
|
|
294
|
+
let columnClasses: DefaultClassValue = {
|
|
295
|
+
mobile: {},
|
|
296
|
+
tablet: {},
|
|
297
|
+
desktop: {},
|
|
298
|
+
};
|
|
299
|
+
let columnHasNoClasses = true;
|
|
300
|
+
|
|
301
|
+
if (
|
|
302
|
+
selectedTargetIndex > 0 &&
|
|
303
|
+
isMarkdownPaneFragmentNode(selectedTargetNode)
|
|
304
|
+
) {
|
|
305
|
+
columnClasses = selectedTargetNode.gridClasses || {
|
|
306
|
+
mobile: {},
|
|
307
|
+
tablet: {},
|
|
308
|
+
desktop: {},
|
|
309
|
+
};
|
|
310
|
+
columnHasNoClasses = !Object.values(columnClasses).some(
|
|
311
|
+
(breakpoint) => Object.keys(breakpoint).length > 0
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return (
|
|
159
316
|
<div className="space-y-4">
|
|
160
|
-
<
|
|
161
|
-
paneId={parentNode.id}
|
|
162
|
-
config={config || undefined}
|
|
163
|
-
/>
|
|
164
|
-
</div>
|
|
317
|
+
{styleTargets.length > 1 && <TargetNavigator />}
|
|
165
318
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
<
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
.map((num, index) => (
|
|
180
|
-
<div
|
|
181
|
-
key={`layer-group-${num}`}
|
|
182
|
-
className="flex items-center gap-1"
|
|
183
|
-
>
|
|
319
|
+
{selectedTargetIndex === 0 && (
|
|
320
|
+
<div className="space-y-3">
|
|
321
|
+
<ColorPickerCombo
|
|
322
|
+
title="Pane Background Color"
|
|
323
|
+
defaultColor={paneNode.bgColour || ''}
|
|
324
|
+
onColorChange={handleColorChange}
|
|
325
|
+
config={config!}
|
|
326
|
+
allowNull={true}
|
|
327
|
+
/>
|
|
328
|
+
<div className="flex items-center justify-between border-t border-gray-200 pt-3">
|
|
329
|
+
<span>Pane Styles:</span>
|
|
330
|
+
<div className="flex items-center gap-2">
|
|
331
|
+
<span className="text-sm text-gray-600">{wrapperSummary}</span>
|
|
184
332
|
<button
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
? 'bg-myblue text-white shadow-sm'
|
|
188
|
-
: 'text-mydarkgrey hover:bg-mydarkgrey/10 bg-white hover:text-black'
|
|
189
|
-
}`}
|
|
190
|
-
onClick={() => setCurrentLayer(num)}
|
|
333
|
+
onClick={() => setCurrentView('wrapperStyles')}
|
|
334
|
+
className="rounded bg-gray-100 px-3 py-1 text-sm font-bold text-gray-700 hover:bg-gray-200"
|
|
191
335
|
>
|
|
192
|
-
|
|
336
|
+
Edit
|
|
193
337
|
</button>
|
|
338
|
+
</div>
|
|
339
|
+
</div>
|
|
340
|
+
<div className="flex items-center justify-between">
|
|
341
|
+
<span>Background Image:</span>
|
|
342
|
+
<div className="flex items-center gap-2">
|
|
343
|
+
<span className="max-w-36 truncate text-right text-sm text-gray-600">
|
|
344
|
+
{bgSummary}
|
|
345
|
+
</span>
|
|
194
346
|
<button
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
onClick={() =>
|
|
198
|
-
handleLayerAdd(
|
|
199
|
-
index === layerCount - 1 ? 'after' : 'before',
|
|
200
|
-
index === layerCount - 1 ? num : num + 1
|
|
201
|
-
)
|
|
202
|
-
}
|
|
347
|
+
onClick={() => setCurrentView('backgroundImage')}
|
|
348
|
+
className="rounded bg-gray-100 px-3 py-1 text-sm font-bold text-gray-700 hover:bg-gray-200"
|
|
203
349
|
>
|
|
204
|
-
|
|
350
|
+
Edit
|
|
205
351
|
</button>
|
|
206
352
|
</div>
|
|
207
|
-
|
|
353
|
+
</div>
|
|
354
|
+
</div>
|
|
355
|
+
)}
|
|
356
|
+
|
|
357
|
+
{selectedTargetIndex > 0 &&
|
|
358
|
+
isMarkdownPaneFragmentNode(selectedTargetNode) && (
|
|
359
|
+
<div className="space-y-4">
|
|
360
|
+
<h3 className="mb-3 text-sm font-bold uppercase text-gray-500">
|
|
361
|
+
Column Styles
|
|
362
|
+
</h3>
|
|
363
|
+
{columnHasNoClasses ? (
|
|
364
|
+
<div>
|
|
365
|
+
<em>No styles for this column.</em>
|
|
366
|
+
</div>
|
|
367
|
+
) : (
|
|
368
|
+
<div className="flex flex-wrap gap-2">
|
|
369
|
+
{Object.entries(columnClasses.mobile || {}).map(
|
|
370
|
+
([className]) => (
|
|
371
|
+
<SelectedTailwindClass
|
|
372
|
+
key={className}
|
|
373
|
+
name={className}
|
|
374
|
+
values={{
|
|
375
|
+
mobile: columnClasses.mobile[className],
|
|
376
|
+
tablet: columnClasses.tablet?.[className],
|
|
377
|
+
desktop: columnClasses.desktop?.[className],
|
|
378
|
+
}}
|
|
379
|
+
onRemove={handleClickRemove}
|
|
380
|
+
onUpdate={handleClickUpdate}
|
|
381
|
+
/>
|
|
382
|
+
)
|
|
383
|
+
)}
|
|
384
|
+
</div>
|
|
385
|
+
)}
|
|
386
|
+
<div>
|
|
387
|
+
<ul className="text-mydarkgrey flex flex-wrap gap-x-4 gap-y-1">
|
|
388
|
+
<li>
|
|
389
|
+
<em>Actions:</em>
|
|
390
|
+
</li>
|
|
391
|
+
<li>
|
|
392
|
+
<button
|
|
393
|
+
onClick={handleClickAdd}
|
|
394
|
+
className="text-myblue font-bold underline hover:text-black"
|
|
395
|
+
>
|
|
396
|
+
Add Style
|
|
397
|
+
</button>
|
|
398
|
+
</li>
|
|
399
|
+
</ul>
|
|
400
|
+
</div>
|
|
401
|
+
</div>
|
|
402
|
+
)}
|
|
403
|
+
|
|
404
|
+
{selectedTargetIndex === 0 && (
|
|
405
|
+
<div className="space-y-3 border-t border-gray-200 pt-4">
|
|
406
|
+
<h3 className="text-sm font-bold uppercase text-gray-500">
|
|
407
|
+
Layout
|
|
408
|
+
</h3>
|
|
409
|
+
{!isGrid ? (
|
|
410
|
+
<button
|
|
411
|
+
onClick={() => convertToGrid(initialNode.id)}
|
|
412
|
+
className="w-full rounded bg-cyan-600 px-4 py-2 text-sm font-bold text-white hover:bg-cyan-700"
|
|
413
|
+
>
|
|
414
|
+
Convert to Grid Layout
|
|
415
|
+
</button>
|
|
416
|
+
) : (
|
|
417
|
+
<>
|
|
418
|
+
{isGridLayoutNode(selectedTargetNode) && (
|
|
419
|
+
<div className="space-y-3 rounded-md border border-gray-200 bg-gray-50 p-3">
|
|
420
|
+
<h4 className="text-xs font-bold uppercase text-gray-500">
|
|
421
|
+
Grid Columns
|
|
422
|
+
</h4>
|
|
423
|
+
<div className="grid grid-cols-3 gap-3">
|
|
424
|
+
{(['mobile', 'tablet', 'desktop'] as const).map(
|
|
425
|
+
(viewport) => (
|
|
426
|
+
<div key={viewport}>
|
|
427
|
+
<label className="block text-center text-xs capitalize text-gray-600">
|
|
428
|
+
{viewport}
|
|
429
|
+
</label>
|
|
430
|
+
<select
|
|
431
|
+
value={selectedTargetNode.gridColumns[viewport]}
|
|
432
|
+
onChange={(e) =>
|
|
433
|
+
handleGridColumnChange(viewport, e.target.value)
|
|
434
|
+
}
|
|
435
|
+
className="mt-1 block w-full rounded-md border-gray-300 py-1 pl-2 pr-7 text-sm focus:border-cyan-500 focus:outline-none focus:ring-cyan-500"
|
|
436
|
+
>
|
|
437
|
+
{Array.from({ length: 12 }, (_, i) => i + 1).map(
|
|
438
|
+
(n) => (
|
|
439
|
+
<option key={n} value={n}>
|
|
440
|
+
{n}
|
|
441
|
+
</option>
|
|
442
|
+
)
|
|
443
|
+
)}
|
|
444
|
+
</select>
|
|
445
|
+
</div>
|
|
446
|
+
)
|
|
447
|
+
)}
|
|
448
|
+
</div>
|
|
449
|
+
</div>
|
|
450
|
+
)}
|
|
451
|
+
<button
|
|
452
|
+
onClick={() => revertFromGrid(initialNode.id)}
|
|
453
|
+
className="w-full rounded bg-gray-200 px-4 py-2 text-sm font-bold text-gray-700 hover:bg-gray-300"
|
|
454
|
+
>
|
|
455
|
+
Revert to Standard Pane
|
|
456
|
+
</button>
|
|
457
|
+
<button
|
|
458
|
+
onClick={() => addColumn(initialNode.id)}
|
|
459
|
+
className="w-full rounded border border-dashed border-gray-400 bg-transparent px-4 py-2 text-sm font-bold text-gray-700 hover:border-gray-600 hover:bg-gray-50"
|
|
460
|
+
>
|
|
461
|
+
Add Column
|
|
462
|
+
</button>
|
|
463
|
+
</>
|
|
464
|
+
)}
|
|
465
|
+
</div>
|
|
466
|
+
)}
|
|
467
|
+
|
|
468
|
+
<div className="space-y-3 border-t border-gray-200 pt-4">
|
|
469
|
+
<h3 className="text-sm font-bold uppercase text-gray-500">
|
|
470
|
+
Hide on Viewport
|
|
471
|
+
</h3>
|
|
472
|
+
<div className="flex justify-around">
|
|
473
|
+
{(['mobile', 'tablet', 'desktop'] as const).map((viewport) => {
|
|
474
|
+
const key: VisibilityKey =
|
|
475
|
+
`hiddenViewport${viewport.charAt(0).toUpperCase() + viewport.slice(1)}` as VisibilityKey;
|
|
476
|
+
return (
|
|
477
|
+
<label key={viewport} className="flex items-center space-x-2">
|
|
478
|
+
<input
|
|
479
|
+
type="checkbox"
|
|
480
|
+
className="h-4 w-4 rounded border-gray-300 text-cyan-600 focus:ring-cyan-500"
|
|
481
|
+
checked={!!selectedTargetNode[key]}
|
|
482
|
+
onChange={() => handleVisibilityChange(viewport)}
|
|
483
|
+
/>
|
|
484
|
+
<span className="text-sm capitalize text-gray-700">
|
|
485
|
+
{viewport}
|
|
486
|
+
</span>
|
|
487
|
+
</label>
|
|
488
|
+
);
|
|
489
|
+
})}
|
|
490
|
+
</div>
|
|
208
491
|
</div>
|
|
209
492
|
</div>
|
|
493
|
+
);
|
|
494
|
+
};
|
|
210
495
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
key={className}
|
|
220
|
-
name={className}
|
|
221
|
-
values={{
|
|
222
|
-
mobile: currentClasses.mobile[className],
|
|
223
|
-
tablet: currentClasses.tablet?.[className],
|
|
224
|
-
desktop: currentClasses.desktop?.[className],
|
|
225
|
-
}}
|
|
226
|
-
onRemove={handleClickRemove}
|
|
227
|
-
onUpdate={handleClickUpdate}
|
|
228
|
-
/>
|
|
229
|
-
))}
|
|
230
|
-
</div>
|
|
231
|
-
) : null}
|
|
496
|
+
const renderWrapperStylesView = () => {
|
|
497
|
+
const layerCount = selectedTargetNode.parentClasses?.length || 0;
|
|
498
|
+
const currentClasses = selectedTargetNode.parentClasses?.[
|
|
499
|
+
currentLayer - 1
|
|
500
|
+
] || { mobile: {}, tablet: {}, desktop: {} };
|
|
501
|
+
const hasNoClasses = !Object.values(currentClasses).some(
|
|
502
|
+
(breakpoint) => Object.keys(breakpoint).length > 0
|
|
503
|
+
);
|
|
232
504
|
|
|
505
|
+
return (
|
|
233
506
|
<div className="space-y-4">
|
|
234
|
-
<
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
<li>
|
|
239
|
-
<button
|
|
240
|
-
onClick={() => handleClickAdd()}
|
|
241
|
-
className="text-myblue font-bold underline hover:text-black"
|
|
242
|
-
>
|
|
243
|
-
Add Style
|
|
244
|
-
</button>
|
|
245
|
-
</li>
|
|
246
|
-
<li>
|
|
507
|
+
<BackButton />
|
|
508
|
+
<div className="mb-4 flex items-center gap-3 rounded-md bg-slate-50 p-3">
|
|
509
|
+
<span className="text-mydarkgrey text-sm font-bold">Layer:</span>
|
|
510
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
247
511
|
<button
|
|
248
|
-
|
|
249
|
-
|
|
512
|
+
className="border-mydarkgrey/30 text-mydarkgrey rounded border border-dashed p-1 text-xs transition-colors hover:bg-white/50 hover:text-black"
|
|
513
|
+
title="Add Layer Here"
|
|
514
|
+
onClick={() => handleLayerAdd('before', 1)}
|
|
250
515
|
>
|
|
251
|
-
|
|
516
|
+
<PlusIcon className="h-3 w-3" />
|
|
252
517
|
</button>
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
518
|
+
{[...Array(layerCount).keys()]
|
|
519
|
+
.map((i) => i + 1)
|
|
520
|
+
.map((num, index) => (
|
|
521
|
+
<div
|
|
522
|
+
key={`layer-group-${num}`}
|
|
523
|
+
className="flex items-center gap-1"
|
|
524
|
+
>
|
|
525
|
+
<button
|
|
526
|
+
className={`min-w-8 rounded-md px-3 py-1.5 text-sm font-bold transition-colors ${
|
|
527
|
+
currentLayer === num
|
|
528
|
+
? 'bg-myblue text-white shadow-sm'
|
|
529
|
+
: 'text-mydarkgrey hover:bg-mydarkgrey/10 bg-white hover:text-black'
|
|
530
|
+
}`}
|
|
531
|
+
onClick={() => setCurrentLayer(num)}
|
|
532
|
+
>
|
|
533
|
+
{num}
|
|
534
|
+
</button>
|
|
535
|
+
<button
|
|
536
|
+
className="border-mydarkgrey/30 text-mydarkgrey rounded border border-dashed p-1 text-xs transition-colors hover:bg-white/50 hover:text-black"
|
|
537
|
+
title="Add Layer Here"
|
|
538
|
+
onClick={() =>
|
|
539
|
+
handleLayerAdd(
|
|
540
|
+
index === layerCount - 1 ? 'after' : 'before',
|
|
541
|
+
index === layerCount - 1 ? num : num + 1
|
|
542
|
+
)
|
|
543
|
+
}
|
|
544
|
+
>
|
|
545
|
+
<PlusIcon className="h-3 w-3" />
|
|
546
|
+
</button>
|
|
547
|
+
</div>
|
|
548
|
+
))}
|
|
549
|
+
</div>
|
|
550
|
+
</div>
|
|
551
|
+
{hasNoClasses ? (
|
|
552
|
+
<div>
|
|
553
|
+
<em>No styles for this layer.</em>
|
|
554
|
+
</div>
|
|
555
|
+
) : (
|
|
556
|
+
<div className="flex flex-wrap gap-2">
|
|
557
|
+
{Object.entries(currentClasses.mobile).map(([className]) => (
|
|
558
|
+
<SelectedTailwindClass
|
|
559
|
+
key={className}
|
|
560
|
+
name={className}
|
|
561
|
+
values={{
|
|
562
|
+
mobile: currentClasses.mobile[className],
|
|
563
|
+
tablet: currentClasses.tablet?.[className],
|
|
564
|
+
desktop: currentClasses.desktop?.[className],
|
|
565
|
+
}}
|
|
566
|
+
onRemove={handleClickRemove}
|
|
567
|
+
onUpdate={handleClickUpdate}
|
|
568
|
+
/>
|
|
569
|
+
))}
|
|
570
|
+
</div>
|
|
571
|
+
)}
|
|
572
|
+
<div>
|
|
573
|
+
<ul className="text-mydarkgrey flex flex-wrap gap-x-4 gap-y-1">
|
|
574
|
+
<li>
|
|
575
|
+
<em>Actions:</em>
|
|
576
|
+
</li>
|
|
577
|
+
<li>
|
|
578
|
+
<button
|
|
579
|
+
onClick={handleClickAdd}
|
|
580
|
+
className="text-myblue font-bold underline hover:text-black"
|
|
581
|
+
>
|
|
582
|
+
Add Style
|
|
583
|
+
</button>
|
|
584
|
+
</li>
|
|
585
|
+
<li>
|
|
586
|
+
<button
|
|
587
|
+
onClick={handleClickDeleteLayer}
|
|
588
|
+
className="text-myblue font-bold underline hover:text-black"
|
|
589
|
+
>
|
|
590
|
+
Delete Layer
|
|
591
|
+
</button>
|
|
592
|
+
</li>
|
|
593
|
+
</ul>
|
|
594
|
+
</div>
|
|
258
595
|
</div>
|
|
596
|
+
);
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
const renderBackgroundImageVIew = () => (
|
|
600
|
+
<div className="space-y-4">
|
|
601
|
+
<BackButton />
|
|
602
|
+
<BackgroundImageWrapper paneId={paneNode.id} config={config!} />
|
|
259
603
|
</div>
|
|
260
604
|
);
|
|
605
|
+
|
|
606
|
+
const renderContent = () => {
|
|
607
|
+
switch (currentView) {
|
|
608
|
+
case 'summary':
|
|
609
|
+
return renderSummaryView();
|
|
610
|
+
case 'wrapperStyles':
|
|
611
|
+
return renderWrapperStylesView();
|
|
612
|
+
case 'backgroundImage':
|
|
613
|
+
return renderBackgroundImageVIew();
|
|
614
|
+
default:
|
|
615
|
+
return renderSummaryView();
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
return <div className="space-y-4">{renderContent()}</div>;
|
|
261
620
|
};
|
|
262
621
|
|
|
263
622
|
export default StyleParentPanel;
|