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,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default keyboard bindings for PixelWeaver.
|
|
3
|
+
*
|
|
4
|
+
* Each entry has a unique name (used for profile export/import and rebinding),
|
|
5
|
+
* a default key combination, and a human-readable description.
|
|
6
|
+
*
|
|
7
|
+
* Key strings use the format: "Ctrl+Shift+Z", "B", etc.
|
|
8
|
+
* Modifier names: Ctrl, Shift, Alt, Meta.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export interface DefaultBinding {
|
|
12
|
+
/** Unique action name, e.g. "tool.pencil" or "edit.undo" */
|
|
13
|
+
name: string;
|
|
14
|
+
/** Default key combination */
|
|
15
|
+
key: string;
|
|
16
|
+
/** Human-readable description for UI display */
|
|
17
|
+
description: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const DEFAULT_BINDINGS: DefaultBinding[] = [
|
|
21
|
+
// -- Tools --
|
|
22
|
+
{ name: 'tool.pencil', key: 'B', description: 'Pencil tool' },
|
|
23
|
+
{ name: 'tool.eraser', key: 'E', description: 'Eraser tool' },
|
|
24
|
+
{ name: 'tool.eyedropper', key: 'I', description: 'Eyedropper tool' },
|
|
25
|
+
{ name: 'tool.fill', key: 'G', description: 'Fill bucket' },
|
|
26
|
+
{ name: 'tool.line', key: 'L', description: 'Line tool' },
|
|
27
|
+
{ name: 'tool.rect', key: 'R', description: 'Rectangle tool' },
|
|
28
|
+
{ name: 'tool.ellipse', key: 'C', description: 'Circle/Ellipse tool' },
|
|
29
|
+
{ name: 'tool.diamond', key: 'D', description: 'Diamond tool' },
|
|
30
|
+
|
|
31
|
+
// -- Edit --
|
|
32
|
+
{ name: 'edit.undo', key: 'Ctrl+Z', description: 'Undo' },
|
|
33
|
+
{ name: 'edit.redo', key: 'Ctrl+Shift+Z', description: 'Redo' },
|
|
34
|
+
|
|
35
|
+
// -- Colors --
|
|
36
|
+
{ name: 'color.swap', key: 'X', description: 'Swap foreground/background colors' },
|
|
37
|
+
|
|
38
|
+
// -- View --
|
|
39
|
+
{ name: 'view.zoom-in', key: '=', description: 'Zoom in' },
|
|
40
|
+
{ name: 'view.zoom-out', key: '-', description: 'Zoom out' },
|
|
41
|
+
{ name: 'view.command-palette', key: 'Ctrl+P', description: 'Command palette' },
|
|
42
|
+
{ name: 'view.toggle-editor-mode', key: 'Ctrl+Tab', description: 'Toggle editor mode' },
|
|
43
|
+
|
|
44
|
+
// -- Layers --
|
|
45
|
+
{ name: 'layer.add', key: 'Ctrl+Shift+N', description: 'Add new layer' },
|
|
46
|
+
{ name: 'layer.delete', key: 'Delete', description: 'Delete active layer' },
|
|
47
|
+
{ name: 'layer.duplicate', key: 'Ctrl+J', description: 'Duplicate active layer' },
|
|
48
|
+
{ name: 'layer.merge-down', key: 'Ctrl+E', description: 'Merge layer down' },
|
|
49
|
+
{ name: 'layer.move-up', key: 'Alt+]', description: 'Move layer up' },
|
|
50
|
+
{ name: 'layer.move-down', key: 'Alt+[', description: 'Move layer down' },
|
|
51
|
+
|
|
52
|
+
// -- Animation --
|
|
53
|
+
{ name: 'anim.play-pause', key: 'Space', description: 'Play/Pause animation' },
|
|
54
|
+
{ name: 'anim.step-prev', key: ',', description: 'Previous frame' },
|
|
55
|
+
{ name: 'anim.step-next', key: '.', description: 'Next frame' },
|
|
56
|
+
{ name: 'anim.first-frame', key: 'Home', description: 'Go to first frame' },
|
|
57
|
+
{ name: 'anim.last-frame', key: 'End', description: 'Go to last frame' },
|
|
58
|
+
|
|
59
|
+
// -- File --
|
|
60
|
+
{ name: 'file.save', key: 'Ctrl+S', description: 'Save project' },
|
|
61
|
+
];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shortcut Editor Panel plugin -- registers the ShortcutEditorPanel with the
|
|
3
|
+
* central panel registry so it can be docked alongside other right-side panels.
|
|
4
|
+
*
|
|
5
|
+
* Discovery is automatic via the `*-plugin.ts` glob in bootstrap.ts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { PluginModule } from '../core/plugin-loader.js';
|
|
9
|
+
import ShortcutEditorPanel from './ShortcutEditorPanel.svelte';
|
|
10
|
+
|
|
11
|
+
export const shortcutEditorPanelPlugin: PluginModule = {
|
|
12
|
+
name: 'ui/shortcut-editor',
|
|
13
|
+
version: '1.0.0',
|
|
14
|
+
description: 'Searchable table of keyboard shortcuts with rebind support',
|
|
15
|
+
dependencies: ['builtin/shortcuts'],
|
|
16
|
+
register(api) {
|
|
17
|
+
api.addPanel('shortcut-editor', {
|
|
18
|
+
title: 'Shortcuts',
|
|
19
|
+
component: ShortcutEditorPanel,
|
|
20
|
+
position: 'right',
|
|
21
|
+
minWidth: 220,
|
|
22
|
+
maxWidth: 420,
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
};
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shortcut Initialization -- wires up the ShortcutManager with default bindings
|
|
3
|
+
* and connects shortcut actions to actual app functions.
|
|
4
|
+
*
|
|
5
|
+
* Call initializeShortcuts() once at app startup (e.g., in the root layout).
|
|
6
|
+
* Call teardownShortcuts() to remove the global listener (e.g., during HMR cleanup).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { ShortcutManager, normalizeShortcut } from './shortcut-manager.js';
|
|
10
|
+
import { DEFAULT_BINDINGS } from './default-bindings.js';
|
|
11
|
+
import {
|
|
12
|
+
getBindings,
|
|
13
|
+
setBindings,
|
|
14
|
+
updateBindingKey,
|
|
15
|
+
setEnabled as setStateEnabled,
|
|
16
|
+
setActiveTool,
|
|
17
|
+
type BindingInfo,
|
|
18
|
+
} from './shortcut-state.svelte.js';
|
|
19
|
+
import { toolRegistry, commandRegistry } from '../core/registries.svelte.js';
|
|
20
|
+
import type { CommandDefinition } from '../core/commands.js';
|
|
21
|
+
import type { CommandType, ParamsOf } from '../core/command-params.js';
|
|
22
|
+
import { undoLast, redoLast } from '../core/dispatcher.js';
|
|
23
|
+
import { executeOrDispatch } from '../core/command-runner.js';
|
|
24
|
+
|
|
25
|
+
// The registry stores CommandDefinition<any> (a type-erasure boundary --
|
|
26
|
+
// see registries.svelte.ts). executeOrDispatch expects the default P, so
|
|
27
|
+
// cast through this helper at the read site.
|
|
28
|
+
type AnyCommand = CommandDefinition;
|
|
29
|
+
import { swapColors } from '../color/color-state.svelte.js';
|
|
30
|
+
import * as layerTree from '../layers/layer-tree.svelte.js';
|
|
31
|
+
import { isInteracting } from '../canvas/input-handler.js';
|
|
32
|
+
import * as animPreview from '../animation/animation-preview.svelte.js';
|
|
33
|
+
import * as frameModel from '../animation/frame-model.svelte.js';
|
|
34
|
+
|
|
35
|
+
// --- Singleton manager instance ---
|
|
36
|
+
|
|
37
|
+
let manager: ShortcutManager | null = null;
|
|
38
|
+
let keydownHandler: ((e: KeyboardEvent) => void) | null = null;
|
|
39
|
+
let focusInHandler: ((e: FocusEvent) => void) | null = null;
|
|
40
|
+
let focusOutHandler: ((e: FocusEvent) => void) | null = null;
|
|
41
|
+
|
|
42
|
+
/** Get the global ShortcutManager instance (null before initialization). */
|
|
43
|
+
export function getShortcutManager(): ShortcutManager | null {
|
|
44
|
+
return manager;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// --- Action resolvers ---
|
|
48
|
+
// These functions resolve action names to their implementations.
|
|
49
|
+
// Tool shortcuts activate the corresponding tool from the tool registry.
|
|
50
|
+
// Edit, color, and view shortcuts call the app subsystems directly.
|
|
51
|
+
|
|
52
|
+
function resolveToolAction(toolName: string): () => void {
|
|
53
|
+
return () => {
|
|
54
|
+
// Activate the tool via its onActivate callback if registered
|
|
55
|
+
const tool = toolRegistry.get(toolName);
|
|
56
|
+
if (tool?.onActivate) {
|
|
57
|
+
tool.onActivate();
|
|
58
|
+
}
|
|
59
|
+
setActiveTool(toolName);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Dispatch a layer command via executeOrDispatch so undoable commands
|
|
65
|
+
* go through the dispatcher and non-undoable ones execute directly.
|
|
66
|
+
*/
|
|
67
|
+
// Typed overload: compile-time param validation for known commands
|
|
68
|
+
function dispatchLayerCommand<T extends CommandType>(commandId: T, params: ParamsOf<T>): void;
|
|
69
|
+
// String fallback: for dynamic dispatch where command type is a variable
|
|
70
|
+
function dispatchLayerCommand(commandId: string, params?: Record<string, unknown>): void;
|
|
71
|
+
function dispatchLayerCommand(commandId: string, params: Record<string, unknown> = {}): void {
|
|
72
|
+
const def = commandRegistry.get(commandId) as AnyCommand | undefined;
|
|
73
|
+
if (!def) return;
|
|
74
|
+
executeOrDispatch(commandId, def, params);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Map of action name prefixes/patterns to their resolution logic */
|
|
78
|
+
function resolveAction(name: string): (() => void) | null {
|
|
79
|
+
// Tool shortcuts: "tool.pencil" -> activate tool "pencil"
|
|
80
|
+
if (name.startsWith('tool.')) {
|
|
81
|
+
const toolName = name.slice('tool.'.length);
|
|
82
|
+
return resolveToolAction(toolName);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Edit shortcuts
|
|
86
|
+
if (name === 'edit.undo') return () => undoLast();
|
|
87
|
+
if (name === 'edit.redo') return () => redoLast();
|
|
88
|
+
|
|
89
|
+
// Color shortcuts
|
|
90
|
+
if (name === 'color.swap') return () => { swapColors(); };
|
|
91
|
+
|
|
92
|
+
// Layer shortcuts: dispatch layer commands through the command system.
|
|
93
|
+
// Commands that operate on the active layer resolve the ID at invocation
|
|
94
|
+
// time so the shortcut always targets the current selection.
|
|
95
|
+
if (name === 'layer.add') {
|
|
96
|
+
return () => {
|
|
97
|
+
const count = layerTree.getFlatList().length;
|
|
98
|
+
dispatchLayerCommand('add_layer', { name: `Layer ${String(count + 1)}` });
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (name === 'layer.delete') {
|
|
102
|
+
return () => {
|
|
103
|
+
const activeId = layerTree.getActiveLayerId();
|
|
104
|
+
if (!activeId) return;
|
|
105
|
+
// Prevent deleting the last pixel layer
|
|
106
|
+
const pixelCount = layerTree.getFlatList().filter((l) => l.type === 'pixel').length;
|
|
107
|
+
if (pixelCount <= 1) return;
|
|
108
|
+
dispatchLayerCommand('remove_layer', { id: activeId });
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (name === 'layer.duplicate') {
|
|
112
|
+
return () => {
|
|
113
|
+
const activeId = layerTree.getActiveLayerId();
|
|
114
|
+
if (!activeId) return;
|
|
115
|
+
dispatchLayerCommand('duplicate_layer', { id: activeId });
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if (name === 'layer.merge-down') {
|
|
119
|
+
return () => { dispatchLayerCommand('merge_down', {}); };
|
|
120
|
+
}
|
|
121
|
+
if (name === 'layer.move-up') {
|
|
122
|
+
return () => { dispatchLayerCommand('move_layer_up', {}); };
|
|
123
|
+
}
|
|
124
|
+
if (name === 'layer.move-down') {
|
|
125
|
+
return () => { dispatchLayerCommand('move_layer_down', {}); };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Animation shortcuts
|
|
129
|
+
// Play/pause is guarded by isInteracting() so Space does not toggle playback
|
|
130
|
+
// while the user is mid-stroke or panning (Space+click = pan in input-handler).
|
|
131
|
+
if (name === 'anim.play-pause') {
|
|
132
|
+
return () => {
|
|
133
|
+
if (isInteracting()) return;
|
|
134
|
+
const def = commandRegistry.get('play_animation') as AnyCommand | undefined;
|
|
135
|
+
if (def) {
|
|
136
|
+
executeOrDispatch('play_animation', def, {});
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (name === 'anim.step-prev') {
|
|
141
|
+
return () => { animPreview.step(-1); };
|
|
142
|
+
}
|
|
143
|
+
if (name === 'anim.step-next') {
|
|
144
|
+
return () => { animPreview.step(1); };
|
|
145
|
+
}
|
|
146
|
+
if (name === 'anim.first-frame') {
|
|
147
|
+
return () => {
|
|
148
|
+
const frames = frameModel.getFrames();
|
|
149
|
+
if (frames.length > 0) frameModel.setCurrentFrame(0);
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (name === 'anim.last-frame') {
|
|
153
|
+
return () => {
|
|
154
|
+
const frames = frameModel.getFrames();
|
|
155
|
+
if (frames.length > 0) frameModel.setCurrentFrame(frames.length - 1);
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// View/file shortcuts -- placeholders that will be connected in later phases
|
|
160
|
+
if (name === 'view.zoom-in') return () => { /* connected in canvas phase */ };
|
|
161
|
+
if (name === 'view.zoom-out') return () => { /* connected in canvas phase */ };
|
|
162
|
+
if (name === 'view.command-palette') return () => { /* connected in UI phase */ };
|
|
163
|
+
if (name === 'file.save') return () => { /* connected in persistence phase */ };
|
|
164
|
+
|
|
165
|
+
// Toggle between Canvas and Level Editor center panels
|
|
166
|
+
if (name === 'view.toggle-editor-mode') {
|
|
167
|
+
return () => {
|
|
168
|
+
const def = commandRegistry.get('toggle_editor_mode') as AnyCommand | undefined;
|
|
169
|
+
if (def) executeOrDispatch('toggle_editor_mode', def, {});
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// --- Focus tracking ---
|
|
177
|
+
// Disable shortcuts when a text input, textarea, or contenteditable has focus.
|
|
178
|
+
|
|
179
|
+
const TEXT_INPUT_TYPES = new Set([
|
|
180
|
+
'text', 'password', 'email', 'number', 'search', 'tel', 'url',
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
function isTextInputElement(el: EventTarget | null): boolean {
|
|
184
|
+
if (!el || !(el instanceof HTMLElement)) return false;
|
|
185
|
+
|
|
186
|
+
if (el instanceof HTMLTextAreaElement) return true;
|
|
187
|
+
if (el.isContentEditable) return true;
|
|
188
|
+
if (el instanceof HTMLInputElement && TEXT_INPUT_TYPES.has(el.type)) return true;
|
|
189
|
+
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// --- Initialization ---
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Initialize the shortcut system: create manager, load default bindings,
|
|
197
|
+
* attach global listeners, and sync reactive state.
|
|
198
|
+
*/
|
|
199
|
+
export function initializeShortcuts(): ShortcutManager {
|
|
200
|
+
// Guard against double initialization
|
|
201
|
+
if (manager) {
|
|
202
|
+
teardownShortcuts();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
manager = new ShortcutManager();
|
|
206
|
+
|
|
207
|
+
// Load default bindings and resolve actions
|
|
208
|
+
const bindingInfos: BindingInfo[] = [];
|
|
209
|
+
|
|
210
|
+
for (const binding of DEFAULT_BINDINGS) {
|
|
211
|
+
const action = resolveAction(binding.name);
|
|
212
|
+
if (action) {
|
|
213
|
+
manager.bind(binding.key, action, binding.description, binding.name);
|
|
214
|
+
bindingInfos.push({
|
|
215
|
+
name: binding.name,
|
|
216
|
+
key: binding.key,
|
|
217
|
+
description: binding.description,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Sync reactive state
|
|
223
|
+
setBindings(bindingInfos);
|
|
224
|
+
|
|
225
|
+
// Capture the non-null manager in closure so handlers don't need to re-check
|
|
226
|
+
// the module-level variable (which is typed as nullable).
|
|
227
|
+
const activeManager = manager;
|
|
228
|
+
|
|
229
|
+
// Attach global keydown listener
|
|
230
|
+
keydownHandler = (event: KeyboardEvent) => {
|
|
231
|
+
activeManager.handleKeyDown(event);
|
|
232
|
+
};
|
|
233
|
+
window.addEventListener('keydown', keydownHandler);
|
|
234
|
+
|
|
235
|
+
// Attach focus tracking to auto-disable shortcuts in text inputs
|
|
236
|
+
focusInHandler = (event: FocusEvent) => {
|
|
237
|
+
if (isTextInputElement(event.target)) {
|
|
238
|
+
activeManager.setEnabled(false);
|
|
239
|
+
setStateEnabled(false);
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
focusOutHandler = (event: FocusEvent) => {
|
|
244
|
+
if (isTextInputElement(event.target)) {
|
|
245
|
+
activeManager.setEnabled(true);
|
|
246
|
+
setStateEnabled(true);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
window.addEventListener('focusin', focusInHandler);
|
|
251
|
+
window.addEventListener('focusout', focusOutHandler);
|
|
252
|
+
|
|
253
|
+
return manager;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// --- Rebinding / reset helpers ---
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Result of attempting a rebind. On success, the manager and reactive state
|
|
260
|
+
* have been updated. On conflict, the caller receives the conflicting action
|
|
261
|
+
* name so it can offer a swap.
|
|
262
|
+
*/
|
|
263
|
+
export interface RebindResult {
|
|
264
|
+
ok: boolean;
|
|
265
|
+
/** Name of the already-bound action that owns the requested key, if any. */
|
|
266
|
+
conflictName?: string;
|
|
267
|
+
/** Description of the conflicting action, for UI display. */
|
|
268
|
+
conflictDescription?: string;
|
|
269
|
+
/** Reason for failure when not a conflict (e.g. manager missing). */
|
|
270
|
+
reason?: string;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Rebind a named action to a new key combination.
|
|
275
|
+
* Keeps the ShortcutManager and the reactive state module in sync.
|
|
276
|
+
*
|
|
277
|
+
* If `newKey` is already bound to a different action, returns a conflict
|
|
278
|
+
* without mutating anything. Callers that want to swap must call
|
|
279
|
+
* `rebindShortcut` twice (once to clear the other action, once to set this).
|
|
280
|
+
*/
|
|
281
|
+
export function rebindShortcut(name: string, newKey: string): RebindResult {
|
|
282
|
+
if (!manager) return { ok: false, reason: 'shortcut-manager-not-initialized' };
|
|
283
|
+
|
|
284
|
+
const currentBinding = getBindings().get(name);
|
|
285
|
+
if (!currentBinding) return { ok: false, reason: 'unknown-action' };
|
|
286
|
+
|
|
287
|
+
const normalizedNew = normalizeShortcut(newKey);
|
|
288
|
+
const normalizedCurrent = normalizeShortcut(currentBinding.key);
|
|
289
|
+
|
|
290
|
+
// No-op: already bound to that key
|
|
291
|
+
if (normalizedNew === normalizedCurrent) return { ok: true };
|
|
292
|
+
|
|
293
|
+
// Check for conflict with a different action
|
|
294
|
+
const existingShortcutName = findBindingNameForKey(normalizedNew);
|
|
295
|
+
if (existingShortcutName && existingShortcutName !== name) {
|
|
296
|
+
const conflicting = getBindings().get(existingShortcutName);
|
|
297
|
+
return {
|
|
298
|
+
ok: false,
|
|
299
|
+
conflictName: existingShortcutName,
|
|
300
|
+
conflictDescription: conflicting?.description ?? '',
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Apply the rebind to manager and reactive state
|
|
305
|
+
manager.rebind(currentBinding.key, newKey);
|
|
306
|
+
updateBindingKey(name, newKey);
|
|
307
|
+
return { ok: true };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Unbind a named action entirely (used as a building block for swap flows
|
|
312
|
+
* in the shortcut editor -- strip the incumbent before assigning the new key).
|
|
313
|
+
*/
|
|
314
|
+
export function clearShortcut(name: string): void {
|
|
315
|
+
if (!manager) return;
|
|
316
|
+
const current = getBindings().get(name);
|
|
317
|
+
if (!current) return;
|
|
318
|
+
manager.unbind(current.key);
|
|
319
|
+
updateBindingKey(name, '');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Reset a single binding back to the default key shipped in DEFAULT_BINDINGS.
|
|
324
|
+
* If the default collides with another action's current key, the other action
|
|
325
|
+
* is cleared so the default can be restored cleanly.
|
|
326
|
+
*/
|
|
327
|
+
export function resetShortcutToDefault(name: string): void {
|
|
328
|
+
if (!manager) return;
|
|
329
|
+
const defaultBinding = DEFAULT_BINDINGS.find((b) => b.name === name);
|
|
330
|
+
if (!defaultBinding) return;
|
|
331
|
+
|
|
332
|
+
// Clear any other action currently occupying the default key
|
|
333
|
+
const occupant = findBindingNameForKey(normalizeShortcut(defaultBinding.key));
|
|
334
|
+
if (occupant && occupant !== name) {
|
|
335
|
+
clearShortcut(occupant);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const current = getBindings().get(name);
|
|
339
|
+
if (current && current.key) {
|
|
340
|
+
manager.rebind(current.key, defaultBinding.key);
|
|
341
|
+
} else {
|
|
342
|
+
// Previously cleared -- re-bind from scratch using the resolved action
|
|
343
|
+
const action = resolveAction(name);
|
|
344
|
+
if (action) {
|
|
345
|
+
manager.bind(defaultBinding.key, action, defaultBinding.description, name);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
updateBindingKey(name, defaultBinding.key);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** Lookup the action name currently bound to a normalized key, if any. */
|
|
352
|
+
function findBindingNameForKey(normalizedKey: string): string | undefined {
|
|
353
|
+
for (const [actionName, info] of getBindings()) {
|
|
354
|
+
if (info.key && normalizeShortcut(info.key) === normalizedKey) {
|
|
355
|
+
return actionName;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return undefined;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** Remove all global listeners and reset the manager. */
|
|
362
|
+
export function teardownShortcuts(): void {
|
|
363
|
+
if (keydownHandler) {
|
|
364
|
+
window.removeEventListener('keydown', keydownHandler);
|
|
365
|
+
keydownHandler = null;
|
|
366
|
+
}
|
|
367
|
+
if (focusInHandler) {
|
|
368
|
+
window.removeEventListener('focusin', focusInHandler);
|
|
369
|
+
focusInHandler = null;
|
|
370
|
+
}
|
|
371
|
+
if (focusOutHandler) {
|
|
372
|
+
window.removeEventListener('focusout', focusOutHandler);
|
|
373
|
+
focusOutHandler = null;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (manager) {
|
|
377
|
+
manager.reset();
|
|
378
|
+
manager = null;
|
|
379
|
+
}
|
|
380
|
+
}
|