astro-tractstack 2.0.0-rc.0
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/LICENSE +110 -0
- package/README.md +56 -0
- package/astro.d.ts +64 -0
- package/bin/create-tractstack.js +483 -0
- package/dist/config.js +80 -0
- package/dist/index.js +2129 -0
- package/package.json +89 -0
- package/templates/artpacks/kCz/captainBreakfast_1080px.webp +0 -0
- package/templates/artpacks/kCz/captainBreakfast_1920px.webp +0 -0
- package/templates/artpacks/kCz/captainBreakfast_600px.webp +0 -0
- package/templates/artpacks/kCz/cleanDrips_1080px.webp +0 -0
- package/templates/artpacks/kCz/cleanDrips_1920px.webp +0 -0
- package/templates/artpacks/kCz/cleanDrips_600px.webp +0 -0
- package/templates/artpacks/kCz/crispwaves_1080px.webp +0 -0
- package/templates/artpacks/kCz/crispwaves_1920px.webp +0 -0
- package/templates/artpacks/kCz/crispwaves_600px.webp +0 -0
- package/templates/artpacks/kCz/dragonSkin_1080px.webp +0 -0
- package/templates/artpacks/kCz/dragonSkin_1920px.webp +0 -0
- package/templates/artpacks/kCz/dragonSkin_600px.webp +0 -0
- package/templates/artpacks/kCz/dragon_1080px.webp +0 -0
- package/templates/artpacks/kCz/dragon_1920px.webp +0 -0
- package/templates/artpacks/kCz/dragon_600px.webp +0 -0
- package/templates/artpacks/kCz/nightcity_1080px.webp +0 -0
- package/templates/artpacks/kCz/nightcity_1920px.webp +0 -0
- package/templates/artpacks/kCz/nightcity_600px.webp +0 -0
- package/templates/artpacks/kCz/pattern1_1080px.webp +0 -0
- package/templates/artpacks/kCz/pattern1_1920px.webp +0 -0
- package/templates/artpacks/kCz/pattern1_600px.webp +0 -0
- package/templates/artpacks/kCz/pattern2_1080px.webp +0 -0
- package/templates/artpacks/kCz/pattern2_1920px.webp +0 -0
- package/templates/artpacks/kCz/pattern2_600px.webp +0 -0
- package/templates/artpacks/kCz/skindrips_1080px.webp +0 -0
- package/templates/artpacks/kCz/skindrips_1920px.webp +0 -0
- package/templates/artpacks/kCz/skindrips_600px.webp +0 -0
- package/templates/artpacks/kCz/slimetime_1080px.webp +0 -0
- package/templates/artpacks/kCz/slimetime_1920px.webp +0 -0
- package/templates/artpacks/kCz/slimetime_600px.webp +0 -0
- package/templates/artpacks/kCz/snake_1080px.webp +0 -0
- package/templates/artpacks/kCz/snake_1920px.webp +0 -0
- package/templates/artpacks/kCz/snake_600px.webp +0 -0
- package/templates/artpacks/kCz/toxicshock_1080px.webp +0 -0
- package/templates/artpacks/kCz/toxicshock_1920px.webp +0 -0
- package/templates/artpacks/kCz/toxicshock_600px.webp +0 -0
- package/templates/artpacks/kCz/tractstack_1080px.webp +0 -0
- package/templates/artpacks/kCz/tractstack_1920px.webp +0 -0
- package/templates/artpacks/kCz/tractstack_600px.webp +0 -0
- package/templates/artpacks/kCz/tripdrips_1080px.webp +0 -0
- package/templates/artpacks/kCz/tripdrips_1920px.webp +0 -0
- package/templates/artpacks/kCz/tripdrips_600px.webp +0 -0
- package/templates/artpacks/kCz/wavedrips_1080px.webp +0 -0
- package/templates/artpacks/kCz/wavedrips_1920px.webp +0 -0
- package/templates/artpacks/kCz/wavedrips_600px.webp +0 -0
- package/templates/artpacks/t8k/beach_1080px.webp +0 -0
- package/templates/artpacks/t8k/beach_1920px.webp +0 -0
- package/templates/artpacks/t8k/beach_600px.webp +0 -0
- package/templates/artpacks/t8k/blast_1080px.webp +0 -0
- package/templates/artpacks/t8k/blast_1920px.webp +0 -0
- package/templates/artpacks/t8k/blast_600px.webp +0 -0
- package/templates/artpacks/t8k/bokeh_1080px.webp +0 -0
- package/templates/artpacks/t8k/bokeh_1920px.webp +0 -0
- package/templates/artpacks/t8k/bokeh_600px.webp +0 -0
- package/templates/artpacks/t8k/cartoon_1080px.webp +0 -0
- package/templates/artpacks/t8k/cartoon_1920px.webp +0 -0
- package/templates/artpacks/t8k/cartoon_600px.webp +0 -0
- package/templates/artpacks/t8k/darkeggshell_1080px.webp +0 -0
- package/templates/artpacks/t8k/darkeggshell_1920px.webp +0 -0
- package/templates/artpacks/t8k/darkeggshell_600px.webp +0 -0
- package/templates/artpacks/t8k/explosion_1080px.webp +0 -0
- package/templates/artpacks/t8k/explosion_1920px.webp +0 -0
- package/templates/artpacks/t8k/explosion_600px.webp +0 -0
- package/templates/artpacks/t8k/floral_1080px.webp +0 -0
- package/templates/artpacks/t8k/floral_1920px.webp +0 -0
- package/templates/artpacks/t8k/floral_600px.webp +0 -0
- package/templates/artpacks/t8k/flower_1080px.webp +0 -0
- package/templates/artpacks/t8k/flower_1920px.webp +0 -0
- package/templates/artpacks/t8k/flower_600px.webp +0 -0
- package/templates/artpacks/t8k/foliage_1080px.webp +0 -0
- package/templates/artpacks/t8k/foliage_1920px.webp +0 -0
- package/templates/artpacks/t8k/foliage_600px.webp +0 -0
- package/templates/artpacks/t8k/mist_1080px.webp +0 -0
- package/templates/artpacks/t8k/mist_1920px.webp +0 -0
- package/templates/artpacks/t8k/mist_600px.webp +0 -0
- package/templates/artpacks/t8k/portal_1080px.webp +0 -0
- package/templates/artpacks/t8k/portal_1920px.webp +0 -0
- package/templates/artpacks/t8k/portal_600px.webp +0 -0
- package/templates/artpacks/t8k/storytime_1080px.webp +0 -0
- package/templates/artpacks/t8k/storytime_1920px.webp +0 -0
- package/templates/artpacks/t8k/storytime_600px.webp +0 -0
- package/templates/artpacks/t8k/tacky_1080px.webp +0 -0
- package/templates/artpacks/t8k/tacky_1920px.webp +0 -0
- package/templates/artpacks/t8k/tacky_600px.webp +0 -0
- package/templates/artpacks/t8k/wallpaper_1080px.webp +0 -0
- package/templates/artpacks/t8k/wallpaper_1920px.webp +0 -0
- package/templates/artpacks/t8k/wallpaper_600px.webp +0 -0
- package/templates/brand/favicon.ico +0 -0
- package/templates/brand/logo.svg +19 -0
- package/templates/brand/static.jpg +0 -0
- package/templates/brand/wordmark.svg +4 -0
- package/templates/css/custom.css +51 -0
- package/templates/css/frontend.css +3519 -0
- package/templates/css/storykeep.css +92872 -0
- package/templates/custom/minimal/CodeHook.astro +53 -0
- package/templates/custom/minimal/CustomRoutes.astro +46 -0
- package/templates/custom/with-examples/CodeHook.astro +49 -0
- package/templates/custom/with-examples/CustomHero.astro +13 -0
- package/templates/custom/with-examples/CustomRoutes.astro +39 -0
- package/templates/custom/with-examples/pages/Collections.astro +110 -0
- package/templates/env.example +8 -0
- package/templates/fonts/Inter-Black.woff2 +0 -0
- package/templates/fonts/Inter-Bold.woff2 +0 -0
- package/templates/fonts/Inter-Regular.woff2 +0 -0
- package/templates/icons/h2.svg +1 -0
- package/templates/icons/h3.svg +1 -0
- package/templates/icons/h4.svg +1 -0
- package/templates/icons/h5.svg +1 -0
- package/templates/icons/image.svg +7 -0
- package/templates/icons/text.svg +6 -0
- package/templates/socials/codepen.svg +1 -0
- package/templates/socials/discord.svg +1 -0
- package/templates/socials/facebook.svg +1 -0
- package/templates/socials/github.svg +1 -0
- package/templates/socials/instagram.svg +1 -0
- package/templates/socials/linkedin.svg +1 -0
- package/templates/socials/mail.svg +1 -0
- package/templates/socials/rumble.svg +1 -0
- package/templates/socials/tiktok.svg +1 -0
- package/templates/socials/twitch.svg +1 -0
- package/templates/socials/twitter.svg +1 -0
- package/templates/socials/x.svg +1 -0
- package/templates/socials/youtube.svg +1 -0
- package/templates/src/client/analytics-events.ts +213 -0
- package/templates/src/client/belief-events.ts +205 -0
- package/templates/src/client/sse.ts +667 -0
- package/templates/src/components/Footer.astro +246 -0
- package/templates/src/components/Fragment.astro +70 -0
- package/templates/src/components/Header.astro +458 -0
- package/templates/src/components/Menu.tsx +196 -0
- package/templates/src/components/codehooks/BunnyVideoSetup.tsx +692 -0
- package/templates/src/components/codehooks/BunnyVideoWrapper.astro +78 -0
- package/templates/src/components/codehooks/EpinetDurationSelector.tsx +1020 -0
- package/templates/src/components/codehooks/EpinetTableView.tsx +594 -0
- package/templates/src/components/codehooks/EpinetWrapper.tsx +424 -0
- package/templates/src/components/codehooks/FeaturedContent.astro +273 -0
- package/templates/src/components/codehooks/FeaturedContentSetup.tsx +738 -0
- package/templates/src/components/codehooks/ListContent.astro +460 -0
- package/templates/src/components/codehooks/ListContentSetup.tsx +649 -0
- package/templates/src/components/codehooks/SankeyDiagram.tsx +359 -0
- package/templates/src/components/compositor/Compositor.tsx +144 -0
- package/templates/src/components/compositor/Node.tsx +415 -0
- package/templates/src/components/compositor/NodeWithGuid.tsx +25 -0
- package/templates/src/components/compositor/PanelVisibilityWrapper.tsx +87 -0
- package/templates/src/components/compositor/elements/Belief.tsx +148 -0
- package/templates/src/components/compositor/elements/BgImage.tsx +118 -0
- package/templates/src/components/compositor/elements/BgVisualBreak.tsx +102 -0
- package/templates/src/components/compositor/elements/BunnyVideo.tsx +63 -0
- package/templates/src/components/compositor/elements/IdentifyAs.tsx +66 -0
- package/templates/src/components/compositor/elements/PlayButton.tsx +19 -0
- package/templates/src/components/compositor/elements/SignUp.tsx +179 -0
- package/templates/src/components/compositor/elements/Svg.tsx +33 -0
- package/templates/src/components/compositor/elements/ToggleBelief.tsx +36 -0
- package/templates/src/components/compositor/elements/YouTubeWrapper.tsx +33 -0
- package/templates/src/components/compositor/nodes/BgPaneWrapper.tsx +35 -0
- package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +189 -0
- package/templates/src/components/compositor/nodes/Markdown.tsx +179 -0
- package/templates/src/components/compositor/nodes/Pane.tsx +277 -0
- package/templates/src/components/compositor/nodes/Pane_eraser.tsx +69 -0
- package/templates/src/components/compositor/nodes/Pane_layout.tsx +77 -0
- package/templates/src/components/compositor/nodes/RenderChildren.tsx +19 -0
- package/templates/src/components/compositor/nodes/StoryFragment.tsx +35 -0
- package/templates/src/components/compositor/nodes/TagElement.tsx +14 -0
- package/templates/src/components/compositor/nodes/Widget.tsx +115 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeA.tsx +4 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeA_eraser.tsx +26 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeAnchorComponent.tsx +248 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag.tsx +684 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_eraser.tsx +62 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_insert.tsx +120 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_settings.tsx +62 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeButton.tsx +5 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeButton_eraser.tsx +26 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeImg.tsx +28 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeText.tsx +18 -0
- package/templates/src/components/compositor/nodes/tagElements/TabIndicator.tsx +51 -0
- package/templates/src/components/compositor/preview/FeaturedContentPreview.tsx +128 -0
- package/templates/src/components/compositor/preview/ListContentPreview.tsx +213 -0
- package/templates/src/components/compositor/preview/OgImagePreview.tsx +223 -0
- package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +199 -0
- package/templates/src/components/compositor/preview/PanesPreviewGenerator.tsx +123 -0
- package/templates/src/components/compositor/preview/VisualBreakPreview.tsx +154 -0
- package/templates/src/components/edit/Header.tsx +181 -0
- package/templates/src/components/edit/PanelSwitch.tsx +446 -0
- package/templates/src/components/edit/SettingsPanel.tsx +70 -0
- package/templates/src/components/edit/ToolBar.tsx +101 -0
- package/templates/src/components/edit/ToolMode.tsx +121 -0
- package/templates/src/components/edit/context/ContextPaneConfig.tsx +91 -0
- package/templates/src/components/edit/context/ContextPaneConfig_slug.tsx +174 -0
- package/templates/src/components/edit/context/ContextPaneConfig_title.tsx +186 -0
- package/templates/src/components/edit/pane/AddPanePanel.tsx +136 -0
- package/templates/src/components/edit/pane/AddPanePanel_break.tsx +470 -0
- package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +264 -0
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +623 -0
- package/templates/src/components/edit/pane/AddPanePanel_newAICopy.tsx +107 -0
- package/templates/src/components/edit/pane/AddPanePanel_newAICopy_modal.tsx +217 -0
- package/templates/src/components/edit/pane/AddPanePanel_newCopyMode.tsx +109 -0
- package/templates/src/components/edit/pane/AddPanePanel_newCustomCopy.tsx +39 -0
- package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +445 -0
- package/templates/src/components/edit/pane/ConfigPanePanel.tsx +245 -0
- package/templates/src/components/edit/pane/PageGen.tsx +485 -0
- package/templates/src/components/edit/pane/PageGenSelector.tsx +238 -0
- package/templates/src/components/edit/pane/PageGenSpecial.tsx +362 -0
- package/templates/src/components/edit/pane/PageGen_preview.tsx +495 -0
- package/templates/src/components/edit/pane/PanePanel_impression.tsx +258 -0
- package/templates/src/components/edit/pane/PanePanel_path.tsx +268 -0
- package/templates/src/components/edit/pane/PanePanel_slug.tsx +219 -0
- package/templates/src/components/edit/pane/PanePanel_title.tsx +142 -0
- package/templates/src/components/edit/panels/StyleBreakPanel.tsx +182 -0
- package/templates/src/components/edit/panels/StyleCodeHookPanel.tsx +439 -0
- package/templates/src/components/edit/panels/StyleElementPanel.tsx +177 -0
- package/templates/src/components/edit/panels/StyleElementPanel_add.tsx +349 -0
- package/templates/src/components/edit/panels/StyleElementPanel_remove.tsx +159 -0
- package/templates/src/components/edit/panels/StyleElementPanel_update.tsx +320 -0
- package/templates/src/components/edit/panels/StyleImagePanel.tsx +460 -0
- package/templates/src/components/edit/panels/StyleImagePanel_add.tsx +296 -0
- package/templates/src/components/edit/panels/StyleImagePanel_remove.tsx +153 -0
- package/templates/src/components/edit/panels/StyleImagePanel_update.tsx +312 -0
- package/templates/src/components/edit/panels/StyleLiElementPanel.tsx +273 -0
- package/templates/src/components/edit/panels/StyleLiElementPanel_add.tsx +301 -0
- package/templates/src/components/edit/panels/StyleLiElementPanel_remove.tsx +132 -0
- package/templates/src/components/edit/panels/StyleLiElementPanel_update.tsx +313 -0
- package/templates/src/components/edit/panels/StyleLinkPanel.tsx +346 -0
- package/templates/src/components/edit/panels/StyleLinkPanel_add.tsx +265 -0
- package/templates/src/components/edit/panels/StyleLinkPanel_config.tsx +240 -0
- package/templates/src/components/edit/panels/StyleLinkPanel_remove.tsx +94 -0
- package/templates/src/components/edit/panels/StyleLinkPanel_update.tsx +110 -0
- package/templates/src/components/edit/panels/StyleParentPanel.tsx +263 -0
- package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +275 -0
- package/templates/src/components/edit/panels/StyleParentPanel_deleteLayer.tsx +112 -0
- package/templates/src/components/edit/panels/StyleParentPanel_remove.tsx +87 -0
- package/templates/src/components/edit/panels/StyleParentPanel_update.tsx +141 -0
- package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +428 -0
- package/templates/src/components/edit/panels/StyleWidgetPanel_add.tsx +292 -0
- package/templates/src/components/edit/panels/StyleWidgetPanel_config.tsx +190 -0
- package/templates/src/components/edit/panels/StyleWidgetPanel_remove.tsx +152 -0
- package/templates/src/components/edit/panels/StyleWidgetPanel_update.tsx +308 -0
- package/templates/src/components/edit/state/SaveModal.tsx +811 -0
- package/templates/src/components/edit/state/StylesMemory.tsx +310 -0
- package/templates/src/components/edit/storyfragment/StoryFragmentConfigPanel.tsx +289 -0
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_menu.tsx +320 -0
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_og.tsx +888 -0
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_slug.tsx +269 -0
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_title.tsx +190 -0
- package/templates/src/components/edit/widgets/BeliefWidget.tsx +183 -0
- package/templates/src/components/edit/widgets/BunnyWidget.tsx +134 -0
- package/templates/src/components/edit/widgets/IdentifyAsWidget.tsx +193 -0
- package/templates/src/components/edit/widgets/SignupWidget.tsx +177 -0
- package/templates/src/components/edit/widgets/ToggleWidget.tsx +152 -0
- package/templates/src/components/edit/widgets/YouTubeWidget.tsx +65 -0
- package/templates/src/components/fields/ActionBuilderTimeSelector.tsx +353 -0
- package/templates/src/components/fields/ArtpackImage.tsx +480 -0
- package/templates/src/components/fields/BackgroundImage.tsx +530 -0
- package/templates/src/components/fields/BackgroundImageWrapper.tsx +192 -0
- package/templates/src/components/fields/BooleanParam.tsx +67 -0
- package/templates/src/components/fields/BunnyMomentSelector.tsx +56 -0
- package/templates/src/components/fields/ColorPickerCombo.tsx +284 -0
- package/templates/src/components/fields/ImageUpload.tsx +405 -0
- package/templates/src/components/fields/MultiParam.tsx +75 -0
- package/templates/src/components/fields/PaneBreakCollectionSelector.tsx +97 -0
- package/templates/src/components/fields/PaneBreakShapeSelector.tsx +134 -0
- package/templates/src/components/fields/SelectedTailwindClass.tsx +44 -0
- package/templates/src/components/fields/SingleParam.tsx +73 -0
- package/templates/src/components/fields/ViewportComboBox.tsx +252 -0
- package/templates/src/components/form/ActionBuilderField.tsx +282 -0
- package/templates/src/components/form/ActionBuilderSlugSelector.tsx +182 -0
- package/templates/src/components/form/BooleanToggle.tsx +94 -0
- package/templates/src/components/form/ColorPicker.tsx +153 -0
- package/templates/src/components/form/DateTimeInput.tsx +638 -0
- package/templates/src/components/form/EnumSelect.tsx +88 -0
- package/templates/src/components/form/FileUpload.tsx +465 -0
- package/templates/src/components/form/MagicPathBuilder.tsx +546 -0
- package/templates/src/components/form/NumberInput.tsx +101 -0
- package/templates/src/components/form/ParagraphArrayInput.tsx +207 -0
- package/templates/src/components/form/StringArrayInput.tsx +163 -0
- package/templates/src/components/form/StringInput.tsx +88 -0
- package/templates/src/components/form/UnsavedChangesBar.tsx +295 -0
- package/templates/src/components/form/advanced/APIConfigSection.tsx +69 -0
- package/templates/src/components/form/advanced/AuthConfigSection.tsx +97 -0
- package/templates/src/components/form/brand/BrandAssetsSection.tsx +93 -0
- package/templates/src/components/form/brand/BrandColorsSection.tsx +201 -0
- package/templates/src/components/form/brand/SEOSection.tsx +101 -0
- package/templates/src/components/form/brand/SiteConfigSection.tsx +61 -0
- package/templates/src/components/form/brand/SocialLinksSection.tsx +393 -0
- package/templates/src/components/profile/ProfileConsent.tsx +65 -0
- package/templates/src/components/profile/ProfileCreate.tsx +462 -0
- package/templates/src/components/profile/ProfileEdit.tsx +409 -0
- package/templates/src/components/profile/ProfileSwitch.tsx +255 -0
- package/templates/src/components/profile/ProfileUnlock.tsx +221 -0
- package/templates/src/components/storykeep/Dashboard.tsx +160 -0
- package/templates/src/components/storykeep/Dashboard_Activity.tsx +56 -0
- package/templates/src/components/storykeep/Dashboard_Advanced.tsx +165 -0
- package/templates/src/components/storykeep/Dashboard_Analytics.tsx +451 -0
- package/templates/src/components/storykeep/Dashboard_Branding.tsx +95 -0
- package/templates/src/components/storykeep/Dashboard_Content.tsx +191 -0
- package/templates/src/components/storykeep/controls/UsageCell.tsx +71 -0
- package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +378 -0
- package/templates/src/components/storykeep/controls/content/BeliefTable.tsx +329 -0
- package/templates/src/components/storykeep/controls/content/ContentBrowser.tsx +385 -0
- package/templates/src/components/storykeep/controls/content/ContentSummary.tsx +149 -0
- package/templates/src/components/storykeep/controls/content/KnownResourceForm.tsx +397 -0
- package/templates/src/components/storykeep/controls/content/KnownResourceTable.tsx +260 -0
- package/templates/src/components/storykeep/controls/content/ManageContent.tsx +439 -0
- package/templates/src/components/storykeep/controls/content/MenuForm.tsx +239 -0
- package/templates/src/components/storykeep/controls/content/MenuTable.tsx +332 -0
- package/templates/src/components/storykeep/controls/content/ResourceBulkIngest.tsx +724 -0
- package/templates/src/components/storykeep/controls/content/ResourceForm.tsx +355 -0
- package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +222 -0
- package/templates/src/components/storykeep/controls/content/StoryFragmentTable.tsx +482 -0
- package/templates/src/components/storykeep/state/BrandingWrapper.tsx +42 -0
- package/templates/src/components/storykeep/state/FetchAnalytics.tsx +350 -0
- package/templates/src/components/storykeep/widgets/ResponsiveLine.tsx +319 -0
- package/templates/src/components/storykeep/widgets/Wizard.tsx +278 -0
- package/templates/src/components/tenant/RegistrationForm.tsx +447 -0
- package/templates/src/components/widgets/BunnyVideoHero.astro +775 -0
- package/templates/src/components/widgets/Impression.tsx +102 -0
- package/templates/src/components/widgets/ImpressionWrapper.tsx +214 -0
- package/templates/src/constants/beliefs.ts +61 -0
- package/templates/src/constants/brandThemes.ts +133 -0
- package/templates/src/constants/prompts.json +55 -0
- package/templates/src/constants/shapes.ts +556 -0
- package/templates/src/constants/stopWords.ts +116 -0
- package/templates/src/constants/tailwindColors.json +344 -0
- package/templates/src/constants.ts +274 -0
- package/templates/src/hooks/useFormState.ts +203 -0
- package/templates/src/layouts/Layout.astro +290 -0
- package/templates/src/lib/session.ts +126 -0
- package/templates/src/lib/storyData.ts +56 -0
- package/templates/src/middleware.ts +52 -0
- package/templates/src/pages/404.astro +54 -0
- package/templates/src/pages/[...slug]/edit.astro +216 -0
- package/templates/src/pages/[...slug].astro +148 -0
- package/templates/src/pages/api/auth/decode.ts +101 -0
- package/templates/src/pages/api/auth/login.ts +122 -0
- package/templates/src/pages/api/auth/logout.ts +37 -0
- package/templates/src/pages/api/auth/profile.ts +76 -0
- package/templates/src/pages/api/orphan-analysis.ts +106 -0
- package/templates/src/pages/api/tailwind.ts +116 -0
- package/templates/src/pages/collections/[param1].astro +65 -0
- package/templates/src/pages/context/[...contextSlug]/edit.astro +207 -0
- package/templates/src/pages/context/[...contextSlug].astro +161 -0
- package/templates/src/pages/llms.txt.ts +122 -0
- package/templates/src/pages/maint.astro +183 -0
- package/templates/src/pages/media/[...slug].astro +67 -0
- package/templates/src/pages/robots.txt.ts +36 -0
- package/templates/src/pages/sandbox/activate.astro +258 -0
- package/templates/src/pages/sandbox/register.astro +44 -0
- package/templates/src/pages/sandbox/success.astro +179 -0
- package/templates/src/pages/sitemap.xml.ts +119 -0
- package/templates/src/pages/storykeep/advanced.astro +69 -0
- package/templates/src/pages/storykeep/branding.astro +57 -0
- package/templates/src/pages/storykeep/content.astro +71 -0
- package/templates/src/pages/storykeep/init.astro +36 -0
- package/templates/src/pages/storykeep/login.astro +266 -0
- package/templates/src/pages/storykeep/logout.astro +84 -0
- package/templates/src/pages/storykeep/profile.astro +98 -0
- package/templates/src/pages/storykeep.astro +81 -0
- package/templates/src/stores/analytics.ts +171 -0
- package/templates/src/stores/backend.ts +16 -0
- package/templates/src/stores/navigation.ts +149 -0
- package/templates/src/stores/nodes.ts +2390 -0
- package/templates/src/stores/nodesHistory.ts +85 -0
- package/templates/src/stores/notificationSystem.ts +41 -0
- package/templates/src/stores/orphanAnalysis.ts +409 -0
- package/templates/src/stores/storykeep.ts +247 -0
- package/templates/src/types/astro.ts +86 -0
- package/templates/src/types/compositorTypes.ts +456 -0
- package/templates/src/types/formTypes.ts +281 -0
- package/templates/src/types/multiTenant.ts +77 -0
- package/templates/src/types/nodeProps.ts +66 -0
- package/templates/src/types/tractstack.ts +445 -0
- package/templates/src/utils/aai/getTitleSlug.ts +72 -0
- package/templates/src/utils/actions/actionButton.ts +101 -0
- package/templates/src/utils/actions/lispLexer.ts +57 -0
- package/templates/src/utils/actions/preParse_Action.ts +85 -0
- package/templates/src/utils/actions/preParse_Bunny.ts +50 -0
- package/templates/src/utils/actions/preParse_Clicked.ts +87 -0
- package/templates/src/utils/actions/preParse_Impression.ts +71 -0
- package/templates/src/utils/api/advancedConfig.ts +66 -0
- package/templates/src/utils/api/advancedHelpers.ts +134 -0
- package/templates/src/utils/api/beliefConfig.ts +87 -0
- package/templates/src/utils/api/beliefHelpers.ts +196 -0
- package/templates/src/utils/api/brandConfig.ts +126 -0
- package/templates/src/utils/api/brandHelpers.ts +155 -0
- package/templates/src/utils/api/fileHelpers.ts +306 -0
- package/templates/src/utils/api/menuConfig.ts +57 -0
- package/templates/src/utils/api/menuHelpers.ts +156 -0
- package/templates/src/utils/api/resourceConfig.ts +158 -0
- package/templates/src/utils/api/resourceHelpers.ts +72 -0
- package/templates/src/utils/api/tenantConfig.ts +97 -0
- package/templates/src/utils/api/tenantHelpers.ts +172 -0
- package/templates/src/utils/api.ts +183 -0
- package/templates/src/utils/auth.ts +150 -0
- package/templates/src/utils/backend.ts +243 -0
- package/templates/src/utils/compositor/TemplateMarkdowns.ts +118 -0
- package/templates/src/utils/compositor/TemplateNodes.ts +138 -0
- package/templates/src/utils/compositor/TemplatePanes.ts +100 -0
- package/templates/src/utils/compositor/allowInsert.ts +100 -0
- package/templates/src/utils/compositor/domHelpers.ts +37 -0
- package/templates/src/utils/compositor/handleClickEvent.ts +131 -0
- package/templates/src/utils/compositor/nodesHelper.ts +491 -0
- package/templates/src/utils/compositor/nodesMarkdownGenerator.ts +292 -0
- package/templates/src/utils/compositor/processMarkdown.ts +431 -0
- package/templates/src/utils/compositor/reduceNodesClassNames.ts +192 -0
- package/templates/src/utils/compositor/tailwindClasses.ts +1795 -0
- package/templates/src/utils/compositor/tailwindColors.ts +227 -0
- package/templates/src/utils/compositor/templateMarkdownStyles.ts +1265 -0
- package/templates/src/utils/compositor/typeGuards.ts +193 -0
- package/templates/src/utils/etl/extractor.ts +119 -0
- package/templates/src/utils/etl/index.ts +88 -0
- package/templates/src/utils/etl/loader.ts +36 -0
- package/templates/src/utils/etl/transformer.ts +286 -0
- package/templates/src/utils/helpers.ts +435 -0
- package/templates/src/utils/layout.ts +209 -0
- package/templates/src/utils/profileStorage.ts +306 -0
- package/templates/src/utils/useInterval.ts +27 -0
- package/templates/tailwind.config.cjs +169 -0
- package/utils/create-resolver.ts +10 -0
- package/utils/inject-files.ts +2140 -0
- package/utils/validate-config.ts +43 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AllowedImageFormat,
|
|
3
|
+
ImageDimensions,
|
|
4
|
+
ImageProcessingResult,
|
|
5
|
+
} from '@/types/formTypes';
|
|
6
|
+
|
|
7
|
+
interface ValidationResult {
|
|
8
|
+
isValid: boolean;
|
|
9
|
+
error?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Validates file size
|
|
14
|
+
*/
|
|
15
|
+
export const validateFileSize = (
|
|
16
|
+
file: File,
|
|
17
|
+
maxSizeKB: number
|
|
18
|
+
): ValidationResult => {
|
|
19
|
+
if (file.size > maxSizeKB * 1024) {
|
|
20
|
+
return {
|
|
21
|
+
isValid: false,
|
|
22
|
+
error: `File size must be less than ${maxSizeKB}KB (current: ${Math.round(file.size / 1024)}KB)`,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return { isValid: true };
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Validates file type against allowed formats
|
|
30
|
+
*/
|
|
31
|
+
export const validateFileType = (
|
|
32
|
+
file: File,
|
|
33
|
+
allowedFormats: AllowedImageFormat[]
|
|
34
|
+
): ValidationResult => {
|
|
35
|
+
const fileExtension = file.name.toLowerCase().split('.').pop();
|
|
36
|
+
const mimeType = file.type.toLowerCase();
|
|
37
|
+
|
|
38
|
+
const formatChecks: Record<
|
|
39
|
+
AllowedImageFormat,
|
|
40
|
+
(ext: string, mime: string) => boolean
|
|
41
|
+
> = {
|
|
42
|
+
svg: (ext, mime) => ext === 'svg' || mime === 'image/svg+xml',
|
|
43
|
+
ico: (ext, mime) =>
|
|
44
|
+
ext === 'ico' ||
|
|
45
|
+
mime === 'image/x-icon' ||
|
|
46
|
+
mime === 'image/vnd.microsoft.icon',
|
|
47
|
+
png: (ext, mime) => ext === 'png' || mime === 'image/png',
|
|
48
|
+
jpg: (ext, mime) =>
|
|
49
|
+
ext === 'jpg' || ext === 'jpeg' || mime === 'image/jpeg',
|
|
50
|
+
jpeg: (ext, mime) =>
|
|
51
|
+
ext === 'jpg' || ext === 'jpeg' || mime === 'image/jpeg',
|
|
52
|
+
webp: (ext, mime) => ext === 'webp' || mime === 'image/webp',
|
|
53
|
+
gif: (ext, mime) => ext === 'gif' || mime === 'image/gif',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const isValid = allowedFormats.some((format) =>
|
|
57
|
+
formatChecks[format]?.(fileExtension || '', mimeType)
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (!isValid) {
|
|
61
|
+
return {
|
|
62
|
+
isValid: false,
|
|
63
|
+
error: `File type not allowed. Accepted formats: ${allowedFormats.join(', ')}`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { isValid: true };
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Gets image dimensions from a file
|
|
72
|
+
*/
|
|
73
|
+
export const getImageDimensions = (file: File): Promise<ImageDimensions> => {
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const img = new Image();
|
|
76
|
+
|
|
77
|
+
img.onload = () => {
|
|
78
|
+
resolve({
|
|
79
|
+
width: img.width,
|
|
80
|
+
height: img.height,
|
|
81
|
+
});
|
|
82
|
+
URL.revokeObjectURL(img.src);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
img.onerror = () => {
|
|
86
|
+
URL.revokeObjectURL(img.src);
|
|
87
|
+
reject(new Error('Failed to load image'));
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
img.src = URL.createObjectURL(file);
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Validates image dimensions
|
|
96
|
+
*/
|
|
97
|
+
export const validateImageDimensions = async (
|
|
98
|
+
file: File,
|
|
99
|
+
requiredDimensions: ImageDimensions
|
|
100
|
+
): Promise<ValidationResult> => {
|
|
101
|
+
try {
|
|
102
|
+
const dimensions = await getImageDimensions(file);
|
|
103
|
+
|
|
104
|
+
if (
|
|
105
|
+
dimensions.width !== requiredDimensions.width ||
|
|
106
|
+
dimensions.height !== requiredDimensions.height
|
|
107
|
+
) {
|
|
108
|
+
return {
|
|
109
|
+
isValid: false,
|
|
110
|
+
error: `Image must be ${requiredDimensions.width}x${requiredDimensions.height}px (got ${dimensions.width}x${dimensions.height}px)`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return { isValid: true };
|
|
115
|
+
} catch (error) {
|
|
116
|
+
return {
|
|
117
|
+
isValid: false,
|
|
118
|
+
error: 'Failed to read image dimensions',
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Checks if a file is an SVG (which doesn't need canvas processing)
|
|
125
|
+
*/
|
|
126
|
+
export const isSvgFile = (file: File): boolean => {
|
|
127
|
+
return (
|
|
128
|
+
file.type === 'image/svg+xml' || file.name.toLowerCase().endsWith('.svg')
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Converts file to base64 (for SVG files and simple conversions)
|
|
134
|
+
*/
|
|
135
|
+
export const fileToBase64 = (file: File): Promise<string> => {
|
|
136
|
+
return new Promise((resolve, reject) => {
|
|
137
|
+
const reader = new FileReader();
|
|
138
|
+
|
|
139
|
+
reader.onload = (e) => {
|
|
140
|
+
const result = e.target?.result as string;
|
|
141
|
+
resolve(result);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
reader.onerror = () => {
|
|
145
|
+
reject(new Error('Failed to read file'));
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
reader.readAsDataURL(file);
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Resizes and crops an image to exact dimensions
|
|
154
|
+
* Uses smart cropping (center crop) to maintain aspect ratio
|
|
155
|
+
*/
|
|
156
|
+
export const resizeAndCropImage = async (
|
|
157
|
+
file: File,
|
|
158
|
+
targetDimensions: ImageDimensions,
|
|
159
|
+
quality: number = 0.9
|
|
160
|
+
): Promise<ImageProcessingResult> => {
|
|
161
|
+
// Handle SVG files differently
|
|
162
|
+
if (isSvgFile(file)) {
|
|
163
|
+
const base64 = await fileToBase64(file);
|
|
164
|
+
return {
|
|
165
|
+
processedFile: base64,
|
|
166
|
+
warnings: ['SVG files cannot be resized. Original file preserved.'],
|
|
167
|
+
originalDimensions: { width: 0, height: 0 }, // SVG dimensions are dynamic
|
|
168
|
+
finalDimensions: targetDimensions,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return new Promise(async (resolve, reject) => {
|
|
173
|
+
try {
|
|
174
|
+
const originalDimensions = await getImageDimensions(file);
|
|
175
|
+
const warnings: string[] = [];
|
|
176
|
+
|
|
177
|
+
// If already correct dimensions, just convert to base64
|
|
178
|
+
if (
|
|
179
|
+
originalDimensions.width === targetDimensions.width &&
|
|
180
|
+
originalDimensions.height === targetDimensions.height
|
|
181
|
+
) {
|
|
182
|
+
const base64 = await fileToBase64(file);
|
|
183
|
+
resolve({
|
|
184
|
+
processedFile: base64,
|
|
185
|
+
warnings: [],
|
|
186
|
+
originalDimensions,
|
|
187
|
+
finalDimensions: targetDimensions,
|
|
188
|
+
});
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const canvas = document.createElement('canvas');
|
|
193
|
+
const ctx = canvas.getContext('2d');
|
|
194
|
+
|
|
195
|
+
if (!ctx) {
|
|
196
|
+
reject(new Error('Could not get canvas context'));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const img = new Image();
|
|
201
|
+
|
|
202
|
+
img.onload = () => {
|
|
203
|
+
// Calculate scaling to fill target dimensions (will crop if needed)
|
|
204
|
+
const scaleX = targetDimensions.width / img.width;
|
|
205
|
+
const scaleY = targetDimensions.height / img.height;
|
|
206
|
+
const scale = Math.max(scaleX, scaleY); // Use larger scale to fill
|
|
207
|
+
|
|
208
|
+
// Calculate scaled dimensions
|
|
209
|
+
const scaledWidth = img.width * scale;
|
|
210
|
+
const scaledHeight = img.height * scale;
|
|
211
|
+
|
|
212
|
+
// Calculate crop offset to center the image
|
|
213
|
+
const cropX = (scaledWidth - targetDimensions.width) / 2;
|
|
214
|
+
const cropY = (scaledHeight - targetDimensions.height) / 2;
|
|
215
|
+
|
|
216
|
+
// Set canvas to target dimensions
|
|
217
|
+
canvas.width = targetDimensions.width;
|
|
218
|
+
canvas.height = targetDimensions.height;
|
|
219
|
+
|
|
220
|
+
// Draw image scaled and centered
|
|
221
|
+
ctx.drawImage(img, -cropX, -cropY, scaledWidth, scaledHeight);
|
|
222
|
+
|
|
223
|
+
// Add warning about resizing
|
|
224
|
+
if (
|
|
225
|
+
originalDimensions.width !== targetDimensions.width ||
|
|
226
|
+
originalDimensions.height !== targetDimensions.height
|
|
227
|
+
) {
|
|
228
|
+
warnings.push(
|
|
229
|
+
`Image resized from ${originalDimensions.width}x${originalDimensions.height} to ${targetDimensions.width}x${targetDimensions.height}`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Add warning about cropping if aspect ratios don't match
|
|
234
|
+
const originalAspect =
|
|
235
|
+
originalDimensions.width / originalDimensions.height;
|
|
236
|
+
const targetAspect = targetDimensions.width / targetDimensions.height;
|
|
237
|
+
|
|
238
|
+
if (Math.abs(originalAspect - targetAspect) > 0.01) {
|
|
239
|
+
warnings.push('Image was cropped to fit the required aspect ratio');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Convert to base64 (use JPEG for photos, PNG for images with transparency)
|
|
243
|
+
const outputFormat = file.type.includes('png')
|
|
244
|
+
? 'image/png'
|
|
245
|
+
: 'image/jpeg';
|
|
246
|
+
const processedFile = canvas.toDataURL(outputFormat, quality);
|
|
247
|
+
|
|
248
|
+
resolve({
|
|
249
|
+
processedFile,
|
|
250
|
+
warnings,
|
|
251
|
+
originalDimensions,
|
|
252
|
+
finalDimensions: targetDimensions,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
URL.revokeObjectURL(img.src);
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
img.onerror = () => {
|
|
259
|
+
URL.revokeObjectURL(img.src);
|
|
260
|
+
reject(new Error('Failed to load image for processing'));
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
img.src = URL.createObjectURL(file);
|
|
264
|
+
} catch (error) {
|
|
265
|
+
reject(error);
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Comprehensive file validation
|
|
272
|
+
*/
|
|
273
|
+
export interface FileValidationOptions {
|
|
274
|
+
maxSizeKB?: number;
|
|
275
|
+
allowedFormats?: AllowedImageFormat[];
|
|
276
|
+
requiredDimensions?: ImageDimensions;
|
|
277
|
+
allowAnyImageWithWarning?: boolean;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export const validateFile = async (
|
|
281
|
+
file: File,
|
|
282
|
+
options: FileValidationOptions
|
|
283
|
+
): Promise<ValidationResult> => {
|
|
284
|
+
// Validate file size
|
|
285
|
+
if (options.maxSizeKB) {
|
|
286
|
+
const sizeResult = validateFileSize(file, options.maxSizeKB);
|
|
287
|
+
if (!sizeResult.isValid) return sizeResult;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Validate file type
|
|
291
|
+
if (options.allowedFormats && !options.allowAnyImageWithWarning) {
|
|
292
|
+
const typeResult = validateFileType(file, options.allowedFormats);
|
|
293
|
+
if (!typeResult.isValid) return typeResult;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Validate dimensions (skip for SVG files)
|
|
297
|
+
if (options.requiredDimensions && !isSvgFile(file)) {
|
|
298
|
+
const dimensionResult = await validateImageDimensions(
|
|
299
|
+
file,
|
|
300
|
+
options.requiredDimensions
|
|
301
|
+
);
|
|
302
|
+
if (!dimensionResult.isValid) return dimensionResult;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return { isValid: true };
|
|
306
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { TractStackAPI } from '../api';
|
|
2
|
+
import { convertToBackendFormat } from './menuHelpers';
|
|
3
|
+
import type { MenuNode, MenuNodeState } from '@/types/tractstack';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Save an existing menu
|
|
7
|
+
*/
|
|
8
|
+
export async function saveMenu(
|
|
9
|
+
tenantId: string,
|
|
10
|
+
menuState: MenuNodeState
|
|
11
|
+
): Promise<MenuNode> {
|
|
12
|
+
const api = new TractStackAPI(tenantId);
|
|
13
|
+
const menuData = convertToBackendFormat(menuState);
|
|
14
|
+
|
|
15
|
+
const response = await api.put(
|
|
16
|
+
`/api/v1/nodes/menus/${menuState.id}`,
|
|
17
|
+
menuData
|
|
18
|
+
);
|
|
19
|
+
return response.data as MenuNode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Create a new menu
|
|
24
|
+
*/
|
|
25
|
+
export async function createMenu(
|
|
26
|
+
tenantId: string,
|
|
27
|
+
menuState: MenuNodeState
|
|
28
|
+
): Promise<MenuNode> {
|
|
29
|
+
const api = new TractStackAPI(tenantId);
|
|
30
|
+
const menuData = convertToBackendFormat(menuState);
|
|
31
|
+
|
|
32
|
+
const response = await api.post('/api/v1/nodes/menus/create', menuData);
|
|
33
|
+
return response.data as MenuNode;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Delete a menu
|
|
38
|
+
*/
|
|
39
|
+
export async function deleteMenu(
|
|
40
|
+
tenantId: string,
|
|
41
|
+
menuId: string
|
|
42
|
+
): Promise<void> {
|
|
43
|
+
const api = new TractStackAPI(tenantId);
|
|
44
|
+
await api.request(`/api/v1/nodes/menus/${menuId}`, { method: 'DELETE' });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get a menu by ID
|
|
49
|
+
*/
|
|
50
|
+
export async function getMenuById(
|
|
51
|
+
tenantId: string,
|
|
52
|
+
menuId: string
|
|
53
|
+
): Promise<MenuNode> {
|
|
54
|
+
const api = new TractStackAPI(tenantId);
|
|
55
|
+
const response = await api.get(`/api/v1/nodes/menus/${menuId}`);
|
|
56
|
+
return response.data as MenuNode;
|
|
57
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { MenuNode, MenuNodeState, FieldErrors } from '@/types/tractstack';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Convert backend MenuNode to frontend MenuNodeState
|
|
5
|
+
*/
|
|
6
|
+
export function convertToLocalState(menuNode: MenuNode): MenuNodeState {
|
|
7
|
+
return {
|
|
8
|
+
id: menuNode.id,
|
|
9
|
+
title: menuNode.title,
|
|
10
|
+
theme: menuNode.theme,
|
|
11
|
+
menuLinks: menuNode.optionsPayload.map((link) => ({
|
|
12
|
+
name: link.name,
|
|
13
|
+
description: link.description,
|
|
14
|
+
featured: link.featured,
|
|
15
|
+
actionLisp: link.actionLisp,
|
|
16
|
+
})),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Convert frontend MenuNodeState to backend MenuNode format
|
|
22
|
+
*/
|
|
23
|
+
export function convertToBackendFormat(state: MenuNodeState): MenuNode {
|
|
24
|
+
return {
|
|
25
|
+
id: state.id,
|
|
26
|
+
title: state.title,
|
|
27
|
+
theme: state.theme,
|
|
28
|
+
optionsPayload: state.menuLinks.map((link) => ({
|
|
29
|
+
name: link.name,
|
|
30
|
+
description: link.description,
|
|
31
|
+
featured: link.featured,
|
|
32
|
+
actionLisp: link.actionLisp,
|
|
33
|
+
})),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Validate menu node state
|
|
39
|
+
*/
|
|
40
|
+
export function validateMenuNode(state: MenuNodeState): FieldErrors {
|
|
41
|
+
const errors: FieldErrors = {};
|
|
42
|
+
|
|
43
|
+
// Validate title
|
|
44
|
+
if (!state.title?.trim()) {
|
|
45
|
+
errors.title = 'Title is required';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Validate theme
|
|
49
|
+
if (!state.theme?.trim()) {
|
|
50
|
+
errors.theme = 'Theme is required';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Validate menu links
|
|
54
|
+
if (!state.menuLinks || state.menuLinks.length === 0) {
|
|
55
|
+
errors.menuLinks = 'At least one menu link is required';
|
|
56
|
+
} else {
|
|
57
|
+
state.menuLinks.forEach((link, index) => {
|
|
58
|
+
if (!link.name?.trim()) {
|
|
59
|
+
errors[`menuLinks.${index}.name`] = 'Link name is required';
|
|
60
|
+
}
|
|
61
|
+
if (!link.actionLisp?.trim()) {
|
|
62
|
+
errors[`menuLinks.${index}.actionLisp`] = 'Action is required';
|
|
63
|
+
} else {
|
|
64
|
+
// Basic ActionLisp validation
|
|
65
|
+
if (!link.actionLisp.startsWith('(goto ')) {
|
|
66
|
+
errors[`menuLinks.${index}.actionLisp`] =
|
|
67
|
+
'Action must start with "(goto "';
|
|
68
|
+
}
|
|
69
|
+
if (!link.actionLisp.endsWith('))')) {
|
|
70
|
+
errors[`menuLinks.${index}.actionLisp`] = 'Action must end with "))"';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return errors;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* State interceptor for form state management
|
|
81
|
+
*/
|
|
82
|
+
export function menuStateIntercept(
|
|
83
|
+
state: MenuNodeState,
|
|
84
|
+
field: keyof MenuNodeState,
|
|
85
|
+
value: any
|
|
86
|
+
): MenuNodeState {
|
|
87
|
+
const newState = { ...state };
|
|
88
|
+
|
|
89
|
+
switch (field) {
|
|
90
|
+
case 'title':
|
|
91
|
+
newState.title = value || '';
|
|
92
|
+
break;
|
|
93
|
+
case 'theme':
|
|
94
|
+
newState.theme = value || '';
|
|
95
|
+
break;
|
|
96
|
+
case 'menuLinks':
|
|
97
|
+
newState.menuLinks = value || [];
|
|
98
|
+
break;
|
|
99
|
+
default:
|
|
100
|
+
(newState as any)[field] = value;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return newState;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Add a new menu link to the state
|
|
108
|
+
*/
|
|
109
|
+
export function addMenuLink(state: MenuNodeState): MenuNodeState {
|
|
110
|
+
return {
|
|
111
|
+
...state,
|
|
112
|
+
menuLinks: [
|
|
113
|
+
...state.menuLinks,
|
|
114
|
+
{
|
|
115
|
+
name: '',
|
|
116
|
+
description: '',
|
|
117
|
+
featured: true,
|
|
118
|
+
actionLisp: '',
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Remove a menu link from the state
|
|
126
|
+
*/
|
|
127
|
+
export function removeMenuLink(
|
|
128
|
+
state: MenuNodeState,
|
|
129
|
+
index: number
|
|
130
|
+
): MenuNodeState {
|
|
131
|
+
return {
|
|
132
|
+
...state,
|
|
133
|
+
menuLinks: state.menuLinks.filter((_, i) => i !== index),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Update a specific menu link in the state
|
|
139
|
+
*/
|
|
140
|
+
export function updateMenuLink(
|
|
141
|
+
state: MenuNodeState,
|
|
142
|
+
index: number,
|
|
143
|
+
field: string,
|
|
144
|
+
value: any
|
|
145
|
+
): MenuNodeState {
|
|
146
|
+
const newMenuLinks = [...state.menuLinks];
|
|
147
|
+
newMenuLinks[index] = {
|
|
148
|
+
...newMenuLinks[index],
|
|
149
|
+
[field]: value,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
...state,
|
|
154
|
+
menuLinks: newMenuLinks,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { TractStackAPI } from '../api';
|
|
2
|
+
import { convertToLocalState, convertToBackendFormat } from './resourceHelpers';
|
|
3
|
+
import type { ResourceConfig, ResourceState } from '@/types/tractstack';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Save resource - handles both create and update operations
|
|
7
|
+
*/
|
|
8
|
+
export async function saveResource(
|
|
9
|
+
tenantId: string,
|
|
10
|
+
resource: Partial<ResourceConfig> & { id?: string }
|
|
11
|
+
): Promise<ResourceConfig> {
|
|
12
|
+
const api = new TractStackAPI(tenantId);
|
|
13
|
+
try {
|
|
14
|
+
const isCreate = !resource.id || resource.id === '';
|
|
15
|
+
|
|
16
|
+
let response;
|
|
17
|
+
|
|
18
|
+
if (isCreate) {
|
|
19
|
+
// Create new resource
|
|
20
|
+
response = await api.post<ResourceConfig>(
|
|
21
|
+
'/api/v1/nodes/resources/create',
|
|
22
|
+
resource
|
|
23
|
+
);
|
|
24
|
+
} else {
|
|
25
|
+
// Update existing resource
|
|
26
|
+
response = await api.put<ResourceConfig>(
|
|
27
|
+
`/api/v1/nodes/resources/${resource.id}`,
|
|
28
|
+
resource
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!response.success || !response.data) {
|
|
33
|
+
throw new Error(response.error || 'Failed to save resource');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return response.data;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error('saveResource error:', error);
|
|
39
|
+
|
|
40
|
+
if (error instanceof Error) {
|
|
41
|
+
// Check if it's a JSON parsing error (from the error message)
|
|
42
|
+
if (
|
|
43
|
+
error.message.includes('Unexpected non-whitespace character after JSON')
|
|
44
|
+
) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
'Server returned invalid JSON response. Please check the backend logs.'
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
throw new Error('Failed to save resource');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function createResource(
|
|
57
|
+
tenantId: string,
|
|
58
|
+
resource: Omit<ResourceConfig, 'id'>
|
|
59
|
+
): Promise<ResourceConfig> {
|
|
60
|
+
const api = new TractStackAPI(tenantId);
|
|
61
|
+
const response = await api.post('/api/v1/nodes/resources/create', resource);
|
|
62
|
+
if (!response.success) {
|
|
63
|
+
throw new Error(response.error || 'Failed to create resource');
|
|
64
|
+
}
|
|
65
|
+
return response.data;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function getResource(
|
|
69
|
+
tenantId: string,
|
|
70
|
+
id: string
|
|
71
|
+
): Promise<ResourceConfig> {
|
|
72
|
+
const api = new TractStackAPI(tenantId);
|
|
73
|
+
const response = await api.get(`/api/v1/nodes/resources/${id}`);
|
|
74
|
+
if (!response.success) {
|
|
75
|
+
throw new Error(response.error || 'Failed to get resource');
|
|
76
|
+
}
|
|
77
|
+
return response.data;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function getResourceBySlug(
|
|
81
|
+
tenantId: string,
|
|
82
|
+
slug: string
|
|
83
|
+
): Promise<ResourceConfig> {
|
|
84
|
+
const api = new TractStackAPI(tenantId);
|
|
85
|
+
const response = await api.get(`/api/v1/nodes/resources/slug/${slug}`);
|
|
86
|
+
if (!response.success) {
|
|
87
|
+
throw new Error(response.error || 'Failed to get resource by slug');
|
|
88
|
+
}
|
|
89
|
+
return response.data;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function deleteResource(
|
|
93
|
+
tenantId: string,
|
|
94
|
+
id: string
|
|
95
|
+
): Promise<void> {
|
|
96
|
+
const api = new TractStackAPI(tenantId);
|
|
97
|
+
const response = await api.request(`/api/v1/nodes/resources/${id}`, {
|
|
98
|
+
method: 'DELETE',
|
|
99
|
+
});
|
|
100
|
+
if (!response.success) {
|
|
101
|
+
throw new Error(response.error || 'Failed to delete resource');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function getAllResourceIds(tenantId: string): Promise<string[]> {
|
|
106
|
+
const api = new TractStackAPI(tenantId);
|
|
107
|
+
const response = await api.get('/api/v1/nodes/resources');
|
|
108
|
+
if (!response.success) {
|
|
109
|
+
throw new Error(response.error || 'Failed to get resource IDs');
|
|
110
|
+
}
|
|
111
|
+
return response.data;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function getResourcesByIds(
|
|
115
|
+
tenantId: string,
|
|
116
|
+
ids: string[]
|
|
117
|
+
): Promise<ResourceConfig[]> {
|
|
118
|
+
const api = new TractStackAPI(tenantId);
|
|
119
|
+
const response = await api.post('/api/v1/nodes/resources', { ids });
|
|
120
|
+
if (!response.success) {
|
|
121
|
+
throw new Error(response.error || 'Failed to get resources by IDs');
|
|
122
|
+
}
|
|
123
|
+
return response.data;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function getResourcesByCategory(
|
|
127
|
+
tenantId: string,
|
|
128
|
+
categorySlug: string
|
|
129
|
+
): Promise<ResourceConfig[]> {
|
|
130
|
+
const allIds = await getAllResourceIds(tenantId);
|
|
131
|
+
const allResources = await getResourcesByIds(tenantId, allIds);
|
|
132
|
+
return allResources.filter(
|
|
133
|
+
(resource) => resource.categorySlug === categorySlug
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function saveResourceWithStateUpdate(
|
|
138
|
+
tenantId: string,
|
|
139
|
+
currentState: ResourceState
|
|
140
|
+
): Promise<ResourceState> {
|
|
141
|
+
// Convert to backend format
|
|
142
|
+
const backendFormat = convertToBackendFormat(currentState);
|
|
143
|
+
|
|
144
|
+
// Determine if this is a create operation
|
|
145
|
+
const isCreate = !currentState.id || currentState.id === '';
|
|
146
|
+
|
|
147
|
+
if (isCreate) {
|
|
148
|
+
// For create, remove id and call createResource
|
|
149
|
+
const { id, ...createData } = backendFormat;
|
|
150
|
+
const createdResource = await createResource(tenantId, createData);
|
|
151
|
+
return convertToLocalState(createdResource);
|
|
152
|
+
} else {
|
|
153
|
+
// For update, send the FULL payload (not just changed fields)
|
|
154
|
+
// This is required for nodes unlike brand/advanced config
|
|
155
|
+
const updatedResource = await saveResource(tenantId, backendFormat);
|
|
156
|
+
return convertToLocalState(updatedResource);
|
|
157
|
+
}
|
|
158
|
+
}
|