astro-tractstack 2.1.3 → 2.2.1
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/README.md +54 -266
- package/bin/create-tractstack.js +9 -6
- package/dist/index.js +109 -71
- package/package.json +4 -2
- package/templates/css/custom.css +5 -0
- package/templates/icons/code.svg +18 -0
- package/templates/icons/li.svg +4 -0
- package/templates/icons/link.svg +22 -0
- package/templates/icons/p.svg +3 -0
- package/templates/src/client/app.js +80 -1
- package/templates/src/components/Footer.astro +1 -1
- package/templates/src/components/codehooks/BunnyVideoSetup.tsx +6 -6
- package/templates/src/components/codehooks/EpinetDurationSelector.tsx +3 -3
- package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +1 -1
- package/templates/src/components/codehooks/ListContentSetup.tsx +2 -2
- package/templates/src/components/codehooks/ProductCardSetup.tsx +1 -1
- package/templates/src/components/codehooks/ProductGridSetup.tsx +2 -2
- package/templates/src/components/codehooks/SandboxRegisterForm.tsx +3 -3
- package/templates/src/components/compositor/Compositor.tsx +25 -9
- package/templates/src/components/compositor/Node.tsx +168 -496
- package/templates/src/components/compositor/PanelVisibilityWrapper.tsx +1 -0
- package/templates/src/components/compositor/elements/SignUp.tsx +1 -1
- package/templates/src/components/compositor/elements/YouTubeWrapper.tsx +2 -0
- package/templates/src/components/compositor/nodes/CreativePane.tsx +262 -0
- package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +4 -6
- package/templates/src/components/compositor/nodes/GridLayout.tsx +4 -2
- package/templates/src/components/compositor/nodes/Markdown.tsx +18 -3
- package/templates/src/components/compositor/nodes/Pane.tsx +11 -5
- package/templates/src/components/compositor/nodes/RenderChildren.tsx +1 -1
- package/templates/src/components/compositor/nodes/tagElements/NodeAnchorComponent.tsx +5 -5
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag.tsx +90 -42
- package/templates/src/components/compositor/nodes/tagElements/NodeImg.tsx +2 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeText.tsx +27 -1
- package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +10 -8
- package/templates/src/components/compositor/tools/NodeOverlay.tsx +224 -0
- package/templates/src/components/compositor/tools/PaneOverlay.tsx +122 -0
- package/templates/src/components/edit/Header.tsx +68 -9
- package/templates/src/components/edit/PanelSwitch.tsx +42 -4
- package/templates/src/components/edit/SettingsPanel.tsx +2 -3
- package/templates/src/components/edit/ToolMode.tsx +1 -31
- package/templates/src/components/edit/pane/AddPanePanel_break.tsx +2 -2
- package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +1 -1
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +193 -659
- package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +15 -82
- package/templates/src/components/edit/pane/AiRestylePaneModal.tsx +95 -45
- package/templates/src/components/edit/pane/ConfigPanePanel.tsx +137 -49
- package/templates/src/components/edit/pane/RestylePaneModal.tsx +1 -1
- package/templates/src/components/edit/pane/steps/AiCreativeDesignStep.tsx +375 -0
- package/templates/src/components/edit/pane/steps/AiDesignStep.tsx +1 -23
- package/templates/src/components/edit/pane/steps/AiLibraryCopyStep.tsx +327 -0
- package/templates/src/components/edit/pane/steps/AiRefineDesignStep.tsx +267 -0
- package/templates/src/components/edit/pane/steps/AiStandardDesignStep.tsx +371 -0
- package/templates/src/components/edit/pane/steps/CopyInputStep.tsx +201 -76
- package/templates/src/components/edit/pane/steps/CreativeInjectStep.tsx +141 -0
- package/templates/src/components/edit/panels/CreativeImagePanel.tsx +435 -0
- package/templates/src/components/edit/panels/CreativeLinkPanel.tsx +110 -0
- package/templates/src/components/edit/panels/StyleCodeHookPanel.tsx +1 -1
- package/templates/src/components/edit/panels/StyleParentPanel.tsx +118 -126
- package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +3 -2
- package/templates/src/components/edit/panels/StyleParentPanel_deleteLayer.tsx +1 -0
- package/templates/src/components/edit/panels/StyleParentPanel_remove.tsx +3 -1
- package/templates/src/components/edit/panels/StyleParentPanel_update.tsx +3 -1
- package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +1 -1
- package/templates/src/components/edit/state/SaveModal.tsx +19 -787
- package/templates/src/components/edit/state/SaveToLibraryModal.tsx +2 -2
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_menu.tsx +1 -1
- package/templates/src/components/edit/widgets/BunnyWidget.tsx +5 -5
- package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +1 -1
- package/templates/src/components/edit/widgets/SignupWidget.tsx +1 -1
- package/templates/src/components/fields/ActionBuilderTimeSelector.tsx +1 -1
- package/templates/src/components/fields/ArtpackImage.tsx +11 -3
- package/templates/src/components/fields/BackgroundImage.tsx +8 -0
- package/templates/src/components/fields/BackgroundImageWrapper.tsx +15 -9
- package/templates/src/components/fields/ImageUpload.tsx +6 -0
- package/templates/src/components/form/ActionBuilderField.tsx +15 -5
- package/templates/src/components/form/ActionBuilderSlugSelector.tsx +1 -1
- package/templates/src/components/form/ColorPicker.tsx +1 -1
- package/templates/src/components/form/EnumSelect.tsx +1 -1
- package/templates/src/components/form/NumberInput.tsx +1 -1
- package/templates/src/components/form/StringArrayInput.tsx +1 -1
- package/templates/src/components/form/StringInput.tsx +1 -1
- package/templates/src/components/form/UnsavedChangesBar.tsx +1 -1
- package/templates/src/components/form/advanced/APIConfigSection.tsx +2 -2
- package/templates/src/components/form/advanced/AuthConfigSection.tsx +2 -2
- package/templates/src/components/profile/ProfileCreate.tsx +1 -1
- package/templates/src/components/profile/ProfileEdit.tsx +1 -1
- package/templates/src/components/storykeep/Dashboard_Advanced.tsx +2 -2
- package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +1 -1
- package/templates/src/components/storykeep/controls/content/ContentSummary.tsx +2 -2
- package/templates/src/components/storykeep/controls/content/KnownResourceTable.tsx +1 -1
- package/templates/src/components/storykeep/controls/content/ManageContent.tsx +6 -6
- package/templates/src/components/storykeep/controls/content/MenuForm.tsx +1 -1
- package/templates/src/components/storykeep/controls/content/PaneTable.tsx +358 -0
- package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +1 -1
- package/templates/src/constants/prompts.json +18 -10
- package/templates/src/constants.ts +3 -0
- package/templates/src/hooks/usePaneFragments.ts +60 -0
- package/templates/src/lib/session.ts +71 -16
- package/templates/src/pages/[...slug].astro +4 -46
- package/templates/src/pages/api/css.ts +149 -0
- package/templates/src/pages/maint.astro +1 -1
- package/templates/src/pages/storykeep/login.astro +2 -2
- package/templates/src/stores/nodes.ts +162 -49
- package/templates/src/stores/orphanAnalysis.ts +6 -30
- package/templates/src/stores/previews.ts +7 -0
- package/templates/src/stores/storykeep.ts +0 -8
- package/templates/src/types/compositorTypes.ts +53 -10
- package/templates/src/utils/compositor/aiGeneration.ts +93 -0
- package/templates/src/utils/compositor/allowInsert.ts +2 -0
- package/templates/src/utils/compositor/htmlAst.ts +704 -0
- package/templates/src/utils/compositor/nodesHelper.ts +281 -102
- package/templates/src/utils/compositor/savePipeline.ts +893 -0
- package/templates/src/utils/etl/index.ts +3 -0
- package/templates/src/utils/etl/transformer.ts +10 -0
- package/templates/src/utils/helpers.ts +101 -0
- package/utils/inject-files.ts +100 -62
- package/templates/icons/text.svg +0 -6
- package/templates/src/components/compositor/NodeWithGuid.tsx +0 -69
- package/templates/src/components/compositor/nodes/GridLayout_eraser.tsx +0 -33
- package/templates/src/components/compositor/nodes/Markdown_eraser.tsx +0 -56
- package/templates/src/components/compositor/nodes/Pane_DesignLibrary.tsx +0 -269
- package/templates/src/components/compositor/nodes/Pane_eraser.tsx +0 -186
- package/templates/src/components/compositor/nodes/Pane_layout.tsx +0 -79
- package/templates/src/components/compositor/nodes/tagElements/NodeA_eraser.tsx +0 -26
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_eraser.tsx +0 -61
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_insert.tsx +0 -120
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_settings.tsx +0 -62
- package/templates/src/components/compositor/nodes/tagElements/NodeButton_eraser.tsx +0 -26
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
2
|
+
import { Combobox } from '@ark-ui/react';
|
|
3
|
+
import { createListCollection } from '@ark-ui/react/collection';
|
|
4
|
+
import { Dialog } from '@ark-ui/react/dialog';
|
|
5
|
+
import { Portal } from '@ark-ui/react/portal';
|
|
6
|
+
import ExclamationTriangleIcon from '@heroicons/react/24/outline/ExclamationTriangleIcon';
|
|
7
|
+
import SwatchIcon from '@heroicons/react/24/outline/SwatchIcon';
|
|
8
|
+
import ArrowUpTrayIcon from '@heroicons/react/24/outline/ArrowUpTrayIcon';
|
|
9
|
+
import ChevronUpDownIcon from '@heroicons/react/24/outline/ChevronUpDownIcon';
|
|
10
|
+
import CheckIcon from '@heroicons/react/24/outline/CheckIcon';
|
|
11
|
+
import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon';
|
|
12
|
+
import { getCtx } from '@/stores/nodes';
|
|
13
|
+
import { hasArtpacksStore } from '@/stores/storykeep';
|
|
14
|
+
import ImageUpload, { type ImageParams } from '@/components/fields/ImageUpload';
|
|
15
|
+
import type { BasePanelProps, PaneNode } from '@/types/compositorTypes';
|
|
16
|
+
|
|
17
|
+
interface CreativeImagePanelProps extends BasePanelProps {
|
|
18
|
+
mode: 'img' | 'bg';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const CreativeImagePanel = ({
|
|
22
|
+
node,
|
|
23
|
+
childId,
|
|
24
|
+
mode,
|
|
25
|
+
}: CreativeImagePanelProps) => {
|
|
26
|
+
if (!node) return null;
|
|
27
|
+
const ctx = getCtx();
|
|
28
|
+
const paneNode = node as unknown as PaneNode;
|
|
29
|
+
const assetMeta = paneNode?.htmlAst?.editableElements?.[childId || ''];
|
|
30
|
+
const $artpacks = hasArtpacksStore.get();
|
|
31
|
+
|
|
32
|
+
const [activeTab, setActiveTab] = useState<'upload' | 'artpack'>('upload');
|
|
33
|
+
const [altDescription, setAltDescription] = useState('');
|
|
34
|
+
const [isExternal, setIsExternal] = useState(false);
|
|
35
|
+
|
|
36
|
+
const [isArtpackModalOpen, setIsArtpackModalOpen] = useState(false);
|
|
37
|
+
const [selectedCollection, setSelectedCollection] = useState<string>('t8k');
|
|
38
|
+
const [availableImages, setAvailableImages] = useState<string[]>([]);
|
|
39
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
40
|
+
const [query, setQuery] = useState('');
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (assetMeta) {
|
|
44
|
+
setAltDescription(assetMeta.alt || '');
|
|
45
|
+
const src = assetMeta.src || '';
|
|
46
|
+
setIsExternal(
|
|
47
|
+
src.startsWith('http') && !src.includes(window.location.origin)
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
if (src.includes('/artpacks/')) {
|
|
51
|
+
setActiveTab('artpack');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}, [assetMeta]);
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (selectedCollection && $artpacks && $artpacks[selectedCollection]) {
|
|
58
|
+
setIsLoading(true);
|
|
59
|
+
const images = $artpacks[selectedCollection];
|
|
60
|
+
setAvailableImages(images);
|
|
61
|
+
setTimeout(() => setIsLoading(false), 0);
|
|
62
|
+
} else {
|
|
63
|
+
setAvailableImages([]);
|
|
64
|
+
setIsLoading(false);
|
|
65
|
+
}
|
|
66
|
+
}, [selectedCollection, $artpacks]);
|
|
67
|
+
|
|
68
|
+
const collectionList = useMemo(() => {
|
|
69
|
+
const filteredCollections =
|
|
70
|
+
query === ''
|
|
71
|
+
? Object.keys($artpacks || {})
|
|
72
|
+
: Object.keys($artpacks || {}).filter((collection) =>
|
|
73
|
+
collection.toLowerCase().includes(query.toLowerCase())
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
return createListCollection({
|
|
77
|
+
items: filteredCollections,
|
|
78
|
+
itemToValue: (item) => item,
|
|
79
|
+
itemToString: (item) => item,
|
|
80
|
+
});
|
|
81
|
+
}, [$artpacks, query]);
|
|
82
|
+
|
|
83
|
+
if (!paneNode || !assetMeta) return null;
|
|
84
|
+
|
|
85
|
+
const handleUpdate = (params: Partial<ImageParams>) => {
|
|
86
|
+
const newSrc = params.src || '';
|
|
87
|
+
const isNowExternal =
|
|
88
|
+
newSrc.startsWith('http') && !newSrc.includes(window.location.origin);
|
|
89
|
+
setIsExternal(isNowExternal);
|
|
90
|
+
|
|
91
|
+
const el = document.querySelector(
|
|
92
|
+
`[data-ast-id="${childId}"]`
|
|
93
|
+
) as HTMLElement;
|
|
94
|
+
if (el) {
|
|
95
|
+
if (mode === 'img') {
|
|
96
|
+
const img = el as HTMLImageElement;
|
|
97
|
+
img.src = newSrc;
|
|
98
|
+
if (params.srcSet) img.srcset = params.srcSet;
|
|
99
|
+
} else if (mode === 'bg') {
|
|
100
|
+
el.style.backgroundImage = `url('${newSrc}')`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
(ctx as any).updateCreativeAsset(paneNode.id, childId, {
|
|
105
|
+
src: params.src,
|
|
106
|
+
srcSet: params.srcSet,
|
|
107
|
+
fileId: params.fileId,
|
|
108
|
+
base64Data: params.base64Data,
|
|
109
|
+
alt: params.altDescription || altDescription,
|
|
110
|
+
isCssBackground: mode === 'bg',
|
|
111
|
+
tagName: mode === 'img' ? 'img' : assetMeta.tagName,
|
|
112
|
+
collection: params.collection,
|
|
113
|
+
image: params.image,
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const handleRemove = () => {
|
|
118
|
+
setIsExternal(false);
|
|
119
|
+
|
|
120
|
+
const el = document.querySelector(
|
|
121
|
+
`[data-ast-id="${childId}"]`
|
|
122
|
+
) as HTMLElement;
|
|
123
|
+
if (el) {
|
|
124
|
+
if (mode === 'img') {
|
|
125
|
+
(el as HTMLImageElement).src = '';
|
|
126
|
+
(el as HTMLImageElement).srcset = '';
|
|
127
|
+
} else if (mode === 'bg') {
|
|
128
|
+
el.style.backgroundImage = 'none';
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
(ctx as any).updateCreativeAsset(paneNode.id, childId, {
|
|
133
|
+
src: '',
|
|
134
|
+
fileId: undefined,
|
|
135
|
+
base64Data: undefined,
|
|
136
|
+
srcSet: undefined,
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const handleAltBlur = (val: string) => {
|
|
141
|
+
if (val !== assetMeta.alt) {
|
|
142
|
+
const el = document.querySelector(
|
|
143
|
+
`[data-ast-id="${childId}"]`
|
|
144
|
+
) as HTMLImageElement;
|
|
145
|
+
if (el && mode === 'img') {
|
|
146
|
+
el.alt = val;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
(ctx as any).updateCreativeAsset(paneNode.id, childId, {
|
|
150
|
+
alt: val,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const buildImageSrcSet = (collection: string, image: string): string => {
|
|
156
|
+
return [
|
|
157
|
+
`/artpacks/${collection}/${image}_1920px.webp 1920w`,
|
|
158
|
+
`/artpacks/${collection}/${image}_1080px.webp 1080w`,
|
|
159
|
+
`/artpacks/${collection}/${image}_600px.webp 600w`,
|
|
160
|
+
].join(', ');
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const handleSelectArtpackImage = (collection: string, image: string) => {
|
|
164
|
+
const src = `/artpacks/${collection}/${image}_1920px.webp`;
|
|
165
|
+
const srcSet = buildImageSrcSet(collection, image);
|
|
166
|
+
const alt = `Artpack image from ${collection} collection`;
|
|
167
|
+
|
|
168
|
+
handleUpdate({
|
|
169
|
+
src,
|
|
170
|
+
srcSet,
|
|
171
|
+
fileId: undefined,
|
|
172
|
+
altDescription: alt,
|
|
173
|
+
collection: collection,
|
|
174
|
+
image: image,
|
|
175
|
+
});
|
|
176
|
+
setAltDescription(alt);
|
|
177
|
+
setIsArtpackModalOpen(false);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const handleCollectionSelect = (details: { value: string[] }) => {
|
|
181
|
+
const newCollection = details.value[0] || '';
|
|
182
|
+
if (newCollection) {
|
|
183
|
+
setIsLoading(true);
|
|
184
|
+
setSelectedCollection(newCollection);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const comboboxItemStyles = `
|
|
189
|
+
.collection-item[data-highlighted] {
|
|
190
|
+
background-color: #0891b2;
|
|
191
|
+
color: white;
|
|
192
|
+
}
|
|
193
|
+
.collection-item[data-highlighted] .collection-indicator {
|
|
194
|
+
color: white;
|
|
195
|
+
}
|
|
196
|
+
.collection-item[data-state="checked"] .collection-indicator {
|
|
197
|
+
display: flex;
|
|
198
|
+
}
|
|
199
|
+
.collection-item .collection-indicator {
|
|
200
|
+
display: none;
|
|
201
|
+
}
|
|
202
|
+
.collection-item[data-state="checked"] {
|
|
203
|
+
font-weight: bold;
|
|
204
|
+
}
|
|
205
|
+
`;
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<div className="space-y-6">
|
|
209
|
+
<style>{comboboxItemStyles}</style>
|
|
210
|
+
<div className="space-y-2">
|
|
211
|
+
<h3 className="text-lg font-bold text-mydarkgrey">
|
|
212
|
+
{mode === 'bg' ? 'Background Image' : 'Image Asset'}
|
|
213
|
+
</h3>
|
|
214
|
+
<p className="text-xs text-gray-500">
|
|
215
|
+
{mode === 'bg'
|
|
216
|
+
? 'Updates the CSS background rule for this element.'
|
|
217
|
+
: 'Updates the source attribute of this image tag.'}
|
|
218
|
+
</p>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
<div className="flex gap-2 border-b border-gray-200">
|
|
222
|
+
<button
|
|
223
|
+
onClick={() => setActiveTab('upload')}
|
|
224
|
+
className={`flex items-center gap-2 border-b-2 px-3 py-2 text-sm ${
|
|
225
|
+
activeTab === 'upload'
|
|
226
|
+
? 'border-cyan-600 text-cyan-700'
|
|
227
|
+
: 'border-transparent text-gray-500 hover:text-gray-700'
|
|
228
|
+
}`}
|
|
229
|
+
>
|
|
230
|
+
<ArrowUpTrayIcon className="h-4 w-4" />
|
|
231
|
+
Upload / File
|
|
232
|
+
</button>
|
|
233
|
+
<button
|
|
234
|
+
onClick={() => setActiveTab('artpack')}
|
|
235
|
+
className={`flex items-center gap-2 border-b-2 px-3 py-2 text-sm ${
|
|
236
|
+
activeTab === 'artpack'
|
|
237
|
+
? 'border-cyan-600 text-cyan-700'
|
|
238
|
+
: 'border-transparent text-gray-500 hover:text-gray-700'
|
|
239
|
+
}`}
|
|
240
|
+
>
|
|
241
|
+
<SwatchIcon className="h-4 w-4" />
|
|
242
|
+
Artpack
|
|
243
|
+
</button>
|
|
244
|
+
</div>
|
|
245
|
+
|
|
246
|
+
<div className="space-y-4">
|
|
247
|
+
{activeTab === 'upload' ? (
|
|
248
|
+
<ImageUpload
|
|
249
|
+
currentFileId={assetMeta.fileId}
|
|
250
|
+
nodeId={paneNode.id}
|
|
251
|
+
onUpdate={(params) => handleUpdate(params)}
|
|
252
|
+
onRemove={handleRemove}
|
|
253
|
+
/>
|
|
254
|
+
) : (
|
|
255
|
+
<div className="space-y-4">
|
|
256
|
+
<div className="rounded-md border border-gray-200 p-4 text-center">
|
|
257
|
+
<p className="mb-3 text-sm text-gray-600">
|
|
258
|
+
Choose a high-quality, optimized image from the library.
|
|
259
|
+
</p>
|
|
260
|
+
<button
|
|
261
|
+
onClick={() => setIsArtpackModalOpen(true)}
|
|
262
|
+
className="inline-flex items-center rounded-md bg-cyan-600 px-4 py-2 text-sm font-bold text-white hover:bg-cyan-700"
|
|
263
|
+
>
|
|
264
|
+
<SwatchIcon className="mr-2 h-4 w-4" />
|
|
265
|
+
Browse Artpacks
|
|
266
|
+
</button>
|
|
267
|
+
</div>
|
|
268
|
+
{assetMeta.src && assetMeta.src.includes('/artpacks/') && (
|
|
269
|
+
<div className="text-xs text-gray-500">
|
|
270
|
+
Current: {assetMeta.src.split('/').pop()}
|
|
271
|
+
</div>
|
|
272
|
+
)}
|
|
273
|
+
</div>
|
|
274
|
+
)}
|
|
275
|
+
|
|
276
|
+
{isExternal && (
|
|
277
|
+
<div className="flex items-start gap-2 rounded-md bg-yellow-50 p-3 text-xs text-yellow-800">
|
|
278
|
+
<ExclamationTriangleIcon className="mt-0.5 h-4 w-4 flex-shrink-0 text-yellow-600" />
|
|
279
|
+
<p>
|
|
280
|
+
You are using an external image URL. This asset will not be
|
|
281
|
+
optimized for performance or responsive sizing.
|
|
282
|
+
</p>
|
|
283
|
+
</div>
|
|
284
|
+
)}
|
|
285
|
+
|
|
286
|
+
{mode === 'img' && (
|
|
287
|
+
<div>
|
|
288
|
+
<label className="mb-1 block text-sm font-bold text-mydarkgrey">
|
|
289
|
+
Alt Description
|
|
290
|
+
</label>
|
|
291
|
+
<input
|
|
292
|
+
type="text"
|
|
293
|
+
value={altDescription}
|
|
294
|
+
onChange={(e) => setAltDescription(e.target.value)}
|
|
295
|
+
onBlur={(e) => handleAltBlur(e.target.value)}
|
|
296
|
+
onKeyDown={(e) => {
|
|
297
|
+
if (e.key === 'Enter') e.currentTarget.blur();
|
|
298
|
+
}}
|
|
299
|
+
className="w-full rounded-md border-gray-300 py-2 pl-3 text-sm shadow-sm focus:border-cyan-700 focus:ring-cyan-700"
|
|
300
|
+
placeholder="Describe the image..."
|
|
301
|
+
/>
|
|
302
|
+
</div>
|
|
303
|
+
)}
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
<Dialog.Root
|
|
307
|
+
open={isArtpackModalOpen}
|
|
308
|
+
onOpenChange={(details) => setIsArtpackModalOpen(details.open)}
|
|
309
|
+
modal={true}
|
|
310
|
+
>
|
|
311
|
+
<Portal>
|
|
312
|
+
<Dialog.Backdrop className="fixed inset-0 bg-black/30 backdrop-blur-sm" />
|
|
313
|
+
<Dialog.Positioner
|
|
314
|
+
className="fixed inset-0 flex items-center justify-center p-4"
|
|
315
|
+
style={{ zIndex: 10010 }}
|
|
316
|
+
>
|
|
317
|
+
<Dialog.Content
|
|
318
|
+
className="w-full max-w-4xl overflow-y-auto rounded-lg bg-white p-6 shadow-xl"
|
|
319
|
+
style={{ maxHeight: '80vh' }}
|
|
320
|
+
>
|
|
321
|
+
<div className="mb-4 flex items-center justify-between">
|
|
322
|
+
<Dialog.Title className="text-lg font-bold">
|
|
323
|
+
Select Artpack Image
|
|
324
|
+
</Dialog.Title>
|
|
325
|
+
<Dialog.CloseTrigger className="rounded-full p-1 hover:bg-gray-100">
|
|
326
|
+
<XMarkIcon className="h-5 w-5 text-gray-500" />
|
|
327
|
+
</Dialog.CloseTrigger>
|
|
328
|
+
</div>
|
|
329
|
+
|
|
330
|
+
{Object.keys($artpacks || {}).length === 0 ? (
|
|
331
|
+
<div className="py-8 text-center text-gray-500">
|
|
332
|
+
No artpack collections available.
|
|
333
|
+
</div>
|
|
334
|
+
) : (
|
|
335
|
+
<div className="space-y-6">
|
|
336
|
+
<div>
|
|
337
|
+
<label className="mb-2 block text-sm font-bold text-mydarkgrey">
|
|
338
|
+
Select Collection
|
|
339
|
+
</label>
|
|
340
|
+
<Combobox.Root
|
|
341
|
+
collection={collectionList}
|
|
342
|
+
value={selectedCollection ? [selectedCollection] : []}
|
|
343
|
+
onValueChange={handleCollectionSelect}
|
|
344
|
+
onInputValueChange={(details) =>
|
|
345
|
+
setQuery(details.inputValue)
|
|
346
|
+
}
|
|
347
|
+
loopFocus={true}
|
|
348
|
+
openOnKeyPress={true}
|
|
349
|
+
composite={true}
|
|
350
|
+
>
|
|
351
|
+
<div className="relative">
|
|
352
|
+
<Combobox.Control className="relative w-full cursor-default overflow-hidden rounded-lg border border-gray-300 bg-white text-left shadow-sm focus-within:border-myblue focus-within:ring-1 focus-within:ring-myblue">
|
|
353
|
+
<Combobox.Input
|
|
354
|
+
className="w-full border-none py-2 pl-3 pr-10 text-sm leading-5 text-mydarkgrey focus:ring-0"
|
|
355
|
+
placeholder="Select a collection..."
|
|
356
|
+
autoComplete="off"
|
|
357
|
+
/>
|
|
358
|
+
<Combobox.Trigger className="absolute inset-y-0 right-0 flex items-center pr-2">
|
|
359
|
+
<ChevronUpDownIcon
|
|
360
|
+
className="h-5 w-5 text-mydarkgrey"
|
|
361
|
+
aria-hidden="true"
|
|
362
|
+
/>
|
|
363
|
+
</Combobox.Trigger>
|
|
364
|
+
</Combobox.Control>
|
|
365
|
+
<Combobox.Content className="absolute z-20 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none md:text-sm">
|
|
366
|
+
{collectionList.items.map((item) => (
|
|
367
|
+
<Combobox.Item
|
|
368
|
+
key={item}
|
|
369
|
+
item={item}
|
|
370
|
+
className="collection-item relative cursor-default select-none py-2 pl-10 pr-4 text-mydarkgrey"
|
|
371
|
+
>
|
|
372
|
+
<span className="block truncate">{item}</span>
|
|
373
|
+
<span className="collection-indicator absolute inset-y-0 left-0 flex items-center pl-3 text-cyan-600">
|
|
374
|
+
<CheckIcon
|
|
375
|
+
className="h-5 w-5"
|
|
376
|
+
aria-hidden="true"
|
|
377
|
+
/>
|
|
378
|
+
</span>
|
|
379
|
+
</Combobox.Item>
|
|
380
|
+
))}
|
|
381
|
+
</Combobox.Content>
|
|
382
|
+
</div>
|
|
383
|
+
</Combobox.Root>
|
|
384
|
+
</div>
|
|
385
|
+
|
|
386
|
+
{!isLoading &&
|
|
387
|
+
selectedCollection &&
|
|
388
|
+
availableImages.length > 0 ? (
|
|
389
|
+
<div>
|
|
390
|
+
<div className="grid grid-cols-2 gap-4 p-2 md:grid-cols-3 xl:grid-cols-4">
|
|
391
|
+
{availableImages.map((image) => (
|
|
392
|
+
<div
|
|
393
|
+
key={image}
|
|
394
|
+
className="group relative cursor-pointer overflow-hidden rounded border border-gray-200 transition-all hover:border-cyan-600 hover:shadow-md"
|
|
395
|
+
onClick={() =>
|
|
396
|
+
handleSelectArtpackImage(
|
|
397
|
+
selectedCollection,
|
|
398
|
+
image
|
|
399
|
+
)
|
|
400
|
+
}
|
|
401
|
+
>
|
|
402
|
+
<img
|
|
403
|
+
src={`/artpacks/${selectedCollection}/${image}_600px.webp`}
|
|
404
|
+
alt={image}
|
|
405
|
+
className="aspect-video w-full object-cover transition-transform duration-300 group-hover:scale-105"
|
|
406
|
+
loading="lazy"
|
|
407
|
+
/>
|
|
408
|
+
<div className="absolute inset-0 flex items-end bg-gradient-to-t from-black/60 to-transparent opacity-0 transition-opacity group-hover:opacity-100">
|
|
409
|
+
<span className="w-full truncate p-2 text-center text-xs text-white">
|
|
410
|
+
{image}
|
|
411
|
+
</span>
|
|
412
|
+
</div>
|
|
413
|
+
</div>
|
|
414
|
+
))}
|
|
415
|
+
</div>
|
|
416
|
+
</div>
|
|
417
|
+
) : isLoading ? (
|
|
418
|
+
<div className="py-12 text-center">
|
|
419
|
+
<div className="mx-auto h-8 w-8 animate-spin rounded-full border-b-2 border-cyan-600"></div>
|
|
420
|
+
<p className="mt-2 text-sm text-gray-500">
|
|
421
|
+
Loading collection...
|
|
422
|
+
</p>
|
|
423
|
+
</div>
|
|
424
|
+
) : null}
|
|
425
|
+
</div>
|
|
426
|
+
)}
|
|
427
|
+
</Dialog.Content>
|
|
428
|
+
</Dialog.Positioner>
|
|
429
|
+
</Portal>
|
|
430
|
+
</Dialog.Root>
|
|
431
|
+
</div>
|
|
432
|
+
);
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
export default CreativeImagePanel;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useStore } from '@nanostores/react';
|
|
3
|
+
import InformationCircleIcon from '@heroicons/react/24/outline/InformationCircleIcon';
|
|
4
|
+
import { getCtx } from '@/stores/nodes';
|
|
5
|
+
import { fullContentMapStore } from '@/stores/storykeep';
|
|
6
|
+
import ActionBuilderField from '@/components/form/ActionBuilderField';
|
|
7
|
+
import { lispLexer } from '@/utils/actions/lispLexer';
|
|
8
|
+
import { preParseAction } from '@/utils/actions/preParse_Action';
|
|
9
|
+
import type { BasePanelProps, PaneNode } from '@/types/compositorTypes';
|
|
10
|
+
|
|
11
|
+
const CreativeLinkPanel = ({ node, childId }: BasePanelProps) => {
|
|
12
|
+
if (!node) return null;
|
|
13
|
+
const pane = node as unknown as PaneNode;
|
|
14
|
+
const assetMeta = pane?.htmlAst?.editableElements?.[childId || ''];
|
|
15
|
+
const contentMap = useStore(fullContentMapStore);
|
|
16
|
+
const ctx = getCtx();
|
|
17
|
+
|
|
18
|
+
const [actionLisp, setActionLisp] = useState('');
|
|
19
|
+
|
|
20
|
+
const isAnchor = assetMeta?.tagName === 'a';
|
|
21
|
+
const restriction = isAnchor ? 'navigation' : 'action';
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (assetMeta) {
|
|
25
|
+
let initialValue = assetMeta.buttonPayload?.callbackPayload || '';
|
|
26
|
+
|
|
27
|
+
if (!initialValue && isAnchor && assetMeta.href) {
|
|
28
|
+
initialValue = `(goto url ${assetMeta.href})`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
setActionLisp(initialValue);
|
|
32
|
+
}
|
|
33
|
+
}, [assetMeta, isAnchor]);
|
|
34
|
+
|
|
35
|
+
if (!pane || !assetMeta) return null;
|
|
36
|
+
|
|
37
|
+
const handleUpdate = (newValue: string) => {
|
|
38
|
+
setActionLisp(newValue);
|
|
39
|
+
|
|
40
|
+
const el = document.querySelector(
|
|
41
|
+
`[data-ast-id="${childId}"]`
|
|
42
|
+
) as HTMLElement;
|
|
43
|
+
if (el) {
|
|
44
|
+
if (isAnchor) {
|
|
45
|
+
try {
|
|
46
|
+
const config = (window as any).TRACTSTACK_CONFIG || {};
|
|
47
|
+
const lexed = lispLexer(newValue);
|
|
48
|
+
const resolvedHref = preParseAction(
|
|
49
|
+
lexed,
|
|
50
|
+
pane.slug,
|
|
51
|
+
!!pane.isContextPane,
|
|
52
|
+
config
|
|
53
|
+
);
|
|
54
|
+
if (resolvedHref) {
|
|
55
|
+
(el as HTMLAnchorElement).href = resolvedHref;
|
|
56
|
+
}
|
|
57
|
+
} catch (e) {
|
|
58
|
+
console.warn('Live DOM href resolution failed:', e);
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
el.setAttribute('data-callback', newValue);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
(ctx as any).updateCreativeAsset(node.id, childId, {
|
|
66
|
+
tagName: assetMeta.tagName,
|
|
67
|
+
buttonPayload: {
|
|
68
|
+
...assetMeta.buttonPayload,
|
|
69
|
+
callbackPayload: newValue,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<div className="space-y-6">
|
|
76
|
+
<div className="space-y-2">
|
|
77
|
+
<h3 className="text-lg font-bold text-mydarkgrey">
|
|
78
|
+
{isAnchor ? 'Link Destination' : 'Button Action'}
|
|
79
|
+
</h3>
|
|
80
|
+
<p className="text-xs text-gray-500">
|
|
81
|
+
{isAnchor
|
|
82
|
+
? 'Configure where this link takes the user.'
|
|
83
|
+
: 'Configure what happens when this button is clicked.'}
|
|
84
|
+
</p>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<div className="space-y-4">
|
|
88
|
+
<ActionBuilderField
|
|
89
|
+
value={actionLisp}
|
|
90
|
+
onChange={handleUpdate}
|
|
91
|
+
contentMap={contentMap}
|
|
92
|
+
label={isAnchor ? 'Destination' : 'Interaction'}
|
|
93
|
+
restriction={restriction}
|
|
94
|
+
/>
|
|
95
|
+
|
|
96
|
+
<div className="flex items-start gap-2 rounded-md bg-blue-50 p-3 text-xs text-blue-800">
|
|
97
|
+
<InformationCircleIcon className="mt-0.5 h-4 w-4 flex-shrink-0 text-blue-600" />
|
|
98
|
+
<p>
|
|
99
|
+
This element is semantically locked as a{' '}
|
|
100
|
+
<strong>{isAnchor ? 'Link (<a>)' : 'Button (<button>)'}</strong>. To
|
|
101
|
+
change its type (e.g. turn a link into a button), use the "Refine
|
|
102
|
+
Design" tool.
|
|
103
|
+
</p>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export default CreativeLinkPanel;
|
|
@@ -345,7 +345,7 @@ const StyleCodeHookPanel = ({
|
|
|
345
345
|
|
|
346
346
|
<Portal>
|
|
347
347
|
<Combobox.Positioner style={{ zIndex: 1002 }}>
|
|
348
|
-
<Combobox.Content className="
|
|
348
|
+
<Combobox.Content className="z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none md:text-sm">
|
|
349
349
|
{collection.items.length === 0 ? (
|
|
350
350
|
<div className="relative cursor-default select-none px-4 py-2 text-mydarkgrey">
|
|
351
351
|
Nothing found.
|