@r2digisolutions/components 0.1.7 → 0.2.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/dist/components/atoms/IconButton/IconButton.svelte +1 -1
- package/dist/components/atoms/MediaKindIcon/MediaKindIcon.stories.js +8 -0
- package/dist/components/atoms/MediaKindIcon/MediaKindIcon.svelte +122 -0
- package/dist/components/atoms/MediaKindIcon/MediaKindIcon.svelte.d.ts +11 -0
- package/dist/components/atoms/MediaKindIcon/MediaKindIconStory.svelte +12 -0
- package/dist/components/atoms/MediaKindIcon/MediaKindIconStory.svelte.d.ts +18 -0
- package/dist/components/atoms/PanKnob/PanKnob.stories.d.ts +9 -0
- package/dist/components/atoms/PanKnob/PanKnob.stories.js +8 -0
- package/dist/components/atoms/PanKnob/PanKnob.svelte +218 -0
- package/dist/components/atoms/PanKnob/PanKnob.svelte.d.ts +19 -0
- package/dist/components/atoms/PanKnob/PanKnobStory.svelte +15 -0
- package/dist/components/atoms/PanKnob/PanKnobStory.svelte.d.ts +3 -0
- package/dist/components/atoms/Playhead/Playhead.stories.js +8 -0
- package/dist/components/atoms/Playhead/Playhead.svelte +59 -0
- package/dist/components/atoms/Playhead/Playhead.svelte.d.ts +13 -0
- package/dist/components/atoms/Playhead/PlayheadStory.svelte +9 -0
- package/dist/components/atoms/Playhead/PlayheadStory.svelte.d.ts +18 -0
- package/dist/components/atoms/TimeRuler/TimeRuler.stories.js +8 -0
- package/dist/components/atoms/TimeRuler/TimeRuler.svelte +58 -0
- package/dist/components/atoms/TimeRuler/TimeRuler.svelte.d.ts +10 -0
- package/dist/components/atoms/TimeRuler/TimeRulerStory.svelte +7 -0
- package/dist/components/atoms/TimeRuler/TimeRulerStory.svelte.d.ts +18 -0
- package/dist/components/atoms/Tooltip/Tooltip.svelte +1 -1
- package/dist/components/atoms/TrimHandle/TrimHandle.stories.js +8 -0
- package/dist/components/atoms/TrimHandle/TrimHandle.svelte +63 -0
- package/dist/components/atoms/TrimHandle/TrimHandle.svelte.d.ts +12 -0
- package/dist/components/atoms/TrimHandle/TrimHandleStory.svelte +11 -0
- package/dist/components/atoms/TrimHandle/TrimHandleStory.svelte.d.ts +18 -0
- package/dist/components/molecules/AiAssistPanel/AiAssistPanel.stories.d.ts +9 -0
- package/dist/components/molecules/AiAssistPanel/AiAssistPanel.stories.js +8 -0
- package/dist/components/molecules/AiAssistPanel/AiAssistPanel.svelte +102 -0
- package/dist/components/molecules/AiAssistPanel/AiAssistPanel.svelte.d.ts +20 -0
- package/dist/components/molecules/AiAssistPanel/AiAssistPanelStory.svelte +34 -0
- package/dist/components/molecules/AiAssistPanel/AiAssistPanelStory.svelte.d.ts +3 -0
- package/dist/components/molecules/BulkActionBar/BulkActionBar.svelte +97 -61
- package/dist/components/molecules/BulkActionBar/BulkActionBar.svelte.d.ts +2 -0
- package/dist/components/molecules/BulkEditPanel/BulkEditPanel.stories.js +8 -0
- package/dist/components/molecules/BulkEditPanel/BulkEditPanel.svelte +117 -0
- package/dist/components/molecules/BulkEditPanel/BulkEditPanel.svelte.d.ts +23 -0
- package/dist/components/molecules/BulkEditPanel/BulkEditPanelStory.svelte +18 -0
- package/dist/components/molecules/BulkEditPanel/BulkEditPanelStory.svelte.d.ts +18 -0
- package/dist/components/molecules/CanvasElementsPanel/CanvasElementsPanel.stories.js +8 -0
- package/dist/components/molecules/CanvasElementsPanel/CanvasElementsPanel.svelte +858 -0
- package/dist/components/molecules/CanvasElementsPanel/CanvasElementsPanel.svelte.d.ts +43 -0
- package/dist/components/molecules/CanvasElementsPanel/CanvasElementsPanelStory.svelte +7 -0
- package/dist/components/molecules/CanvasElementsPanel/CanvasElementsPanelStory.svelte.d.ts +18 -0
- package/dist/components/molecules/CanvasInspector/CanvasInspector.stories.d.ts +9 -0
- package/dist/components/molecules/CanvasInspector/CanvasInspector.stories.js +8 -0
- package/dist/components/molecules/CanvasInspector/CanvasInspector.svelte +1140 -0
- package/dist/components/molecules/CanvasInspector/CanvasInspector.svelte.d.ts +28 -0
- package/dist/components/molecules/CanvasInspector/CanvasInspectorStory.svelte +20 -0
- package/dist/components/molecules/CanvasInspector/CanvasInspectorStory.svelte.d.ts +3 -0
- package/dist/components/molecules/ColorPicker/ColorPicker.svelte +1 -1
- package/dist/components/molecules/ColumnVisibilityMenu/ColumnVisibilityMenu.stories.d.ts +9 -0
- package/dist/components/molecules/ColumnVisibilityMenu/ColumnVisibilityMenu.stories.js +8 -0
- package/dist/components/molecules/ColumnVisibilityMenu/ColumnVisibilityMenu.svelte +108 -0
- package/dist/components/molecules/ColumnVisibilityMenu/ColumnVisibilityMenu.svelte.d.ts +18 -0
- package/dist/components/molecules/ColumnVisibilityMenu/ColumnVisibilityMenuStory.svelte +15 -0
- package/dist/components/molecules/ColumnVisibilityMenu/ColumnVisibilityMenuStory.svelte.d.ts +3 -0
- package/dist/components/molecules/ConflictResolver/ConflictResolver.stories.js +8 -0
- package/dist/components/molecules/ConflictResolver/ConflictResolver.svelte +73 -0
- package/dist/components/molecules/ConflictResolver/ConflictResolver.svelte.d.ts +16 -0
- package/dist/components/molecules/ConflictResolver/ConflictResolverStory.svelte +11 -0
- package/dist/components/molecules/ConflictResolver/ConflictResolverStory.svelte.d.ts +18 -0
- package/dist/components/molecules/ContextMenu/ContextMenu.svelte +204 -60
- package/dist/components/molecules/ContextMenu/ContextMenu.svelte.d.ts +9 -0
- package/dist/components/molecules/ExpressionEditor/ExpressionEditor.stories.d.ts +9 -0
- package/dist/components/molecules/ExpressionEditor/ExpressionEditor.stories.js +8 -0
- package/dist/components/molecules/ExpressionEditor/ExpressionEditor.svelte +56 -0
- package/dist/components/molecules/ExpressionEditor/ExpressionEditor.svelte.d.ts +12 -0
- package/dist/components/molecules/ExpressionEditor/ExpressionEditorStory.svelte +12 -0
- package/dist/components/molecules/ExpressionEditor/ExpressionEditorStory.svelte.d.ts +3 -0
- package/dist/components/molecules/FilterList/FilterList.stories.d.ts +9 -0
- package/dist/components/molecules/FilterList/FilterList.stories.js +8 -0
- package/dist/components/molecules/FilterList/FilterList.svelte +101 -0
- package/dist/components/molecules/FilterList/FilterList.svelte.d.ts +18 -0
- package/dist/components/molecules/FilterList/FilterListStory.svelte +31 -0
- package/dist/components/molecules/FilterList/FilterListStory.svelte.d.ts +3 -0
- package/dist/components/molecules/MediaAssetBrowser/MediaAssetBrowser.stories.d.ts +9 -0
- package/dist/components/molecules/MediaAssetBrowser/MediaAssetBrowser.stories.js +8 -0
- package/dist/components/molecules/MediaAssetBrowser/MediaAssetBrowser.svelte +94 -0
- package/dist/components/molecules/MediaAssetBrowser/MediaAssetBrowser.svelte.d.ts +15 -0
- package/dist/components/molecules/MediaAssetBrowser/MediaAssetBrowserStory.svelte +18 -0
- package/dist/components/molecules/MediaAssetBrowser/MediaAssetBrowserStory.svelte.d.ts +3 -0
- package/dist/components/molecules/MediaAssetCard/MediaAssetCard.stories.js +8 -0
- package/dist/components/molecules/MediaAssetCard/MediaAssetCard.svelte +50 -0
- package/dist/components/molecules/MediaAssetCard/MediaAssetCard.svelte.d.ts +11 -0
- package/dist/components/molecules/MediaAssetCard/MediaAssetCardStory.svelte +13 -0
- package/dist/components/molecules/MediaAssetCard/MediaAssetCardStory.svelte.d.ts +18 -0
- package/dist/components/molecules/MediaLayerItem/MediaLayerItem.stories.js +8 -0
- package/dist/components/molecules/MediaLayerItem/MediaLayerItem.svelte +380 -0
- package/dist/components/molecules/MediaLayerItem/MediaLayerItem.svelte.d.ts +33 -0
- package/dist/components/molecules/MediaLayerItem/MediaLayerItemStory.svelte +14 -0
- package/dist/components/molecules/MediaLayerItem/MediaLayerItemStory.svelte.d.ts +18 -0
- package/dist/components/molecules/MediaLayerList/MediaLayerList.stories.d.ts +9 -0
- package/dist/components/molecules/MediaLayerList/MediaLayerList.stories.js +8 -0
- package/dist/components/molecules/MediaLayerList/MediaLayerList.svelte +399 -0
- package/dist/components/molecules/MediaLayerList/MediaLayerList.svelte.d.ts +37 -0
- package/dist/components/molecules/MediaLayerList/MediaLayerListStory.svelte +24 -0
- package/dist/components/molecules/MediaLayerList/MediaLayerListStory.svelte.d.ts +3 -0
- package/dist/components/molecules/MediaStage/MediaStage.stories.d.ts +9 -0
- package/dist/components/molecules/MediaStage/MediaStage.stories.js +8 -0
- package/dist/components/molecules/MediaStage/MediaStage.svelte +1498 -0
- package/dist/components/molecules/MediaStage/MediaStage.svelte.d.ts +45 -0
- package/dist/components/molecules/MediaStage/MediaStageStory.svelte +35 -0
- package/dist/components/molecules/MediaStage/MediaStageStory.svelte.d.ts +3 -0
- package/dist/components/molecules/MediaTimeline/MediaTimeline.stories.d.ts +9 -0
- package/dist/components/molecules/MediaTimeline/MediaTimeline.stories.js +8 -0
- package/dist/components/molecules/MediaTimeline/MediaTimeline.svelte +131 -0
- package/dist/components/molecules/MediaTimeline/MediaTimeline.svelte.d.ts +19 -0
- package/dist/components/molecules/MediaTimeline/MediaTimelineStory.svelte +20 -0
- package/dist/components/molecules/MediaTimeline/MediaTimelineStory.svelte.d.ts +3 -0
- package/dist/components/molecules/MixerChannel/MixerChannel.stories.d.ts +9 -0
- package/dist/components/molecules/MixerChannel/MixerChannel.stories.js +8 -0
- package/dist/components/molecules/MixerChannel/MixerChannel.svelte +92 -0
- package/dist/components/molecules/MixerChannel/MixerChannel.svelte.d.ts +11 -0
- package/dist/components/molecules/MixerChannel/MixerChannelStory.svelte +13 -0
- package/dist/components/molecules/MixerChannel/MixerChannelStory.svelte.d.ts +3 -0
- package/dist/components/molecules/NodeGraph/GraphBlueprintNode.svelte +454 -0
- package/dist/components/molecules/NodeGraph/GraphBlueprintNode.svelte.d.ts +8 -0
- package/dist/components/molecules/NodeGraph/GraphFlowBridge.svelte +26 -0
- package/dist/components/molecules/NodeGraph/GraphFlowBridge.svelte.d.ts +22 -0
- package/dist/components/molecules/NodeGraph/NodeGraph.stories.d.ts +12 -0
- package/dist/components/molecules/NodeGraph/NodeGraph.stories.js +9 -0
- package/dist/components/molecules/NodeGraph/NodeGraph.svelte +1409 -0
- package/dist/components/molecules/NodeGraph/NodeGraph.svelte.d.ts +33 -0
- package/dist/components/molecules/NodeGraph/NodeGraphStory.svelte +90 -0
- package/dist/components/molecules/NodeGraph/NodeGraphStory.svelte.d.ts +3 -0
- package/dist/components/molecules/NodePalette/NodePalette.stories.js +8 -0
- package/dist/components/molecules/NodePalette/NodePalette.svelte +86 -0
- package/dist/components/molecules/NodePalette/NodePalette.svelte.d.ts +11 -0
- package/dist/components/molecules/NodePalette/NodePaletteStory.svelte +11 -0
- package/dist/components/molecules/NodePalette/NodePaletteStory.svelte.d.ts +18 -0
- package/dist/components/molecules/Poll/Poll.stories.d.ts +1 -1
- package/dist/components/molecules/ProgramMonitor/ProgramMonitor.stories.js +8 -0
- package/dist/components/molecules/ProgramMonitor/ProgramMonitor.svelte +72 -0
- package/dist/components/molecules/ProgramMonitor/ProgramMonitor.svelte.d.ts +12 -0
- package/dist/components/molecules/ProgramMonitor/ProgramMonitorStory.svelte +16 -0
- package/dist/components/molecules/ProgramMonitor/ProgramMonitorStory.svelte.d.ts +18 -0
- package/dist/components/molecules/PropertyField/PropertyField.svelte +57 -3
- package/dist/components/molecules/PropertyField/PropertyField.svelte.d.ts +10 -0
- package/dist/components/molecules/PropertyField/PropertyFieldStory.svelte +21 -0
- package/dist/components/molecules/PropertyGroup/PropertyGroup.svelte +1 -1
- package/dist/components/molecules/QueryBuilder/QueryBuilder.stories.d.ts +9 -0
- package/dist/components/molecules/QueryBuilder/QueryBuilder.stories.js +8 -0
- package/dist/components/molecules/QueryBuilder/QueryBuilder.svelte +52 -0
- package/dist/components/molecules/QueryBuilder/QueryBuilder.svelte.d.ts +18 -0
- package/dist/components/molecules/QueryBuilder/QueryBuilderStory.svelte +62 -0
- package/dist/components/molecules/QueryBuilder/QueryBuilderStory.svelte.d.ts +3 -0
- package/dist/components/molecules/RelationPicker/RelationPicker.stories.d.ts +9 -0
- package/dist/components/molecules/RelationPicker/RelationPicker.stories.js +8 -0
- package/dist/components/molecules/RelationPicker/RelationPicker.svelte +180 -0
- package/dist/components/molecules/RelationPicker/RelationPicker.svelte.d.ts +24 -0
- package/dist/components/molecules/RelationPicker/RelationPickerStory.svelte +16 -0
- package/dist/components/molecules/RelationPicker/RelationPickerStory.svelte.d.ts +3 -0
- package/dist/components/molecules/SchemaFieldBuilder/SchemaFieldBuilder.stories.d.ts +9 -0
- package/dist/components/molecules/SchemaFieldBuilder/SchemaFieldBuilder.stories.js +8 -0
- package/dist/components/molecules/SchemaFieldBuilder/SchemaFieldBuilder.svelte +154 -0
- package/dist/components/molecules/SchemaFieldBuilder/SchemaFieldBuilder.svelte.d.ts +12 -0
- package/dist/components/molecules/SchemaFieldBuilder/SchemaFieldBuilderStory.svelte +16 -0
- package/dist/components/molecules/SchemaFieldBuilder/SchemaFieldBuilderStory.svelte.d.ts +3 -0
- package/dist/components/molecules/SecretField/SecretField.stories.d.ts +9 -0
- package/dist/components/molecules/SecretField/SecretField.stories.js +8 -0
- package/dist/components/molecules/SecretField/SecretField.svelte +58 -0
- package/dist/components/molecules/SecretField/SecretField.svelte.d.ts +15 -0
- package/dist/components/molecules/SecretField/SecretFieldStory.svelte +8 -0
- package/dist/components/molecules/SecretField/SecretFieldStory.svelte.d.ts +3 -0
- package/dist/components/molecules/SplitPane/SplitPane.svelte +96 -37
- package/dist/components/molecules/SplitPane/SplitPane.svelte.d.ts +10 -2
- package/dist/components/molecules/TableToolbar/TableToolbar.svelte.d.ts +1 -1
- package/dist/components/molecules/Terminal/Terminal.stories.js +20 -0
- package/dist/components/molecules/Terminal/Terminal.svelte +238 -0
- package/dist/components/molecules/Terminal/Terminal.svelte.d.ts +19 -0
- package/dist/components/molecules/Terminal/TerminalStory.svelte +47 -0
- package/dist/components/molecules/Terminal/TerminalStory.svelte.d.ts +6 -0
- package/dist/components/molecules/TimelineClip/TimelineClip.stories.js +8 -0
- package/dist/components/molecules/TimelineClip/TimelineClip.svelte +116 -0
- package/dist/components/molecules/TimelineClip/TimelineClip.svelte.d.ts +15 -0
- package/dist/components/molecules/TimelineClip/TimelineClipStory.svelte +18 -0
- package/dist/components/molecules/TimelineClip/TimelineClipStory.svelte.d.ts +18 -0
- package/dist/components/molecules/TimelineTrack/TimelineTrack.stories.js +8 -0
- package/dist/components/molecules/TimelineTrack/TimelineTrack.svelte +56 -0
- package/dist/components/molecules/TimelineTrack/TimelineTrack.svelte.d.ts +16 -0
- package/dist/components/molecules/TimelineTrack/TimelineTrackStory.svelte +35 -0
- package/dist/components/molecules/TimelineTrack/TimelineTrackStory.svelte.d.ts +18 -0
- package/dist/components/molecules/TraceWaterfall/TraceWaterfall.stories.js +8 -0
- package/dist/components/molecules/TraceWaterfall/TraceWaterfall.svelte +158 -0
- package/dist/components/molecules/TraceWaterfall/TraceWaterfall.svelte.d.ts +18 -0
- package/dist/components/molecules/TraceWaterfall/TraceWaterfallStory.svelte +55 -0
- package/dist/components/molecules/TraceWaterfall/TraceWaterfallStory.svelte.d.ts +18 -0
- package/dist/components/molecules/TrackHeader/TrackHeader.stories.d.ts +9 -0
- package/dist/components/molecules/TrackHeader/TrackHeader.stories.js +8 -0
- package/dist/components/molecules/TrackHeader/TrackHeader.svelte +54 -0
- package/dist/components/molecules/TrackHeader/TrackHeader.svelte.d.ts +9 -0
- package/dist/components/molecules/TrackHeader/TrackHeaderStory.svelte +10 -0
- package/dist/components/molecules/TrackHeader/TrackHeaderStory.svelte.d.ts +3 -0
- package/dist/components/molecules/TransportControls/TransportControls.stories.d.ts +9 -0
- package/dist/components/molecules/TransportControls/TransportControls.stories.js +8 -0
- package/dist/components/molecules/TransportControls/TransportControls.svelte +53 -0
- package/dist/components/molecules/TransportControls/TransportControls.svelte.d.ts +12 -0
- package/dist/components/molecules/TransportControls/TransportControlsStory.svelte +18 -0
- package/dist/components/molecules/TransportControls/TransportControlsStory.svelte.d.ts +3 -0
- package/dist/components/molecules/VersionHistoryPanel/VersionHistoryPanel.stories.js +8 -0
- package/dist/components/molecules/VersionHistoryPanel/VersionHistoryPanel.svelte +117 -0
- package/dist/components/molecules/VersionHistoryPanel/VersionHistoryPanel.svelte.d.ts +20 -0
- package/dist/components/molecules/VersionHistoryPanel/VersionHistoryPanelStory.svelte +36 -0
- package/dist/components/molecules/VersionHistoryPanel/VersionHistoryPanelStory.svelte.d.ts +18 -0
- package/dist/components/molecules/VideoPlayer/VideoPlayer.stories.d.ts +1 -1
- package/dist/components/molecules/VideoTrackHeader/VideoTrackHeader.stories.d.ts +9 -0
- package/dist/components/molecules/VideoTrackHeader/VideoTrackHeader.stories.js +8 -0
- package/dist/components/molecules/VideoTrackHeader/VideoTrackHeader.svelte +59 -0
- package/dist/components/molecules/VideoTrackHeader/VideoTrackHeader.svelte.d.ts +9 -0
- package/dist/components/molecules/VideoTrackHeader/VideoTrackHeaderStory.svelte +10 -0
- package/dist/components/molecules/VideoTrackHeader/VideoTrackHeaderStory.svelte.d.ts +3 -0
- package/dist/components/molecules/WaveformRegion/WaveformRegion.stories.js +8 -0
- package/dist/components/molecules/WaveformRegion/WaveformRegion.svelte +82 -0
- package/dist/components/molecules/WaveformRegion/WaveformRegion.svelte.d.ts +13 -0
- package/dist/components/molecules/WaveformRegion/WaveformRegionStory.svelte +17 -0
- package/dist/components/molecules/WaveformRegion/WaveformRegionStory.svelte.d.ts +18 -0
- package/dist/components/molecules/WidgetCanvas/WidgetCanvas.svelte +13 -4
- package/dist/components/molecules/WidgetCanvas/WidgetCanvas.svelte.d.ts +3 -1
- package/dist/components/molecules/WidgetCanvas/widgetCanvasContext.d.ts +2 -0
- package/dist/components/molecules/WidgetFrame/WidgetFrame.svelte +206 -37
- package/dist/components/molecules/WidgetFrame/WidgetFrame.svelte.d.ts +31 -0
- package/dist/components/organisms/AudioEditor/AudioEditor.stories.d.ts +12 -0
- package/dist/components/organisms/AudioEditor/AudioEditor.stories.js +9 -0
- package/dist/components/organisms/AudioEditor/AudioEditor.svelte +273 -0
- package/dist/components/organisms/AudioEditor/AudioEditor.svelte.d.ts +13 -0
- package/dist/components/organisms/AudioEditor/AudioEditorStory.svelte +10 -0
- package/dist/components/organisms/AudioEditor/AudioEditorStory.svelte.d.ts +3 -0
- package/dist/components/organisms/BlogEditor/BlogEditor.stories.d.ts +12 -0
- package/dist/components/organisms/BlogEditor/BlogEditor.stories.js +9 -0
- package/dist/components/organisms/BlogEditor/BlogEditor.svelte +1208 -0
- package/dist/components/organisms/BlogEditor/BlogEditor.svelte.d.ts +16 -0
- package/dist/components/organisms/BlogEditor/BlogEditorStory.svelte +144 -0
- package/dist/components/organisms/BlogEditor/BlogEditorStory.svelte.d.ts +3 -0
- package/dist/components/organisms/BlogEditor/DemoBlogCard.svelte +28 -0
- package/dist/components/organisms/BlogEditor/DemoBlogCard.svelte.d.ts +10 -0
- package/dist/components/organisms/BlogEditor/DemoBlogStats.svelte +34 -0
- package/dist/components/organisms/BlogEditor/DemoBlogStats.svelte.d.ts +11 -0
- package/dist/components/organisms/BlogEditor/DemoBlogWidget.svelte +13 -0
- package/dist/components/organisms/BlogEditor/DemoBlogWidget.svelte.d.ts +8 -0
- package/dist/components/organisms/BlogRenderer/BlogRenderer.stories.js +8 -0
- package/dist/components/organisms/BlogRenderer/BlogRenderer.svelte +469 -0
- package/dist/components/organisms/BlogRenderer/BlogRenderer.svelte.d.ts +14 -0
- package/dist/components/organisms/BlogRenderer/BlogRendererStory.svelte +20 -0
- package/dist/components/organisms/BlogRenderer/BlogRendererStory.svelte.d.ts +18 -0
- package/dist/components/organisms/BlueprintEditor/BlueprintEditor.stories.d.ts +12 -0
- package/dist/components/organisms/BlueprintEditor/BlueprintEditor.stories.js +9 -0
- package/dist/components/organisms/BlueprintEditor/BlueprintEditor.svelte +1012 -0
- package/dist/components/organisms/BlueprintEditor/BlueprintEditor.svelte.d.ts +16 -0
- package/dist/components/organisms/BlueprintEditor/BlueprintEditorStory.svelte +76 -0
- package/dist/components/organisms/BlueprintEditor/BlueprintEditorStory.svelte.d.ts +3 -0
- package/dist/components/organisms/CanvasEditor/CanvasEditor.stories.d.ts +12 -0
- package/dist/components/organisms/CanvasEditor/CanvasEditor.stories.js +9 -0
- package/dist/components/organisms/CanvasEditor/CanvasEditor.svelte +1680 -0
- package/dist/components/organisms/CanvasEditor/CanvasEditor.svelte.d.ts +18 -0
- package/dist/components/organisms/CanvasEditor/CanvasEditorStory.svelte +329 -0
- package/dist/components/organisms/CanvasEditor/CanvasEditorStory.svelte.d.ts +3 -0
- package/dist/components/organisms/DashboardBuilderPage/DashboardBuilderPage.svelte.d.ts +1 -1
- package/dist/components/organisms/DesktopShell/DesktopShell.svelte.d.ts +1 -1
- package/dist/components/organisms/EditorShell/EditorShell.svelte +123 -28
- package/dist/components/organisms/EditorShell/EditorShell.svelte.d.ts +21 -1
- package/dist/components/organisms/FileUploader/FileUploader.svelte +77 -15
- package/dist/components/organisms/FileUploader/FileUploader.svelte.d.ts +11 -0
- package/dist/components/organisms/ImportWizard/ImportWizard.stories.js +8 -0
- package/dist/components/organisms/ImportWizard/ImportWizard.svelte +209 -0
- package/dist/components/organisms/ImportWizard/ImportWizard.svelte.d.ts +22 -0
- package/dist/components/organisms/ImportWizard/ImportWizardStory.svelte +16 -0
- package/dist/components/organisms/ImportWizard/ImportWizardStory.svelte.d.ts +18 -0
- package/dist/components/organisms/VideoEditor/VideoEditor.stories.d.ts +12 -0
- package/dist/components/organisms/VideoEditor/VideoEditor.stories.js +9 -0
- package/dist/components/organisms/VideoEditor/VideoEditor.svelte +308 -0
- package/dist/components/organisms/VideoEditor/VideoEditor.svelte.d.ts +13 -0
- package/dist/components/organisms/VideoEditor/VideoEditorStory.svelte +10 -0
- package/dist/components/organisms/VideoEditor/VideoEditorStory.svelte.d.ts +3 -0
- package/dist/components/templates/AudioEditorTemplate/AudioEditorTemplate.stories.d.ts +12 -0
- package/dist/components/templates/AudioEditorTemplate/AudioEditorTemplate.stories.js +9 -0
- package/dist/components/templates/AudioEditorTemplate/AudioEditorTemplate.svelte +22 -0
- package/dist/components/templates/AudioEditorTemplate/AudioEditorTemplate.svelte.d.ts +13 -0
- package/dist/components/templates/AudioEditorTemplate/AudioEditorTemplateStory.svelte +8 -0
- package/dist/components/templates/AudioEditorTemplate/AudioEditorTemplateStory.svelte.d.ts +3 -0
- package/dist/components/templates/CanvasEditorTemplate/CanvasEditorTemplate.stories.d.ts +12 -0
- package/dist/components/templates/CanvasEditorTemplate/CanvasEditorTemplate.stories.js +9 -0
- package/dist/components/templates/CanvasEditorTemplate/CanvasEditorTemplate.svelte +32 -0
- package/dist/components/templates/CanvasEditorTemplate/CanvasEditorTemplate.svelte.d.ts +17 -0
- package/dist/components/templates/CanvasEditorTemplate/CanvasEditorTemplateStory.svelte +21 -0
- package/dist/components/templates/CanvasEditorTemplate/CanvasEditorTemplateStory.svelte.d.ts +3 -0
- package/dist/components/templates/VideoEditorTemplate/VideoEditorTemplate.stories.d.ts +12 -0
- package/dist/components/templates/VideoEditorTemplate/VideoEditorTemplate.stories.js +9 -0
- package/dist/components/templates/VideoEditorTemplate/VideoEditorTemplate.svelte +22 -0
- package/dist/components/templates/VideoEditorTemplate/VideoEditorTemplate.svelte.d.ts +13 -0
- package/dist/components/templates/VideoEditorTemplate/VideoEditorTemplateStory.svelte +8 -0
- package/dist/components/templates/VideoEditorTemplate/VideoEditorTemplateStory.svelte.d.ts +3 -0
- package/dist/index.d.ts +83 -1
- package/dist/index.js +60 -0
- package/dist/types/xyflow-svelte.d.ts +62 -0
- package/dist/utils/audioProject.d.ts +19 -0
- package/dist/utils/audioProject.js +70 -0
- package/dist/utils/blogDocument.d.ts +175 -0
- package/dist/utils/blogDocument.js +220 -0
- package/dist/utils/blueprintRuntime.d.ts +34 -0
- package/dist/utils/blueprintRuntime.js +289 -0
- package/dist/utils/canvasDocument.d.ts +239 -0
- package/dist/utils/canvasDocument.js +443 -0
- package/dist/utils/canvasExport.d.ts +10 -0
- package/dist/utils/canvasExport.js +469 -0
- package/dist/utils/canvasHierarchy.d.ts +189 -0
- package/dist/utils/canvasHierarchy.js +674 -0
- package/dist/utils/canvasHistory.d.ts +19 -0
- package/dist/utils/canvasHistory.js +48 -0
- package/dist/utils/canvasWidget.d.ts +81 -0
- package/dist/utils/canvasWidget.js +597 -0
- package/dist/utils/mediaTime.d.ts +16 -0
- package/dist/utils/mediaTime.js +57 -0
- package/dist/utils/mediaTracks.d.ts +61 -0
- package/dist/utils/mediaTracks.js +102 -0
- package/dist/utils/nodeGraph.d.ts +354 -0
- package/dist/utils/nodeGraph.js +1453 -0
- package/dist/utils/queryBridge.d.ts +18 -0
- package/dist/utils/queryBridge.js +118 -0
- package/dist/utils/videoSequence.d.ts +13 -0
- package/dist/utils/videoSequence.js +63 -0
- package/package.json +5 -2
|
@@ -0,0 +1,1680 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import EditorShell from '../EditorShell/EditorShell.svelte';
|
|
3
|
+
import MediaAssetBrowser from '../../molecules/MediaAssetBrowser/MediaAssetBrowser.svelte';
|
|
4
|
+
import MediaLayerList from '../../molecules/MediaLayerList/MediaLayerList.svelte';
|
|
5
|
+
import MediaStage from '../../molecules/MediaStage/MediaStage.svelte';
|
|
6
|
+
import CanvasInspector from '../../molecules/CanvasInspector/CanvasInspector.svelte';
|
|
7
|
+
import CanvasElementsPanel, {
|
|
8
|
+
type CanvasElementDef
|
|
9
|
+
} from '../../molecules/CanvasElementsPanel/CanvasElementsPanel.svelte';
|
|
10
|
+
import ContextMenu from '../../molecules/ContextMenu/ContextMenu.svelte';
|
|
11
|
+
import type { ContextMenuItem } from '../../molecules/ContextMenu/ContextMenu.svelte';
|
|
12
|
+
import BulkActionBar from '../../molecules/BulkActionBar/BulkActionBar.svelte';
|
|
13
|
+
import Panel from '../../molecules/Panel/Panel.svelte';
|
|
14
|
+
import SplitPane from '../../molecules/SplitPane/SplitPane.svelte';
|
|
15
|
+
import IconButton from '../../atoms/IconButton/IconButton.svelte';
|
|
16
|
+
import Select from '../../molecules/Select/Select.svelte';
|
|
17
|
+
import DropdownMenu, {
|
|
18
|
+
type DropdownItem
|
|
19
|
+
} from '../../molecules/DropdownMenu/DropdownMenu.svelte';
|
|
20
|
+
import {
|
|
21
|
+
CANVAS_PRESETS,
|
|
22
|
+
alignLayerRect,
|
|
23
|
+
createCanvasGuide,
|
|
24
|
+
createCanvasLayer,
|
|
25
|
+
createPathFromDocPoints,
|
|
26
|
+
defaultSlotFromRect,
|
|
27
|
+
emptyCanvasDocument,
|
|
28
|
+
migrateCanvasDocument,
|
|
29
|
+
presetIdForSize,
|
|
30
|
+
type CanvasAlign,
|
|
31
|
+
type CanvasDocument,
|
|
32
|
+
type CanvasLayer,
|
|
33
|
+
type CanvasLayerKind
|
|
34
|
+
} from '../../../utils/canvasDocument.js';
|
|
35
|
+
import {
|
|
36
|
+
deleteSubtree,
|
|
37
|
+
duplicateSubtree,
|
|
38
|
+
groupLayers,
|
|
39
|
+
isContainerKind,
|
|
40
|
+
computeAbsoluteRects,
|
|
41
|
+
contentPadding,
|
|
42
|
+
reparentLayer,
|
|
43
|
+
reorderSiblings,
|
|
44
|
+
ungroupLayers,
|
|
45
|
+
wrapSelection,
|
|
46
|
+
slotFromLocalRect
|
|
47
|
+
} from '../../../utils/canvasHierarchy.js';
|
|
48
|
+
import {
|
|
49
|
+
breakWidgetInstance,
|
|
50
|
+
createWidgetFromSelection,
|
|
51
|
+
deleteWidgetDefinition,
|
|
52
|
+
duplicateWidgetDefinition,
|
|
53
|
+
placeWidgetInstance,
|
|
54
|
+
renameWidgetDefinition,
|
|
55
|
+
setExposedProp,
|
|
56
|
+
setInstanceOverride,
|
|
57
|
+
syncInstanceSizesToDefinition,
|
|
58
|
+
updateWidgetDefinition
|
|
59
|
+
} from '../../../utils/canvasWidget.js';
|
|
60
|
+
import {
|
|
61
|
+
createCanvasHistory,
|
|
62
|
+
pushCanvasHistory,
|
|
63
|
+
redoCanvasHistory,
|
|
64
|
+
undoCanvasHistory,
|
|
65
|
+
type CanvasHistoryState
|
|
66
|
+
} from '../../../utils/canvasHistory.js';
|
|
67
|
+
import {
|
|
68
|
+
downloadBlob,
|
|
69
|
+
exportCanvasBlob,
|
|
70
|
+
type CanvasExportFormat
|
|
71
|
+
} from '../../../utils/canvasExport.js';
|
|
72
|
+
import { toast } from '../Toast/toast.svelte.js';
|
|
73
|
+
import type { MediaAsset } from '../../../utils/mediaTracks.js';
|
|
74
|
+
import Undo2 from '@lucide/svelte/icons/undo-2';
|
|
75
|
+
import Redo2 from '@lucide/svelte/icons/redo-2';
|
|
76
|
+
import CopyPlus from '@lucide/svelte/icons/copy-plus';
|
|
77
|
+
import Download from '@lucide/svelte/icons/download';
|
|
78
|
+
import PanelLeft from '@lucide/svelte/icons/panel-left';
|
|
79
|
+
import PanelRight from '@lucide/svelte/icons/panel-right';
|
|
80
|
+
import ZoomIn from '@lucide/svelte/icons/zoom-in';
|
|
81
|
+
import ZoomOut from '@lucide/svelte/icons/zoom-out';
|
|
82
|
+
import Grid3x3 from '@lucide/svelte/icons/grid-3x3';
|
|
83
|
+
import Magnet from '@lucide/svelte/icons/magnet';
|
|
84
|
+
import Copy from '@lucide/svelte/icons/copy';
|
|
85
|
+
import Trash2 from '@lucide/svelte/icons/trash-2';
|
|
86
|
+
import Lock from '@lucide/svelte/icons/lock';
|
|
87
|
+
import Unlock from '@lucide/svelte/icons/unlock';
|
|
88
|
+
import EyeOff from '@lucide/svelte/icons/eye-off';
|
|
89
|
+
import BringToFront from '@lucide/svelte/icons/bring-to-front';
|
|
90
|
+
import ArrowDownToLine from '@lucide/svelte/icons/arrow-down-to-line';
|
|
91
|
+
import AlignHorizontalJustifyCenter from '@lucide/svelte/icons/align-horizontal-justify-center';
|
|
92
|
+
import AlignStartVertical from '@lucide/svelte/icons/align-start-vertical';
|
|
93
|
+
import AlignCenterVertical from '@lucide/svelte/icons/align-center-vertical';
|
|
94
|
+
import AlignEndVertical from '@lucide/svelte/icons/align-end-vertical';
|
|
95
|
+
import AlignStartHorizontal from '@lucide/svelte/icons/align-start-horizontal';
|
|
96
|
+
import AlignCenterHorizontal from '@lucide/svelte/icons/align-center-horizontal';
|
|
97
|
+
import AlignEndHorizontal from '@lucide/svelte/icons/align-end-horizontal';
|
|
98
|
+
import AlignCenter from '@lucide/svelte/icons/align-center';
|
|
99
|
+
import Maximize2 from '@lucide/svelte/icons/maximize-2';
|
|
100
|
+
import FileJson from '@lucide/svelte/icons/file-json';
|
|
101
|
+
import ImageIcon from '@lucide/svelte/icons/image';
|
|
102
|
+
import FileImage from '@lucide/svelte/icons/file-image';
|
|
103
|
+
import SeparatorVertical from '@lucide/svelte/icons/separator-vertical';
|
|
104
|
+
import SeparatorHorizontal from '@lucide/svelte/icons/separator-horizontal';
|
|
105
|
+
import Eye from '@lucide/svelte/icons/eye';
|
|
106
|
+
import Pencil from '@lucide/svelte/icons/pencil';
|
|
107
|
+
import TextCursorInput from '@lucide/svelte/icons/text-cursor-input';
|
|
108
|
+
import Group from '@lucide/svelte/icons/group';
|
|
109
|
+
import Ungroup from '@lucide/svelte/icons/ungroup';
|
|
110
|
+
import Component from '@lucide/svelte/icons/component';
|
|
111
|
+
import ChevronLeft from '@lucide/svelte/icons/chevron-left';
|
|
112
|
+
import Tooltip from '../../atoms/Tooltip/Tooltip.svelte';
|
|
113
|
+
|
|
114
|
+
interface CanvasEditorProps {
|
|
115
|
+
value?: CanvasDocument;
|
|
116
|
+
assets?: MediaAsset[];
|
|
117
|
+
class?: string;
|
|
118
|
+
onchange?: (doc: CanvasDocument) => void;
|
|
119
|
+
onassetschange?: (assets: MediaAsset[]) => void;
|
|
120
|
+
onexport?: (payload: {
|
|
121
|
+
format: CanvasExportFormat;
|
|
122
|
+
document: CanvasDocument;
|
|
123
|
+
blob?: Blob;
|
|
124
|
+
}) => void;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let {
|
|
128
|
+
value = $bindable(emptyCanvasDocument()),
|
|
129
|
+
assets = $bindable<MediaAsset[]>([]),
|
|
130
|
+
class: className = '',
|
|
131
|
+
onchange,
|
|
132
|
+
onassetschange,
|
|
133
|
+
onexport
|
|
134
|
+
}: CanvasEditorProps = $props();
|
|
135
|
+
|
|
136
|
+
// Ensure v2 document shape
|
|
137
|
+
$effect.pre(() => {
|
|
138
|
+
if (value.version !== 2 || !value.widgets) {
|
|
139
|
+
value = migrateCanvasDocument(value);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
let selectedIds = $state<string[]>([]);
|
|
144
|
+
let showSidebar = $state(true);
|
|
145
|
+
let showInspector = $state(true);
|
|
146
|
+
let assetsSize = $state(58);
|
|
147
|
+
let sidebarTab = $state<'elements' | 'uploads' | 'widgets'>('elements');
|
|
148
|
+
let zoom = $state(1);
|
|
149
|
+
let showGrid = $state(false);
|
|
150
|
+
let showGuides = $state(true);
|
|
151
|
+
let snap = $state(true);
|
|
152
|
+
let cellSize = $state(8);
|
|
153
|
+
let drawMode = $state(false);
|
|
154
|
+
let ctxOpen = $state(false);
|
|
155
|
+
let ctxAnchor = $state<{ x: number; y: number } | null>(null);
|
|
156
|
+
let ctxLayerId = $state<string | null>(null);
|
|
157
|
+
let exporting = $state(false);
|
|
158
|
+
/** When set, stage edits this widget definition instead of the scene. */
|
|
159
|
+
let editingWidgetId = $state<string | null>(null);
|
|
160
|
+
let history = $state<CanvasHistoryState>(createCanvasHistory(value));
|
|
161
|
+
let applyingHistory = false;
|
|
162
|
+
let syncInstanceSizes = $state(true);
|
|
163
|
+
|
|
164
|
+
const gridSizeOptions = [
|
|
165
|
+
{ value: '4', label: '4px' },
|
|
166
|
+
{ value: '8', label: '8px' },
|
|
167
|
+
{ value: '16', label: '16px' },
|
|
168
|
+
{ value: '24', label: '24px' },
|
|
169
|
+
{ value: '32', label: '32px' },
|
|
170
|
+
{ value: '48', label: '48px' }
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
const editingWidget = $derived(
|
|
174
|
+
editingWidgetId
|
|
175
|
+
? ((value.widgets ?? []).find((w) => w.id === editingWidgetId) ?? null)
|
|
176
|
+
: null
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const stageDoc = $derived.by((): CanvasDocument => {
|
|
180
|
+
if (!editingWidget) return value;
|
|
181
|
+
return {
|
|
182
|
+
...value,
|
|
183
|
+
width: editingWidget.width,
|
|
184
|
+
height: editingWidget.height,
|
|
185
|
+
background: editingWidget.background ?? '#ffffff',
|
|
186
|
+
layers: editingWidget.layers,
|
|
187
|
+
guides: [],
|
|
188
|
+
widgets: value.widgets
|
|
189
|
+
};
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const selectedLayer = $derived(
|
|
193
|
+
selectedIds.length === 1
|
|
194
|
+
? (stageDoc.layers.find((l) => l.id === selectedIds[0]) ?? null)
|
|
195
|
+
: null
|
|
196
|
+
);
|
|
197
|
+
const zoomLabel = $derived(zoom === 1 ? 'Fit' : `${Math.round(zoom * 100)}%`);
|
|
198
|
+
const presetValue = $derived(presetIdForSize(value.width, value.height));
|
|
199
|
+
const presetOptions = $derived([
|
|
200
|
+
...CANVAS_PRESETS.map((p) => ({ value: p.id, label: p.label })),
|
|
201
|
+
...(presetValue === 'custom'
|
|
202
|
+
? [{ value: 'custom', label: `${value.width}×${value.height}` }]
|
|
203
|
+
: [])
|
|
204
|
+
]);
|
|
205
|
+
|
|
206
|
+
const wrapItems: DropdownItem[] = [
|
|
207
|
+
{
|
|
208
|
+
id: 'group',
|
|
209
|
+
label: 'Group',
|
|
210
|
+
icon: Group,
|
|
211
|
+
description: 'Logical folder — no layout'
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: 'border',
|
|
215
|
+
label: 'Border',
|
|
216
|
+
description: 'Background panel that can clip children'
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
id: 'overlay',
|
|
220
|
+
label: 'Overlay',
|
|
221
|
+
description: 'Stack children on top of each other'
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
id: 'canvasPanel',
|
|
225
|
+
label: 'Canvas Panel',
|
|
226
|
+
description: 'Absolute-positioned children (UMG canvas)'
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
id: 'hBox',
|
|
230
|
+
label: 'Horizontal Box',
|
|
231
|
+
description: 'Lay out children in a row'
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
id: 'vBox',
|
|
235
|
+
label: 'Vertical Box',
|
|
236
|
+
description: 'Lay out children in a column'
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
id: 'scrollBox',
|
|
240
|
+
label: 'Scroll Box',
|
|
241
|
+
description: 'Scrollable container'
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
id: 'wrapBox',
|
|
245
|
+
label: 'Wrap Box',
|
|
246
|
+
description: 'Flow children and wrap to next line'
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
id: 'uniformGrid',
|
|
250
|
+
label: 'Uniform Grid',
|
|
251
|
+
description: 'Fixed columns grid'
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
id: 'sizeBox',
|
|
255
|
+
label: 'Size Box',
|
|
256
|
+
description: 'Constrain child size'
|
|
257
|
+
},
|
|
258
|
+
{ id: 'sep-w', label: '', separator: true },
|
|
259
|
+
{
|
|
260
|
+
id: 'widget',
|
|
261
|
+
label: 'Create Widget',
|
|
262
|
+
icon: Component,
|
|
263
|
+
description: 'Reusable User Widget from selection'
|
|
264
|
+
},
|
|
265
|
+
{ id: 'sep-u', label: '', separator: true },
|
|
266
|
+
{
|
|
267
|
+
id: 'detach',
|
|
268
|
+
label: 'Remove from parent',
|
|
269
|
+
description: 'Move out of group / wrapper (keep parent)'
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
id: 'ungroup',
|
|
273
|
+
label: 'Ungroup / unwrap',
|
|
274
|
+
description: 'Dissolve selected group and keep children'
|
|
275
|
+
}
|
|
276
|
+
];
|
|
277
|
+
|
|
278
|
+
const alignItems: DropdownItem[] = [
|
|
279
|
+
{ id: 'left', label: 'Align left', icon: AlignStartVertical },
|
|
280
|
+
{ id: 'center-h', label: 'Align center', icon: AlignCenterVertical },
|
|
281
|
+
{ id: 'right', label: 'Align right', icon: AlignEndVertical },
|
|
282
|
+
{ id: 'sep-a', label: '', separator: true },
|
|
283
|
+
{ id: 'top', label: 'Align top', icon: AlignStartHorizontal },
|
|
284
|
+
{ id: 'center-v', label: 'Align middle', icon: AlignCenterHorizontal },
|
|
285
|
+
{ id: 'bottom', label: 'Align bottom', icon: AlignEndHorizontal },
|
|
286
|
+
{ id: 'sep-b', label: '', separator: true },
|
|
287
|
+
{ id: 'center', label: 'Center on canvas', icon: AlignCenter },
|
|
288
|
+
{ id: 'full', label: 'Fill canvas', icon: Maximize2 }
|
|
289
|
+
];
|
|
290
|
+
|
|
291
|
+
const exportItems: DropdownItem[] = [
|
|
292
|
+
{ id: 'png', label: 'PNG image', icon: ImageIcon, description: 'Lossless · transparent ok' },
|
|
293
|
+
{ id: 'jpeg', label: 'JPEG image', icon: FileImage, description: 'Smaller file size' },
|
|
294
|
+
{ id: 'sep-e', label: '', separator: true },
|
|
295
|
+
{ id: 'json', label: 'JSON document', icon: FileJson, description: 'Editable project data' }
|
|
296
|
+
];
|
|
297
|
+
|
|
298
|
+
const ctxItems = $derived.by((): ContextMenuItem[] => {
|
|
299
|
+
const focusIds =
|
|
300
|
+
ctxLayerId && !selectedIds.includes(ctxLayerId) ? [ctxLayerId] : selectedIds;
|
|
301
|
+
const hasParent = focusIds.some((id) => {
|
|
302
|
+
const l = stageDoc.layers.find((x) => x.id === id);
|
|
303
|
+
return !!l?.parentId;
|
|
304
|
+
});
|
|
305
|
+
const canUngroup = focusIds.some((id) => {
|
|
306
|
+
const l = stageDoc.layers.find((x) => x.id === id);
|
|
307
|
+
return !!l && (l.kind === 'group' || isContainerKind(l.kind));
|
|
308
|
+
});
|
|
309
|
+
return [
|
|
310
|
+
{ id: 'duplicate', label: 'Duplicate', shortcut: '⌘D' },
|
|
311
|
+
{ id: 'bring-front', label: 'Bring to front' },
|
|
312
|
+
{ id: 'send-back', label: 'Send to back' },
|
|
313
|
+
{ id: 'sep-wrap', label: '', separator: true },
|
|
314
|
+
{ id: 'hdr-wrap', label: 'Add parent', header: true },
|
|
315
|
+
{
|
|
316
|
+
id: 'group',
|
|
317
|
+
label: 'Group',
|
|
318
|
+
shortcut: '⌘G',
|
|
319
|
+
description: 'Logical folder — no layout'
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
id: 'wrap-border',
|
|
323
|
+
label: 'Border',
|
|
324
|
+
description: 'Background panel that can clip children'
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
id: 'wrap-overlay',
|
|
328
|
+
label: 'Overlay',
|
|
329
|
+
description: 'Stack children on top of each other'
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
id: 'wrap-canvasPanel',
|
|
333
|
+
label: 'Canvas Panel',
|
|
334
|
+
description: 'Absolute-positioned children'
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
id: 'wrap-hBox',
|
|
338
|
+
label: 'Horizontal Box',
|
|
339
|
+
description: 'Lay out children in a row'
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
id: 'wrap-vBox',
|
|
343
|
+
label: 'Vertical Box',
|
|
344
|
+
description: 'Lay out children in a column'
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
id: 'wrap-scrollBox',
|
|
348
|
+
label: 'Scroll Box',
|
|
349
|
+
description: 'Scrollable container'
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
id: 'wrap-wrapBox',
|
|
353
|
+
label: 'Wrap Box',
|
|
354
|
+
description: 'Flow children and wrap lines'
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
id: 'wrap-uniformGrid',
|
|
358
|
+
label: 'Uniform Grid',
|
|
359
|
+
description: 'Fixed columns grid'
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
id: 'create-widget',
|
|
363
|
+
label: 'Create Widget',
|
|
364
|
+
description: 'Reusable User Widget from selection'
|
|
365
|
+
},
|
|
366
|
+
{ id: 'sep-detach', label: '', separator: true },
|
|
367
|
+
{
|
|
368
|
+
id: 'detach',
|
|
369
|
+
label: 'Remove from parent',
|
|
370
|
+
disabled: !hasParent,
|
|
371
|
+
description: 'Move out of group / wrapper'
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
id: 'ungroup',
|
|
375
|
+
label: 'Ungroup / unwrap',
|
|
376
|
+
shortcut: '⇧⌘G',
|
|
377
|
+
disabled: !canUngroup,
|
|
378
|
+
description: 'Dissolve selected container'
|
|
379
|
+
},
|
|
380
|
+
{ id: 'sep1', label: '', separator: true },
|
|
381
|
+
{ id: 'lock', label: 'Lock / Unlock' },
|
|
382
|
+
{ id: 'hide', label: 'Hide' },
|
|
383
|
+
{ id: 'sep2', label: '', separator: true },
|
|
384
|
+
{ id: 'delete', label: 'Delete', destructive: true, shortcut: '⌫' }
|
|
385
|
+
];
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
function emit(next: CanvasDocument) {
|
|
389
|
+
const migrated = migrateCanvasDocument(next);
|
|
390
|
+
if (!applyingHistory) {
|
|
391
|
+
history = pushCanvasHistory(history, migrated);
|
|
392
|
+
} else {
|
|
393
|
+
history = { ...history, last: JSON.stringify(migrated) };
|
|
394
|
+
}
|
|
395
|
+
value = migrated;
|
|
396
|
+
onchange?.(value);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function undo() {
|
|
400
|
+
const result = undoCanvasHistory(history);
|
|
401
|
+
if (!result) return;
|
|
402
|
+
applyingHistory = true;
|
|
403
|
+
history = result.state;
|
|
404
|
+
value = migrateCanvasDocument(result.doc);
|
|
405
|
+
applyingHistory = false;
|
|
406
|
+
onchange?.(value);
|
|
407
|
+
selectedIds = selectedIds.filter((id) => stageDoc.layers.some((l) => l.id === id));
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function redo() {
|
|
411
|
+
const result = redoCanvasHistory(history);
|
|
412
|
+
if (!result) return;
|
|
413
|
+
applyingHistory = true;
|
|
414
|
+
history = result.state;
|
|
415
|
+
value = migrateCanvasDocument(result.doc);
|
|
416
|
+
applyingHistory = false;
|
|
417
|
+
onchange?.(value);
|
|
418
|
+
selectedIds = selectedIds.filter((id) => stageDoc.layers.some((l) => l.id === id));
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function emitStageLayers(layers: CanvasLayer[], extra?: Partial<CanvasDocument>) {
|
|
422
|
+
if (editingWidgetId) {
|
|
423
|
+
let next = updateWidgetDefinition(value, editingWidgetId, (d) => ({
|
|
424
|
+
...d,
|
|
425
|
+
layers,
|
|
426
|
+
width: extra?.width ?? d.width,
|
|
427
|
+
height: extra?.height ?? d.height,
|
|
428
|
+
background: extra?.background ?? d.background
|
|
429
|
+
}));
|
|
430
|
+
const sizeChanged =
|
|
431
|
+
extra?.width != null || extra?.height != null;
|
|
432
|
+
if (sizeChanged && syncInstanceSizes) {
|
|
433
|
+
next = syncInstanceSizesToDefinition(next, editingWidgetId);
|
|
434
|
+
}
|
|
435
|
+
emit(next);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
emit({ ...value, ...extra, layers });
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function patchLayer(layer: CanvasLayer) {
|
|
442
|
+
emitStageLayers(stageDoc.layers.map((l) => (l.id === layer.id ? layer : l)));
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function addLayer(layer: CanvasLayer, opts?: { parentId?: string | null }) {
|
|
446
|
+
let parentId: string | null = null;
|
|
447
|
+
if (opts?.parentId !== undefined) {
|
|
448
|
+
if (opts.parentId) {
|
|
449
|
+
const candidate = stageDoc.layers.find((l) => l.id === opts.parentId);
|
|
450
|
+
if (candidate && isContainerKind(candidate.kind)) parentId = candidate.id;
|
|
451
|
+
}
|
|
452
|
+
} else if (selectedIds.length === 1) {
|
|
453
|
+
const candidate = stageDoc.layers.find((l) => l.id === selectedIds[0]);
|
|
454
|
+
if (candidate && isContainerKind(candidate.kind)) parentId = candidate.id;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
let nextLayers = [...stageDoc.layers, layer];
|
|
458
|
+
if (parentId) {
|
|
459
|
+
nextLayers = reparentLayer(nextLayers, layer.id, parentId, {
|
|
460
|
+
width: stageDoc.width,
|
|
461
|
+
height: stageDoc.height
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
emitStageLayers(nextLayers);
|
|
465
|
+
selectedIds = [layer.id];
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function nextOffset() {
|
|
469
|
+
const n = stageDoc.layers.length;
|
|
470
|
+
return { x: 80 + (n % 8) * 24, y: 80 + (n % 8) * 24 };
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function addFromAsset(asset: MediaAsset) {
|
|
474
|
+
const kind = asset.kind === 'audio' ? 'image' : asset.kind;
|
|
475
|
+
const offset = nextOffset();
|
|
476
|
+
addLayer(
|
|
477
|
+
createCanvasLayer(kind, {
|
|
478
|
+
name: asset.name,
|
|
479
|
+
src: asset.src,
|
|
480
|
+
text: asset.kind === 'text' ? 'Text' : undefined,
|
|
481
|
+
zIndex: stageDoc.layers.length,
|
|
482
|
+
rect: {
|
|
483
|
+
x: offset.x,
|
|
484
|
+
y: offset.y,
|
|
485
|
+
w: asset.kind === 'text' ? 280 : 320,
|
|
486
|
+
h: asset.kind === 'text' ? 80 : 240
|
|
487
|
+
}
|
|
488
|
+
})
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function addElement(
|
|
493
|
+
def: CanvasElementDef,
|
|
494
|
+
at?: { x?: number; y?: number; parentId?: string | null }
|
|
495
|
+
) {
|
|
496
|
+
if (def.tool === 'pen') {
|
|
497
|
+
drawMode = true;
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
const base = def.partial ?? {};
|
|
501
|
+
const defaults = { w: 240, h: 160 };
|
|
502
|
+
const rectSize = base.rect ?? defaults;
|
|
503
|
+
let w = rectSize.w;
|
|
504
|
+
let h = rectSize.h;
|
|
505
|
+
let x: number;
|
|
506
|
+
let y: number;
|
|
507
|
+
|
|
508
|
+
// Resolve nest target the same way addLayer does (selected container or explicit parentId).
|
|
509
|
+
let nestParentId: string | null | undefined = at?.parentId;
|
|
510
|
+
if (nestParentId === undefined && selectedIds.length === 1) {
|
|
511
|
+
const candidate = stageDoc.layers.find((l) => l.id === selectedIds[0]);
|
|
512
|
+
if (candidate && isContainerKind(candidate.kind)) nestParentId = candidate.id;
|
|
513
|
+
}
|
|
514
|
+
const nesting =
|
|
515
|
+
nestParentId !== undefined ? { parentId: nestParentId } : undefined;
|
|
516
|
+
|
|
517
|
+
const fillRootCanvas =
|
|
518
|
+
def.kind === 'canvasPanel' &&
|
|
519
|
+
!nestParentId &&
|
|
520
|
+
at?.x == null &&
|
|
521
|
+
!stageDoc.layers.some((l) => (l.parentId ?? null) === null && l.kind === 'canvasPanel');
|
|
522
|
+
|
|
523
|
+
if (fillRootCanvas) {
|
|
524
|
+
x = 0;
|
|
525
|
+
y = 0;
|
|
526
|
+
w = stageDoc.width;
|
|
527
|
+
h = stageDoc.height;
|
|
528
|
+
} else if (at?.x != null && at?.y != null) {
|
|
529
|
+
x = Math.min(Math.max(0, Math.round(at.x - w / 2)), Math.max(0, stageDoc.width - w));
|
|
530
|
+
y = Math.min(Math.max(0, Math.round(at.y - h / 2)), Math.max(0, stageDoc.height - h));
|
|
531
|
+
if (snap) {
|
|
532
|
+
x = Math.round(x / cellSize) * cellSize;
|
|
533
|
+
y = Math.round(y / cellSize) * cellSize;
|
|
534
|
+
}
|
|
535
|
+
} else if (nestParentId) {
|
|
536
|
+
// Spawn in absolute coords inside the parent so reparent doesn't create negative locals (e.g. -20).
|
|
537
|
+
const absMap = computeAbsoluteRects(stageDoc.layers, {
|
|
538
|
+
width: stageDoc.width,
|
|
539
|
+
height: stageDoc.height
|
|
540
|
+
});
|
|
541
|
+
const pa = absMap.get(nestParentId);
|
|
542
|
+
const parent = stageDoc.layers.find((l) => l.id === nestParentId);
|
|
543
|
+
if (pa) {
|
|
544
|
+
const pad = parent ? contentPadding(parent) : { left: 0, top: 0, right: 0, bottom: 0 };
|
|
545
|
+
const cw = Math.max(1, pa.w - pad.left - pad.right);
|
|
546
|
+
const ch = Math.max(1, pa.h - pad.top - pad.bottom);
|
|
547
|
+
x = Math.round(pa.x + pad.left + Math.max(0, (cw - w) / 2));
|
|
548
|
+
y = Math.round(pa.y + pad.top + Math.max(0, (ch - h) / 2));
|
|
549
|
+
} else {
|
|
550
|
+
const offset = nextOffset();
|
|
551
|
+
x = offset.x;
|
|
552
|
+
y = offset.y;
|
|
553
|
+
}
|
|
554
|
+
} else {
|
|
555
|
+
const offset = nextOffset();
|
|
556
|
+
x = offset.x;
|
|
557
|
+
y = offset.y;
|
|
558
|
+
}
|
|
559
|
+
addLayer(
|
|
560
|
+
createCanvasLayer(def.kind, {
|
|
561
|
+
...base,
|
|
562
|
+
name: fillRootCanvas ? (base.name ?? 'Root Canvas') : base.name,
|
|
563
|
+
zIndex: stageDoc.layers.length,
|
|
564
|
+
rect: { x, y, w, h },
|
|
565
|
+
slot: fillRootCanvas
|
|
566
|
+
? {
|
|
567
|
+
anchors: { minX: 0, minY: 0, maxX: 1, maxY: 1 },
|
|
568
|
+
offsets: { left: 0, top: 0, right: 0, bottom: 0 }
|
|
569
|
+
}
|
|
570
|
+
: defaultSlotFromRect({ x, y, w, h }),
|
|
571
|
+
slotName: base.slotName,
|
|
572
|
+
columns: base.columns,
|
|
573
|
+
gap: base.gap,
|
|
574
|
+
clipChildren: base.clipChildren
|
|
575
|
+
}),
|
|
576
|
+
nesting
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function setZoom(next: number) {
|
|
581
|
+
zoom = Math.min(3, Math.max(0.25, Math.round(next * 100) / 100));
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function addGuide(orientation: 'horizontal' | 'vertical') {
|
|
585
|
+
const position =
|
|
586
|
+
orientation === 'vertical'
|
|
587
|
+
? Math.round(stageDoc.width / 2)
|
|
588
|
+
: Math.round(stageDoc.height / 2);
|
|
589
|
+
emit({
|
|
590
|
+
...value,
|
|
591
|
+
guides: [...(value.guides ?? []), createCanvasGuide(orientation, position)]
|
|
592
|
+
});
|
|
593
|
+
showGuides = true;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function toggleGuidesLocked() {
|
|
597
|
+
emit({ ...value, guidesLocked: !value.guidesLocked });
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function nudgeSelected(dx: number, dy: number) {
|
|
601
|
+
if (!selectedIds.length) return;
|
|
602
|
+
const set = new Set(selectedIds);
|
|
603
|
+
emitStageLayers(
|
|
604
|
+
stageDoc.layers.map((l) => {
|
|
605
|
+
if (!set.has(l.id) || l.locked) return l;
|
|
606
|
+
const x = l.rect.x + dx;
|
|
607
|
+
const y = l.rect.y + dy;
|
|
608
|
+
const rect = { ...l.rect, x, y };
|
|
609
|
+
return {
|
|
610
|
+
...l,
|
|
611
|
+
rect,
|
|
612
|
+
slot: l.slot
|
|
613
|
+
? {
|
|
614
|
+
...l.slot,
|
|
615
|
+
offsets: {
|
|
616
|
+
...l.slot.offsets,
|
|
617
|
+
left: l.slot.offsets.left + dx,
|
|
618
|
+
top: l.slot.offsets.top + dy
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
: defaultSlotFromRect(rect)
|
|
622
|
+
};
|
|
623
|
+
})
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function applyPreset(id: string) {
|
|
628
|
+
if (editingWidgetId) return;
|
|
629
|
+
const preset = CANVAS_PRESETS.find((p) => p.id === id);
|
|
630
|
+
if (!preset) return;
|
|
631
|
+
emit({ ...value, width: preset.width, height: preset.height });
|
|
632
|
+
zoom = 1;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function alignSelected(align: CanvasAlign) {
|
|
636
|
+
if (!selectedIds.length) return;
|
|
637
|
+
const set = new Set(selectedIds);
|
|
638
|
+
const absMap = computeAbsoluteRects(stageDoc.layers, {
|
|
639
|
+
width: stageDoc.width,
|
|
640
|
+
height: stageDoc.height
|
|
641
|
+
});
|
|
642
|
+
emitStageLayers(
|
|
643
|
+
stageDoc.layers.map((l) => {
|
|
644
|
+
if (!set.has(l.id)) return l;
|
|
645
|
+
const parentId = l.parentId ?? null;
|
|
646
|
+
let bounds = { width: stageDoc.width, height: stageDoc.height };
|
|
647
|
+
if (parentId) {
|
|
648
|
+
const parent = stageDoc.layers.find((p) => p.id === parentId);
|
|
649
|
+
const parentAbs = absMap.get(parentId);
|
|
650
|
+
if (parent && parentAbs) {
|
|
651
|
+
const pad = contentPadding(parent);
|
|
652
|
+
bounds = {
|
|
653
|
+
width: Math.max(1, parentAbs.w - pad.left - pad.right),
|
|
654
|
+
height: Math.max(1, parentAbs.h - pad.top - pad.bottom)
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
const rect = alignLayerRect(l.rect, bounds, align);
|
|
659
|
+
const anchors = l.slot?.anchors ?? { minX: 0, minY: 0, maxX: 0, maxY: 0 };
|
|
660
|
+
const slot = {
|
|
661
|
+
...slotFromLocalRect(bounds, rect, anchors),
|
|
662
|
+
padding: l.slot?.padding,
|
|
663
|
+
sizeRule: l.slot?.sizeRule,
|
|
664
|
+
alignment: l.slot?.alignment,
|
|
665
|
+
order: l.slot?.order
|
|
666
|
+
};
|
|
667
|
+
return { ...l, rect, slot };
|
|
668
|
+
})
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function deleteSelected() {
|
|
673
|
+
if (!selectedIds.length) return;
|
|
674
|
+
const next = deleteSubtree(
|
|
675
|
+
{ ...stageDoc, widgets: value.widgets },
|
|
676
|
+
selectedIds
|
|
677
|
+
);
|
|
678
|
+
emitStageLayers(next.layers);
|
|
679
|
+
selectedIds = [];
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function duplicateSelected() {
|
|
683
|
+
const result = duplicateSubtree(
|
|
684
|
+
{ ...stageDoc, widgets: value.widgets },
|
|
685
|
+
selectedIds
|
|
686
|
+
);
|
|
687
|
+
emitStageLayers(result.doc.layers);
|
|
688
|
+
selectedIds = result.newIds;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function bringFront() {
|
|
692
|
+
const set = new Set(selectedIds);
|
|
693
|
+
const byParent = new Map<string | null, string[]>();
|
|
694
|
+
for (const l of stageDoc.layers) {
|
|
695
|
+
if (!set.has(l.id)) continue;
|
|
696
|
+
const pid = l.parentId ?? null;
|
|
697
|
+
const list = byParent.get(pid) ?? [];
|
|
698
|
+
list.push(l.id);
|
|
699
|
+
byParent.set(pid, list);
|
|
700
|
+
}
|
|
701
|
+
let layers = stageDoc.layers;
|
|
702
|
+
for (const [pid, ids] of byParent) {
|
|
703
|
+
const siblings = layers
|
|
704
|
+
.filter((l) => (l.parentId ?? null) === pid)
|
|
705
|
+
.sort((a, b) => a.zIndex - b.zIndex)
|
|
706
|
+
.map((l) => l.id);
|
|
707
|
+
const rest = siblings.filter((id) => !set.has(id));
|
|
708
|
+
const selected = siblings.filter((id) => set.has(id));
|
|
709
|
+
layers = reorderSiblings(layers, pid, [...rest, ...selected]);
|
|
710
|
+
}
|
|
711
|
+
emitStageLayers(layers);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function sendBack() {
|
|
715
|
+
const set = new Set(selectedIds);
|
|
716
|
+
let layers = stageDoc.layers;
|
|
717
|
+
const parents = new Set(stageDoc.layers.filter((l) => set.has(l.id)).map((l) => l.parentId ?? null));
|
|
718
|
+
for (const pid of parents) {
|
|
719
|
+
const siblings = layers
|
|
720
|
+
.filter((l) => (l.parentId ?? null) === pid)
|
|
721
|
+
.sort((a, b) => a.zIndex - b.zIndex)
|
|
722
|
+
.map((l) => l.id);
|
|
723
|
+
const rest = siblings.filter((id) => !set.has(id));
|
|
724
|
+
const selected = siblings.filter((id) => set.has(id));
|
|
725
|
+
layers = reorderSiblings(layers, pid, [...selected, ...rest]);
|
|
726
|
+
}
|
|
727
|
+
emitStageLayers(layers);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function wrapSelected(kind: CanvasLayerKind) {
|
|
731
|
+
const result = wrapSelection(
|
|
732
|
+
{ ...stageDoc, version: 2, widgets: value.widgets ?? [] },
|
|
733
|
+
selectedIds,
|
|
734
|
+
kind
|
|
735
|
+
);
|
|
736
|
+
if (!result) return;
|
|
737
|
+
emitStageLayers(result.doc.layers);
|
|
738
|
+
selectedIds = [result.wrapperId];
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function groupSelected() {
|
|
742
|
+
const result = groupLayers(
|
|
743
|
+
{ ...stageDoc, version: 2, widgets: value.widgets ?? [] },
|
|
744
|
+
selectedIds
|
|
745
|
+
);
|
|
746
|
+
if (!result) return;
|
|
747
|
+
emitStageLayers(result.doc.layers);
|
|
748
|
+
selectedIds = [result.wrapperId];
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function ungroupSelected() {
|
|
752
|
+
if (selectedIds.length !== 1) return;
|
|
753
|
+
const id = selectedIds[0];
|
|
754
|
+
const layer = stageDoc.layers.find((l) => l.id === id);
|
|
755
|
+
if (!layer || (layer.kind !== 'group' && !isContainerKind(layer.kind))) return;
|
|
756
|
+
const kids = stageDoc.layers.filter((l) => (l.parentId ?? null) === id).map((l) => l.id);
|
|
757
|
+
const next = ungroupLayers({ ...stageDoc, version: 2, widgets: value.widgets ?? [] }, id);
|
|
758
|
+
emitStageLayers(next.layers);
|
|
759
|
+
selectedIds = kids;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/** Move selected layers out of their current parent (grandparent or root). */
|
|
763
|
+
function detachSelected() {
|
|
764
|
+
const rootSize = { width: stageDoc.width, height: stageDoc.height };
|
|
765
|
+
let layers = stageDoc.layers;
|
|
766
|
+
let changed = false;
|
|
767
|
+
for (const id of selectedIds) {
|
|
768
|
+
const layer = layers.find((l) => l.id === id);
|
|
769
|
+
if (!layer?.parentId) continue;
|
|
770
|
+
const parent = layers.find((l) => l.id === layer.parentId);
|
|
771
|
+
const newParentId = parent?.parentId ?? null;
|
|
772
|
+
layers = reparentLayer(layers, id, newParentId, rootSize);
|
|
773
|
+
changed = true;
|
|
774
|
+
}
|
|
775
|
+
if (changed) emitStageLayers(layers);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function createWidgetSelected() {
|
|
779
|
+
if (editingWidgetId) return;
|
|
780
|
+
if (!selectedIds.length) {
|
|
781
|
+
toast.warning('Select one or more layers to create a widget');
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
const result = createWidgetFromSelection(value, selectedIds, 'Widget');
|
|
785
|
+
if (!result) {
|
|
786
|
+
toast.warning('Create Widget failed', 'Layers must share the same parent');
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
emit(result.doc);
|
|
790
|
+
selectedIds = [result.instanceId];
|
|
791
|
+
toast.success('Widget created');
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function handleWrapMenu(id: string) {
|
|
795
|
+
if (id === 'group') groupSelected();
|
|
796
|
+
else if (id === 'widget') createWidgetSelected();
|
|
797
|
+
else if (id === 'detach') detachSelected();
|
|
798
|
+
else if (id === 'ungroup') ungroupSelected();
|
|
799
|
+
else wrapSelected(id as CanvasLayerKind);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function handleCtx(id: string) {
|
|
803
|
+
const targetId = ctxLayerId;
|
|
804
|
+
if (targetId && !selectedIds.includes(targetId)) selectedIds = [targetId];
|
|
805
|
+
if (id === 'duplicate') duplicateSelected();
|
|
806
|
+
else if (id === 'delete') deleteSelected();
|
|
807
|
+
else if (id === 'bring-front') bringFront();
|
|
808
|
+
else if (id === 'send-back') sendBack();
|
|
809
|
+
else if (id === 'group') groupSelected();
|
|
810
|
+
else if (id === 'ungroup') ungroupSelected();
|
|
811
|
+
else if (id === 'detach') detachSelected();
|
|
812
|
+
else if (id.startsWith('wrap-')) wrapSelected(id.slice(5) as CanvasLayerKind);
|
|
813
|
+
else if (id === 'create-widget') createWidgetSelected();
|
|
814
|
+
else if (id === 'lock') {
|
|
815
|
+
const set = new Set(selectedIds);
|
|
816
|
+
emitStageLayers(
|
|
817
|
+
stageDoc.layers.map((l) => (set.has(l.id) ? { ...l, locked: !l.locked } : l))
|
|
818
|
+
);
|
|
819
|
+
} else if (id === 'hide') {
|
|
820
|
+
const set = new Set(selectedIds);
|
|
821
|
+
emitStageLayers(
|
|
822
|
+
stageDoc.layers.map((l) => (set.has(l.id) ? { ...l, visible: false } : l))
|
|
823
|
+
);
|
|
824
|
+
selectedIds = [];
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function handleBulk(id: string) {
|
|
829
|
+
if (id === 'duplicate') duplicateSelected();
|
|
830
|
+
else if (id === 'delete') deleteSelected();
|
|
831
|
+
else if (id === 'lock') {
|
|
832
|
+
const set = new Set(selectedIds);
|
|
833
|
+
emitStageLayers(
|
|
834
|
+
stageDoc.layers.map((l) => (set.has(l.id) ? { ...l, locked: true } : l))
|
|
835
|
+
);
|
|
836
|
+
} else if (id === 'hide') {
|
|
837
|
+
const set = new Set(selectedIds);
|
|
838
|
+
emitStageLayers(
|
|
839
|
+
stageDoc.layers.map((l) => (set.has(l.id) ? { ...l, visible: false } : l))
|
|
840
|
+
);
|
|
841
|
+
selectedIds = [];
|
|
842
|
+
} else if (id === 'front') bringFront();
|
|
843
|
+
else if (id === 'back') sendBack();
|
|
844
|
+
else if (id === 'group') groupSelected();
|
|
845
|
+
else if (id === 'detach') detachSelected();
|
|
846
|
+
else if (id === 'ungroup') ungroupSelected();
|
|
847
|
+
else if (id === 'widget') createWidgetSelected();
|
|
848
|
+
else if (
|
|
849
|
+
[
|
|
850
|
+
'border',
|
|
851
|
+
'overlay',
|
|
852
|
+
'canvasPanel',
|
|
853
|
+
'hBox',
|
|
854
|
+
'vBox',
|
|
855
|
+
'scrollBox',
|
|
856
|
+
'wrapBox',
|
|
857
|
+
'uniformGrid',
|
|
858
|
+
'sizeBox'
|
|
859
|
+
].includes(id)
|
|
860
|
+
) {
|
|
861
|
+
wrapSelected(id as CanvasLayerKind);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function selectFromList(id: string, e?: MouseEvent) {
|
|
866
|
+
if (e?.shiftKey || e?.metaKey || e?.ctrlKey) {
|
|
867
|
+
const set = new Set(selectedIds);
|
|
868
|
+
if (set.has(id)) set.delete(id);
|
|
869
|
+
else set.add(id);
|
|
870
|
+
selectedIds = [...set];
|
|
871
|
+
} else {
|
|
872
|
+
selectedIds = [id];
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
877
|
+
const t = e.target as HTMLElement | null;
|
|
878
|
+
if (t?.closest?.('input, textarea, select, [contenteditable="true"]')) return;
|
|
879
|
+
|
|
880
|
+
if (drawMode) {
|
|
881
|
+
if (e.key === 'Escape') drawMode = false;
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
if (e.key === 'Delete' || e.key === 'Backspace') {
|
|
886
|
+
if (!selectedIds.length) return;
|
|
887
|
+
e.preventDefault();
|
|
888
|
+
deleteSelected();
|
|
889
|
+
}
|
|
890
|
+
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'd') {
|
|
891
|
+
e.preventDefault();
|
|
892
|
+
duplicateSelected();
|
|
893
|
+
}
|
|
894
|
+
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'g') {
|
|
895
|
+
e.preventDefault();
|
|
896
|
+
if (e.shiftKey) ungroupSelected();
|
|
897
|
+
else groupSelected();
|
|
898
|
+
}
|
|
899
|
+
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'z') {
|
|
900
|
+
e.preventDefault();
|
|
901
|
+
if (e.shiftKey) redo();
|
|
902
|
+
else undo();
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'y') {
|
|
906
|
+
e.preventDefault();
|
|
907
|
+
redo();
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'a') {
|
|
911
|
+
e.preventDefault();
|
|
912
|
+
// Contextual select: siblings under shared parent, or root layers
|
|
913
|
+
if (selectedIds.length === 1) {
|
|
914
|
+
const cur = stageDoc.layers.find((l) => l.id === selectedIds[0]);
|
|
915
|
+
const pid = cur?.parentId ?? null;
|
|
916
|
+
selectedIds = stageDoc.layers
|
|
917
|
+
.filter((l) => (l.parentId ?? null) === pid)
|
|
918
|
+
.map((l) => l.id);
|
|
919
|
+
} else if (selectedIds.length > 1) {
|
|
920
|
+
const parents = new Set(
|
|
921
|
+
selectedIds.map((id) => stageDoc.layers.find((l) => l.id === id)?.parentId ?? null)
|
|
922
|
+
);
|
|
923
|
+
if (parents.size === 1) {
|
|
924
|
+
const pid = [...parents][0];
|
|
925
|
+
selectedIds = stageDoc.layers
|
|
926
|
+
.filter((l) => (l.parentId ?? null) === pid)
|
|
927
|
+
.map((l) => l.id);
|
|
928
|
+
} else {
|
|
929
|
+
selectedIds = stageDoc.layers
|
|
930
|
+
.filter((l) => (l.parentId ?? null) === null)
|
|
931
|
+
.map((l) => l.id);
|
|
932
|
+
}
|
|
933
|
+
} else {
|
|
934
|
+
selectedIds = stageDoc.layers
|
|
935
|
+
.filter((l) => (l.parentId ?? null) === null)
|
|
936
|
+
.map((l) => l.id);
|
|
937
|
+
}
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
if ((e.metaKey || e.ctrlKey) && (e.key === '=' || e.key === '+')) {
|
|
941
|
+
e.preventDefault();
|
|
942
|
+
setZoom(zoom + 0.1);
|
|
943
|
+
}
|
|
944
|
+
if ((e.metaKey || e.ctrlKey) && e.key === '-') {
|
|
945
|
+
e.preventDefault();
|
|
946
|
+
setZoom(zoom - 0.1);
|
|
947
|
+
}
|
|
948
|
+
if ((e.metaKey || e.ctrlKey) && e.key === '0') {
|
|
949
|
+
e.preventDefault();
|
|
950
|
+
setZoom(1);
|
|
951
|
+
}
|
|
952
|
+
if (e.key === 'Escape') {
|
|
953
|
+
if (drawMode) {
|
|
954
|
+
drawMode = false;
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
if (selectedIds.length) {
|
|
958
|
+
selectedIds = [];
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
if (editingWidgetId) {
|
|
962
|
+
exitWidgetEdit();
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight' || e.key === 'ArrowUp' || e.key === 'ArrowDown') {
|
|
968
|
+
if (!selectedIds.length) return;
|
|
969
|
+
e.preventDefault();
|
|
970
|
+
const step = e.shiftKey ? (snap ? cellSize * 2 : 10) : snap ? cellSize : 1;
|
|
971
|
+
if (e.key === 'ArrowLeft') nudgeSelected(-step, 0);
|
|
972
|
+
else if (e.key === 'ArrowRight') nudgeSelected(step, 0);
|
|
973
|
+
else if (e.key === 'ArrowUp') nudgeSelected(0, -step);
|
|
974
|
+
else nudgeSelected(0, step);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
async function handleExport(format: CanvasExportFormat) {
|
|
979
|
+
if (exporting) return;
|
|
980
|
+
exporting = true;
|
|
981
|
+
try {
|
|
982
|
+
if (format === 'json') {
|
|
983
|
+
const blob = new Blob([JSON.stringify(value, null, 2)], { type: 'application/json' });
|
|
984
|
+
onexport?.({ format: 'json', document: value, blob });
|
|
985
|
+
downloadBlob(blob, 'canvas-document.json');
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
const blob = await exportCanvasBlob(value, format);
|
|
989
|
+
onexport?.({ format, document: value, blob });
|
|
990
|
+
downloadBlob(blob, format === 'jpeg' ? 'canvas.jpg' : 'canvas.png');
|
|
991
|
+
} catch (err) {
|
|
992
|
+
console.error('Canvas export failed', err);
|
|
993
|
+
} finally {
|
|
994
|
+
exporting = false;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
function exitWidgetEdit() {
|
|
999
|
+
if (!editingWidgetId) return;
|
|
1000
|
+
editingWidgetId = null;
|
|
1001
|
+
history = createCanvasHistory(value);
|
|
1002
|
+
selectedIds = selectedIds.filter((id) => value.layers.some((l) => l.id === id));
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
function enterWidgetEdit(definitionId: string) {
|
|
1006
|
+
const def = (value.widgets ?? []).find((w) => w.id === definitionId);
|
|
1007
|
+
if (!def) return;
|
|
1008
|
+
editingWidgetId = definitionId;
|
|
1009
|
+
history = createCanvasHistory(value);
|
|
1010
|
+
const roots = def.layers.filter((l) => (l.parentId ?? null) === null);
|
|
1011
|
+
selectedIds = roots.length ? [roots.sort((a, b) => a.zIndex - b.zIndex)[0].id] : [];
|
|
1012
|
+
zoom = 1;
|
|
1013
|
+
sidebarTab = 'elements';
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
function deleteWidget(definitionId: string) {
|
|
1017
|
+
const next = deleteWidgetDefinition(value, definitionId);
|
|
1018
|
+
if (editingWidgetId === definitionId) {
|
|
1019
|
+
exitWidgetEdit();
|
|
1020
|
+
selectedIds = [];
|
|
1021
|
+
} else {
|
|
1022
|
+
selectedIds = selectedIds.filter((id) => next.layers.some((l) => l.id === id));
|
|
1023
|
+
}
|
|
1024
|
+
emit(next);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
function renameWidget(definitionId: string) {
|
|
1028
|
+
const def = (value.widgets ?? []).find((w) => w.id === definitionId);
|
|
1029
|
+
if (!def) return;
|
|
1030
|
+
const name = window.prompt('Widget name', def.name);
|
|
1031
|
+
if (name == null) return;
|
|
1032
|
+
emit(renameWidgetDefinition(value, definitionId, name));
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
function duplicateWidget(definitionId: string) {
|
|
1036
|
+
const result = duplicateWidgetDefinition(value, definitionId);
|
|
1037
|
+
if (!result) return;
|
|
1038
|
+
emit(result.doc);
|
|
1039
|
+
toast.success('Widget duplicated');
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
function enterLayer(id: string) {
|
|
1043
|
+
const layer = stageDoc.layers.find((l) => l.id === id);
|
|
1044
|
+
if (!layer) return;
|
|
1045
|
+
if (layer.kind === 'widget' && layer.definitionId && !editingWidgetId) {
|
|
1046
|
+
enterWidgetEdit(layer.definitionId);
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
// Double-click a scene child that lives under a widget instance → enter that blueprint
|
|
1050
|
+
if (!editingWidgetId && layer.parentId) {
|
|
1051
|
+
const parent = stageDoc.layers.find((l) => l.id === layer.parentId);
|
|
1052
|
+
if (parent?.kind === 'widget' && parent.definitionId) {
|
|
1053
|
+
enterWidgetEdit(parent.definitionId);
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
selectedIds = [id];
|
|
1058
|
+
}
|
|
1059
|
+
</script>
|
|
1060
|
+
|
|
1061
|
+
<svelte:window onkeydown={handleKeydown} />
|
|
1062
|
+
|
|
1063
|
+
<div class={['relative flex h-full min-h-0 flex-col', className]}>
|
|
1064
|
+
<header
|
|
1065
|
+
class="flex shrink-0 items-center gap-3 border-b border-border bg-surface-elevated px-3 py-2"
|
|
1066
|
+
>
|
|
1067
|
+
<div class="flex min-w-0 items-center gap-2">
|
|
1068
|
+
{#if editingWidget}
|
|
1069
|
+
<button
|
|
1070
|
+
type="button"
|
|
1071
|
+
class="inline-flex items-center gap-1 rounded-md px-1.5 py-1 text-xs font-medium text-secondary hover:bg-surface-overlay"
|
|
1072
|
+
onclick={() => {
|
|
1073
|
+
exitWidgetEdit();
|
|
1074
|
+
selectedIds = [];
|
|
1075
|
+
}}
|
|
1076
|
+
>
|
|
1077
|
+
<ChevronLeft class="h-3.5 w-3.5" />
|
|
1078
|
+
Scene
|
|
1079
|
+
</button>
|
|
1080
|
+
<span class="text-muted">/</span>
|
|
1081
|
+
<span class="truncate text-sm font-semibold text-primary">{editingWidget.name}</span>
|
|
1082
|
+
{:else}
|
|
1083
|
+
<span class="hidden text-sm font-semibold text-primary sm:inline">Canvas</span>
|
|
1084
|
+
<Select
|
|
1085
|
+
size="sm"
|
|
1086
|
+
class="w-44"
|
|
1087
|
+
options={presetOptions}
|
|
1088
|
+
value={presetValue}
|
|
1089
|
+
onchange={(id) => {
|
|
1090
|
+
if (id !== 'custom') applyPreset(id);
|
|
1091
|
+
}}
|
|
1092
|
+
/>
|
|
1093
|
+
{/if}
|
|
1094
|
+
</div>
|
|
1095
|
+
|
|
1096
|
+
<div class="hidden h-5 w-px bg-border sm:block"></div>
|
|
1097
|
+
|
|
1098
|
+
<div class="flex items-center rounded-lg border border-border bg-surface p-0.5">
|
|
1099
|
+
<IconButton label="Zoom out" size="sm" onclick={() => setZoom(zoom - 0.1)}>
|
|
1100
|
+
<ZoomOut class="h-3.5 w-3.5" />
|
|
1101
|
+
</IconButton>
|
|
1102
|
+
<button
|
|
1103
|
+
type="button"
|
|
1104
|
+
class="min-w-11 rounded-md px-1 py-1 font-mono text-[11px] tabular-nums text-secondary hover:bg-surface-overlay"
|
|
1105
|
+
onclick={() => setZoom(1)}
|
|
1106
|
+
title="Fit to view"
|
|
1107
|
+
>
|
|
1108
|
+
{zoomLabel}
|
|
1109
|
+
</button>
|
|
1110
|
+
<IconButton label="Zoom in" size="sm" onclick={() => setZoom(zoom + 0.1)}>
|
|
1111
|
+
<ZoomIn class="h-3.5 w-3.5" />
|
|
1112
|
+
</IconButton>
|
|
1113
|
+
</div>
|
|
1114
|
+
|
|
1115
|
+
<div class="flex items-center rounded-lg border border-border bg-surface p-0.5">
|
|
1116
|
+
<IconButton
|
|
1117
|
+
label="Undo"
|
|
1118
|
+
size="sm"
|
|
1119
|
+
disabled={!history.past.length}
|
|
1120
|
+
onclick={undo}
|
|
1121
|
+
>
|
|
1122
|
+
<Undo2 class="h-3.5 w-3.5" />
|
|
1123
|
+
</IconButton>
|
|
1124
|
+
<IconButton
|
|
1125
|
+
label="Redo"
|
|
1126
|
+
size="sm"
|
|
1127
|
+
disabled={!history.future.length}
|
|
1128
|
+
onclick={redo}
|
|
1129
|
+
>
|
|
1130
|
+
<Redo2 class="h-3.5 w-3.5" />
|
|
1131
|
+
</IconButton>
|
|
1132
|
+
</div>
|
|
1133
|
+
|
|
1134
|
+
<div class="flex items-center gap-0.5">
|
|
1135
|
+
<Tooltip content="Toggle grid">
|
|
1136
|
+
<IconButton
|
|
1137
|
+
label="Toggle grid"
|
|
1138
|
+
size="sm"
|
|
1139
|
+
onclick={() => (showGrid = !showGrid)}
|
|
1140
|
+
class={showGrid ? 'bg-brand-500/10 text-brand-600' : undefined}
|
|
1141
|
+
>
|
|
1142
|
+
<Grid3x3 class="h-3.5 w-3.5" />
|
|
1143
|
+
</IconButton>
|
|
1144
|
+
</Tooltip>
|
|
1145
|
+
{#if showGrid}
|
|
1146
|
+
<Select
|
|
1147
|
+
size="sm"
|
|
1148
|
+
class="w-[4.5rem] shrink-0"
|
|
1149
|
+
options={gridSizeOptions}
|
|
1150
|
+
value={String(cellSize)}
|
|
1151
|
+
onchange={(v) => (cellSize = Number(v))}
|
|
1152
|
+
/>
|
|
1153
|
+
{/if}
|
|
1154
|
+
<Tooltip content="Snap to grid & guides">
|
|
1155
|
+
<IconButton
|
|
1156
|
+
label="Toggle snap"
|
|
1157
|
+
size="sm"
|
|
1158
|
+
onclick={() => (snap = !snap)}
|
|
1159
|
+
class={snap ? 'bg-brand-500/10 text-brand-600' : undefined}
|
|
1160
|
+
>
|
|
1161
|
+
<Magnet class="h-3.5 w-3.5" />
|
|
1162
|
+
</IconButton>
|
|
1163
|
+
</Tooltip>
|
|
1164
|
+
</div>
|
|
1165
|
+
|
|
1166
|
+
<div class="hidden h-5 w-px bg-border sm:block"></div>
|
|
1167
|
+
|
|
1168
|
+
<div class="flex items-center gap-0.5">
|
|
1169
|
+
<Tooltip content={drawMode ? 'Exit pen tool (Esc)' : 'Pen — draw path / shape'}>
|
|
1170
|
+
<IconButton
|
|
1171
|
+
label="Pen tool"
|
|
1172
|
+
size="sm"
|
|
1173
|
+
onclick={() => (drawMode = !drawMode)}
|
|
1174
|
+
class={drawMode ? 'bg-brand-500/10 text-brand-600' : undefined}
|
|
1175
|
+
>
|
|
1176
|
+
<Pencil class="h-3.5 w-3.5" />
|
|
1177
|
+
</IconButton>
|
|
1178
|
+
</Tooltip>
|
|
1179
|
+
</div>
|
|
1180
|
+
|
|
1181
|
+
<div class="hidden h-5 w-px bg-border sm:block"></div>
|
|
1182
|
+
|
|
1183
|
+
<div class="flex items-center gap-0.5">
|
|
1184
|
+
<Tooltip content={showGuides ? 'Hide guides' : 'Show guides'}>
|
|
1185
|
+
<IconButton
|
|
1186
|
+
label="Toggle guides"
|
|
1187
|
+
size="sm"
|
|
1188
|
+
onclick={() => (showGuides = !showGuides)}
|
|
1189
|
+
class={showGuides ? 'bg-brand-500/10 text-brand-600' : undefined}
|
|
1190
|
+
>
|
|
1191
|
+
<Eye class="h-3.5 w-3.5" />
|
|
1192
|
+
</IconButton>
|
|
1193
|
+
</Tooltip>
|
|
1194
|
+
<Tooltip content="Add vertical guide">
|
|
1195
|
+
<IconButton label="Add vertical guide" size="sm" onclick={() => addGuide('vertical')}>
|
|
1196
|
+
<SeparatorVertical class="h-3.5 w-3.5" />
|
|
1197
|
+
</IconButton>
|
|
1198
|
+
</Tooltip>
|
|
1199
|
+
<Tooltip content="Add horizontal guide">
|
|
1200
|
+
<IconButton label="Add horizontal guide" size="sm" onclick={() => addGuide('horizontal')}>
|
|
1201
|
+
<SeparatorHorizontal class="h-3.5 w-3.5" />
|
|
1202
|
+
</IconButton>
|
|
1203
|
+
</Tooltip>
|
|
1204
|
+
<Tooltip content={value.guidesLocked ? 'Unlock guides' : 'Lock guides'}>
|
|
1205
|
+
<IconButton
|
|
1206
|
+
label={value.guidesLocked ? 'Unlock guides' : 'Lock guides'}
|
|
1207
|
+
size="sm"
|
|
1208
|
+
onclick={toggleGuidesLocked}
|
|
1209
|
+
class={value.guidesLocked ? 'bg-brand-500/10 text-brand-600' : undefined}
|
|
1210
|
+
>
|
|
1211
|
+
{#if value.guidesLocked}
|
|
1212
|
+
<Lock class="h-3.5 w-3.5" />
|
|
1213
|
+
{:else}
|
|
1214
|
+
<Unlock class="h-3.5 w-3.5" />
|
|
1215
|
+
{/if}
|
|
1216
|
+
</IconButton>
|
|
1217
|
+
</Tooltip>
|
|
1218
|
+
</div>
|
|
1219
|
+
|
|
1220
|
+
{#if selectedIds.length}
|
|
1221
|
+
<div class="hidden h-5 w-px bg-border md:block"></div>
|
|
1222
|
+
<div class="hidden items-center gap-1 md:flex">
|
|
1223
|
+
<DropdownMenu
|
|
1224
|
+
size="sm"
|
|
1225
|
+
align="start"
|
|
1226
|
+
label="Align"
|
|
1227
|
+
items={alignItems}
|
|
1228
|
+
onselect={(id) => alignSelected(id as CanvasAlign)}
|
|
1229
|
+
>
|
|
1230
|
+
{#snippet trigger()}
|
|
1231
|
+
<span
|
|
1232
|
+
class="inline-flex items-center gap-1.5 text-xs font-medium text-secondary"
|
|
1233
|
+
>
|
|
1234
|
+
<AlignHorizontalJustifyCenter class="h-3.5 w-3.5" />
|
|
1235
|
+
Align
|
|
1236
|
+
</span>
|
|
1237
|
+
{/snippet}
|
|
1238
|
+
</DropdownMenu>
|
|
1239
|
+
<DropdownMenu
|
|
1240
|
+
size="sm"
|
|
1241
|
+
align="start"
|
|
1242
|
+
label="Group"
|
|
1243
|
+
items={wrapItems}
|
|
1244
|
+
onselect={handleWrapMenu}
|
|
1245
|
+
>
|
|
1246
|
+
{#snippet trigger()}
|
|
1247
|
+
<span class="inline-flex items-center gap-1.5 text-xs font-medium text-secondary">
|
|
1248
|
+
<Group class="h-3.5 w-3.5" />
|
|
1249
|
+
Group
|
|
1250
|
+
</span>
|
|
1251
|
+
{/snippet}
|
|
1252
|
+
</DropdownMenu>
|
|
1253
|
+
</div>
|
|
1254
|
+
{/if}
|
|
1255
|
+
|
|
1256
|
+
<div class="ml-auto flex items-center gap-1">
|
|
1257
|
+
<IconButton
|
|
1258
|
+
label="Toggle sidebar"
|
|
1259
|
+
size="sm"
|
|
1260
|
+
onclick={() => (showSidebar = !showSidebar)}
|
|
1261
|
+
class={!showSidebar ? 'text-muted' : undefined}
|
|
1262
|
+
>
|
|
1263
|
+
<PanelLeft class="h-3.5 w-3.5" />
|
|
1264
|
+
</IconButton>
|
|
1265
|
+
<IconButton
|
|
1266
|
+
label="Toggle inspector"
|
|
1267
|
+
size="sm"
|
|
1268
|
+
onclick={() => (showInspector = !showInspector)}
|
|
1269
|
+
class={!showInspector ? 'text-muted' : undefined}
|
|
1270
|
+
>
|
|
1271
|
+
<PanelRight class="h-3.5 w-3.5" />
|
|
1272
|
+
</IconButton>
|
|
1273
|
+
|
|
1274
|
+
<DropdownMenu
|
|
1275
|
+
size="sm"
|
|
1276
|
+
align="end"
|
|
1277
|
+
label="Export"
|
|
1278
|
+
items={exportItems}
|
|
1279
|
+
disabled={exporting}
|
|
1280
|
+
onselect={(id) => handleExport(id as CanvasExportFormat)}
|
|
1281
|
+
>
|
|
1282
|
+
{#snippet trigger()}
|
|
1283
|
+
<span class="inline-flex items-center gap-1.5 px-1 text-xs font-medium">
|
|
1284
|
+
<Download class="h-3.5 w-3.5" />
|
|
1285
|
+
{exporting ? 'Exporting…' : 'Export'}
|
|
1286
|
+
</span>
|
|
1287
|
+
{/snippet}
|
|
1288
|
+
</DropdownMenu>
|
|
1289
|
+
</div>
|
|
1290
|
+
</header>
|
|
1291
|
+
|
|
1292
|
+
<div class="relative min-h-0 flex-1">
|
|
1293
|
+
<EditorShell
|
|
1294
|
+
sidebarTitle="Assets"
|
|
1295
|
+
inspectorTitle="Inspector"
|
|
1296
|
+
workspaceTitle="Artboard"
|
|
1297
|
+
showBottom={false}
|
|
1298
|
+
{showSidebar}
|
|
1299
|
+
{showInspector}
|
|
1300
|
+
wrapSidebar={false}
|
|
1301
|
+
wrapInspector={true}
|
|
1302
|
+
wrapWorkspace={false}
|
|
1303
|
+
collapsiblePanels={false}
|
|
1304
|
+
sidebarSize={300}
|
|
1305
|
+
inspectorSize={300}
|
|
1306
|
+
sizeUnit="px"
|
|
1307
|
+
sidebarMin={220}
|
|
1308
|
+
sidebarMax={480}
|
|
1309
|
+
inspectorMin={240}
|
|
1310
|
+
inspectorMax={420}
|
|
1311
|
+
fullHeight
|
|
1312
|
+
class="rounded-none"
|
|
1313
|
+
>
|
|
1314
|
+
{#snippet sidebar()}
|
|
1315
|
+
<SplitPane
|
|
1316
|
+
orientation="vertical"
|
|
1317
|
+
bind:size={assetsSize}
|
|
1318
|
+
unit="percent"
|
|
1319
|
+
minSize={28}
|
|
1320
|
+
maxSize={78}
|
|
1321
|
+
revealOnHover
|
|
1322
|
+
class="h-full"
|
|
1323
|
+
>
|
|
1324
|
+
{#snippet start()}
|
|
1325
|
+
<div class="flex h-full min-h-0 flex-col bg-surface-elevated">
|
|
1326
|
+
<div class="flex shrink-0 gap-1 border-b border-border p-2">
|
|
1327
|
+
<button
|
|
1328
|
+
type="button"
|
|
1329
|
+
class={[
|
|
1330
|
+
'flex-1 rounded-md px-2 py-1.5 text-xs font-medium transition-colors',
|
|
1331
|
+
sidebarTab === 'elements'
|
|
1332
|
+
? 'bg-brand-500 text-white'
|
|
1333
|
+
: 'text-secondary hover:bg-surface-overlay'
|
|
1334
|
+
]}
|
|
1335
|
+
onclick={() => (sidebarTab = 'elements')}
|
|
1336
|
+
>
|
|
1337
|
+
Elements
|
|
1338
|
+
</button>
|
|
1339
|
+
<button
|
|
1340
|
+
type="button"
|
|
1341
|
+
class={[
|
|
1342
|
+
'flex-1 rounded-md px-2 py-1.5 text-xs font-medium transition-colors',
|
|
1343
|
+
sidebarTab === 'widgets'
|
|
1344
|
+
? 'bg-brand-500 text-white'
|
|
1345
|
+
: 'text-secondary hover:bg-surface-overlay'
|
|
1346
|
+
]}
|
|
1347
|
+
onclick={() => (sidebarTab = 'widgets')}
|
|
1348
|
+
>
|
|
1349
|
+
Widgets
|
|
1350
|
+
</button>
|
|
1351
|
+
<button
|
|
1352
|
+
type="button"
|
|
1353
|
+
class={[
|
|
1354
|
+
'flex-1 rounded-md px-2 py-1.5 text-xs font-medium transition-colors',
|
|
1355
|
+
sidebarTab === 'uploads'
|
|
1356
|
+
? 'bg-brand-500 text-white'
|
|
1357
|
+
: 'text-secondary hover:bg-surface-overlay'
|
|
1358
|
+
]}
|
|
1359
|
+
onclick={() => (sidebarTab = 'uploads')}
|
|
1360
|
+
>
|
|
1361
|
+
Uploads
|
|
1362
|
+
</button>
|
|
1363
|
+
</div>
|
|
1364
|
+
<div class="min-h-0 flex-1 overflow-auto">
|
|
1365
|
+
{#if sidebarTab === 'elements'}
|
|
1366
|
+
<CanvasElementsPanel onadd={addElement} />
|
|
1367
|
+
{:else if sidebarTab === 'widgets'}
|
|
1368
|
+
<div class="flex flex-col gap-2 p-3">
|
|
1369
|
+
<p class="text-[11px] text-muted">
|
|
1370
|
+
Click to place · pencil to edit · dblclick name to rename
|
|
1371
|
+
</p>
|
|
1372
|
+
<label class="flex items-center gap-2 text-[11px] text-secondary">
|
|
1373
|
+
<input
|
|
1374
|
+
type="checkbox"
|
|
1375
|
+
class="rounded border-border"
|
|
1376
|
+
bind:checked={syncInstanceSizes}
|
|
1377
|
+
/>
|
|
1378
|
+
Match instance size when blueprint size changes
|
|
1379
|
+
</label>
|
|
1380
|
+
{#each value.widgets ?? [] as w (w.id)}
|
|
1381
|
+
<div
|
|
1382
|
+
class={[
|
|
1383
|
+
'flex items-center gap-0.5 rounded-lg border bg-surface px-1 py-1 text-xs',
|
|
1384
|
+
editingWidgetId === w.id
|
|
1385
|
+
? 'border-brand-500 ring-1 ring-brand-500/30'
|
|
1386
|
+
: 'border-border'
|
|
1387
|
+
]}
|
|
1388
|
+
>
|
|
1389
|
+
<button
|
|
1390
|
+
type="button"
|
|
1391
|
+
class="flex min-w-0 flex-1 items-center gap-2 rounded-md px-1.5 py-1.5 text-left hover:bg-surface-overlay"
|
|
1392
|
+
title="Place instance on canvas"
|
|
1393
|
+
onclick={() => {
|
|
1394
|
+
if (editingWidgetId) {
|
|
1395
|
+
enterWidgetEdit(w.id);
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
const next = placeWidgetInstance(value, w.id);
|
|
1399
|
+
if (next) {
|
|
1400
|
+
emit(next);
|
|
1401
|
+
const inst = next.layers[next.layers.length - 1];
|
|
1402
|
+
selectedIds = [inst.id];
|
|
1403
|
+
}
|
|
1404
|
+
}}
|
|
1405
|
+
>
|
|
1406
|
+
<Component class="h-4 w-4 shrink-0 text-brand-600" />
|
|
1407
|
+
<span class="min-w-0 flex-1 truncate font-medium" title={w.name}>
|
|
1408
|
+
{w.name}
|
|
1409
|
+
</span>
|
|
1410
|
+
<span class="shrink-0 text-[10px] text-muted"
|
|
1411
|
+
>{w.width}×{w.height}</span
|
|
1412
|
+
>
|
|
1413
|
+
</button>
|
|
1414
|
+
<IconButton
|
|
1415
|
+
label="Rename widget"
|
|
1416
|
+
size="xs"
|
|
1417
|
+
onclick={(e) => {
|
|
1418
|
+
e.stopPropagation();
|
|
1419
|
+
renameWidget(w.id);
|
|
1420
|
+
}}
|
|
1421
|
+
>
|
|
1422
|
+
<TextCursorInput class="h-3.5 w-3.5" />
|
|
1423
|
+
</IconButton>
|
|
1424
|
+
<IconButton
|
|
1425
|
+
label="Edit widget"
|
|
1426
|
+
size="xs"
|
|
1427
|
+
onclick={(e) => {
|
|
1428
|
+
e.stopPropagation();
|
|
1429
|
+
enterWidgetEdit(w.id);
|
|
1430
|
+
}}
|
|
1431
|
+
>
|
|
1432
|
+
<Pencil class="h-3.5 w-3.5" />
|
|
1433
|
+
</IconButton>
|
|
1434
|
+
<IconButton
|
|
1435
|
+
label="Duplicate widget"
|
|
1436
|
+
size="xs"
|
|
1437
|
+
onclick={(e) => {
|
|
1438
|
+
e.stopPropagation();
|
|
1439
|
+
duplicateWidget(w.id);
|
|
1440
|
+
}}
|
|
1441
|
+
>
|
|
1442
|
+
<CopyPlus class="h-3.5 w-3.5" />
|
|
1443
|
+
</IconButton>
|
|
1444
|
+
<IconButton
|
|
1445
|
+
label="Delete widget"
|
|
1446
|
+
size="xs"
|
|
1447
|
+
onclick={(e) => {
|
|
1448
|
+
e.stopPropagation();
|
|
1449
|
+
deleteWidget(w.id);
|
|
1450
|
+
}}
|
|
1451
|
+
>
|
|
1452
|
+
<Trash2 class="h-3.5 w-3.5" />
|
|
1453
|
+
</IconButton>
|
|
1454
|
+
</div>
|
|
1455
|
+
{:else}
|
|
1456
|
+
<p class="py-6 text-center text-xs text-muted">
|
|
1457
|
+
Select layers and Create Widget, or Group → Create Widget
|
|
1458
|
+
</p>
|
|
1459
|
+
{/each}
|
|
1460
|
+
</div>
|
|
1461
|
+
{:else}
|
|
1462
|
+
<MediaAssetBrowser
|
|
1463
|
+
bind:assets
|
|
1464
|
+
selectedId={null}
|
|
1465
|
+
accept="image/*,video/*"
|
|
1466
|
+
helperText="Images or video"
|
|
1467
|
+
allowText={false}
|
|
1468
|
+
onassetschange={onassetschange}
|
|
1469
|
+
onselect={(id) => {
|
|
1470
|
+
const asset = assets.find((a) => a.id === id);
|
|
1471
|
+
if (asset) addFromAsset(asset);
|
|
1472
|
+
}}
|
|
1473
|
+
/>
|
|
1474
|
+
{/if}
|
|
1475
|
+
</div>
|
|
1476
|
+
</div>
|
|
1477
|
+
{/snippet}
|
|
1478
|
+
{#snippet end()}
|
|
1479
|
+
<Panel title="Layers" collapsible={false} class="h-full border-0 shadow-none">
|
|
1480
|
+
<MediaLayerList
|
|
1481
|
+
layers={stageDoc.layers.map((l) => {
|
|
1482
|
+
if (l.kind !== 'widget' || !l.definitionId) return l;
|
|
1483
|
+
const def = (value.widgets ?? []).find((w) => w.id === l.definitionId);
|
|
1484
|
+
const namedSlotCount = def
|
|
1485
|
+
? def.layers.filter((x) => x.kind === 'namedSlot').length
|
|
1486
|
+
: 0;
|
|
1487
|
+
return { ...l, namedSlotCount };
|
|
1488
|
+
})}
|
|
1489
|
+
{selectedIds}
|
|
1490
|
+
onselect={selectFromList}
|
|
1491
|
+
ontogglevisible={(id) =>
|
|
1492
|
+
emitStageLayers(
|
|
1493
|
+
stageDoc.layers.map((l) =>
|
|
1494
|
+
l.id === id ? { ...l, visible: !l.visible } : l
|
|
1495
|
+
)
|
|
1496
|
+
)}
|
|
1497
|
+
ontogglelocked={(id) =>
|
|
1498
|
+
emitStageLayers(
|
|
1499
|
+
stageDoc.layers.map((l) =>
|
|
1500
|
+
l.id === id ? { ...l, locked: !l.locked } : l
|
|
1501
|
+
)
|
|
1502
|
+
)}
|
|
1503
|
+
onreorder={(ids, parentId) =>
|
|
1504
|
+
emitStageLayers(reorderSiblings(stageDoc.layers, parentId, ids))}
|
|
1505
|
+
onreparent={(layerId, newParentId) =>
|
|
1506
|
+
emitStageLayers(
|
|
1507
|
+
reparentLayer(stageDoc.layers, layerId, newParentId, {
|
|
1508
|
+
width: stageDoc.width,
|
|
1509
|
+
height: stageDoc.height
|
|
1510
|
+
})
|
|
1511
|
+
)}
|
|
1512
|
+
ondropelement={({ def, parentId }) => addElement(def, { parentId })}
|
|
1513
|
+
oneditwidget={(layerId) => {
|
|
1514
|
+
const layer = stageDoc.layers.find((l) => l.id === layerId);
|
|
1515
|
+
if (layer?.kind === 'widget' && layer.definitionId) {
|
|
1516
|
+
enterWidgetEdit(layer.definitionId);
|
|
1517
|
+
}
|
|
1518
|
+
}}
|
|
1519
|
+
/>
|
|
1520
|
+
</Panel>
|
|
1521
|
+
{/snippet}
|
|
1522
|
+
</SplitPane>
|
|
1523
|
+
{/snippet}
|
|
1524
|
+
|
|
1525
|
+
{#snippet workspace()}
|
|
1526
|
+
<div class="h-full min-h-0">
|
|
1527
|
+
<MediaStage
|
|
1528
|
+
document={stageDoc}
|
|
1529
|
+
{selectedIds}
|
|
1530
|
+
{zoom}
|
|
1531
|
+
{showGrid}
|
|
1532
|
+
{showGuides}
|
|
1533
|
+
{snap}
|
|
1534
|
+
{cellSize}
|
|
1535
|
+
{drawMode}
|
|
1536
|
+
expandWidgets={!editingWidget}
|
|
1537
|
+
onselect={(ids) => (selectedIds = ids)}
|
|
1538
|
+
onzoom={setZoom}
|
|
1539
|
+
onlayerchange={patchLayer}
|
|
1540
|
+
ondocumentchange={(doc) => {
|
|
1541
|
+
if (editingWidget) {
|
|
1542
|
+
emitStageLayers(doc.layers, {
|
|
1543
|
+
width: doc.width,
|
|
1544
|
+
height: doc.height,
|
|
1545
|
+
background: doc.background
|
|
1546
|
+
});
|
|
1547
|
+
} else {
|
|
1548
|
+
emit(doc);
|
|
1549
|
+
if ((doc.guides?.length ?? 0) > 0) showGuides = true;
|
|
1550
|
+
}
|
|
1551
|
+
}}
|
|
1552
|
+
oncontextlayer={({ id, x, y }) => {
|
|
1553
|
+
ctxLayerId = id;
|
|
1554
|
+
ctxAnchor = { x, y };
|
|
1555
|
+
ctxOpen = true;
|
|
1556
|
+
}}
|
|
1557
|
+
ondropelement={({ def, x, y }) => addElement(def, { x, y })}
|
|
1558
|
+
ondrawcomplete={({ points, closed }) => {
|
|
1559
|
+
const layer = createPathFromDocPoints(points, {
|
|
1560
|
+
closed,
|
|
1561
|
+
zIndex: stageDoc.layers.length
|
|
1562
|
+
});
|
|
1563
|
+
addLayer(layer);
|
|
1564
|
+
drawMode = false;
|
|
1565
|
+
}}
|
|
1566
|
+
ondrawcancel={() => (drawMode = false)}
|
|
1567
|
+
onenterlayer={enterLayer}
|
|
1568
|
+
/>
|
|
1569
|
+
</div>
|
|
1570
|
+
{/snippet}
|
|
1571
|
+
|
|
1572
|
+
{#snippet inspector()}
|
|
1573
|
+
<CanvasInspector
|
|
1574
|
+
document={editingWidget ? stageDoc : value}
|
|
1575
|
+
layer={selectedLayer}
|
|
1576
|
+
selectionCount={selectedIds.length}
|
|
1577
|
+
widgetDefinition={editingWidget}
|
|
1578
|
+
ondocumentchange={(doc) => {
|
|
1579
|
+
if (editingWidget) {
|
|
1580
|
+
emitStageLayers(doc.layers, {
|
|
1581
|
+
width: doc.width,
|
|
1582
|
+
height: doc.height,
|
|
1583
|
+
background: doc.background
|
|
1584
|
+
});
|
|
1585
|
+
} else {
|
|
1586
|
+
emit(doc);
|
|
1587
|
+
}
|
|
1588
|
+
}}
|
|
1589
|
+
onchange={patchLayer}
|
|
1590
|
+
onalign={alignSelected}
|
|
1591
|
+
onexpose={({ layerId, field, exposed, label }) => {
|
|
1592
|
+
if (!editingWidgetId) return;
|
|
1593
|
+
emit(
|
|
1594
|
+
updateWidgetDefinition(value, editingWidgetId, (d) =>
|
|
1595
|
+
setExposedProp(d, layerId, field, exposed, label)
|
|
1596
|
+
)
|
|
1597
|
+
);
|
|
1598
|
+
}}
|
|
1599
|
+
onoverride={({ propId, value: v }) => {
|
|
1600
|
+
if (!selectedLayer || selectedLayer.kind !== 'widget') return;
|
|
1601
|
+
emit(setInstanceOverride(value, selectedLayer.id, propId, v));
|
|
1602
|
+
}}
|
|
1603
|
+
oneditwidget={(definitionId) => {
|
|
1604
|
+
enterWidgetEdit(definitionId);
|
|
1605
|
+
}}
|
|
1606
|
+
onbreakwidget={() => {
|
|
1607
|
+
if (!selectedLayer || selectedLayer.kind !== 'widget') return;
|
|
1608
|
+
emit(breakWidgetInstance(value, selectedLayer.id));
|
|
1609
|
+
selectedIds = [];
|
|
1610
|
+
}}
|
|
1611
|
+
/>
|
|
1612
|
+
{/snippet}
|
|
1613
|
+
</EditorShell>
|
|
1614
|
+
|
|
1615
|
+
{#if selectedIds.length > 1}
|
|
1616
|
+
<div class="pointer-events-none absolute inset-x-0 bottom-4 z-30 flex justify-center px-4">
|
|
1617
|
+
<div class="pointer-events-auto">
|
|
1618
|
+
<BulkActionBar
|
|
1619
|
+
count={selectedIds.length}
|
|
1620
|
+
itemLabel="layer"
|
|
1621
|
+
itemLabelPlural="layers"
|
|
1622
|
+
placement="dock"
|
|
1623
|
+
compact
|
|
1624
|
+
showSelectAll={false}
|
|
1625
|
+
maxPrimary={5}
|
|
1626
|
+
actions={[
|
|
1627
|
+
{ id: 'duplicate', label: 'Duplicate', icon: Copy, variant: 'ghost' },
|
|
1628
|
+
{ id: 'front', label: 'Bring to front', icon: BringToFront, variant: 'ghost' },
|
|
1629
|
+
{ id: 'back', label: 'Send to back', icon: ArrowDownToLine, variant: 'ghost' },
|
|
1630
|
+
{ id: 'detach', label: 'Remove from parent', icon: Ungroup, variant: 'ghost' },
|
|
1631
|
+
{ id: 'lock', label: 'Lock', icon: Lock, variant: 'ghost', overflow: true },
|
|
1632
|
+
{ id: 'hide', label: 'Hide', icon: EyeOff, variant: 'ghost', overflow: true },
|
|
1633
|
+
{
|
|
1634
|
+
id: 'delete',
|
|
1635
|
+
label: 'Delete',
|
|
1636
|
+
icon: Trash2,
|
|
1637
|
+
variant: 'destructive',
|
|
1638
|
+
confirm: true,
|
|
1639
|
+
overflow: true
|
|
1640
|
+
}
|
|
1641
|
+
]}
|
|
1642
|
+
onaction={handleBulk}
|
|
1643
|
+
onclear={() => (selectedIds = [])}
|
|
1644
|
+
>
|
|
1645
|
+
{#snippet extra()}
|
|
1646
|
+
<DropdownMenu
|
|
1647
|
+
size="sm"
|
|
1648
|
+
align="start"
|
|
1649
|
+
side="top"
|
|
1650
|
+
label="Add parent"
|
|
1651
|
+
items={wrapItems}
|
|
1652
|
+
onselect={handleWrapMenu}
|
|
1653
|
+
>
|
|
1654
|
+
{#snippet trigger()}
|
|
1655
|
+
<Tooltip content="Wrap in parent" side="top">
|
|
1656
|
+
<span
|
|
1657
|
+
class="inline-flex h-7 w-7 items-center justify-center rounded-md text-secondary hover:bg-surface-overlay hover:text-primary"
|
|
1658
|
+
aria-label="Wrap in parent"
|
|
1659
|
+
>
|
|
1660
|
+
<Group class="h-3.5 w-3.5" />
|
|
1661
|
+
</span>
|
|
1662
|
+
</Tooltip>
|
|
1663
|
+
{/snippet}
|
|
1664
|
+
</DropdownMenu>
|
|
1665
|
+
{/snippet}
|
|
1666
|
+
</BulkActionBar>
|
|
1667
|
+
</div>
|
|
1668
|
+
</div>
|
|
1669
|
+
{/if}
|
|
1670
|
+
</div>
|
|
1671
|
+
</div>
|
|
1672
|
+
|
|
1673
|
+
<ContextMenu
|
|
1674
|
+
items={ctxItems}
|
|
1675
|
+
bind:open={ctxOpen}
|
|
1676
|
+
bind:anchor={ctxAnchor}
|
|
1677
|
+
searchable
|
|
1678
|
+
searchPlaceholder="Filter actions…"
|
|
1679
|
+
onselect={handleCtx}
|
|
1680
|
+
/>
|