astro-tractstack 2.0.18 → 2.0.20

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.
Files changed (58) hide show
  1. package/dist/index.js +6 -32
  2. package/package.json +1 -1
  3. package/templates/src/components/codehooks/BunnyVideoSetup.tsx +1 -4
  4. package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +0 -4
  5. package/templates/src/components/codehooks/ListContentSetup.tsx +1 -8
  6. package/templates/src/components/codehooks/ProductCardSetup.tsx +0 -2
  7. package/templates/src/components/codehooks/ProductGridSetup.tsx +0 -2
  8. package/templates/src/components/compositor/Compositor.tsx +3 -6
  9. package/templates/src/components/compositor/Node.tsx +13 -32
  10. package/templates/src/components/compositor/NodeWithGuid.tsx +49 -5
  11. package/templates/src/components/compositor/nodes/Pane.tsx +4 -21
  12. package/templates/src/components/compositor/nodes/Pane_DesignLibrary.tsx +27 -7
  13. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag.tsx +3 -1
  14. package/templates/src/components/compositor/preview/OgImagePreview.tsx +0 -5
  15. package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +5 -6
  16. package/templates/src/components/compositor/preview/PanesPreviewGenerator.tsx +1 -0
  17. package/templates/src/components/edit/PanelSwitch.tsx +3 -24
  18. package/templates/src/components/edit/SettingsPanel.tsx +0 -1
  19. package/templates/src/components/edit/ToolMode.tsx +6 -14
  20. package/templates/src/components/edit/pane/AddPanePanel.tsx +45 -25
  21. package/templates/src/components/edit/pane/AddPanePanel_new.tsx +277 -70
  22. package/templates/src/components/edit/pane/AddPanePanel_paste.tsx +111 -0
  23. package/templates/src/components/edit/pane/RestylePaneModal.tsx +7 -14
  24. package/templates/src/components/edit/pane/steps/AiDesignStep.tsx +0 -5
  25. package/templates/src/components/edit/pane/steps/DesignLibraryStep.tsx +4 -11
  26. package/templates/src/components/edit/panels/StyleBreakPanel.tsx +1 -3
  27. package/templates/src/components/edit/panels/StyleElementPanel_update.tsx +0 -6
  28. package/templates/src/components/edit/panels/StyleImagePanel.tsx +0 -1
  29. package/templates/src/components/edit/panels/StyleImagePanel_update.tsx +0 -3
  30. package/templates/src/components/edit/panels/StyleLiElementPanel_update.tsx +0 -4
  31. package/templates/src/components/edit/panels/StyleLinkPanel_config.tsx +8 -5
  32. package/templates/src/components/edit/panels/StyleLinkPanel_update.tsx +1 -2
  33. package/templates/src/components/edit/panels/StyleParentPanel.tsx +1 -3
  34. package/templates/src/components/edit/panels/StyleParentPanel_update.tsx +2 -5
  35. package/templates/src/components/edit/panels/StyleWidgetPanel_config.tsx +2 -8
  36. package/templates/src/components/edit/panels/StyleWidgetPanel_update.tsx +0 -4
  37. package/templates/src/components/edit/state/SaveToLibraryModal.tsx +27 -16
  38. package/templates/src/components/edit/storyfragment/StoryFragmentConfigPanel.tsx +9 -26
  39. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_og.tsx +7 -16
  40. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_slug.tsx +5 -6
  41. package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +0 -5
  42. package/templates/src/components/fields/BackgroundImageWrapper.tsx +1 -7
  43. package/templates/src/components/fields/ColorPickerCombo.tsx +8 -12
  44. package/templates/src/components/fields/ViewportComboBox.tsx +4 -6
  45. package/templates/src/constants/prompts.json +22 -1
  46. package/templates/src/stores/nodes.ts +297 -222
  47. package/templates/src/stores/storykeep.ts +3 -3
  48. package/templates/src/types/compositorTypes.ts +21 -1
  49. package/templates/src/types/tractstack.ts +1 -0
  50. package/templates/src/utils/compositor/TemplatePanes.ts +0 -76
  51. package/templates/src/utils/compositor/aiPaneParser.ts +265 -83
  52. package/templates/src/utils/compositor/designLibraryHelper.ts +252 -26
  53. package/templates/src/utils/helpers.ts +5 -4
  54. package/utils/inject-files.ts +6 -32
  55. package/templates/src/components/compositor/preview/VisualBreakPreview.tsx +0 -154
  56. package/templates/src/components/edit/pane/PageGen_preview.tsx +0 -511
  57. package/templates/src/utils/compositor/processMarkdown.ts +0 -445
  58. package/templates/src/utils/compositor/templateMarkdownStyles.ts +0 -1273
@@ -3,11 +3,13 @@ import { useState, useCallback } from 'react';
3
3
  import DocumentPlusIcon from '@heroicons/react/24/outline/DocumentPlusIcon';
4
4
  import SparklesIcon from '@heroicons/react/24/outline/SparklesIcon';
5
5
  import SwatchIcon from '@heroicons/react/24/outline/SwatchIcon';
6
+ import SquaresPlusIcon from '@heroicons/react/24/outline/SquaresPlusIcon';
7
+ import DocumentIcon from '@heroicons/react/24/outline/DocumentIcon';
6
8
  import { NodesContext, getCtx } from '@/stores/nodes';
7
9
  import { cloneDeep } from '@/utils/helpers';
8
10
  import { hasAssemblyAIStore } from '@/stores/storykeep';
9
11
  import prompts from '@/constants/prompts.json';
10
- import type { BrandConfig, DesignLibraryEntry } from '@/types/tractstack';
12
+ import type { DesignLibraryEntry } from '@/types/tractstack';
11
13
  import { PaneAddMode, type TemplatePane } from '@/types/compositorTypes';
12
14
  import { useStore } from '@nanostores/react';
13
15
  import { CopyInputStep } from './steps/CopyInputStep';
@@ -25,6 +27,7 @@ type Step =
25
27
  | 'initial'
26
28
  | 'copyInput'
27
29
  | 'designLibrary'
30
+ | 'layoutChoice'
28
31
  | 'aiDesign'
29
32
  | 'loading'
30
33
  | 'error'
@@ -32,6 +35,8 @@ type Step =
32
35
 
33
36
  type InitialChoice = 'library' | 'ai' | 'blank';
34
37
  type CopyMode = 'prompt' | 'raw';
38
+ type LayoutChoice = 'standard' | 'grid';
39
+ type ColumnPresetKey = 'left' | 'right';
35
40
 
36
41
  interface GenerationResponse {
37
42
  success: boolean;
@@ -115,7 +120,6 @@ interface AddPaneNewPanelProps {
115
120
  ctx?: NodesContext;
116
121
  isStoryFragment?: boolean;
117
122
  isContextPane?: boolean;
118
- config?: BrandConfig;
119
123
  isSandboxMode?: boolean;
120
124
  }
121
125
 
@@ -126,7 +130,6 @@ const AddPaneNewPanel = ({
126
130
  ctx: providedCtx,
127
131
  isStoryFragment = false,
128
132
  isContextPane = false,
129
- config,
130
133
  isSandboxMode = false,
131
134
  }: AddPaneNewPanelProps) => {
132
135
  const ctx = providedCtx || getCtx();
@@ -135,10 +138,23 @@ const AddPaneNewPanel = ({
135
138
  const [initialChoice, setInitialChoice] = useState<InitialChoice | null>(
136
139
  null
137
140
  );
141
+ const [layoutChoice, setLayoutChoice] = useState<LayoutChoice>('standard');
138
142
  const [error, setError] = useState<string | null>(null);
143
+
139
144
  const [copyMode, setCopyMode] = useState<CopyMode>('prompt');
140
145
  const [promptValue, setPromptValue] = useState('');
141
146
  const [copyValue, setCopyValue] = useState('');
147
+
148
+ const [overallPrompt, setOverallPrompt] = useState(
149
+ prompts.aiPaneCopyPrompt_2cols.presets.heroDefault.default
150
+ );
151
+ const [copyModeCol1, setCopyModeCol1] = useState<CopyMode>('prompt');
152
+ const [promptValueCol1, setPromptValueCol1] = useState('');
153
+ const [copyValueCol1, setCopyValueCol1] = useState('');
154
+ const [copyModeCol2, setCopyModeCol2] = useState<CopyMode>('prompt');
155
+ const [promptValueCol2, setPromptValueCol2] = useState('');
156
+ const [copyValueCol2, setCopyValueCol2] = useState('');
157
+
142
158
  const [selectedLibraryEntry, setSelectedLibraryEntry] =
143
159
  useState<DesignLibraryEntry | null>(null);
144
160
  const [aiDesignConfig, setAiDesignConfig] = useState<AiDesignConfig>({
@@ -158,10 +174,15 @@ const AddPaneNewPanel = ({
158
174
  } else if (choice === 'library') {
159
175
  setStep('designLibrary');
160
176
  } else if (choice === 'ai') {
161
- setStep('aiDesign');
177
+ setStep('layoutChoice');
162
178
  }
163
179
  };
164
180
 
181
+ const handleLayoutChoice = (choice: LayoutChoice) => {
182
+ setLayoutChoice(choice);
183
+ setStep('aiDesign');
184
+ };
185
+
165
186
  const handleBack = () => {
166
187
  setError(null);
167
188
  if (step === 'copyInput') {
@@ -172,13 +193,13 @@ const AddPaneNewPanel = ({
172
193
  } else {
173
194
  setStep('initial');
174
195
  }
196
+ } else if (step === 'aiDesign') {
197
+ setStep('layoutChoice');
175
198
  } else if (step === 'directInject') {
176
199
  setStep('aiDesign');
177
- } else if (
178
- step === 'designLibrary' ||
179
- step === 'aiDesign' ||
180
- step === 'error'
181
- ) {
200
+ } else if (step === 'layoutChoice') {
201
+ setStep('initial');
202
+ } else if (step === 'designLibrary' || step === 'error') {
182
203
  setStep('initial');
183
204
  }
184
205
  };
@@ -214,7 +235,7 @@ const AddPaneNewPanel = ({
214
235
  setStep('copyInput');
215
236
  };
216
237
 
217
- const handleApplyTemplate = async (template: TemplatePane) => {
238
+ const handleApplyTemplate = async (template: any) => {
218
239
  if (!ctx) return;
219
240
  try {
220
241
  const insertTemplate = cloneDeep(template);
@@ -326,38 +347,102 @@ const AddPaneNewPanel = ({
326
347
  if (aiDesignConfig.additionalNotes)
327
348
  designInput += ` Refine with these notes: "${aiDesignConfig.additionalNotes}"`;
328
349
 
329
- const shellPromptDetails = prompts.aiPaneShellPrompt;
330
- const copyPromptDetails = prompts.aiPaneCopyPrompt;
331
350
  const layout = 'Text Only';
332
351
 
333
- const formattedShellPrompt = shellPromptDetails.user_template
334
- .replace('{{DESIGN_INPUT}}', designInput)
335
- .replace('{{LAYOUT_TYPE}}', layout);
336
-
337
- const shellResult = await callAskLemurAPI(
338
- formattedShellPrompt,
339
- shellPromptDetails.system || '',
340
- true,
341
- isSandboxMode
342
- );
343
-
344
- const copyInputContent =
345
- copyMode === 'prompt' ? promptValue : copyValue;
346
- const formattedCopyPrompt = copyPromptDetails.user_template
347
- .replace('{{COPY_INPUT}}', copyInputContent)
348
- .replace('{{DESIGN_INPUT}}', designInput)
349
- .replace('{{LAYOUT_TYPE}}', layout)
350
- .replace('{{SHELL_JSON}}', shellResult);
351
-
352
- const copyResult = await callAskLemurAPI(
353
- formattedCopyPrompt,
354
- copyPromptDetails.system || '',
355
- false,
356
- isSandboxMode
357
- );
358
-
359
- const finalPane = parseAiPane(shellResult, copyResult, layout);
360
- handleApplyTemplate(finalPane);
352
+ if (layoutChoice === 'standard') {
353
+ const shellPromptDetails = prompts.aiPaneShellPrompt;
354
+ const formattedShellPrompt = shellPromptDetails.user_template
355
+ .replace('{{DESIGN_INPUT}}', designInput)
356
+ .replace('{{LAYOUT_TYPE}}', layout);
357
+
358
+ const shellResult = await callAskLemurAPI(
359
+ formattedShellPrompt,
360
+ shellPromptDetails.system || '',
361
+ true,
362
+ isSandboxMode
363
+ );
364
+
365
+ const copyPromptDetails = prompts.aiPaneCopyPrompt;
366
+ const copyInputContent =
367
+ copyMode === 'prompt' ? promptValue : copyValue;
368
+ const formattedCopyPrompt = copyPromptDetails.user_template
369
+ .replace('{{COPY_INPUT}}', copyInputContent)
370
+ .replace('{{DESIGN_INPUT}}', designInput)
371
+ .replace('{{LAYOUT_TYPE}}', layout)
372
+ .replace('{{SHELL_JSON}}', shellResult);
373
+
374
+ const copyResult = await callAskLemurAPI(
375
+ formattedCopyPrompt,
376
+ copyPromptDetails.system || '',
377
+ false,
378
+ isSandboxMode
379
+ );
380
+ const finalPane = parseAiPane(shellResult, copyResult, layout);
381
+ handleApplyTemplate(finalPane);
382
+ } else if (layoutChoice === 'grid') {
383
+ const shellPromptDetails = prompts.aiPaneShellPrompt_2cols;
384
+ const formattedShellPrompt = shellPromptDetails.user_template
385
+ .replace('{{COPY_INPUT}}', overallPrompt)
386
+ .replace('{{DESIGN_INPUT}}', designInput);
387
+
388
+ const shellResult = await callAskLemurAPI(
389
+ formattedShellPrompt,
390
+ shellPromptDetails.system || '',
391
+ true,
392
+ isSandboxMode
393
+ );
394
+
395
+ const copyPromptDetails = prompts.aiPaneCopyPrompt_2cols;
396
+ const preset = copyPromptDetails.presets.heroDefault;
397
+ const copyResults: string[] = [];
398
+
399
+ const promptsToRun: {
400
+ prompt: string;
401
+ copy: string;
402
+ mode: CopyMode;
403
+ presetKey: ColumnPresetKey;
404
+ }[] = [
405
+ {
406
+ prompt: promptValueCol1,
407
+ copy: copyValueCol1,
408
+ mode: copyModeCol1,
409
+ presetKey: 'left',
410
+ },
411
+ {
412
+ prompt: promptValueCol2,
413
+ copy: copyValueCol2,
414
+ mode: copyModeCol2,
415
+ presetKey: 'right',
416
+ },
417
+ ];
418
+
419
+ for (const item of promptsToRun) {
420
+ if (item.mode === 'raw') {
421
+ copyResults.push(item.copy);
422
+ continue;
423
+ }
424
+
425
+ const columnPreset = preset[item.presetKey];
426
+ const formattedCopyPrompt = copyPromptDetails.user_template
427
+ .replace('{{SHELL_JSON}}', shellResult)
428
+ .replace('{{COPY_INPUT}}', overallPrompt)
429
+ .replace('{{COLUMN_PROMPT}}', item.prompt)
430
+ .replace('{{DESIGN_INPUT}}', designInput)
431
+ .replace('{{LAYOUT_TYPE}}', layout)
432
+ .replace('{{COLUMN_EXAMPLE}}', columnPreset.example);
433
+
434
+ const copyResult = await callAskLemurAPI(
435
+ formattedCopyPrompt,
436
+ copyPromptDetails.system || '',
437
+ false,
438
+ isSandboxMode
439
+ );
440
+ copyResults.push(copyResult);
441
+ }
442
+
443
+ const finalPane = parseAiPane(shellResult, copyResults, layout);
444
+ handleApplyTemplate(finalPane);
445
+ }
361
446
  }
362
447
  } catch (err: any) {
363
448
  setError(err.message || 'Failed to generate AI pane.');
@@ -368,8 +453,16 @@ const AddPaneNewPanel = ({
368
453
  copyMode,
369
454
  promptValue,
370
455
  copyValue,
456
+ overallPrompt,
457
+ copyModeCol1,
458
+ promptValueCol1,
459
+ copyValueCol1,
460
+ copyModeCol2,
461
+ promptValueCol2,
462
+ copyValueCol2,
371
463
  isSandboxMode,
372
464
  initialChoice,
465
+ layoutChoice,
373
466
  selectedLibraryEntry,
374
467
  handleApplyTemplate,
375
468
  ]);
@@ -416,41 +509,157 @@ const AddPaneNewPanel = ({
416
509
  </div>
417
510
  );
418
511
 
419
- const renderContentStep = () => (
420
- <div className="space-y-4 p-4">
421
- <CopyInputStep
422
- copyMode={copyMode}
423
- onCopyModeChange={setCopyMode}
424
- promptValue={promptValue}
425
- onPromptValueChange={setPromptValue}
426
- copyValue={copyValue}
427
- onCopyValueChange={setCopyValue}
428
- defaultPrompt={
429
- first
430
- ? prompts.aiPaneCopyPrompt.heroDefault
431
- : prompts.aiPaneCopyPrompt.contentDefault
432
- }
433
- />
434
- <div className="flex justify-between">
512
+ const renderLayoutChoiceStep = () => (
513
+ <div className="p-4">
514
+ <h3 className="font-action mb-4 text-center text-xl font-bold text-gray-800">
515
+ Choose a Layout Structure
516
+ </h3>
517
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
435
518
  <button
436
- onClick={handleBack}
437
- className="rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-bold text-gray-700 shadow-sm hover:bg-gray-50"
519
+ onClick={() => handleLayoutChoice('standard')}
520
+ className="group flex flex-col items-center space-y-3 rounded-lg border bg-white p-6 text-center shadow-sm transition-all hover:border-cyan-600 hover:shadow-lg"
438
521
  >
439
- Back
522
+ <DocumentIcon className="h-10 w-10 text-gray-500 transition-colors group-hover:text-cyan-600" />
523
+ <h4 className="font-bold text-gray-800">Standard Layout</h4>
524
+ <p className="text-sm text-gray-600">
525
+ A single, continuous column of content.
526
+ </p>
440
527
  </button>
441
528
  <button
442
- onClick={handleFinalGenerate}
443
- disabled={
444
- copyMode === 'prompt' ? !promptValue.trim() : !copyValue.trim()
445
- }
446
- 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"
529
+ onClick={() => handleLayoutChoice('grid')}
530
+ className="group flex flex-col items-center space-y-3 rounded-lg border bg-white p-6 text-center shadow-sm transition-all hover:border-cyan-600 hover:shadow-lg"
447
531
  >
448
- Generate Pane
532
+ <SquaresPlusIcon className="h-10 w-10 text-gray-500 transition-colors group-hover:text-cyan-600" />
533
+ <h4 className="font-bold text-gray-800">2-Column Grid</h4>
534
+ <p className="text-sm text-gray-600">
535
+ Side-by-side content that stacks on mobile.
536
+ </p>
537
+ </button>
538
+ </div>
539
+ <div className="mt-6 flex justify-center">
540
+ <button
541
+ onClick={handleBack}
542
+ className="rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-bold text-gray-700 shadow-sm hover:bg-gray-50"
543
+ >
544
+ ← Back
449
545
  </button>
450
546
  </div>
451
547
  </div>
452
548
  );
453
549
 
550
+ const renderContentStep = () => {
551
+ if (layoutChoice === 'grid') {
552
+ const isGenerateDisabled =
553
+ (copyModeCol1 === 'prompt' && !promptValueCol1.trim()) ||
554
+ (copyModeCol1 === 'raw' && !copyValueCol1.trim()) ||
555
+ (copyModeCol2 === 'prompt' && !promptValueCol2.trim()) ||
556
+ (copyModeCol2 === 'raw' && !copyValueCol2.trim()) ||
557
+ !overallPrompt.trim();
558
+
559
+ return (
560
+ <div className="space-y-4 p-4">
561
+ <div>
562
+ <h4 className="mb-2 block text-sm font-bold text-gray-700">
563
+ Overall Component Brief
564
+ </h4>
565
+ <textarea
566
+ value={overallPrompt}
567
+ onChange={(e) => setOverallPrompt(e.target.value)}
568
+ placeholder="e.g., A compelling hero section for a website about Tract Stack..."
569
+ rows={3}
570
+ className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
571
+ />
572
+ </div>
573
+ <div className="grid grid-cols-1 gap-6 md:grid-cols-2">
574
+ <div>
575
+ <h4 className="mb-2 block text-sm font-bold text-gray-700">
576
+ Left Column Content
577
+ </h4>
578
+ <CopyInputStep
579
+ copyMode={copyModeCol1}
580
+ onCopyModeChange={setCopyModeCol1}
581
+ promptValue={promptValueCol1}
582
+ onPromptValueChange={setPromptValueCol1}
583
+ copyValue={copyValueCol1}
584
+ onCopyValueChange={setCopyValueCol1}
585
+ defaultPrompt={
586
+ prompts.aiPaneCopyPrompt_2cols.presets.heroDefault.left.prompt
587
+ }
588
+ />
589
+ </div>
590
+ <div>
591
+ <h4 className="mb-2 block text-sm font-bold text-gray-700">
592
+ Right Column Content
593
+ </h4>
594
+ <CopyInputStep
595
+ copyMode={copyModeCol2}
596
+ onCopyModeChange={setCopyModeCol2}
597
+ promptValue={promptValueCol2}
598
+ onPromptValueChange={setPromptValueCol2}
599
+ copyValue={copyValueCol2}
600
+ onCopyValueChange={setCopyValueCol2}
601
+ defaultPrompt={
602
+ prompts.aiPaneCopyPrompt_2cols.presets.heroDefault.right
603
+ .prompt
604
+ }
605
+ />
606
+ </div>
607
+ </div>
608
+ <div className="flex justify-between">
609
+ <button
610
+ onClick={handleBack}
611
+ className="rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-bold text-gray-700 shadow-sm hover:bg-gray-50"
612
+ >
613
+ ← Back
614
+ </button>
615
+ <button
616
+ onClick={handleFinalGenerate}
617
+ disabled={isGenerateDisabled}
618
+ 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"
619
+ >
620
+ ✨ Generate Pane
621
+ </button>
622
+ </div>
623
+ </div>
624
+ );
625
+ }
626
+
627
+ return (
628
+ <div className="space-y-4 p-4">
629
+ <CopyInputStep
630
+ copyMode={copyMode}
631
+ onCopyModeChange={setCopyMode}
632
+ promptValue={promptValue}
633
+ onPromptValueChange={setPromptValue}
634
+ copyValue={copyValue}
635
+ onCopyValueChange={setCopyValue}
636
+ defaultPrompt={
637
+ first
638
+ ? prompts.aiPaneCopyPrompt.heroDefault
639
+ : prompts.aiPaneCopyPrompt.contentDefault
640
+ }
641
+ />
642
+ <div className="flex justify-between">
643
+ <button
644
+ onClick={handleBack}
645
+ className="rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-bold text-gray-700 shadow-sm hover:bg-gray-50"
646
+ >
647
+ ← Back
648
+ </button>
649
+ <button
650
+ onClick={handleFinalGenerate}
651
+ disabled={
652
+ copyMode === 'prompt' ? !promptValue.trim() : !copyValue.trim()
653
+ }
654
+ 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"
655
+ >
656
+ ✨ Generate Pane
657
+ </button>
658
+ </div>
659
+ </div>
660
+ );
661
+ };
662
+
454
663
  const renderDesignLibraryStep = () => (
455
664
  <div className="space-y-4 p-4">
456
665
  <div className="flex justify-start">
@@ -461,17 +670,13 @@ const AddPaneNewPanel = ({
461
670
  ← Back to Choice
462
671
  </button>
463
672
  </div>
464
- <DesignLibraryStep
465
- config={config!}
466
- onSelect={handleDesignLibrarySelect}
467
- />
673
+ <DesignLibraryStep onSelect={handleDesignLibrarySelect} />
468
674
  </div>
469
675
  );
470
676
 
471
677
  const renderAiDesignStep = () => (
472
678
  <div className="space-y-4 p-4">
473
679
  <AiDesignStep
474
- config={config!}
475
680
  designConfig={aiDesignConfig}
476
681
  onDesignConfigChange={setAiDesignConfig}
477
682
  />
@@ -532,6 +737,8 @@ const AddPaneNewPanel = ({
532
737
  switch (step) {
533
738
  case 'initial':
534
739
  return renderInitialStep();
740
+ case 'layoutChoice':
741
+ return renderLayoutChoiceStep();
535
742
  case 'copyInput':
536
743
  return renderContentStep();
537
744
  case 'designLibrary':
@@ -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;
@@ -15,6 +15,7 @@ import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon';
15
15
  import { selectionStore } from '@/stores/selection';
16
16
  import { getCtx, NodesContext } from '@/stores/nodes';
17
17
  import { createEmptyStorykeep } from '@/utils/compositor/nodesHelper';
18
+ import { brandConfigStore } from '@/stores/storykeep';
18
19
  import {
19
20
  extractPaneCopy,
20
21
  mergeCopyIntoTemplate,
@@ -24,10 +25,10 @@ import type {
24
25
  PaneNode,
25
26
  StoragePane,
26
27
  TemplatePane,
27
- TemplateMarkdown, // Added import
28
- BaseNode, // Added import
28
+ TemplateMarkdown,
29
+ BaseNode,
29
30
  } from '@/types/compositorTypes';
30
- import type { BrandConfig, DesignLibraryEntry } from '@/types/tractstack';
31
+ import type { DesignLibraryEntry } from '@/types/tractstack';
31
32
  import {
32
33
  PaneSnapshotGenerator,
33
34
  type SnapshotData,
@@ -44,13 +45,11 @@ const VERBOSE = false;
44
45
 
45
46
  interface TemplatePreviewItemProps {
46
47
  template: TemplatePane;
47
- config: BrandConfig;
48
48
  onClick: () => void;
49
49
  }
50
50
 
51
51
  const TemplatePreviewItem = ({
52
52
  template,
53
- config,
54
53
  onClick,
55
54
  }: TemplatePreviewItemProps) => {
56
55
  const [previewState, setPreviewState] = useState<{
@@ -111,7 +110,6 @@ const TemplatePreviewItem = ({
111
110
  id={template.id}
112
111
  htmlString={previewState.htmlFragment}
113
112
  outputWidth={800}
114
- config={config}
115
113
  onComplete={(_id, data) => handleSnapshotComplete(data)}
116
114
  onError={(_id, err) =>
117
115
  setPreviewState((prev) =>
@@ -141,16 +139,12 @@ const TemplatePreviewItem = ({
141
139
  );
142
140
  };
143
141
 
144
- interface RestylePaneModalProps {
145
- config: BrandConfig;
146
- }
147
-
148
- export const RestylePaneModal = ({ config }: RestylePaneModalProps) => {
142
+ export const RestylePaneModal = () => {
149
143
  const ctx = getCtx();
150
144
  const { isRestyleModalOpen, paneToRestyleId } = useStore(selectionStore, {
151
145
  keys: ['isRestyleModalOpen', 'paneToRestyleId'],
152
146
  });
153
- const designLibrary = config?.DESIGN_LIBRARY || [];
147
+ const designLibrary = brandConfigStore.get()?.DESIGN_LIBRARY || [];
154
148
 
155
149
  const [selectedCategory, setSelectedCategory] = useState<string>('all');
156
150
  const [searchTerm, setSearchTerm] = useState('');
@@ -406,7 +400,7 @@ export const RestylePaneModal = ({ config }: RestylePaneModalProps) => {
406
400
  <Dialog.Positioner className="z-104 fixed inset-0 flex items-center justify-center">
407
401
  <Dialog.Content
408
402
  className="flex flex-col rounded-lg bg-white shadow-2xl"
409
- style={{ height: '90vw', width: '90vw' }}
403
+ style={{ maxHeight: '90vw', width: '90vw' }}
410
404
  >
411
405
  <header className="flex items-center justify-between border-b p-4">
412
406
  <Dialog.Title className="text-xl font-bold">
@@ -502,7 +496,6 @@ export const RestylePaneModal = ({ config }: RestylePaneModalProps) => {
502
496
  <TemplatePreviewItem
503
497
  key={template.id}
504
498
  template={template}
505
- config={config}
506
499
  onClick={() => handleSelectTemplate(template)}
507
500
  />
508
501
  ))}