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
|
@@ -9,7 +9,7 @@ import { NodesContext, getCtx } from '@/stores/nodes';
|
|
|
9
9
|
import { cloneDeep } from '@/utils/helpers';
|
|
10
10
|
import { hasAssemblyAIStore } from '@/stores/storykeep';
|
|
11
11
|
import prompts from '@/constants/prompts.json';
|
|
12
|
-
import type {
|
|
12
|
+
import type { DesignLibraryEntry } from '@/types/tractstack';
|
|
13
13
|
import { PaneAddMode, type TemplatePane } from '@/types/compositorTypes';
|
|
14
14
|
import { useStore } from '@nanostores/react';
|
|
15
15
|
import { CopyInputStep } from './steps/CopyInputStep';
|
|
@@ -18,7 +18,6 @@ import { AiDesignStep, type AiDesignConfig } from './steps/AiDesignStep';
|
|
|
18
18
|
import { parseAiPane, parseAiCopyHtml } from '@/utils/compositor/aiPaneParser';
|
|
19
19
|
import {
|
|
20
20
|
convertStorageToLiveTemplate,
|
|
21
|
-
mergeCopyIntoTemplate,
|
|
22
21
|
convertTemplateToAIShell,
|
|
23
22
|
} from '@/utils/compositor/designLibraryHelper';
|
|
24
23
|
import { DirectInjectStep } from './steps/DirectInjectStep';
|
|
@@ -120,7 +119,6 @@ interface AddPaneNewPanelProps {
|
|
|
120
119
|
ctx?: NodesContext;
|
|
121
120
|
isStoryFragment?: boolean;
|
|
122
121
|
isContextPane?: boolean;
|
|
123
|
-
config?: BrandConfig;
|
|
124
122
|
isSandboxMode?: boolean;
|
|
125
123
|
}
|
|
126
124
|
|
|
@@ -131,7 +129,6 @@ const AddPaneNewPanel = ({
|
|
|
131
129
|
ctx: providedCtx,
|
|
132
130
|
isStoryFragment = false,
|
|
133
131
|
isContextPane = false,
|
|
134
|
-
config,
|
|
135
132
|
isSandboxMode = false,
|
|
136
133
|
}: AddPaneNewPanelProps) => {
|
|
137
134
|
const ctx = providedCtx || getCtx();
|
|
@@ -143,19 +140,21 @@ const AddPaneNewPanel = ({
|
|
|
143
140
|
const [layoutChoice, setLayoutChoice] = useState<LayoutChoice>('standard');
|
|
144
141
|
const [error, setError] = useState<string | null>(null);
|
|
145
142
|
|
|
143
|
+
// Standard / Single Column State
|
|
146
144
|
const [copyMode, setCopyMode] = useState<CopyMode>('prompt');
|
|
147
145
|
const [promptValue, setPromptValue] = useState('');
|
|
148
146
|
const [copyValue, setCopyValue] = useState('');
|
|
149
147
|
|
|
148
|
+
// Grid / 2-Column State (Strictly Prompt-Only)
|
|
150
149
|
const [overallPrompt, setOverallPrompt] = useState(
|
|
151
150
|
prompts.aiPaneCopyPrompt_2cols.presets.heroDefault.default
|
|
152
151
|
);
|
|
153
|
-
const [
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const [
|
|
157
|
-
|
|
158
|
-
|
|
152
|
+
const [promptValueCol1, setPromptValueCol1] = useState(
|
|
153
|
+
prompts.aiPaneCopyPrompt_2cols.presets.heroDefault.left.prompt
|
|
154
|
+
);
|
|
155
|
+
const [promptValueCol2, setPromptValueCol2] = useState(
|
|
156
|
+
prompts.aiPaneCopyPrompt_2cols.presets.heroDefault.right.prompt
|
|
157
|
+
);
|
|
159
158
|
|
|
160
159
|
const [selectedLibraryEntry, setSelectedLibraryEntry] =
|
|
161
160
|
useState<DesignLibraryEntry | null>(null);
|
|
@@ -228,11 +227,6 @@ const AddPaneNewPanel = ({
|
|
|
228
227
|
handleApplyTemplate(blankTemplate);
|
|
229
228
|
};
|
|
230
229
|
|
|
231
|
-
const handleDesignLibrarySelect = (entry: DesignLibraryEntry) => {
|
|
232
|
-
setSelectedLibraryEntry(entry);
|
|
233
|
-
setStep('copyInput');
|
|
234
|
-
};
|
|
235
|
-
|
|
236
230
|
const handleAiDesignContinue = () => {
|
|
237
231
|
setStep('copyInput');
|
|
238
232
|
};
|
|
@@ -276,6 +270,18 @@ const AddPaneNewPanel = ({
|
|
|
276
270
|
}
|
|
277
271
|
};
|
|
278
272
|
|
|
273
|
+
const handleDesignLibrarySelect = (entry: DesignLibraryEntry) => {
|
|
274
|
+
setSelectedLibraryEntry(entry);
|
|
275
|
+
|
|
276
|
+
if (entry.template.gridLayout) {
|
|
277
|
+
setLayoutChoice('grid');
|
|
278
|
+
} else {
|
|
279
|
+
setLayoutChoice('standard');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
setStep('copyInput');
|
|
283
|
+
};
|
|
284
|
+
|
|
279
285
|
const handleFinalGenerate = useCallback(async () => {
|
|
280
286
|
setError(null);
|
|
281
287
|
setStep('loading');
|
|
@@ -286,59 +292,99 @@ const AddPaneNewPanel = ({
|
|
|
286
292
|
throw new Error('No design library item was selected.');
|
|
287
293
|
}
|
|
288
294
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
liveTemplate.markdown.markdownBody = copyValue;
|
|
295
|
-
}
|
|
296
|
-
handleApplyTemplate(liveTemplate);
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
295
|
+
const liveTemplate = convertStorageToLiveTemplate(
|
|
296
|
+
selectedLibraryEntry.template
|
|
297
|
+
);
|
|
298
|
+
const shellResult = convertTemplateToAIShell(liveTemplate);
|
|
299
|
+
const layout = 'Text Only';
|
|
299
300
|
|
|
300
|
-
if (
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
301
|
+
if (layoutChoice === 'grid' && liveTemplate.gridLayout) {
|
|
302
|
+
const copyPromptDetails = prompts.aiPaneCopyPrompt_2cols;
|
|
303
|
+
const preset = copyPromptDetails.presets.heroDefault;
|
|
304
|
+
const copyResults: string[] = [];
|
|
305
|
+
|
|
306
|
+
// Only prompt mode supported for grid now
|
|
307
|
+
const promptsToRun: {
|
|
308
|
+
prompt: string;
|
|
309
|
+
presetKey: ColumnPresetKey;
|
|
310
|
+
}[] = [
|
|
311
|
+
{
|
|
312
|
+
prompt: promptValueCol1,
|
|
313
|
+
presetKey: 'left',
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
prompt: promptValueCol2,
|
|
317
|
+
presetKey: 'right',
|
|
318
|
+
},
|
|
319
|
+
];
|
|
320
|
+
|
|
321
|
+
for (const item of promptsToRun) {
|
|
322
|
+
const columnPreset = preset[item.presetKey];
|
|
323
|
+
const formattedCopyPrompt = copyPromptDetails.user_template
|
|
324
|
+
.replace('{{SHELL_JSON}}', shellResult)
|
|
325
|
+
.replace('{{COPY_INPUT}}', overallPrompt)
|
|
326
|
+
.replace('{{COLUMN_PROMPT}}', item.prompt)
|
|
327
|
+
.replace(
|
|
328
|
+
'{{DESIGN_INPUT}}',
|
|
329
|
+
"N/A - Use the provided Shell JSON's design."
|
|
330
|
+
)
|
|
331
|
+
.replace('{{LAYOUT_TYPE}}', layout)
|
|
332
|
+
.replace('{{COLUMN_EXAMPLE}}', columnPreset.example);
|
|
333
|
+
|
|
334
|
+
const copyResult = await callAskLemurAPI(
|
|
335
|
+
formattedCopyPrompt,
|
|
336
|
+
copyPromptDetails.system || '',
|
|
337
|
+
false,
|
|
338
|
+
isSandboxMode
|
|
307
339
|
);
|
|
340
|
+
copyResults.push(copyResult);
|
|
308
341
|
}
|
|
309
342
|
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
343
|
+
const finalPane = parseAiPane(shellResult, copyResults, layout);
|
|
344
|
+
handleApplyTemplate(finalPane);
|
|
345
|
+
} else if (layoutChoice === 'standard' && liveTemplate.markdown) {
|
|
346
|
+
if (copyMode === 'raw') {
|
|
347
|
+
liveTemplate.markdown.markdownBody = copyValue;
|
|
348
|
+
handleApplyTemplate(liveTemplate);
|
|
349
|
+
return;
|
|
315
350
|
}
|
|
316
351
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
"N/A - Use the provided Shell JSON's design."
|
|
324
|
-
)
|
|
325
|
-
.replace('{{LAYOUT_TYPE}}', layout)
|
|
326
|
-
.replace('{{SHELL_JSON}}', shellJson);
|
|
352
|
+
if (copyMode === 'prompt') {
|
|
353
|
+
if (!shellResult || shellResult === '{}') {
|
|
354
|
+
throw new Error(
|
|
355
|
+
'Could not generate a valid AI shell from this design.'
|
|
356
|
+
);
|
|
357
|
+
}
|
|
327
358
|
|
|
328
|
-
|
|
329
|
-
formattedCopyPrompt
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
359
|
+
const copyPromptDetails = prompts.aiPaneCopyPrompt;
|
|
360
|
+
const formattedCopyPrompt = copyPromptDetails.user_template
|
|
361
|
+
.replace('{{COPY_INPUT}}', promptValue)
|
|
362
|
+
.replace(
|
|
363
|
+
'{{DESIGN_INPUT}}',
|
|
364
|
+
"N/A - Use the provided Shell JSON's design."
|
|
365
|
+
)
|
|
366
|
+
.replace('{{LAYOUT_TYPE}}', layout)
|
|
367
|
+
.replace('{{SHELL_JSON}}', shellResult);
|
|
334
368
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
369
|
+
const copyResult = await callAskLemurAPI(
|
|
370
|
+
formattedCopyPrompt,
|
|
371
|
+
copyPromptDetails.system || '',
|
|
372
|
+
false,
|
|
373
|
+
isSandboxMode
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
const newNodes = parseAiCopyHtml(
|
|
377
|
+
copyResult,
|
|
378
|
+
liveTemplate.markdown.id
|
|
379
|
+
);
|
|
380
|
+
const finalPane = cloneDeep(liveTemplate);
|
|
381
|
+
finalPane.markdown!.nodes = newNodes;
|
|
382
|
+
handleApplyTemplate(finalPane);
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
throw new Error(
|
|
386
|
+
'Template and layout mismatch. Please go back and try again.'
|
|
338
387
|
);
|
|
339
|
-
const finalPane = cloneDeep(liveTemplate);
|
|
340
|
-
finalPane.markdown!.nodes = newNodes;
|
|
341
|
-
handleApplyTemplate(finalPane);
|
|
342
388
|
}
|
|
343
389
|
} else if (initialChoice === 'ai') {
|
|
344
390
|
let designInput = `Generate a design using a **${aiDesignConfig.harmony.toLowerCase()}** color scheme with a **${aiDesignConfig.theme.toLowerCase()}** theme.`;
|
|
@@ -398,32 +444,22 @@ const AddPaneNewPanel = ({
|
|
|
398
444
|
const preset = copyPromptDetails.presets.heroDefault;
|
|
399
445
|
const copyResults: string[] = [];
|
|
400
446
|
|
|
447
|
+
// Grid is strictly prompt-based
|
|
401
448
|
const promptsToRun: {
|
|
402
449
|
prompt: string;
|
|
403
|
-
copy: string;
|
|
404
|
-
mode: CopyMode;
|
|
405
450
|
presetKey: ColumnPresetKey;
|
|
406
451
|
}[] = [
|
|
407
452
|
{
|
|
408
453
|
prompt: promptValueCol1,
|
|
409
|
-
copy: copyValueCol1,
|
|
410
|
-
mode: copyModeCol1,
|
|
411
454
|
presetKey: 'left',
|
|
412
455
|
},
|
|
413
456
|
{
|
|
414
457
|
prompt: promptValueCol2,
|
|
415
|
-
copy: copyValueCol2,
|
|
416
|
-
mode: copyModeCol2,
|
|
417
458
|
presetKey: 'right',
|
|
418
459
|
},
|
|
419
460
|
];
|
|
420
461
|
|
|
421
462
|
for (const item of promptsToRun) {
|
|
422
|
-
if (item.mode === 'raw') {
|
|
423
|
-
copyResults.push(item.copy);
|
|
424
|
-
continue;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
463
|
const columnPreset = preset[item.presetKey];
|
|
428
464
|
const formattedCopyPrompt = copyPromptDetails.user_template
|
|
429
465
|
.replace('{{SHELL_JSON}}', shellResult)
|
|
@@ -456,12 +492,8 @@ const AddPaneNewPanel = ({
|
|
|
456
492
|
promptValue,
|
|
457
493
|
copyValue,
|
|
458
494
|
overallPrompt,
|
|
459
|
-
copyModeCol1,
|
|
460
495
|
promptValueCol1,
|
|
461
|
-
copyValueCol1,
|
|
462
|
-
copyModeCol2,
|
|
463
496
|
promptValueCol2,
|
|
464
|
-
copyValueCol2,
|
|
465
497
|
isSandboxMode,
|
|
466
498
|
initialChoice,
|
|
467
499
|
layoutChoice,
|
|
@@ -552,58 +584,47 @@ const AddPaneNewPanel = ({
|
|
|
552
584
|
const renderContentStep = () => {
|
|
553
585
|
if (layoutChoice === 'grid') {
|
|
554
586
|
const isGenerateDisabled =
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
(copyModeCol2 === 'raw' && !copyValueCol2.trim()) ||
|
|
559
|
-
!overallPrompt.trim();
|
|
587
|
+
!overallPrompt.trim() ||
|
|
588
|
+
!promptValueCol1.trim() ||
|
|
589
|
+
!promptValueCol2.trim();
|
|
560
590
|
|
|
561
591
|
return (
|
|
562
592
|
<div className="space-y-4 p-4">
|
|
563
593
|
<div>
|
|
564
|
-
<
|
|
594
|
+
<label className="mb-2 block text-sm font-bold text-gray-700">
|
|
565
595
|
Overall Component Brief
|
|
566
|
-
</
|
|
596
|
+
</label>
|
|
567
597
|
<textarea
|
|
568
598
|
value={overallPrompt}
|
|
569
599
|
onChange={(e) => setOverallPrompt(e.target.value)}
|
|
570
|
-
placeholder="e.g., A compelling hero section for a website about Tract Stack..."
|
|
571
600
|
rows={3}
|
|
572
601
|
className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
|
|
573
602
|
/>
|
|
603
|
+
<p className="mt-1 text-xs text-gray-500">
|
|
604
|
+
This context is applied to both columns.
|
|
605
|
+
</p>
|
|
574
606
|
</div>
|
|
575
607
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
|
576
608
|
<div>
|
|
577
|
-
<
|
|
578
|
-
Left Column
|
|
579
|
-
</
|
|
580
|
-
<
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
copyValue={copyValueCol1}
|
|
586
|
-
onCopyValueChange={setCopyValueCol1}
|
|
587
|
-
defaultPrompt={
|
|
588
|
-
prompts.aiPaneCopyPrompt_2cols.presets.heroDefault.left.prompt
|
|
589
|
-
}
|
|
609
|
+
<label className="mb-2 block text-sm font-bold text-gray-700">
|
|
610
|
+
Left Column Prompt
|
|
611
|
+
</label>
|
|
612
|
+
<textarea
|
|
613
|
+
value={promptValueCol1}
|
|
614
|
+
onChange={(e) => setPromptValueCol1(e.target.value)}
|
|
615
|
+
rows={4}
|
|
616
|
+
className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
|
|
590
617
|
/>
|
|
591
618
|
</div>
|
|
592
619
|
<div>
|
|
593
|
-
<
|
|
594
|
-
Right Column
|
|
595
|
-
</
|
|
596
|
-
<
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
copyValue={copyValueCol2}
|
|
602
|
-
onCopyValueChange={setCopyValueCol2}
|
|
603
|
-
defaultPrompt={
|
|
604
|
-
prompts.aiPaneCopyPrompt_2cols.presets.heroDefault.right
|
|
605
|
-
.prompt
|
|
606
|
-
}
|
|
620
|
+
<label className="mb-2 block text-sm font-bold text-gray-700">
|
|
621
|
+
Right Column Prompt
|
|
622
|
+
</label>
|
|
623
|
+
<textarea
|
|
624
|
+
value={promptValueCol2}
|
|
625
|
+
onChange={(e) => setPromptValueCol2(e.target.value)}
|
|
626
|
+
rows={4}
|
|
627
|
+
className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
|
|
607
628
|
/>
|
|
608
629
|
</div>
|
|
609
630
|
</div>
|
|
@@ -672,17 +693,13 @@ const AddPaneNewPanel = ({
|
|
|
672
693
|
← Back to Choice
|
|
673
694
|
</button>
|
|
674
695
|
</div>
|
|
675
|
-
<DesignLibraryStep
|
|
676
|
-
config={config!}
|
|
677
|
-
onSelect={handleDesignLibrarySelect}
|
|
678
|
-
/>
|
|
696
|
+
<DesignLibraryStep onSelect={handleDesignLibrarySelect} />
|
|
679
697
|
</div>
|
|
680
698
|
);
|
|
681
699
|
|
|
682
700
|
const renderAiDesignStep = () => (
|
|
683
701
|
<div className="space-y-4 p-4">
|
|
684
702
|
<AiDesignStep
|
|
685
|
-
config={config!}
|
|
686
703
|
designConfig={aiDesignConfig}
|
|
687
704
|
onDesignConfigChange={setAiDesignConfig}
|
|
688
705
|
/>
|
|
@@ -765,25 +782,15 @@ const AddPaneNewPanel = ({
|
|
|
765
782
|
return (
|
|
766
783
|
<div className="bg-white p-2 shadow-inner">
|
|
767
784
|
<div className="group mb-2 flex w-full items-center gap-1 rounded-md bg-white p-1.5">
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
onClick={() => setParentMode(PaneAddMode.DEFAULT, first)}
|
|
778
|
-
className="w-fit rounded bg-gray-100 px-3 py-1 text-sm text-gray-700 transition-colors hover:bg-gray-200"
|
|
779
|
-
>
|
|
780
|
-
← Go Back
|
|
781
|
-
</button>
|
|
782
|
-
<div className="font-action ml-4 flex-none rounded px-2 py-2.5 text-sm font-bold text-cyan-700 shadow-sm">
|
|
783
|
-
+ Design New Pane
|
|
784
|
-
</div>
|
|
785
|
-
</>
|
|
786
|
-
)}
|
|
785
|
+
<button
|
|
786
|
+
onClick={() => setParentMode(PaneAddMode.DEFAULT, first)}
|
|
787
|
+
className="w-fit rounded bg-gray-100 px-3 py-1 text-sm text-gray-700 transition-colors hover:bg-gray-200"
|
|
788
|
+
>
|
|
789
|
+
← Go Back
|
|
790
|
+
</button>
|
|
791
|
+
<div className="font-action ml-4 flex-none rounded px-2 py-2.5 text-sm font-bold text-cyan-700 shadow-sm">
|
|
792
|
+
+ Design New Pane
|
|
793
|
+
</div>
|
|
787
794
|
</div>
|
|
788
795
|
<div className="min-h-96 rounded-md border bg-gray-50">
|
|
789
796
|
{renderStep()}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { getCtx, type NodesContext } from '@/stores/nodes';
|
|
3
|
+
import { PaneAddMode, type StoragePane } from '@/types/compositorTypes';
|
|
4
|
+
import {
|
|
5
|
+
remapPaneIds,
|
|
6
|
+
convertStorageToLiveTemplate,
|
|
7
|
+
} from '@/utils/compositor/designLibraryHelper';
|
|
8
|
+
|
|
9
|
+
interface AddPanePanelPasteProps {
|
|
10
|
+
nodeId: string;
|
|
11
|
+
first: boolean;
|
|
12
|
+
setMode: (mode: PaneAddMode, reset?: boolean) => void;
|
|
13
|
+
ctx?: NodesContext;
|
|
14
|
+
isStoryFragment?: boolean;
|
|
15
|
+
isContextPane?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const AddPanePanel_paste = ({
|
|
19
|
+
nodeId,
|
|
20
|
+
first,
|
|
21
|
+
setMode,
|
|
22
|
+
ctx: providedCtx,
|
|
23
|
+
isStoryFragment,
|
|
24
|
+
isContextPane,
|
|
25
|
+
}: AddPanePanelPasteProps) => {
|
|
26
|
+
const [jsonInput, setJsonInput] = useState('');
|
|
27
|
+
const [error, setError] = useState<string | null>(null);
|
|
28
|
+
const ctx = providedCtx || getCtx();
|
|
29
|
+
|
|
30
|
+
const handleCreate = () => {
|
|
31
|
+
setError(null);
|
|
32
|
+
if (!jsonInput.trim()) {
|
|
33
|
+
setError('Paste content cannot be empty.');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const parsedPane = JSON.parse(jsonInput) as StoragePane;
|
|
39
|
+
if (parsedPane.nodeType !== 'Pane') {
|
|
40
|
+
throw new Error('Pasted content is not a valid Pane object.');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const remappedPane = remapPaneIds(parsedPane);
|
|
44
|
+
const liveTemplate = convertStorageToLiveTemplate(remappedPane);
|
|
45
|
+
|
|
46
|
+
const ownerId =
|
|
47
|
+
isStoryFragment || isContextPane
|
|
48
|
+
? nodeId
|
|
49
|
+
: ctx.getClosestNodeTypeFromId(nodeId, 'StoryFragment');
|
|
50
|
+
|
|
51
|
+
ctx.addTemplatePane(
|
|
52
|
+
ownerId,
|
|
53
|
+
liveTemplate,
|
|
54
|
+
nodeId,
|
|
55
|
+
first ? 'before' : 'after'
|
|
56
|
+
);
|
|
57
|
+
ctx.notifyNode('root');
|
|
58
|
+
setMode(PaneAddMode.DEFAULT, true);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
const message =
|
|
61
|
+
err instanceof Error
|
|
62
|
+
? err.message
|
|
63
|
+
: 'An unknown error occurred during parsing.';
|
|
64
|
+
setError(`Invalid Pane JSON: ${message}`);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div className="p-2 shadow-inner">
|
|
70
|
+
<div className="rounded-md border bg-gray-50 p-4">
|
|
71
|
+
<div className="flex items-center justify-between pb-4">
|
|
72
|
+
<div className="flex items-center gap-2">
|
|
73
|
+
<button
|
|
74
|
+
onClick={() => setMode(PaneAddMode.DEFAULT)}
|
|
75
|
+
className="w-fit rounded bg-gray-200 px-3 py-1 text-sm text-gray-800 transition-colors hover:bg-gray-300"
|
|
76
|
+
>
|
|
77
|
+
← Go Back
|
|
78
|
+
</button>
|
|
79
|
+
<h3 className="font-action text-sm font-bold text-cyan-700">
|
|
80
|
+
Paste Pane
|
|
81
|
+
</h3>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
<div className="space-y-4">
|
|
85
|
+
<p className="text-sm text-gray-600">
|
|
86
|
+
Paste the JSON content of a copied pane into the text area below.
|
|
87
|
+
</p>
|
|
88
|
+
<textarea
|
|
89
|
+
value={jsonInput}
|
|
90
|
+
onChange={(e) => setJsonInput(e.target.value)}
|
|
91
|
+
placeholder="Paste pane JSON here..."
|
|
92
|
+
className="h-48 w-full rounded-md border border-gray-300 bg-white p-2 font-mono text-xs focus:border-cyan-500 focus:ring-cyan-500"
|
|
93
|
+
spellCheck={false}
|
|
94
|
+
/>
|
|
95
|
+
{error && <p className="text-sm text-red-600">{error}</p>}
|
|
96
|
+
<div className="flex justify-end">
|
|
97
|
+
<button
|
|
98
|
+
onClick={handleCreate}
|
|
99
|
+
disabled={!jsonInput.trim()}
|
|
100
|
+
className="rounded-md bg-cyan-600 px-4 py-2 text-sm font-bold text-white shadow-sm hover:bg-cyan-700 disabled:cursor-not-allowed disabled:bg-gray-400"
|
|
101
|
+
>
|
|
102
|
+
Create Pane from Paste
|
|
103
|
+
</button>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export default AddPanePanel_paste;
|