pixelweaver 0.1.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/.env.development +1 -0
- package/.github/workflows/ci.yml +22 -0
- package/.github/workflows/publish.yml +18 -0
- package/.prettierignore +5 -0
- package/.prettierrc +16 -0
- package/.python-version +1 -0
- package/.rlsbl/bases/.github/workflows/ci.yml +21 -0
- package/.rlsbl/bases/.github/workflows/publish.yml +18 -0
- package/.rlsbl/bases/.rlsbl/changes/unreleased.jsonl +0 -0
- package/.rlsbl/bases/.rlsbl/hooks/post-release.sh +8 -0
- package/.rlsbl/bases/.rlsbl/hooks/pre-checks.sh +5 -0
- package/.rlsbl/bases/.rlsbl/hooks/pre-release.sh +8 -0
- package/.rlsbl/bases/.rlsbl/lint/go.toml +17 -0
- package/.rlsbl/bases/.rlsbl/lint/npm.toml +19 -0
- package/.rlsbl/bases/.rlsbl/lint/python.toml +25 -0
- package/.rlsbl/bases/CHANGELOG.md +5 -0
- package/.rlsbl/bases/LICENSE +21 -0
- package/.rlsbl/changes/.validated +1 -0
- package/.rlsbl/changes/0.1.0.jsonl +85 -0
- package/.rlsbl/changes/0.1.0.md +13 -0
- package/.rlsbl/changes/unreleased.jsonl +0 -0
- package/.rlsbl/config.json +6 -0
- package/.rlsbl/hashes.json +14 -0
- package/.rlsbl/hooks/post-release.sh +8 -0
- package/.rlsbl/hooks/pre-checks.sh +5 -0
- package/.rlsbl/hooks/pre-release.sh +8 -0
- package/.rlsbl/lint/go.toml +17 -0
- package/.rlsbl/lint/npm.toml +19 -0
- package/.rlsbl/lint/python.toml +25 -0
- package/.rlsbl/releases/unreleased.toml +0 -0
- package/.rlsbl/releases/v0.1.0.toml +3 -0
- package/.rlsbl/version +1 -0
- package/.selfdoc/hashes/hashes.json +146 -0
- package/.strictcli/schema.json +227 -0
- package/CHANGELOG.md +17 -0
- package/CLAUDE.md +100 -0
- package/LICENSE +21 -0
- package/README.md +116 -0
- package/assets/icon.png +0 -0
- package/docs/_README.md +117 -0
- package/docs/cli-config.md +35 -0
- package/docs/cli-dev.md +21 -0
- package/docs/cli-diagnose.md +12 -0
- package/docs/cli-index.md +30 -0
- package/docs/cli-list.md +18 -0
- package/docs/cli-mcp.md +18 -0
- package/docs/cli-new.md +26 -0
- package/docs/cli-open.md +21 -0
- package/docs/cli-serve.md +21 -0
- package/docs/cli-stop.md +12 -0
- package/docs/gen-index.md +36 -0
- package/docs/index.md +13 -0
- package/docs/server-src-pixelweaver-__main__.md +12 -0
- package/docs/server-src-pixelweaver-autosave.md +12 -0
- package/docs/server-src-pixelweaver-bridge.md +12 -0
- package/docs/server-src-pixelweaver-cli.md +12 -0
- package/docs/server-src-pixelweaver-config.md +12 -0
- package/docs/server-src-pixelweaver-connections.md +12 -0
- package/docs/server-src-pixelweaver-main.md +12 -0
- package/docs/server-src-pixelweaver-mcp_bridge.md +12 -0
- package/docs/server-src-pixelweaver-mcp_drawing_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_export_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_frame_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_history_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_layer_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_lock.md +12 -0
- package/docs/server-src-pixelweaver-mcp_project_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_read_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_registry.md +12 -0
- package/docs/server-src-pixelweaver-mcp_resources.md +12 -0
- package/docs/server-src-pixelweaver-mcp_server.md +12 -0
- package/docs/server-src-pixelweaver-protocol.md +12 -0
- package/docs/server-src-pixelweaver-state.md +12 -0
- package/docs/server-src-pixelweaver-storage.md +12 -0
- package/docs/server-src-pixelweaver-websocket.md +12 -0
- package/docs/server-src-pixelweaver.md +12 -0
- package/e2e/app-launch.test.ts +35 -0
- package/e2e/menus.test.ts +26 -0
- package/e2e/tools.test.ts +27 -0
- package/e2e/undo-redo.test.ts +11 -0
- package/eslint.config.js +62 -0
- package/index.html +13 -0
- package/package.json +48 -0
- package/playwright.config.ts +19 -0
- package/plugins/builtin/.gitkeep +0 -0
- package/plugins/builtin/advanced-fill-tool.ts +146 -0
- package/plugins/builtin/circle-tool.ts +186 -0
- package/plugins/builtin/diamond-tool.ts +182 -0
- package/plugins/builtin/dither-tool.ts +186 -0
- package/plugins/builtin/drawing-primitives-plugin.ts +362 -0
- package/plugins/builtin/drawing-utils.test.ts +495 -0
- package/plugins/builtin/drawing-utils.ts +431 -0
- package/plugins/builtin/effects/blur.ts +97 -0
- package/plugins/builtin/effects/color-effects.ts +278 -0
- package/plugins/builtin/effects/flip.ts +83 -0
- package/plugins/builtin/effects/glow.ts +118 -0
- package/plugins/builtin/effects/outline.ts +110 -0
- package/plugins/builtin/effects/rotate.ts +357 -0
- package/plugins/builtin/effects/scale.ts +258 -0
- package/plugins/builtin/effects/shadow.ts +111 -0
- package/plugins/builtin/effects/sharpen.ts +102 -0
- package/plugins/builtin/effects.test.ts +715 -0
- package/plugins/builtin/eraser-tool.ts +23 -0
- package/plugins/builtin/eyedropper-tool.ts +83 -0
- package/plugins/builtin/fill-tool.ts +93 -0
- package/plugins/builtin/gradient-tool.ts +204 -0
- package/plugins/builtin/gradient.test.ts +142 -0
- package/plugins/builtin/importers/aseprite-importer-plugin.ts +174 -0
- package/plugins/builtin/importers/aseprite-parser.ts +497 -0
- package/plugins/builtin/importers/piskel-importer-plugin.ts +222 -0
- package/plugins/builtin/importers/sky-spec-plugin.ts +409 -0
- package/plugins/builtin/line-tool.ts +267 -0
- package/plugins/builtin/make-stroke-tool.ts +271 -0
- package/plugins/builtin/noise-dither.test.ts +151 -0
- package/plugins/builtin/noise-tool.ts +131 -0
- package/plugins/builtin/pattern-stamp-tool.ts +162 -0
- package/plugins/builtin/pencil-tool.ts +25 -0
- package/plugins/builtin/rect-tool.ts +179 -0
- package/plugins/builtin/selection-tool.ts +388 -0
- package/plugins/builtin/selection.test.ts +195 -0
- package/plugins/builtin/tool-plugins.test.ts +529 -0
- package/public/favicon.svg +7 -0
- package/public/sw.js +91 -0
- package/pyproject.toml +49 -0
- package/scripts/eslint-wave-a-list.sh +24 -0
- package/scripts/eslint-wave-a-status.sh +25 -0
- package/scripts/fix-index-signature-access.py +127 -0
- package/scripts/fix-unchecked-index.py +128 -0
- package/scripts/fix-unchecked-vars.py +127 -0
- package/scripts/fix-wave-a-bangs.py +36 -0
- package/scripts/fix-wave-a-templates.py +108 -0
- package/scripts/fix-wave-b-void-expr.py +167 -0
- package/scripts/generate-command-params.py +540 -0
- package/scripts/migrate-single-frame-to-multi.py +171 -0
- package/scripts/smoke-test.sh +77 -0
- package/selfdoc.json +10 -0
- package/server/README.md +0 -0
- package/server/src/pixelweaver/__init__.py +1 -0
- package/server/src/pixelweaver/__main__.py +4 -0
- package/server/src/pixelweaver/autosave.py +114 -0
- package/server/src/pixelweaver/bridge.py +127 -0
- package/server/src/pixelweaver/cli.py +199 -0
- package/server/src/pixelweaver/config.py +24 -0
- package/server/src/pixelweaver/connections.py +54 -0
- package/server/src/pixelweaver/main.py +271 -0
- package/server/src/pixelweaver/mcp_bridge.py +189 -0
- package/server/src/pixelweaver/mcp_drawing_tools.py +178 -0
- package/server/src/pixelweaver/mcp_export_tools.py +291 -0
- package/server/src/pixelweaver/mcp_frame_tools.py +423 -0
- package/server/src/pixelweaver/mcp_history_tools.py +106 -0
- package/server/src/pixelweaver/mcp_layer_tools.py +64 -0
- package/server/src/pixelweaver/mcp_lock.py +37 -0
- package/server/src/pixelweaver/mcp_project_tools.py +302 -0
- package/server/src/pixelweaver/mcp_read_tools.py +163 -0
- package/server/src/pixelweaver/mcp_registry.py +247 -0
- package/server/src/pixelweaver/mcp_resources.py +312 -0
- package/server/src/pixelweaver/mcp_server.py +234 -0
- package/server/src/pixelweaver/protocol.py +219 -0
- package/server/src/pixelweaver/state.py +267 -0
- package/server/src/pixelweaver/storage.py +293 -0
- package/server/src/pixelweaver/websocket.py +282 -0
- package/server/tests/__init__.py +0 -0
- package/server/tests/conftest.py +17 -0
- package/server/tests/test_api.py +96 -0
- package/server/tests/test_bridge.py +161 -0
- package/server/tests/test_health.py +9 -0
- package/server/tests/test_integration.py +86 -0
- package/server/tests/test_mcp_bridge.py +293 -0
- package/server/tests/test_mcp_lock.py +34 -0
- package/server/tests/test_mcp_registry.py +679 -0
- package/server/tests/test_mcp_resources.py +648 -0
- package/server/tests/test_protocol.py +125 -0
- package/server/tests/test_state.py +87 -0
- package/server/tests/test_storage.py +306 -0
- package/server/tests/test_websocket.py +275 -0
- package/src/App.svelte +107 -0
- package/src/app.css +215 -0
- package/src/lib/animation/AnimationPreviewPanel.svelte +667 -0
- package/src/lib/animation/animation-commands.test.ts +228 -0
- package/src/lib/animation/animation-commands.ts +540 -0
- package/src/lib/animation/animation-preview-panel-plugin.ts +25 -0
- package/src/lib/animation/animation-preview.svelte.ts +151 -0
- package/src/lib/animation/clipboard.ts +134 -0
- package/src/lib/animation/frame-model.svelte.ts +437 -0
- package/src/lib/animation/frame-model.test.ts +314 -0
- package/src/lib/animation/frame-selection.svelte.ts +77 -0
- package/src/lib/animation/frame-tags.svelte.ts +238 -0
- package/src/lib/animation/frame-tags.test.ts +136 -0
- package/src/lib/animation/import.test.ts +141 -0
- package/src/lib/animation/import.ts +112 -0
- package/src/lib/animation/spritesheet-export.test.ts +239 -0
- package/src/lib/animation/spritesheet-export.ts +314 -0
- package/src/lib/canvas/CanvasViewport.svelte +216 -0
- package/src/lib/canvas/canvas-init-plugin.ts +178 -0
- package/src/lib/canvas/canvas-renderer.ts +408 -0
- package/src/lib/canvas/canvas-state.svelte.ts +232 -0
- package/src/lib/canvas/canvas-state.test.ts +139 -0
- package/src/lib/canvas/input-handler.ts +221 -0
- package/src/lib/canvas/input-plugin.ts +150 -0
- package/src/lib/canvas/onion-skin.ts +94 -0
- package/src/lib/canvas/pixel-buffer.test.ts +249 -0
- package/src/lib/canvas/pixel-buffer.ts +151 -0
- package/src/lib/canvas/render-state.svelte.ts +18 -0
- package/src/lib/canvas/shape-preview-state.svelte.ts +36 -0
- package/src/lib/canvas/tile-mode.test.ts +53 -0
- package/src/lib/canvas/tile-mode.ts +92 -0
- package/src/lib/canvas/viewport-utils.ts +24 -0
- package/src/lib/canvas/zoom-utils.ts +31 -0
- package/src/lib/color/.gitkeep +0 -0
- package/src/lib/color/color-commands.ts +87 -0
- package/src/lib/color/color-state.svelte.ts +98 -0
- package/src/lib/color/color-state.test.ts +91 -0
- package/src/lib/color/color-utils.test.ts +220 -0
- package/src/lib/color/color-utils.ts +243 -0
- package/src/lib/color/palette-state.svelte.ts +127 -0
- package/src/lib/color/palette-state.test.ts +154 -0
- package/src/lib/color/palette.ts +79 -0
- package/src/lib/core/bootstrap.ts +66 -0
- package/src/lib/core/command-params.generated.ts +1549 -0
- package/src/lib/core/command-params.ts +20 -0
- package/src/lib/core/command-runner.ts +79 -0
- package/src/lib/core/commands.ts +134 -0
- package/src/lib/core/dispatcher.test.ts +548 -0
- package/src/lib/core/dispatcher.ts +361 -0
- package/src/lib/core/index.test.ts +7 -0
- package/src/lib/core/notification-state.svelte.ts +119 -0
- package/src/lib/core/plugin-api.ts +210 -0
- package/src/lib/core/plugin-discovery.test.ts +53 -0
- package/src/lib/core/plugin-discovery.ts +65 -0
- package/src/lib/core/plugin-loader.test.ts +159 -0
- package/src/lib/core/plugin-loader.ts +240 -0
- package/src/lib/core/plugin-types.ts +286 -0
- package/src/lib/core/registries.svelte.ts +74 -0
- package/src/lib/core/tool-options-state.svelte.ts +61 -0
- package/src/lib/dock/DockLayout.svelte +375 -0
- package/src/lib/dock/TabAddMenu.svelte +90 -0
- package/src/lib/dock/dock-persistence.ts +46 -0
- package/src/lib/dock/dock-plugin.ts +49 -0
- package/src/lib/dock/dock-presets.ts +156 -0
- package/src/lib/dock/dock-state.svelte.ts +77 -0
- package/src/lib/dock/dock-types.ts +2 -0
- package/src/lib/dock/dockview-theme.css +226 -0
- package/src/lib/dock/dockview-theme.ts +17 -0
- package/src/lib/dock/header-action-renderer.ts +77 -0
- package/src/lib/edit/clipboard-state.ts +34 -0
- package/src/lib/export/download.ts +201 -0
- package/src/lib/export/png-metadata.ts +181 -0
- package/src/lib/history/ActionLogPanel.svelte +418 -0
- package/src/lib/history/action-log-panel-plugin.ts +24 -0
- package/src/lib/history/action-log.svelte.ts +172 -0
- package/src/lib/history/action-log.test.ts +168 -0
- package/src/lib/history/history-commands.ts +403 -0
- package/src/lib/history/macros.svelte.ts +320 -0
- package/src/lib/history/macros.test.ts +224 -0
- package/src/lib/history/replay-engine.test.ts +241 -0
- package/src/lib/history/replay-engine.ts +149 -0
- package/src/lib/history/spec-format.test.ts +250 -0
- package/src/lib/history/spec-format.ts +210 -0
- package/src/lib/iso/SeamCheckerPanel.svelte +251 -0
- package/src/lib/iso/iso-math.test.ts +77 -0
- package/src/lib/iso/iso-math.ts +77 -0
- package/src/lib/iso/seam-checker-panel-plugin.ts +25 -0
- package/src/lib/iso/seam-checker.test.ts +126 -0
- package/src/lib/iso/seam-checker.ts +93 -0
- package/src/lib/iso/tessellation.ts +67 -0
- package/src/lib/layers/compositor.test.ts +193 -0
- package/src/lib/layers/compositor.ts +175 -0
- package/src/lib/layers/layer-commands.test.ts +263 -0
- package/src/lib/layers/layer-commands.ts +429 -0
- package/src/lib/layers/layer-tree.svelte.ts +516 -0
- package/src/lib/layers/layer-tree.test.ts +383 -0
- package/src/lib/layers/layer-types.ts +56 -0
- package/src/lib/leveleditor/LevelEditorViewport.svelte +1808 -0
- package/src/lib/leveleditor/MapPropertiesPanel.svelte +266 -0
- package/src/lib/leveleditor/TilePickerPanel.svelte +324 -0
- package/src/lib/leveleditor/depth-sort.test.ts +70 -0
- package/src/lib/leveleditor/depth-sort.ts +39 -0
- package/src/lib/leveleditor/level-editor-commands.ts +353 -0
- package/src/lib/leveleditor/level-editor-viewport-plugin.ts +25 -0
- package/src/lib/leveleditor/map-properties-panel-plugin.ts +25 -0
- package/src/lib/leveleditor/map-state.svelte.ts +372 -0
- package/src/lib/leveleditor/map-state.test.ts +243 -0
- package/src/lib/leveleditor/tile-picker-panel-plugin.ts +25 -0
- package/src/lib/leveleditor/tile-source-registry.svelte.ts +91 -0
- package/src/lib/leveleditor/tiled-export.test.ts +144 -0
- package/src/lib/leveleditor/tiled-export.ts +374 -0
- package/src/lib/pywebview.d.ts +15 -0
- package/src/lib/recovery/.gitkeep +0 -0
- package/src/lib/recovery/idb-store.ts +118 -0
- package/src/lib/recovery/recovery-manager.test.ts +321 -0
- package/src/lib/recovery/recovery-manager.ts +115 -0
- package/src/lib/recovery/recovery-plugin.ts +55 -0
- package/src/lib/recovery/recovery-state.svelte.ts +21 -0
- package/src/lib/recovery/sw-plugin.ts +18 -0
- package/src/lib/recovery/sw-register.ts +17 -0
- package/src/lib/save/directory-format.ts +42 -0
- package/src/lib/save/project-snapshot.ts +139 -0
- package/src/lib/save/recent-projects.ts +56 -0
- package/src/lib/save/save-state.svelte.ts +35 -0
- package/src/lib/save/storage.ts +167 -0
- package/src/lib/save/zip-format.ts +45 -0
- package/src/lib/shortcuts/.gitkeep +0 -0
- package/src/lib/shortcuts/ShortcutEditorPanel.svelte +690 -0
- package/src/lib/shortcuts/default-bindings.ts +61 -0
- package/src/lib/shortcuts/shortcut-editor-panel-plugin.ts +25 -0
- package/src/lib/shortcuts/shortcut-init.ts +380 -0
- package/src/lib/shortcuts/shortcut-manager.test.ts +466 -0
- package/src/lib/shortcuts/shortcut-manager.ts +281 -0
- package/src/lib/shortcuts/shortcut-state.svelte.ts +78 -0
- package/src/lib/shortcuts/shortcuts-plugin.ts +17 -0
- package/src/lib/sync/patch-applicator.ts +300 -0
- package/src/lib/sync/patch-types.ts +65 -0
- package/src/lib/sync/project-manager.ts +108 -0
- package/src/lib/sync/sync-init.ts +152 -0
- package/src/lib/sync/sync-plugin.ts +19 -0
- package/src/lib/sync/sync-state.svelte.ts +56 -0
- package/src/lib/sync/ws-client.test.ts +604 -0
- package/src/lib/sync/ws-client.ts +574 -0
- package/src/lib/tools/.gitkeep +0 -0
- package/src/lib/ui/.gitkeep +0 -0
- package/src/lib/ui/AboutDialog.svelte +113 -0
- package/src/lib/ui/ColorPicker.svelte +761 -0
- package/src/lib/ui/ContextMenu.svelte +216 -0
- package/src/lib/ui/ExportDialog.svelte +747 -0
- package/src/lib/ui/FrameStrip.svelte +854 -0
- package/src/lib/ui/LayerPanel.svelte +810 -0
- package/src/lib/ui/MenuBar.svelte +590 -0
- package/src/lib/ui/NewProjectDialog.svelte +803 -0
- package/src/lib/ui/PluginManagerPanel.svelte +475 -0
- package/src/lib/ui/PromptDialog.svelte +252 -0
- package/src/lib/ui/RecoveryDialog.svelte +295 -0
- package/src/lib/ui/ResizeDialog.svelte +416 -0
- package/src/lib/ui/StatusBar.svelte +145 -0
- package/src/lib/ui/ToolbarPanel.svelte +488 -0
- package/src/lib/ui/animation-menu-commands.ts +194 -0
- package/src/lib/ui/command-palette/CommandPalette.svelte +232 -0
- package/src/lib/ui/command-palette/command-palette-plugin.ts +30 -0
- package/src/lib/ui/command-palette/command-palette-state.svelte.ts +190 -0
- package/src/lib/ui/command-palette/command-palette.test.ts +129 -0
- package/src/lib/ui/dialog-state.svelte.ts +70 -0
- package/src/lib/ui/edit-commands.ts +271 -0
- package/src/lib/ui/file-commands.ts +275 -0
- package/src/lib/ui/file-open.ts +99 -0
- package/src/lib/ui/help-commands.ts +93 -0
- package/src/lib/ui/image-commands.ts +181 -0
- package/src/lib/ui/layer-menu-commands.ts +420 -0
- package/src/lib/ui/menu-builder.ts +224 -0
- package/src/lib/ui/notifications/NotificationBanner.svelte +137 -0
- package/src/lib/ui/notifications/notification-plugin.ts +29 -0
- package/src/lib/ui/notifications/notification-state.svelte.ts +9 -0
- package/src/lib/ui/plugin-manager-panel-plugin.ts +26 -0
- package/src/lib/ui/plugin-state.svelte.ts +62 -0
- package/src/lib/ui/select-commands.ts +75 -0
- package/src/lib/ui/theme-plugin.ts +18 -0
- package/src/lib/ui/theme.svelte.ts +45 -0
- package/src/lib/ui/theme.test.ts +51 -0
- package/src/lib/ui/toolbar-config.ts +90 -0
- package/src/lib/ui/toolbar-plugin.ts +39 -0
- package/src/lib/ui/view-commands.ts +629 -0
- package/src/lib/variants/BisectionExportDialog.svelte +500 -0
- package/src/lib/variants/VariantPanel.svelte +822 -0
- package/src/lib/variants/bisection-export.test.ts +113 -0
- package/src/lib/variants/bisection-export.ts +148 -0
- package/src/lib/variants/palette-extraction.test.ts +111 -0
- package/src/lib/variants/palette-extraction.ts +84 -0
- package/src/lib/variants/palette-interpolation.test.ts +113 -0
- package/src/lib/variants/palette-interpolation.ts +87 -0
- package/src/lib/variants/palette-swap.test.ts +101 -0
- package/src/lib/variants/palette-swap.ts +114 -0
- package/src/lib/variants/variant-commands.ts +594 -0
- package/src/lib/variants/variant-panel-plugin.ts +27 -0
- package/src/lib/variants/variant-randomizer.ts +101 -0
- package/src/lib/variants/variant-state.svelte.ts +166 -0
- package/src/lib/variants/variant-state.test.ts +138 -0
- package/src/main.ts +14 -0
- package/src/vite-env.d.ts +3 -0
- package/svelte.config.js +2 -0
- package/todo/.done/audit-design-decisions.md +812 -0
- package/todo/.done/audit-implementation-plan.md +1235 -0
- package/todo/.done/happy-path-polish.md +177 -0
- package/todo/.done/pixelweaver-full-build.md +937 -0
- package/todo/.done/server-multi-frame-design.md +405 -0
- package/todo/.done/typed-dispatcher-design.md +435 -0
- package/todo/.done/unified-toolbar-and-action-system.md +323 -0
- package/todo/.obsolete/comprehensive-audit-obsolete-items.md +33 -0
- package/todo/.obsolete/tauri-desktop-bundle.md +424 -0
- package/todo/comprehensive-audit.md +1085 -0
- package/tsconfig.app.json +26 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +20 -0
- package/uv.lock +1167 -0
- package/vite.config.ts +32 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
NotificationBanner -- horizontal bar between MenuBar and workspace.
|
|
3
|
+
Shows the highest-priority notification with optional action and count badge.
|
|
4
|
+
-->
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
import { notificationState } from './notification-state.svelte.js';
|
|
7
|
+
|
|
8
|
+
const current = $derived(notificationState.current);
|
|
9
|
+
const count = $derived(notificationState.count);
|
|
10
|
+
|
|
11
|
+
function handleDismiss(): void {
|
|
12
|
+
if (current) {
|
|
13
|
+
notificationState.dismiss(current.id);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function handleAction(): void {
|
|
18
|
+
current?.action?.callback();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Map notification type to a CSS custom property for background color.
|
|
23
|
+
* Falls back to the design tokens defined in app.css.
|
|
24
|
+
*/
|
|
25
|
+
function bgVar(type: 'info' | 'warning' | 'success'): string {
|
|
26
|
+
switch (type) {
|
|
27
|
+
case 'info': return 'var(--accent)';
|
|
28
|
+
case 'warning': return 'var(--warning, #d4a04a)';
|
|
29
|
+
case 'success': return 'var(--success, #34d67c)';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
{#if current}
|
|
35
|
+
<div
|
|
36
|
+
class="notification-banner"
|
|
37
|
+
role="alert"
|
|
38
|
+
style:--banner-bg={bgVar(current.type)}
|
|
39
|
+
>
|
|
40
|
+
<span class="notification-message">{current.message}</span>
|
|
41
|
+
|
|
42
|
+
{#if current.action}
|
|
43
|
+
<button class="notification-action" onclick={handleAction}>
|
|
44
|
+
{current.action.label}
|
|
45
|
+
</button>
|
|
46
|
+
{/if}
|
|
47
|
+
|
|
48
|
+
{#if count > 1}
|
|
49
|
+
<span class="notification-badge">{count}</span>
|
|
50
|
+
{/if}
|
|
51
|
+
|
|
52
|
+
<button
|
|
53
|
+
class="notification-dismiss"
|
|
54
|
+
onclick={handleDismiss}
|
|
55
|
+
aria-label="Dismiss notification"
|
|
56
|
+
>
|
|
57
|
+
×
|
|
58
|
+
</button>
|
|
59
|
+
</div>
|
|
60
|
+
{/if}
|
|
61
|
+
|
|
62
|
+
<style>
|
|
63
|
+
.notification-banner {
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
gap: var(--space-3);
|
|
67
|
+
padding: var(--space-2) var(--space-5);
|
|
68
|
+
background: var(--banner-bg);
|
|
69
|
+
color: #fff;
|
|
70
|
+
font-size: var(--text-sm);
|
|
71
|
+
font-family: var(--font-ui);
|
|
72
|
+
/* Slide-down animation */
|
|
73
|
+
animation: slide-down var(--transition-normal) ease forwards;
|
|
74
|
+
overflow: hidden;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@keyframes slide-down {
|
|
78
|
+
from {
|
|
79
|
+
max-height: 0;
|
|
80
|
+
padding-top: 0;
|
|
81
|
+
padding-bottom: 0;
|
|
82
|
+
opacity: 0;
|
|
83
|
+
}
|
|
84
|
+
to {
|
|
85
|
+
max-height: 40px;
|
|
86
|
+
opacity: 1;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.notification-message {
|
|
91
|
+
flex: 1;
|
|
92
|
+
white-space: nowrap;
|
|
93
|
+
overflow: hidden;
|
|
94
|
+
text-overflow: ellipsis;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.notification-action {
|
|
98
|
+
background: rgba(255, 255, 255, 0.2);
|
|
99
|
+
border: 1px solid rgba(255, 255, 255, 0.4);
|
|
100
|
+
border-radius: var(--radius-sm);
|
|
101
|
+
color: #fff;
|
|
102
|
+
padding: var(--space-1) var(--space-3);
|
|
103
|
+
cursor: pointer;
|
|
104
|
+
font-size: var(--text-xs);
|
|
105
|
+
transition: background var(--transition-fast);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.notification-action:hover {
|
|
109
|
+
background: rgba(255, 255, 255, 0.35);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.notification-badge {
|
|
113
|
+
background: rgba(0, 0, 0, 0.3);
|
|
114
|
+
border-radius: var(--radius-lg);
|
|
115
|
+
padding: 0 var(--space-2);
|
|
116
|
+
font-size: var(--text-xs);
|
|
117
|
+
font-weight: 600;
|
|
118
|
+
min-width: 18px;
|
|
119
|
+
text-align: center;
|
|
120
|
+
line-height: 18px;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.notification-dismiss {
|
|
124
|
+
background: none;
|
|
125
|
+
border: none;
|
|
126
|
+
color: rgba(255, 255, 255, 0.8);
|
|
127
|
+
cursor: pointer;
|
|
128
|
+
font-size: var(--text-lg);
|
|
129
|
+
line-height: 1;
|
|
130
|
+
padding: var(--space-1);
|
|
131
|
+
transition: color var(--transition-fast);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.notification-dismiss:hover {
|
|
135
|
+
color: #fff;
|
|
136
|
+
}
|
|
137
|
+
</style>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notification Plugin -- registers a command to dismiss all notifications.
|
|
3
|
+
* Auto-discovered by bootstrap via the *-plugin.ts glob pattern.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { PluginModule } from '../../core/plugin-loader.js';
|
|
7
|
+
import { notificationState } from './notification-state.svelte.js';
|
|
8
|
+
|
|
9
|
+
export const notificationPlugin: PluginModule = {
|
|
10
|
+
name: 'builtin/notifications',
|
|
11
|
+
version: '1.0.0',
|
|
12
|
+
description: 'In-app notification banner system',
|
|
13
|
+
|
|
14
|
+
register(api) {
|
|
15
|
+
api.addCommand('dismiss_all_notifications', {
|
|
16
|
+
label: 'Dismiss All Notifications',
|
|
17
|
+
category: 'Notifications',
|
|
18
|
+
execute() {
|
|
19
|
+
notificationState.dismissAll();
|
|
20
|
+
},
|
|
21
|
+
undo() {
|
|
22
|
+
// Dismissing notifications is not undoable
|
|
23
|
+
},
|
|
24
|
+
describe() {
|
|
25
|
+
return 'Dismiss all notifications';
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
},
|
|
29
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-export barrel -- the canonical notification state now lives in core.
|
|
3
|
+
* This file exists so that UI-layer imports continue to work without changes.
|
|
4
|
+
*/
|
|
5
|
+
export {
|
|
6
|
+
notificationState,
|
|
7
|
+
type AppNotification,
|
|
8
|
+
type NotificationLevel,
|
|
9
|
+
} from '../../core/notification-state.svelte.js';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Manager Panel plugin -- registers the PluginManagerPanel with the
|
|
3
|
+
* central panel registry so plugin metadata and contributions can be
|
|
4
|
+
* inspected from the docked UI.
|
|
5
|
+
*
|
|
6
|
+
* Discovery is automatic via the `*-plugin.ts` glob in bootstrap.ts.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { PluginModule } from '../core/plugin-loader.js';
|
|
10
|
+
import PluginManagerPanel from './PluginManagerPanel.svelte';
|
|
11
|
+
|
|
12
|
+
export const pluginManagerPanelPlugin: PluginModule = {
|
|
13
|
+
name: 'ui/plugin-manager',
|
|
14
|
+
version: '1.0.0',
|
|
15
|
+
description: 'Read-only inspector for loaded plugins and their contributions',
|
|
16
|
+
dependencies: [],
|
|
17
|
+
register(api) {
|
|
18
|
+
api.addPanel('plugin-manager', {
|
|
19
|
+
title: 'Plugins',
|
|
20
|
+
component: PluginManagerPanel,
|
|
21
|
+
position: 'right',
|
|
22
|
+
minWidth: 240,
|
|
23
|
+
maxWidth: 440,
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin State -- reactive registry of loaded plugins for the plugin manager UI.
|
|
3
|
+
*
|
|
4
|
+
* Populated by the plugin loader after each plugin's register() call. Records
|
|
5
|
+
* include basic metadata (name, version, description, author) plus the set of
|
|
6
|
+
* contributions (commands, tools, panels, exporters, importers, shortcuts,
|
|
7
|
+
* menu items, toolbar items) each plugin registered.
|
|
8
|
+
*
|
|
9
|
+
* Read-only from the UI's perspective; the loader is the sole writer.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// --- Types ---
|
|
13
|
+
|
|
14
|
+
/** Per-plugin summary of what was registered during register(). */
|
|
15
|
+
export interface PluginContributions {
|
|
16
|
+
commands: string[];
|
|
17
|
+
tools: string[];
|
|
18
|
+
panels: string[];
|
|
19
|
+
exporters: string[];
|
|
20
|
+
importers: string[];
|
|
21
|
+
shortcuts: string[];
|
|
22
|
+
menuItems: string[];
|
|
23
|
+
toolbarItems: string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** A record describing one loaded plugin and its contributions. */
|
|
27
|
+
export interface LoadedPluginRecord {
|
|
28
|
+
name: string;
|
|
29
|
+
version: string;
|
|
30
|
+
description?: string;
|
|
31
|
+
author?: string;
|
|
32
|
+
dependencies: string[];
|
|
33
|
+
loaded: boolean;
|
|
34
|
+
error?: string;
|
|
35
|
+
contributions: PluginContributions;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// --- Reactive state ---
|
|
39
|
+
|
|
40
|
+
let plugins: LoadedPluginRecord[] = $state([]);
|
|
41
|
+
|
|
42
|
+
// --- Operations (writer API used by the plugin loader) ---
|
|
43
|
+
|
|
44
|
+
/** Append a loaded plugin record. The loader calls this after register() runs. */
|
|
45
|
+
function addPlugin(record: LoadedPluginRecord): void {
|
|
46
|
+
plugins = [...plugins, record];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Reset the state. Intended for tests only. */
|
|
50
|
+
function _resetForTesting(): void {
|
|
51
|
+
plugins = [];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// --- Public API ---
|
|
55
|
+
|
|
56
|
+
export const pluginState = {
|
|
57
|
+
get plugins() {
|
|
58
|
+
return plugins;
|
|
59
|
+
},
|
|
60
|
+
addPlugin,
|
|
61
|
+
_resetForTesting,
|
|
62
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Select Commands -- Invert Selection, Select by Color, plus menu items
|
|
3
|
+
* under the Select top-level menu that reference the shared select_all
|
|
4
|
+
* and deselect commands (registered by edit-commands and selection-tool,
|
|
5
|
+
* respectively).
|
|
6
|
+
*
|
|
7
|
+
* Invert and Select by Color are placeholders until selection-tool grows
|
|
8
|
+
* the necessary primitives.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { PluginModule } from '../core/plugin-loader.js';
|
|
12
|
+
import FlipHorizontal from '~icons/lucide/flip-horizontal';
|
|
13
|
+
import Pipette from '~icons/lucide/pipette';
|
|
14
|
+
|
|
15
|
+
export const selectCommandsPlugin: PluginModule = {
|
|
16
|
+
name: 'ui/select-commands',
|
|
17
|
+
version: '1.0.0',
|
|
18
|
+
dependencies: [],
|
|
19
|
+
register(api) {
|
|
20
|
+
api.addMenuItem('menu:select:select-all', {
|
|
21
|
+
commandId: 'select_all',
|
|
22
|
+
menuPath: 'select',
|
|
23
|
+
group: 'basic',
|
|
24
|
+
order: 10,
|
|
25
|
+
label: 'Select All',
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
api.addMenuItem('menu:select:deselect', {
|
|
29
|
+
commandId: 'deselect',
|
|
30
|
+
menuPath: 'select',
|
|
31
|
+
group: 'basic',
|
|
32
|
+
order: 20,
|
|
33
|
+
label: 'Deselect',
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
api.addCommand('invert_selection', {
|
|
37
|
+
tier: 'frame',
|
|
38
|
+
execute() {
|
|
39
|
+
// selection-tool does not export an invert function yet
|
|
40
|
+
console.warn('invert_selection: not yet implemented -- requires an invert function in selection-tool that flips all pixels within canvas bounds');
|
|
41
|
+
},
|
|
42
|
+
undo() {},
|
|
43
|
+
describe() { return 'Inverted selection'; },
|
|
44
|
+
label: 'Invert Selection',
|
|
45
|
+
category: 'Select',
|
|
46
|
+
icon: FlipHorizontal,
|
|
47
|
+
});
|
|
48
|
+
api.addMenuItem('menu:select:invert', {
|
|
49
|
+
commandId: 'invert_selection',
|
|
50
|
+
menuPath: 'select',
|
|
51
|
+
group: 'advanced',
|
|
52
|
+
order: 30,
|
|
53
|
+
label: 'Invert Selection',
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
api.addCommand('select_by_color', {
|
|
57
|
+
tier: 'frame',
|
|
58
|
+
execute() {
|
|
59
|
+
console.warn('select_by_color: not yet implemented -- requires pixel sampling from the active buffer and a color-matching tolerance scan');
|
|
60
|
+
},
|
|
61
|
+
undo() {},
|
|
62
|
+
describe() { return 'Selected pixels by color'; },
|
|
63
|
+
label: 'Select by Color',
|
|
64
|
+
category: 'Select',
|
|
65
|
+
icon: Pipette,
|
|
66
|
+
});
|
|
67
|
+
api.addMenuItem('menu:select:by-color', {
|
|
68
|
+
commandId: 'select_by_color',
|
|
69
|
+
menuPath: 'select',
|
|
70
|
+
group: 'advanced',
|
|
71
|
+
order: 40,
|
|
72
|
+
label: 'Select by Color',
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Plugin -- initializes the theme system by applying the current theme
|
|
3
|
+
* to the document root on startup.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { PluginModule } from '../core/plugin-loader.js';
|
|
7
|
+
import { themeState } from './theme.svelte.js';
|
|
8
|
+
|
|
9
|
+
export const themePlugin: PluginModule = {
|
|
10
|
+
name: 'builtin/theme',
|
|
11
|
+
version: '1.0.0',
|
|
12
|
+
description: 'Dark/light theme management',
|
|
13
|
+
|
|
14
|
+
register() {
|
|
15
|
+
// Apply the current theme (defaults to "dark") to the document root
|
|
16
|
+
themeState.set(themeState.current);
|
|
17
|
+
},
|
|
18
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme System -- reactive state for switching between dark and light themes.
|
|
3
|
+
*
|
|
4
|
+
* Applies the active theme by setting a `data-theme` attribute on the
|
|
5
|
+
* document root element, which CSS selectors in app.css use to swap
|
|
6
|
+
* variable values. Uses Svelte 5 runes for reactivity.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// --- Types ---
|
|
10
|
+
|
|
11
|
+
export type Theme = 'dark' | 'light';
|
|
12
|
+
|
|
13
|
+
// --- Reactive state ---
|
|
14
|
+
|
|
15
|
+
let currentTheme: Theme = $state('dark');
|
|
16
|
+
|
|
17
|
+
// --- Functions ---
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Apply a theme by setting the `data-theme` attribute on <html>.
|
|
21
|
+
* Safe to call in non-browser environments (no-ops if document is unavailable).
|
|
22
|
+
*/
|
|
23
|
+
function applyTheme(theme: Theme): void {
|
|
24
|
+
if (typeof document !== 'undefined') {
|
|
25
|
+
document.documentElement.dataset["theme"] = theme;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function setTheme(theme: Theme): void {
|
|
30
|
+
currentTheme = theme;
|
|
31
|
+
applyTheme(theme);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function toggleTheme(): void {
|
|
35
|
+
setTheme(currentTheme === 'dark' ? 'light' : 'dark');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// --- Public API ---
|
|
39
|
+
|
|
40
|
+
export const themeState = {
|
|
41
|
+
get current() { return currentTheme; },
|
|
42
|
+
|
|
43
|
+
set: setTheme,
|
|
44
|
+
toggle: toggleTheme,
|
|
45
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the theme system.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
6
|
+
import { themeState } from './theme.svelte.js';
|
|
7
|
+
|
|
8
|
+
describe('themeState', () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
// Reset to dark (the default)
|
|
11
|
+
themeState.set('dark');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('defaults to dark theme', () => {
|
|
15
|
+
expect(themeState.current).toBe('dark');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('setTheme changes the current theme', () => {
|
|
19
|
+
themeState.set('light');
|
|
20
|
+
expect(themeState.current).toBe('light');
|
|
21
|
+
|
|
22
|
+
themeState.set('dark');
|
|
23
|
+
expect(themeState.current).toBe('dark');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('toggleTheme switches between dark and light', () => {
|
|
27
|
+
expect(themeState.current).toBe('dark');
|
|
28
|
+
|
|
29
|
+
themeState.toggle();
|
|
30
|
+
expect(themeState.current).toBe('light');
|
|
31
|
+
|
|
32
|
+
themeState.toggle();
|
|
33
|
+
expect(themeState.current).toBe('dark');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('setTheme updates the data-theme attribute on the document', () => {
|
|
37
|
+
themeState.set('light');
|
|
38
|
+
expect(document.documentElement.dataset["theme"]).toBe('light');
|
|
39
|
+
|
|
40
|
+
themeState.set('dark');
|
|
41
|
+
expect(document.documentElement.dataset["theme"]).toBe('dark');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('toggleTheme updates the data-theme attribute', () => {
|
|
45
|
+
themeState.toggle();
|
|
46
|
+
expect(document.documentElement.dataset["theme"]).toBe('light');
|
|
47
|
+
|
|
48
|
+
themeState.toggle();
|
|
49
|
+
expect(document.documentElement.dataset["theme"]).toBe('dark');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toolbar configuration persistence -- stores user overrides for toolbar
|
|
3
|
+
* icon size in localStorage.
|
|
4
|
+
* Plugin-declared defaults are merged with user overrides at read time.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const STORAGE_KEY = 'pixelweaver:toolbar-config';
|
|
8
|
+
|
|
9
|
+
/** User overrides for a single toolbar's appearance */
|
|
10
|
+
export interface ToolbarUserOverrides {
|
|
11
|
+
iconSize?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Top-level config shape stored in localStorage */
|
|
15
|
+
interface StoredConfig {
|
|
16
|
+
toolbars: Record<string, ToolbarUserOverrides>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Default settings for known toolbars */
|
|
20
|
+
const DEFAULTS: Record<string, Required<ToolbarUserOverrides>> = {
|
|
21
|
+
'drawing-tools': { iconSize: 20 },
|
|
22
|
+
'view-controls': { iconSize: 18 },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Load the full stored config from localStorage */
|
|
26
|
+
function loadStored(): StoredConfig {
|
|
27
|
+
try {
|
|
28
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
29
|
+
if (raw) {
|
|
30
|
+
const parsed = JSON.parse(raw) as Partial<StoredConfig>;
|
|
31
|
+
if (parsed.toolbars && typeof parsed.toolbars === 'object') {
|
|
32
|
+
return { toolbars: parsed.toolbars };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} catch {
|
|
36
|
+
// Corrupted data, reset
|
|
37
|
+
}
|
|
38
|
+
return { toolbars: {} };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Save config to localStorage */
|
|
42
|
+
function saveStored(config: StoredConfig): void {
|
|
43
|
+
try {
|
|
44
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
|
|
45
|
+
} catch {
|
|
46
|
+
// Storage full or unavailable
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get the effective configuration for a toolbar by merging defaults
|
|
52
|
+
* with any user overrides.
|
|
53
|
+
*/
|
|
54
|
+
export function getToolbarConfig(toolbarId: string): Required<ToolbarUserOverrides> {
|
|
55
|
+
const defaults = DEFAULTS[toolbarId] ?? { iconSize: 20 };
|
|
56
|
+
const stored = loadStored();
|
|
57
|
+
const overrides = stored.toolbars[toolbarId] ?? {};
|
|
58
|
+
return { ...defaults, ...overrides };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Save user overrides for a specific toolbar.
|
|
63
|
+
*/
|
|
64
|
+
export function setToolbarConfig(toolbarId: string, overrides: ToolbarUserOverrides): void {
|
|
65
|
+
const stored = loadStored();
|
|
66
|
+
stored.toolbars[toolbarId] = { ...(stored.toolbars[toolbarId] ?? {}), ...overrides };
|
|
67
|
+
saveStored(stored);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Reset a toolbar's config to plugin defaults (remove user overrides).
|
|
72
|
+
*/
|
|
73
|
+
export function resetToolbarConfig(toolbarId: string): void {
|
|
74
|
+
const stored = loadStored();
|
|
75
|
+
// Rebuild without the target key (dynamic delete is disallowed)
|
|
76
|
+
const { [toolbarId]: _removed, ...rest } = stored.toolbars;
|
|
77
|
+
void _removed;
|
|
78
|
+
saveStored({ toolbars: rest });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Reset ALL toolbar configs to defaults.
|
|
83
|
+
*/
|
|
84
|
+
export function resetAllToolbarConfigs(): void {
|
|
85
|
+
try {
|
|
86
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
87
|
+
} catch {
|
|
88
|
+
// Ignore
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toolbar Plugin -- registers toolbar panels as dockable panels in the dock
|
|
3
|
+
* system. Each toolbar panel wraps ToolbarPanel.svelte with a specific
|
|
4
|
+
* toolbarId, making toolbar contributions visible in the UI.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PluginModule } from '../core/plugin-loader.js';
|
|
8
|
+
import ToolbarPanel from './ToolbarPanel.svelte';
|
|
9
|
+
|
|
10
|
+
export const toolbarPlugin: PluginModule = {
|
|
11
|
+
name: 'ui/toolbars',
|
|
12
|
+
version: '1.0.0',
|
|
13
|
+
dependencies: [],
|
|
14
|
+
register(api) {
|
|
15
|
+
// Drawing tools toolbar -- left side, 1 item per row, fixed width
|
|
16
|
+
// btnSize = iconSize(20) + padding(16) = 36 + toolbar chrome(14) = 50
|
|
17
|
+
api.addPanel('toolbar-drawing-tools', {
|
|
18
|
+
title: 'Drawing Tools',
|
|
19
|
+
component: ToolbarPanel,
|
|
20
|
+
props: { toolbarId: 'drawing-tools' },
|
|
21
|
+
position: 'left',
|
|
22
|
+
closeable: false,
|
|
23
|
+
minWidth: 50,
|
|
24
|
+
maxWidth: 50,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// View controls toolbar -- horizontal strip under the menu bar, fixed height
|
|
28
|
+
// btnSize = iconSize(18) + padding(16) = 34 + toolbar chrome(14) = 48
|
|
29
|
+
api.addPanel('toolbar-view-controls', {
|
|
30
|
+
title: 'View Controls',
|
|
31
|
+
component: ToolbarPanel,
|
|
32
|
+
props: { toolbarId: 'view-controls' },
|
|
33
|
+
position: 'top',
|
|
34
|
+
closeable: false,
|
|
35
|
+
minHeight: 48,
|
|
36
|
+
maxHeight: 48,
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
};
|