@kritzel/engine 0.4.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +50 -0
- package/dist/cjs/app-globals-V2Kpy_OQ.js +5 -0
- package/dist/cjs/engine.constants-CakmdKMW.js +28206 -0
- package/dist/cjs/index-BbbWU2aN.js +1742 -0
- package/dist/cjs/index.cjs.js +4232 -0
- package/dist/cjs/kritzel-awareness-cursors_5.cjs.entry.js +28004 -0
- package/dist/cjs/loader.cjs.js +13 -0
- package/dist/cjs/stencil.cjs.js +25 -0
- package/dist/collection/classes/core/core.class.js +1389 -0
- package/dist/collection/classes/core/reviver.class.js +124 -0
- package/dist/collection/classes/core/store.class.js +337 -0
- package/dist/collection/classes/core/viewport.class.js +794 -0
- package/dist/collection/classes/core/workspace.class.js +112 -0
- package/dist/collection/classes/handlers/base.handler.js +20 -0
- package/dist/collection/classes/handlers/context-menu.handler.js +170 -0
- package/dist/collection/classes/handlers/hover.handler.js +35 -0
- package/dist/collection/classes/handlers/key.handler.js +162 -0
- package/dist/collection/classes/handlers/line-handle.handler.js +475 -0
- package/dist/collection/classes/handlers/move.handler.js +295 -0
- package/dist/collection/classes/handlers/resize.handler.js +327 -0
- package/dist/collection/classes/handlers/rotation.handler.js +174 -0
- package/dist/collection/classes/handlers/selection.handler.js +526 -0
- package/dist/collection/classes/managers/anchor.manager.js +1476 -0
- package/dist/collection/classes/managers/browser-permission.manager.js +64 -0
- package/dist/collection/classes/managers/clipboard.manager.js +346 -0
- package/dist/collection/classes/managers/clipboard.types.js +1 -0
- package/dist/collection/classes/managers/cursor.manager.js +139 -0
- package/dist/collection/classes/managers/license.manager.js +341 -0
- package/dist/collection/classes/managers/localization.manager.js +227 -0
- package/dist/collection/classes/managers/theme.manager.js +206 -0
- package/dist/collection/classes/objects/base-object.class.js +462 -0
- package/dist/collection/classes/objects/dynamic-object.class.js +437 -0
- package/dist/collection/classes/objects/group.class.js +590 -0
- package/dist/collection/classes/objects/image.class.js +500 -0
- package/dist/collection/classes/objects/line.class.js +789 -0
- package/dist/collection/classes/objects/path.class.js +578 -0
- package/dist/collection/classes/objects/selection-box.class.js +32 -0
- package/dist/collection/classes/objects/selection-group.class.js +752 -0
- package/dist/collection/classes/objects/shape.class.js +628 -0
- package/dist/collection/classes/objects/text.class.js +461 -0
- package/dist/collection/classes/providers/assets/asset-resolver.class.js +536 -0
- package/dist/collection/classes/providers/assets/http-asset-provider.class.js +111 -0
- package/dist/collection/classes/providers/assets/indexeddb-asset-provider.class.js +241 -0
- package/dist/collection/classes/providers/assets/presigned-asset-provider.class.js +86 -0
- package/dist/collection/classes/providers/paste-intent/event-paste-intent.provider.js +48 -0
- package/dist/collection/classes/providers/paste-intent/ios-paste-intent.provider.js +16 -0
- package/dist/collection/classes/providers/paste-intent/navigator-paste-intent.provider.js +86 -0
- package/dist/collection/classes/providers/sync/broadcast-sync-provider.class.js +100 -0
- package/dist/collection/classes/providers/sync/hocuspocus-sync-provider.class.js +333 -0
- package/dist/collection/classes/providers/sync/in-memory-sync-provider.class.js +69 -0
- package/dist/collection/classes/providers/sync/indexeddb-sync-provider.class.js +39 -0
- package/dist/collection/classes/providers/sync/websocket-sync-provider.class.js +110 -0
- package/dist/collection/classes/registries/dynamic-object-renderer.registry.js +52 -0
- package/dist/collection/classes/registries/font-registry.class.js +161 -0
- package/dist/collection/classes/registries/icon-registry.class.js +118 -0
- package/dist/collection/classes/registries/paste-intent-provider.registry.js +26 -0
- package/dist/collection/classes/registries/tool.registry.js +69 -0
- package/dist/collection/classes/structures/app-state-map.structure.js +461 -0
- package/dist/collection/classes/structures/object-map.structure.js +1103 -0
- package/dist/collection/classes/structures/quadtree.structure.js +251 -0
- package/dist/collection/classes/tools/base-tool.class.js +88 -0
- package/dist/collection/classes/tools/brush-tool.class.js +236 -0
- package/dist/collection/classes/tools/eraser-tool.class.js +133 -0
- package/dist/collection/classes/tools/image-tool.class.js +152 -0
- package/dist/collection/classes/tools/line-tool.class.js +249 -0
- package/dist/collection/classes/tools/selection-tool.class.js +611 -0
- package/dist/collection/classes/tools/shape-tool.class.js +251 -0
- package/dist/collection/classes/tools/text-tool.class.js +179 -0
- package/dist/collection/collection-manifest.json +17 -0
- package/dist/collection/components/kritzel-awareness-cursors/kritzel-awareness-cursors.css +115 -0
- package/dist/collection/components/kritzel-awareness-cursors/kritzel-awareness-cursors.js +380 -0
- package/dist/collection/components/kritzel-cursor-trail/kritzel-cursor-trail.css +10 -0
- package/dist/collection/components/kritzel-cursor-trail/kritzel-cursor-trail.js +153 -0
- package/dist/collection/components/kritzel-engine/kritzel-engine.css +81 -0
- package/dist/collection/components/kritzel-engine/kritzel-engine.js +6345 -0
- package/dist/collection/components/kritzel-icon/kritzel-icon.css +18 -0
- package/dist/collection/components/kritzel-icon/kritzel-icon.js +93 -0
- package/dist/collection/components/kritzel-watermark/kritzel-watermark.css +29 -0
- package/dist/collection/components/kritzel-watermark/kritzel-watermark.js +83 -0
- package/dist/collection/configs/default-asset-storage.config.js +9 -0
- package/dist/collection/configs/default-brush-tool.config.js +9 -0
- package/dist/collection/configs/default-engine-config.js +73 -0
- package/dist/collection/configs/default-line-tool.config.js +12 -0
- package/dist/collection/configs/default-shape-tool.config.js +15 -0
- package/dist/collection/configs/default-sync.config.js +6 -0
- package/dist/collection/configs/default-text-tool.config.js +12 -0
- package/dist/collection/constants/color-palette.constants.js +40 -0
- package/dist/collection/constants/core.constants.js +1 -0
- package/dist/collection/constants/engine.constants.js +4 -0
- package/dist/collection/constants/license.constants.js +25 -0
- package/dist/collection/constants/schema.constants.js +4 -0
- package/dist/collection/constants/storage.constants.js +1 -0
- package/dist/collection/constants/stroke-size.constants.js +2 -0
- package/dist/collection/constants/text-tool-fonts.constants.js +18 -0
- package/dist/collection/constants/version.js +6 -0
- package/dist/collection/enums/alignment.enum.js +9 -0
- package/dist/collection/enums/event-button.enum.js +6 -0
- package/dist/collection/enums/handle-type.enum.js +7 -0
- package/dist/collection/enums/shape-type.enum.js +6 -0
- package/dist/collection/events/active-workspace-change.event.js +1 -0
- package/dist/collection/events/editor-is-ready.event.js +1 -0
- package/dist/collection/events/login.event.js +1 -0
- package/dist/collection/helpers/alignment.helper.js +73 -0
- package/dist/collection/helpers/class.helper.js +5 -0
- package/dist/collection/helpers/color.helper.js +106 -0
- package/dist/collection/helpers/cursor.helper.js +57 -0
- package/dist/collection/helpers/devices.helper.js +28 -0
- package/dist/collection/helpers/event.helper.js +58 -0
- package/dist/collection/helpers/geometry.helper.js +149 -0
- package/dist/collection/helpers/html.helper.js +116 -0
- package/dist/collection/helpers/image-file.helper.js +6 -0
- package/dist/collection/helpers/keyboard.helper.js +55 -0
- package/dist/collection/helpers/localization.helper.js +25 -0
- package/dist/collection/helpers/math.helper.js +8 -0
- package/dist/collection/helpers/migration.helper.js +45 -0
- package/dist/collection/helpers/object.helper.js +29 -0
- package/dist/collection/helpers/svg-export.helper.js +655 -0
- package/dist/collection/helpers/theme.helper.js +77 -0
- package/dist/collection/helpers/tool-config.helper.js +63 -0
- package/dist/collection/helpers/wheel.helper.js +113 -0
- package/dist/collection/index.js +107 -0
- package/dist/collection/interfaces/anchor.interface.js +1 -0
- package/dist/collection/interfaces/arrow-head.interface.js +1 -0
- package/dist/collection/interfaces/asset-provider.interface.js +1 -0
- package/dist/collection/interfaces/asset-storage-config.interface.js +1 -0
- package/dist/collection/interfaces/asset.interface.js +1 -0
- package/dist/collection/interfaces/bounding-box.interface.js +1 -0
- package/dist/collection/interfaces/clonable.interface.js +1 -0
- package/dist/collection/interfaces/context-menu-item.interface.js +1 -0
- package/dist/collection/interfaces/context-menu-state.interface.js +1 -0
- package/dist/collection/interfaces/debug-info.interface.js +1 -0
- package/dist/collection/interfaces/dialog.interface.js +1 -0
- package/dist/collection/interfaces/document-metadata.interface.js +1 -0
- package/dist/collection/interfaces/dynamic-object-renderer.interface.js +1 -0
- package/dist/collection/interfaces/engine-state.interface.js +1 -0
- package/dist/collection/interfaces/font.interface.js +1 -0
- package/dist/collection/interfaces/localization.interface.js +1 -0
- package/dist/collection/interfaces/login-config.interface.js +1 -0
- package/dist/collection/interfaces/master-detail.interface.js +1 -0
- package/dist/collection/interfaces/menu-item.interface.js +1 -0
- package/dist/collection/interfaces/migration.interface.js +1 -0
- package/dist/collection/interfaces/notification.interface.js +1 -0
- package/dist/collection/interfaces/object-change-event.interface.js +1 -0
- package/dist/collection/interfaces/object-export.interface.js +1 -0
- package/dist/collection/interfaces/object.interface.js +1 -0
- package/dist/collection/interfaces/paste-intent-provider.interface.js +1 -0
- package/dist/collection/interfaces/point.interface.js +1 -0
- package/dist/collection/interfaces/polygon.interface.js +1 -0
- package/dist/collection/interfaces/remote-cursor.interface.js +1 -0
- package/dist/collection/interfaces/selection-state.interface.js +1 -0
- package/dist/collection/interfaces/serializable.interface.js +1 -0
- package/dist/collection/interfaces/settings.interface.js +1 -0
- package/dist/collection/interfaces/share.interface.js +1 -0
- package/dist/collection/interfaces/shortcut.interface.js +1 -0
- package/dist/collection/interfaces/sync-config.interface.js +21 -0
- package/dist/collection/interfaces/sync-provider.interface.js +1 -0
- package/dist/collection/interfaces/theme.interface.js +1 -0
- package/dist/collection/interfaces/tool-config.interface.js +1 -0
- package/dist/collection/interfaces/tool-type.interface.js +1 -0
- package/dist/collection/interfaces/tool.interface.js +1 -0
- package/dist/collection/interfaces/toolbar-item.interface.js +1 -0
- package/dist/collection/interfaces/undo-state.interface.js +1 -0
- package/dist/collection/interfaces/user.interface.js +1 -0
- package/dist/collection/interfaces/viewport-state.interface.js +1 -0
- package/dist/collection/locales/de-locale.js +119 -0
- package/dist/collection/locales/en-locale.js +120 -0
- package/dist/collection/locales/fr-locale.js +119 -0
- package/dist/collection/migrations/app-state.migrations.js +47 -0
- package/dist/collection/migrations/workspace.migrations.js +67 -0
- package/dist/collection/themes/dark-theme.js +340 -0
- package/dist/collection/themes/light-theme.js +346 -0
- package/dist/collection/types/deep-readonly.type.js +1 -0
- package/dist/collection/types/state.types.js +1 -0
- package/dist/components/index.d.ts +47 -0
- package/dist/components/index.js +4143 -0
- package/dist/components/kritzel-awareness-cursors.d.ts +11 -0
- package/dist/components/kritzel-awareness-cursors.js +6 -0
- package/dist/components/kritzel-cursor-trail.d.ts +11 -0
- package/dist/components/kritzel-cursor-trail.js +6 -0
- package/dist/components/kritzel-engine.d.ts +11 -0
- package/dist/components/kritzel-engine.js +52641 -0
- package/dist/components/kritzel-icon.d.ts +11 -0
- package/dist/components/kritzel-icon.js +6 -0
- package/dist/components/kritzel-watermark.d.ts +11 -0
- package/dist/components/kritzel-watermark.js +6 -0
- package/dist/components/p-B2Htv9ou.js +49 -0
- package/dist/components/p-BvRGQvVI.js +3456 -0
- package/dist/components/p-CfSx6HU_.js +1469 -0
- package/dist/components/p-YP8wAcF4.js +122 -0
- package/dist/components/p-fx6msYHJ.js +167 -0
- package/dist/esm/app-globals-DQuL1Twl.js +3 -0
- package/dist/esm/engine.constants-Bl5-pbq-.js +28143 -0
- package/dist/esm/index-BU9Mv7y0.js +1715 -0
- package/dist/esm/index.js +4134 -0
- package/dist/esm/kritzel-awareness-cursors_5.entry.js +27978 -0
- package/dist/esm/loader.js +11 -0
- package/dist/esm/stencil.js +21 -0
- package/dist/index.cjs.js +1 -0
- package/dist/index.js +1 -0
- package/dist/stencil/index.esm.js +4134 -0
- package/dist/stencil/p-BU9Mv7y0.js +1702 -0
- package/dist/stencil/p-Bl5-pbq-.js +28143 -0
- package/dist/stencil/p-DQuL1Twl.js +3 -0
- package/dist/stencil/p-f0a8b7af.entry.js +27978 -0
- package/dist/stencil/stencil.esm.js +21 -0
- package/dist/types/classes/core/core.class.d.ts +532 -0
- package/dist/types/classes/core/reviver.class.d.ts +32 -0
- package/dist/types/classes/core/store.class.d.ts +198 -0
- package/dist/types/classes/core/viewport.class.d.ts +256 -0
- package/dist/types/classes/core/workspace.class.d.ts +95 -0
- package/dist/types/classes/handlers/base.handler.d.ts +18 -0
- package/dist/types/classes/handlers/context-menu.handler.d.ts +67 -0
- package/dist/types/classes/handlers/hover.handler.d.ts +23 -0
- package/dist/types/classes/handlers/key.handler.d.ts +44 -0
- package/dist/types/classes/handlers/line-handle.handler.d.ts +115 -0
- package/dist/types/classes/handlers/move.handler.d.ts +94 -0
- package/dist/types/classes/handlers/resize.handler.d.ts +95 -0
- package/dist/types/classes/handlers/rotation.handler.d.ts +50 -0
- package/dist/types/classes/handlers/selection.handler.d.ts +153 -0
- package/dist/types/classes/managers/anchor.manager.d.ts +513 -0
- package/dist/types/classes/managers/browser-permission.manager.d.ts +41 -0
- package/dist/types/classes/managers/clipboard.manager.d.ts +71 -0
- package/dist/types/classes/managers/clipboard.types.d.ts +23 -0
- package/dist/types/classes/managers/cursor.manager.d.ts +66 -0
- package/dist/types/classes/managers/license.manager.d.ts +147 -0
- package/dist/types/classes/managers/localization.manager.d.ts +145 -0
- package/dist/types/classes/managers/theme.manager.d.ts +112 -0
- package/dist/types/classes/objects/base-object.class.d.ts +266 -0
- package/dist/types/classes/objects/dynamic-object.class.d.ts +148 -0
- package/dist/types/classes/objects/group.class.d.ts +231 -0
- package/dist/types/classes/objects/image.class.d.ts +246 -0
- package/dist/types/classes/objects/line.class.d.ts +285 -0
- package/dist/types/classes/objects/path.class.d.ts +220 -0
- package/dist/types/classes/objects/selection-box.class.d.ts +19 -0
- package/dist/types/classes/objects/selection-group.class.d.ts +267 -0
- package/dist/types/classes/objects/shape.class.d.ts +322 -0
- package/dist/types/classes/objects/text.class.d.ts +197 -0
- package/dist/types/classes/providers/assets/asset-resolver.class.d.ts +141 -0
- package/dist/types/classes/providers/assets/http-asset-provider.class.d.ts +79 -0
- package/dist/types/classes/providers/assets/indexeddb-asset-provider.class.d.ts +69 -0
- package/dist/types/classes/providers/assets/presigned-asset-provider.class.d.ts +69 -0
- package/dist/types/classes/providers/paste-intent/event-paste-intent.provider.d.ts +8 -0
- package/dist/types/classes/providers/paste-intent/ios-paste-intent.provider.d.ts +6 -0
- package/dist/types/classes/providers/paste-intent/navigator-paste-intent.provider.d.ts +9 -0
- package/dist/types/classes/providers/sync/broadcast-sync-provider.class.d.ts +23 -0
- package/dist/types/classes/providers/sync/hocuspocus-sync-provider.class.d.ts +163 -0
- package/dist/types/classes/providers/sync/in-memory-sync-provider.class.d.ts +39 -0
- package/dist/types/classes/providers/sync/indexeddb-sync-provider.class.d.ts +25 -0
- package/dist/types/classes/providers/sync/websocket-sync-provider.class.d.ts +63 -0
- package/dist/types/classes/registries/dynamic-object-renderer.registry.d.ts +32 -0
- package/dist/types/classes/registries/font-registry.class.d.ts +19 -0
- package/dist/types/classes/registries/icon-registry.class.d.ts +39 -0
- package/dist/types/classes/registries/paste-intent-provider.registry.d.ts +10 -0
- package/dist/types/classes/registries/tool.registry.d.ts +53 -0
- package/dist/types/classes/structures/app-state-map.structure.d.ts +171 -0
- package/dist/types/classes/structures/object-map.structure.d.ts +403 -0
- package/dist/types/classes/structures/quadtree.structure.d.ts +128 -0
- package/dist/types/classes/tools/base-tool.class.d.ts +72 -0
- package/dist/types/classes/tools/brush-tool.class.d.ts +68 -0
- package/dist/types/classes/tools/eraser-tool.class.d.ts +38 -0
- package/dist/types/classes/tools/image-tool.class.d.ts +74 -0
- package/dist/types/classes/tools/line-tool.class.d.ts +63 -0
- package/dist/types/classes/tools/selection-tool.class.d.ts +188 -0
- package/dist/types/classes/tools/shape-tool.class.d.ts +90 -0
- package/dist/types/classes/tools/text-tool.class.d.ts +66 -0
- package/dist/types/components/kritzel-awareness-cursors/kritzel-awareness-cursors.d.ts +29 -0
- package/dist/types/components/kritzel-cursor-trail/kritzel-cursor-trail.d.ts +19 -0
- package/dist/types/components/kritzel-engine/kritzel-engine.d.ts +695 -0
- package/dist/types/components/kritzel-icon/kritzel-icon.d.ts +6 -0
- package/dist/types/components/kritzel-watermark/kritzel-watermark.d.ts +20 -0
- package/dist/types/components.d.ts +981 -0
- package/dist/types/configs/default-asset-storage.config.d.ts +8 -0
- package/dist/types/configs/default-brush-tool.config.d.ts +2 -0
- package/dist/types/configs/default-engine-config.d.ts +2 -0
- package/dist/types/configs/default-line-tool.config.d.ts +2 -0
- package/dist/types/configs/default-shape-tool.config.d.ts +2 -0
- package/dist/types/configs/default-sync.config.d.ts +5 -0
- package/dist/types/configs/default-text-tool.config.d.ts +2 -0
- package/dist/types/constants/color-palette.constants.d.ts +31 -0
- package/dist/types/constants/core.constants.d.ts +0 -0
- package/dist/types/constants/engine.constants.d.ts +4 -0
- package/dist/types/constants/license.constants.d.ts +25 -0
- package/dist/types/constants/schema.constants.d.ts +4 -0
- package/dist/types/constants/storage.constants.d.ts +1 -0
- package/dist/types/constants/stroke-size.constants.d.ts +2 -0
- package/dist/types/constants/text-tool-fonts.constants.d.ts +3 -0
- package/dist/types/constants/version.d.ts +6 -0
- package/dist/types/enums/alignment.enum.d.ts +8 -0
- package/dist/types/enums/event-button.enum.d.ts +5 -0
- package/dist/types/enums/handle-type.enum.d.ts +6 -0
- package/dist/types/enums/shape-type.enum.d.ts +5 -0
- package/dist/types/events/active-workspace-change.event.d.ts +7 -0
- package/dist/types/events/editor-is-ready.event.d.ts +18 -0
- package/dist/types/events/login.event.d.ts +3 -0
- package/dist/types/helpers/alignment.helper.d.ts +21 -0
- package/dist/types/helpers/class.helper.d.ts +3 -0
- package/dist/types/helpers/color.helper.d.ts +34 -0
- package/dist/types/helpers/cursor.helper.d.ts +22 -0
- package/dist/types/helpers/devices.helper.d.ts +8 -0
- package/dist/types/helpers/event.helper.d.ts +6 -0
- package/dist/types/helpers/geometry.helper.d.ts +38 -0
- package/dist/types/helpers/html.helper.d.ts +8 -0
- package/dist/types/helpers/image-file.helper.d.ts +3 -0
- package/dist/types/helpers/keyboard.helper.d.ts +6 -0
- package/dist/types/helpers/localization.helper.d.ts +18 -0
- package/dist/types/helpers/math.helper.d.ts +4 -0
- package/dist/types/helpers/migration.helper.d.ts +30 -0
- package/dist/types/helpers/object.helper.d.ts +9 -0
- package/dist/types/helpers/svg-export.helper.d.ts +231 -0
- package/dist/types/helpers/theme.helper.d.ts +42 -0
- package/dist/types/helpers/tool-config.helper.d.ts +4 -0
- package/dist/types/helpers/wheel.helper.d.ts +9 -0
- package/dist/types/index.d.ts +109 -0
- package/dist/types/interfaces/anchor.interface.d.ts +147 -0
- package/dist/types/interfaces/arrow-head.interface.d.ts +27 -0
- package/dist/types/interfaces/asset-provider.interface.d.ts +88 -0
- package/dist/types/interfaces/asset-storage-config.interface.d.ts +40 -0
- package/dist/types/interfaces/asset.interface.d.ts +43 -0
- package/dist/types/interfaces/bounding-box.interface.d.ts +8 -0
- package/dist/types/interfaces/clonable.interface.d.ts +3 -0
- package/dist/types/interfaces/context-menu-item.interface.d.ts +25 -0
- package/dist/types/interfaces/context-menu-state.interface.d.ts +16 -0
- package/dist/types/interfaces/debug-info.interface.d.ts +7 -0
- package/dist/types/interfaces/dialog.interface.d.ts +4 -0
- package/dist/types/interfaces/document-metadata.interface.d.ts +6 -0
- package/dist/types/interfaces/dynamic-object-renderer.interface.d.ts +40 -0
- package/dist/types/interfaces/engine-state.interface.d.ts +82 -0
- package/dist/types/interfaces/font.interface.d.ts +15 -0
- package/dist/types/interfaces/localization.interface.d.ts +143 -0
- package/dist/types/interfaces/login-config.interface.d.ts +24 -0
- package/dist/types/interfaces/master-detail.interface.d.ts +14 -0
- package/dist/types/interfaces/menu-item.interface.d.ts +27 -0
- package/dist/types/interfaces/migration.interface.d.ts +16 -0
- package/dist/types/interfaces/notification.interface.d.ts +10 -0
- package/dist/types/interfaces/object-change-event.interface.d.ts +31 -0
- package/dist/types/interfaces/object-export.interface.d.ts +20 -0
- package/dist/types/interfaces/object.interface.d.ts +53 -0
- package/dist/types/interfaces/paste-intent-provider.interface.d.ts +10 -0
- package/dist/types/interfaces/point.interface.d.ts +4 -0
- package/dist/types/interfaces/polygon.interface.d.ts +7 -0
- package/dist/types/interfaces/remote-cursor.interface.d.ts +18 -0
- package/dist/types/interfaces/selection-state.interface.d.ts +7 -0
- package/dist/types/interfaces/serializable.interface.d.ts +5 -0
- package/dist/types/interfaces/settings.interface.d.ts +25 -0
- package/dist/types/interfaces/share.interface.d.ts +9 -0
- package/dist/types/interfaces/shortcut.interface.d.ts +10 -0
- package/dist/types/interfaces/sync-config.interface.d.ts +41 -0
- package/dist/types/interfaces/sync-provider.interface.d.ts +66 -0
- package/dist/types/interfaces/theme.interface.d.ts +553 -0
- package/dist/types/interfaces/tool-config.interface.d.ts +24 -0
- package/dist/types/interfaces/tool-type.interface.d.ts +1 -0
- package/dist/types/interfaces/tool.interface.d.ts +8 -0
- package/dist/types/interfaces/toolbar-item.interface.d.ts +66 -0
- package/dist/types/interfaces/undo-state.interface.d.ts +6 -0
- package/dist/types/interfaces/user.interface.d.ts +22 -0
- package/dist/types/interfaces/viewport-state.interface.d.ts +7 -0
- package/dist/types/locales/de-locale.d.ts +5 -0
- package/dist/types/locales/en-locale.d.ts +6 -0
- package/dist/types/locales/fr-locale.d.ts +5 -0
- package/dist/types/migrations/app-state.migrations.d.ts +11 -0
- package/dist/types/migrations/workspace.migrations.d.ts +11 -0
- package/dist/types/stencil-public-runtime.d.ts +1873 -0
- package/dist/types/themes/dark-theme.d.ts +5 -0
- package/dist/types/themes/light-theme.d.ts +5 -0
- package/dist/types/types/deep-readonly.type.d.ts +7 -0
- package/dist/types/types/state.types.d.ts +3 -0
- package/loader/cdn.js +1 -0
- package/loader/index.cjs.js +1 -0
- package/loader/index.d.ts +24 -0
- package/loader/index.es2017.js +1 -0
- package/loader/index.js +2 -0
- package/package.json +101 -0
- package/readme.md +11 -0
|
@@ -0,0 +1,794 @@
|
|
|
1
|
+
import { KritzelMouseButton } from "../../enums/event-button.enum";
|
|
2
|
+
import { KritzelSelectionBox } from "../objects/selection-box.class";
|
|
3
|
+
import { debounce } from "lodash";
|
|
4
|
+
/**
|
|
5
|
+
* Manages viewport transformations including panning, zooming, and centering.
|
|
6
|
+
* Handles mouse, touch, and wheel events for viewport manipulation.
|
|
7
|
+
* Supports viewport boundaries and animated transitions.
|
|
8
|
+
*/
|
|
9
|
+
export class KritzelViewport {
|
|
10
|
+
/** Reference to the KritzelCore instance */
|
|
11
|
+
_core;
|
|
12
|
+
/** Debounced function to persist viewport state to workspace */
|
|
13
|
+
_debounceUpdate;
|
|
14
|
+
/** Debounced function to end scaling state after pinch gestures */
|
|
15
|
+
_debounceEndScaling;
|
|
16
|
+
/** Animation frame ID for cancellable viewport animations */
|
|
17
|
+
_animationFrameId = null;
|
|
18
|
+
/** Initial distance between two touch points for pinch-to-zoom */
|
|
19
|
+
initialTouchDistance = 0;
|
|
20
|
+
/** Starting X position for pan/zoom gestures */
|
|
21
|
+
startX = 0;
|
|
22
|
+
/** Starting Y position for pan/zoom gestures */
|
|
23
|
+
startY = 0;
|
|
24
|
+
/** Minimum movement distance (in screen pixels) before broadcasting touch cursor position */
|
|
25
|
+
static TOUCH_CURSOR_BROADCAST_THRESHOLD = 5;
|
|
26
|
+
/** Screen X position where the current touch interaction started */
|
|
27
|
+
_touchStartScreenX = 0;
|
|
28
|
+
/** Screen Y position where the current touch interaction started */
|
|
29
|
+
_touchStartScreenY = 0;
|
|
30
|
+
/** Whether the touch movement threshold has been exceeded for cursor broadcasting */
|
|
31
|
+
_touchCursorBroadcastActive = false;
|
|
32
|
+
/**
|
|
33
|
+
* Creates a new KritzelViewport instance and initializes viewport state.
|
|
34
|
+
* Sets up debounced handlers for viewport updates and scaling end events.
|
|
35
|
+
* @param core - The KritzelCore instance to manage viewport for
|
|
36
|
+
* @param host - The HTML element hosting the canvas
|
|
37
|
+
*/
|
|
38
|
+
constructor(core, host) {
|
|
39
|
+
this._core = core;
|
|
40
|
+
this._core.store.state.host = host;
|
|
41
|
+
this._core.store.state.viewportWidth = host.clientWidth;
|
|
42
|
+
this._core.store.state.viewportHeight = host.clientHeight;
|
|
43
|
+
this._core.store.state.startX = 0;
|
|
44
|
+
this._core.store.state.startY = 0;
|
|
45
|
+
this._core.store.state.translateX = 0;
|
|
46
|
+
this._core.store.state.translateY = 0;
|
|
47
|
+
this._debounceUpdate = debounce(() => {
|
|
48
|
+
this._core.updateWorkspaceViewport(this._core.store.state.translateX, this._core.store.state.translateY, this._core.store.state.scale);
|
|
49
|
+
}, 300);
|
|
50
|
+
this._debounceEndScaling = debounce(() => {
|
|
51
|
+
this._core.store.state.isScaling = false;
|
|
52
|
+
this._core.rerender();
|
|
53
|
+
}, 100);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Cancels any pending debounced viewport updates.
|
|
57
|
+
* Should be called before switching workspaces to prevent the old workspace's
|
|
58
|
+
* viewport state from being saved to the new workspace.
|
|
59
|
+
*/
|
|
60
|
+
cancelPendingUpdates() {
|
|
61
|
+
this._debounceUpdate.cancel();
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Clamps translateX and translateY to respect viewport boundaries.
|
|
65
|
+
* Boundaries are defined in world coordinates and converted to screen-space translate limits.
|
|
66
|
+
* @param translateX - The proposed translateX value
|
|
67
|
+
* @param translateY - The proposed translateY value
|
|
68
|
+
* @returns Clamped translate values
|
|
69
|
+
*/
|
|
70
|
+
clampTranslate(translateX, translateY) {
|
|
71
|
+
const state = this._core.store.state;
|
|
72
|
+
const { scale, viewportWidth, viewportHeight } = state;
|
|
73
|
+
const { viewportBoundaryLeft, viewportBoundaryRight, viewportBoundaryTop, viewportBoundaryBottom } = state;
|
|
74
|
+
// Convert world boundaries to screen-space translate limits
|
|
75
|
+
// translateX = viewportX - worldX * scale, so:
|
|
76
|
+
// - To show boundaryLeft at left edge (viewportX=0): translateX = -boundaryLeft * scale
|
|
77
|
+
// - To show boundaryRight at right edge (viewportX=viewportWidth): translateX = viewportWidth - boundaryRight * scale
|
|
78
|
+
const translateXMax = -viewportBoundaryLeft * scale;
|
|
79
|
+
const translateXMin = viewportWidth - viewportBoundaryRight * scale;
|
|
80
|
+
const translateYMax = -viewportBoundaryTop * scale;
|
|
81
|
+
const translateYMin = viewportHeight - viewportBoundaryBottom * scale;
|
|
82
|
+
// Only clamp if boundaries are finite
|
|
83
|
+
let clampedX = translateX;
|
|
84
|
+
let clampedY = translateY;
|
|
85
|
+
if (Number.isFinite(translateXMin) && Number.isFinite(translateXMax)) {
|
|
86
|
+
clampedX = Math.max(translateXMin, Math.min(translateXMax, translateX));
|
|
87
|
+
}
|
|
88
|
+
else if (Number.isFinite(translateXMin)) {
|
|
89
|
+
clampedX = Math.max(translateXMin, translateX);
|
|
90
|
+
}
|
|
91
|
+
else if (Number.isFinite(translateXMax)) {
|
|
92
|
+
clampedX = Math.min(translateXMax, translateX);
|
|
93
|
+
}
|
|
94
|
+
if (Number.isFinite(translateYMin) && Number.isFinite(translateYMax)) {
|
|
95
|
+
clampedY = Math.max(translateYMin, Math.min(translateYMax, translateY));
|
|
96
|
+
}
|
|
97
|
+
else if (Number.isFinite(translateYMin)) {
|
|
98
|
+
clampedY = Math.max(translateYMin, translateY);
|
|
99
|
+
}
|
|
100
|
+
else if (Number.isFinite(translateYMax)) {
|
|
101
|
+
clampedY = Math.min(translateYMax, translateY);
|
|
102
|
+
}
|
|
103
|
+
return { translateX: clampedX, translateY: clampedY };
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Calculates the effective minimum scale considering both the configured scaleMin
|
|
107
|
+
* and the minimum scale required to keep the viewport within boundaries.
|
|
108
|
+
* This prevents users from zooming out far enough to see past the boundaries.
|
|
109
|
+
* @returns The effective minimum scale value
|
|
110
|
+
*/
|
|
111
|
+
getEffectiveMinScale() {
|
|
112
|
+
const state = this._core.store.state;
|
|
113
|
+
const { viewportWidth, viewportHeight, scaleMin } = state;
|
|
114
|
+
const { viewportBoundaryLeft, viewportBoundaryRight, viewportBoundaryTop, viewportBoundaryBottom } = state;
|
|
115
|
+
const boundaryWidth = viewportBoundaryRight - viewportBoundaryLeft;
|
|
116
|
+
const boundaryHeight = viewportBoundaryBottom - viewportBoundaryTop;
|
|
117
|
+
// If boundaries are infinite, just use the configured scaleMin
|
|
118
|
+
if (!Number.isFinite(boundaryWidth) || !Number.isFinite(boundaryHeight)) {
|
|
119
|
+
return scaleMin;
|
|
120
|
+
}
|
|
121
|
+
// Calculate minimum scale to fit viewport within boundaries
|
|
122
|
+
// scale >= viewportWidth / boundaryWidth and scale >= viewportHeight / boundaryHeight
|
|
123
|
+
const minScaleX = boundaryWidth > 0 ? viewportWidth / boundaryWidth : scaleMin;
|
|
124
|
+
const minScaleY = boundaryHeight > 0 ? viewportHeight / boundaryHeight : scaleMin;
|
|
125
|
+
const minScaleForBoundaries = Math.max(minScaleX, minScaleY);
|
|
126
|
+
return Math.max(scaleMin, minScaleForBoundaries);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Handles viewport resize events.
|
|
130
|
+
* Updates the stored viewport dimensions and triggers a rerender.
|
|
131
|
+
*/
|
|
132
|
+
handleResize() {
|
|
133
|
+
this._core.store.state.viewportWidth = this._core.store.host.clientWidth;
|
|
134
|
+
this._core.store.state.viewportHeight = this._core.store.host.clientHeight;
|
|
135
|
+
this._core.store.state.hasViewportChanged = true;
|
|
136
|
+
this._core.rerender();
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Handles pointer down events for initiating viewport interactions.
|
|
140
|
+
* For mouse: Right-click starts panning mode.
|
|
141
|
+
* For touch/pen: Two-finger touch initiates pinch-to-zoom.
|
|
142
|
+
* Cancels any ongoing viewport animation when user starts interacting.
|
|
143
|
+
* @param event - The pointer down event
|
|
144
|
+
*/
|
|
145
|
+
handlePointerDown(event) {
|
|
146
|
+
// Cancel any ongoing viewport animation when user starts interacting
|
|
147
|
+
this.cancelViewportAnimation();
|
|
148
|
+
if (event.pointerType === 'mouse') {
|
|
149
|
+
const adjustedClientX = event.clientX - this._core.store.offsetX;
|
|
150
|
+
const adjustedClientY = event.clientY - this._core.store.offsetY;
|
|
151
|
+
if (event.button === KritzelMouseButton.Right) {
|
|
152
|
+
this._core.store.state.isPanning = true;
|
|
153
|
+
this._core.store.state.startX = adjustedClientX;
|
|
154
|
+
this._core.store.state.startY = adjustedClientY;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (event.pointerType === 'touch' || event.pointerType === 'pen') {
|
|
158
|
+
const activePointers = Array.from(this._core.store.state.pointers.values());
|
|
159
|
+
if (activePointers.length === 1) {
|
|
160
|
+
this._touchStartScreenX = event.clientX;
|
|
161
|
+
this._touchStartScreenY = event.clientY;
|
|
162
|
+
this._touchCursorBroadcastActive = false;
|
|
163
|
+
}
|
|
164
|
+
if (activePointers.length === 2) {
|
|
165
|
+
this._core.store.objects?.clearCursorPosition();
|
|
166
|
+
const currentPath = this._core.store.currentPath;
|
|
167
|
+
if (currentPath) {
|
|
168
|
+
this._core.store.objects.remove(obj => obj.id === currentPath.id);
|
|
169
|
+
}
|
|
170
|
+
// Clear selection box when scaling starts to prevent it from remaining on screen
|
|
171
|
+
if (this._core.store.state.isSelecting) {
|
|
172
|
+
this._core.store.state.isSelecting = false;
|
|
173
|
+
this._core.store.objects.remove(obj => obj instanceof KritzelSelectionBox);
|
|
174
|
+
this._core.store.setSelectionBox(null);
|
|
175
|
+
// Clear selection preview state from objects - use selectedObjects for efficiency
|
|
176
|
+
this._core.store.selectedObjects.forEach(object => {
|
|
177
|
+
object.isSelected = false;
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
this._core.store.state.isScaling = true;
|
|
181
|
+
const firstTouchX = activePointers[0].clientX - this._core.store.offsetX;
|
|
182
|
+
const firstTouchY = activePointers[0].clientY - this._core.store.offsetY;
|
|
183
|
+
const secondTouchX = activePointers[1].clientX - this._core.store.offsetX;
|
|
184
|
+
const secondTouchY = activePointers[1].clientY - this._core.store.offsetY;
|
|
185
|
+
this.initialTouchDistance = Math.sqrt(Math.pow(firstTouchX - secondTouchX, 2) + Math.pow(firstTouchY - secondTouchY, 2));
|
|
186
|
+
this.startX = (firstTouchX + secondTouchX) / 2;
|
|
187
|
+
this.startY = (firstTouchY + secondTouchY) / 2;
|
|
188
|
+
this._core.rerender();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Handles pointer move events for viewport panning and zooming.
|
|
194
|
+
* For mouse: Updates pointer position and handles right-click pan dragging.
|
|
195
|
+
* For touch/pen: Handles two-finger pinch-to-zoom with translation.
|
|
196
|
+
* @param event - The pointer move event
|
|
197
|
+
*/
|
|
198
|
+
handlePointerMove(event) {
|
|
199
|
+
if (event.pointerType === 'mouse') {
|
|
200
|
+
const hostRect = this._core.store.host.getBoundingClientRect();
|
|
201
|
+
const xRelativeToHost = event.clientX - hostRect.left;
|
|
202
|
+
const yRelativeToHost = event.clientY - hostRect.top;
|
|
203
|
+
this._core.store.state.pointerX = (xRelativeToHost - this._core.store.state.translateX) / this._core.store.state.scale;
|
|
204
|
+
this._core.store.state.pointerY = (yRelativeToHost - this._core.store.state.translateY) / this._core.store.state.scale;
|
|
205
|
+
this._core.store.objects?.updateCursorPosition(this._core.store.state.pointerX, this._core.store.state.pointerY);
|
|
206
|
+
if (this._core.store.state.isPanning) {
|
|
207
|
+
const dx = xRelativeToHost - this._core.store.state.startX;
|
|
208
|
+
const dy = yRelativeToHost - this._core.store.state.startY;
|
|
209
|
+
const newTranslateX = this._core.store.state.translateX + dx;
|
|
210
|
+
const newTranslateY = this._core.store.state.translateY + dy;
|
|
211
|
+
const clamped = this.clampTranslate(newTranslateX, newTranslateY);
|
|
212
|
+
this._core.store.state.translateX = clamped.translateX;
|
|
213
|
+
this._core.store.state.translateY = clamped.translateY;
|
|
214
|
+
this._core.store.state.startX = xRelativeToHost;
|
|
215
|
+
this._core.store.state.startY = yRelativeToHost;
|
|
216
|
+
this._core.store.state.hasViewportChanged = true;
|
|
217
|
+
this._core.store.state.skipContextMenu = true;
|
|
218
|
+
this._core.rerender();
|
|
219
|
+
this._debounceUpdate();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (event.pointerType === 'touch' || event.pointerType === 'pen') {
|
|
223
|
+
const hostRect = this._core.store.host.getBoundingClientRect();
|
|
224
|
+
const xRelativeToHost = event.clientX - hostRect.left;
|
|
225
|
+
const yRelativeToHost = event.clientY - hostRect.top;
|
|
226
|
+
const activePointers = Array.from(this._core.store.state.pointers.values());
|
|
227
|
+
if (this._core.store.state.isScaling || activePointers.length > 1) {
|
|
228
|
+
this._core.store.objects?.clearCursorPosition();
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
this._core.store.state.pointerX = (xRelativeToHost - this._core.store.state.translateX) / this._core.store.state.scale;
|
|
232
|
+
this._core.store.state.pointerY = (yRelativeToHost - this._core.store.state.translateY) / this._core.store.state.scale;
|
|
233
|
+
if (!this._touchCursorBroadcastActive) {
|
|
234
|
+
const dx = event.clientX - this._touchStartScreenX;
|
|
235
|
+
const dy = event.clientY - this._touchStartScreenY;
|
|
236
|
+
if (Math.sqrt(dx * dx + dy * dy) >= KritzelViewport.TOUCH_CURSOR_BROADCAST_THRESHOLD) {
|
|
237
|
+
this._touchCursorBroadcastActive = true;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (this._touchCursorBroadcastActive) {
|
|
241
|
+
this._core.store.objects?.updateCursorPosition(this._core.store.state.pointerX, this._core.store.state.pointerY);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (activePointers.length === 2) {
|
|
245
|
+
const firstTouchX = activePointers[0].clientX - this._core.store.offsetX;
|
|
246
|
+
const firstTouchY = activePointers[0].clientY - this._core.store.offsetY;
|
|
247
|
+
const secondTouchX = activePointers[1].clientX - this._core.store.offsetX;
|
|
248
|
+
const secondTouchY = activePointers[1].clientY - this._core.store.offsetY;
|
|
249
|
+
const currentTouchDistance = Math.sqrt(Math.pow(firstTouchX - secondTouchX, 2) + Math.pow(firstTouchY - secondTouchY, 2));
|
|
250
|
+
const midpointX = (firstTouchX + secondTouchX) / 2;
|
|
251
|
+
const midpointY = (firstTouchY + secondTouchY) / 2;
|
|
252
|
+
const scaleRatio = currentTouchDistance / this.initialTouchDistance;
|
|
253
|
+
const newScale = this._core.store.state.scale * scaleRatio;
|
|
254
|
+
const effectiveMinScale = this.getEffectiveMinScale();
|
|
255
|
+
if (newScale > this._core.store.state.scaleMax || newScale < effectiveMinScale) {
|
|
256
|
+
const newTranslateX = this._core.store.state.translateX + midpointX - this.startX;
|
|
257
|
+
const newTranslateY = this._core.store.state.translateY + midpointY - this.startY;
|
|
258
|
+
const clamped = this.clampTranslate(newTranslateX, newTranslateY);
|
|
259
|
+
this._core.store.state.translateX = clamped.translateX;
|
|
260
|
+
this._core.store.state.translateY = clamped.translateY;
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
const translateXAdjustment = (midpointX - this._core.store.state.translateX) * (scaleRatio - 1);
|
|
264
|
+
const translateYAdjustment = (midpointY - this._core.store.state.translateY) * (scaleRatio - 1);
|
|
265
|
+
const newTranslateX = this._core.store.state.translateX + midpointX - this.startX - translateXAdjustment;
|
|
266
|
+
const newTranslateY = this._core.store.state.translateY + midpointY - this.startY - translateYAdjustment;
|
|
267
|
+
this._core.store.state.scale = newScale;
|
|
268
|
+
const clamped = this.clampTranslate(newTranslateX, newTranslateY);
|
|
269
|
+
this._core.store.state.translateX = clamped.translateX;
|
|
270
|
+
this._core.store.state.translateY = clamped.translateY;
|
|
271
|
+
this.initialTouchDistance = currentTouchDistance;
|
|
272
|
+
}
|
|
273
|
+
this.startX = midpointX;
|
|
274
|
+
this.startY = midpointY;
|
|
275
|
+
this._core.store.state.hasViewportChanged = true;
|
|
276
|
+
this._core.rerender();
|
|
277
|
+
this._debounceUpdate();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Handles pointer up events to end viewport interactions.
|
|
283
|
+
* For mouse: Ends panning mode on right-click release.
|
|
284
|
+
* For touch/pen: Triggers debounced end of scaling when all fingers are lifted.
|
|
285
|
+
* @param event - The pointer up event
|
|
286
|
+
*/
|
|
287
|
+
handlePointerUp(event) {
|
|
288
|
+
if (event.pointerType === 'mouse') {
|
|
289
|
+
if (this._core.store.state.isPanning) {
|
|
290
|
+
this._core.store.state.isPanning = false;
|
|
291
|
+
this._core.rerender();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (event.pointerType === 'touch' || event.pointerType === 'pen') {
|
|
295
|
+
this._touchCursorBroadcastActive = false;
|
|
296
|
+
if (this._core.store.state.pointers.size === 0) {
|
|
297
|
+
this._debounceEndScaling();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Handles mouse wheel events for panning and zooming.
|
|
303
|
+
* Ctrl+wheel triggers zoom; regular wheel triggers pan.
|
|
304
|
+
* Cancels any ongoing viewport animation when user scrolls.
|
|
305
|
+
* @param event - The wheel event
|
|
306
|
+
*/
|
|
307
|
+
handleWheel(event) {
|
|
308
|
+
// Cancel any ongoing viewport animation when user scrolls
|
|
309
|
+
this.cancelViewportAnimation();
|
|
310
|
+
if (event.ctrlKey) {
|
|
311
|
+
this.handleZoom(event);
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
this.handlePan(event);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Returns the current viewport state.
|
|
319
|
+
* @returns The current viewport position, scale, and dimensions.
|
|
320
|
+
*/
|
|
321
|
+
getViewport() {
|
|
322
|
+
const state = this._core.store.state;
|
|
323
|
+
return {
|
|
324
|
+
translateX: state.translateX,
|
|
325
|
+
translateY: state.translateY,
|
|
326
|
+
scale: state.scale,
|
|
327
|
+
width: state.viewportWidth,
|
|
328
|
+
height: state.viewportHeight,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Converts screen-relative pixel coordinates to world coordinates.
|
|
333
|
+
* @param x - X position in screen pixels (relative to the host element)
|
|
334
|
+
* @param y - Y position in screen pixels (relative to the host element)
|
|
335
|
+
* @returns World coordinates { x, y }
|
|
336
|
+
*/
|
|
337
|
+
screenToWorld(x, y) {
|
|
338
|
+
const state = this._core.store.state;
|
|
339
|
+
return {
|
|
340
|
+
x: (x - state.translateX) / state.scale,
|
|
341
|
+
y: (y - state.translateY) / state.scale,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Converts world coordinates to screen-relative pixel coordinates.
|
|
346
|
+
* @param x - X position in world coordinates
|
|
347
|
+
* @param y - Y position in world coordinates
|
|
348
|
+
* @returns Screen coordinates { x, y } relative to the host element
|
|
349
|
+
*/
|
|
350
|
+
worldToScreen(x, y) {
|
|
351
|
+
const state = this._core.store.state;
|
|
352
|
+
return {
|
|
353
|
+
x: x * state.scale + state.translateX,
|
|
354
|
+
y: y * state.scale + state.translateY,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Animates the viewport to center on the given world coordinates at the specified scale.
|
|
359
|
+
* Respects scale limits and viewport boundaries.
|
|
360
|
+
* @param x - X position in world coordinates to center on
|
|
361
|
+
* @param y - Y position in world coordinates to center on
|
|
362
|
+
* @param scale - Target scale level
|
|
363
|
+
*/
|
|
364
|
+
setViewport(x, y, scale) {
|
|
365
|
+
const state = this._core.store.state;
|
|
366
|
+
const clampedScale = Math.min(state.scaleMax, Math.max(this.getEffectiveMinScale(), scale));
|
|
367
|
+
const targetTranslateX = state.viewportWidth / 2 - x * clampedScale;
|
|
368
|
+
const targetTranslateY = state.viewportHeight / 2 - y * clampedScale;
|
|
369
|
+
this.animateViewportTo(targetTranslateX, targetTranslateY, clampedScale);
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Animates the viewport to center on the given world coordinates without changing the scale.
|
|
373
|
+
* @param x - X position in world coordinates to center on
|
|
374
|
+
* @param y - Y position in world coordinates to center on
|
|
375
|
+
*/
|
|
376
|
+
panTo(x, y) {
|
|
377
|
+
this.setViewport(x, y, this._core.store.state.scale);
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Animates the viewport to the given scale level.
|
|
381
|
+
* Optionally centers on a world point; if omitted, zooms around the viewport center.
|
|
382
|
+
* @param scale - Target scale level
|
|
383
|
+
* @param worldX - Optional X position in world coordinates to center on
|
|
384
|
+
* @param worldY - Optional Y position in world coordinates to center on
|
|
385
|
+
*/
|
|
386
|
+
zoomTo(scale, worldX, worldY) {
|
|
387
|
+
const state = this._core.store.state;
|
|
388
|
+
if (worldX !== undefined && worldY !== undefined) {
|
|
389
|
+
this.setViewport(worldX, worldY, scale);
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
// Zoom around viewport center
|
|
393
|
+
const centerWorldX = (state.viewportWidth / 2 - state.translateX) / state.scale;
|
|
394
|
+
const centerWorldY = (state.viewportHeight / 2 - state.translateY) / state.scale;
|
|
395
|
+
this.setViewport(centerWorldX, centerWorldY, scale);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Zooms in by a fixed step, centered on the current viewport center,
|
|
400
|
+
* with a smooth animation. The resulting scale is clamped to the allowed range.
|
|
401
|
+
* @param factor - Multiplicative zoom-in step
|
|
402
|
+
* @param duration - Animation duration in milliseconds
|
|
403
|
+
*/
|
|
404
|
+
zoomIn(factor, duration) {
|
|
405
|
+
this.animateZoomAroundViewportCenter(factor, duration);
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Zooms out by a fixed step, centered on the current viewport center,
|
|
409
|
+
* with a smooth animation. The resulting scale is clamped to the allowed range.
|
|
410
|
+
* @param factor - Multiplicative zoom-out step
|
|
411
|
+
* @param duration - Animation duration in milliseconds
|
|
412
|
+
*/
|
|
413
|
+
zoomOut(factor, duration) {
|
|
414
|
+
this.animateZoomAroundViewportCenter(1 / factor, duration);
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Smoothly zooms around the viewport center, keeping the center world point
|
|
418
|
+
* fixed for the entire animation. The translation is recomputed from the fixed
|
|
419
|
+
* center point on every frame (rather than interpolated independently), which
|
|
420
|
+
* guarantees the center never drifts and the viewport does not jump.
|
|
421
|
+
* The resulting scale is clamped to the allowed range and the translation is
|
|
422
|
+
* clamped to the viewport boundaries.
|
|
423
|
+
* @param scaleFactor - Multiplicative scale factor (>1 zooms in, <1 zooms out)
|
|
424
|
+
* @param duration - Animation duration in milliseconds
|
|
425
|
+
*/
|
|
426
|
+
animateZoomAroundViewportCenter(scaleFactor, duration) {
|
|
427
|
+
this.cancelViewportAnimation();
|
|
428
|
+
const state = this._core.store.state;
|
|
429
|
+
const startScale = state.scale;
|
|
430
|
+
const effectiveMinScale = this.getEffectiveMinScale();
|
|
431
|
+
const targetScale = Math.min(state.scaleMax, Math.max(effectiveMinScale, startScale * scaleFactor));
|
|
432
|
+
// The world point currently under the viewport center stays fixed.
|
|
433
|
+
const centerScreenX = state.viewportWidth / 2;
|
|
434
|
+
const centerScreenY = state.viewportHeight / 2;
|
|
435
|
+
const centerWorldX = (centerScreenX - state.translateX) / startScale;
|
|
436
|
+
const centerWorldY = (centerScreenY - state.translateY) / startScale;
|
|
437
|
+
if (targetScale === startScale || duration <= 0) {
|
|
438
|
+
this.applyZoomAroundCenter(targetScale, centerScreenX, centerScreenY, centerWorldX, centerWorldY);
|
|
439
|
+
state.isScaling = true;
|
|
440
|
+
this._core.rerender();
|
|
441
|
+
this._debounceUpdate();
|
|
442
|
+
this._debounceEndScaling();
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const startTime = performance.now();
|
|
446
|
+
state.isScaling = true;
|
|
447
|
+
const animate = (currentTime) => {
|
|
448
|
+
const elapsed = currentTime - startTime;
|
|
449
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
450
|
+
// easeInOutCubic for a smooth ramp in and out
|
|
451
|
+
const eased = progress < 0.5 ? 4 * progress * progress * progress : 1 - Math.pow(-2 * progress + 2, 3) / 2;
|
|
452
|
+
const frameScale = startScale + (targetScale - startScale) * eased;
|
|
453
|
+
this.applyZoomAroundCenter(frameScale, centerScreenX, centerScreenY, centerWorldX, centerWorldY);
|
|
454
|
+
this._core.rerender();
|
|
455
|
+
if (progress < 1) {
|
|
456
|
+
this._animationFrameId = requestAnimationFrame(animate);
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
this._animationFrameId = null;
|
|
460
|
+
state.isScaling = false;
|
|
461
|
+
this._core.rerender();
|
|
462
|
+
this._debounceUpdate();
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
this._animationFrameId = requestAnimationFrame(animate);
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Applies a single zoom step that keeps the given world point anchored to the
|
|
469
|
+
* given screen point at the specified scale, clamped to the viewport boundaries.
|
|
470
|
+
*/
|
|
471
|
+
applyZoomAroundCenter(scale, screenX, screenY, worldX, worldY) {
|
|
472
|
+
const state = this._core.store.state;
|
|
473
|
+
state.scale = scale;
|
|
474
|
+
const clamped = this.clampTranslate(screenX - worldX * scale, screenY - worldY * scale);
|
|
475
|
+
state.translateX = clamped.translateX;
|
|
476
|
+
state.translateY = clamped.translateY;
|
|
477
|
+
state.hasViewportChanged = true;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Centers a given object in the viewport without changing the scale.
|
|
481
|
+
* The object's center point will be positioned at the viewport center.
|
|
482
|
+
* Respects viewport boundary constraints.
|
|
483
|
+
* @param object - The object to center in the viewport
|
|
484
|
+
*/
|
|
485
|
+
centerInViewport(object) {
|
|
486
|
+
const { scale, viewportWidth, viewportHeight } = this._core.store.state;
|
|
487
|
+
const { x, y, width, height } = object.rotatedBoundingBox;
|
|
488
|
+
const objectCenterX = x + width / 2;
|
|
489
|
+
const objectCenterY = y + height / 2;
|
|
490
|
+
const newTranslateX = viewportWidth / 2 - objectCenterX * scale;
|
|
491
|
+
const newTranslateY = viewportHeight / 2 - objectCenterY * scale;
|
|
492
|
+
const clamped = this.clampTranslate(newTranslateX, newTranslateY);
|
|
493
|
+
this._core.store.state.translateX = clamped.translateX;
|
|
494
|
+
this._core.store.state.translateY = clamped.translateY;
|
|
495
|
+
this._core.store.state.hasViewportChanged = true;
|
|
496
|
+
this._core.rerender();
|
|
497
|
+
this._debounceUpdate();
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Centers and scales the viewport to fit a given object with 10% padding.
|
|
501
|
+
* Calculates the optimal scale to show the entire object while respecting
|
|
502
|
+
* scale limits and viewport boundaries.
|
|
503
|
+
* @param object - The object to fit in the viewport
|
|
504
|
+
*/
|
|
505
|
+
centerFitInViewport(object) {
|
|
506
|
+
const scaleX = this._core.store.state.viewportWidth / (object.rotatedBoundingBox.width * 1.1);
|
|
507
|
+
const scaleY = this._core.store.state.viewportHeight / (object.rotatedBoundingBox.height * 1.1);
|
|
508
|
+
const newScale = Math.min(scaleX, scaleY, this._core.store.state.scaleMax);
|
|
509
|
+
const clampedScale = Math.max(newScale, this.getEffectiveMinScale());
|
|
510
|
+
this._core.store.state.scale = clampedScale;
|
|
511
|
+
this.centerInViewport(object);
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Centers the viewport on a given object and zooms out only if the object
|
|
515
|
+
* does not fit at the current scale. Never zooms in — if the object already
|
|
516
|
+
* fits, the scale is left unchanged.
|
|
517
|
+
* @param object - The object to fit in the viewport
|
|
518
|
+
*/
|
|
519
|
+
centerFitInViewportIfNeeded(object) {
|
|
520
|
+
const scaleX = this._core.store.state.viewportWidth / (object.rotatedBoundingBox.width * 1.1);
|
|
521
|
+
const scaleY = this._core.store.state.viewportHeight / (object.rotatedBoundingBox.height * 1.1);
|
|
522
|
+
const newScale = Math.min(scaleX, scaleY, this._core.store.state.scaleMax);
|
|
523
|
+
const clampedScale = Math.max(newScale, this.getEffectiveMinScale());
|
|
524
|
+
if (clampedScale < this._core.store.state.scale) {
|
|
525
|
+
this._core.store.state.scale = clampedScale;
|
|
526
|
+
}
|
|
527
|
+
this.centerInViewport(object);
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Zooms out and centers the viewport on a given object only if the object
|
|
531
|
+
* does not fit at the current scale. If the object already fits, the
|
|
532
|
+
* viewport is left completely unchanged.
|
|
533
|
+
* @param object - The object to check and fit in the viewport
|
|
534
|
+
*/
|
|
535
|
+
fitInViewportIfNeeded(object) {
|
|
536
|
+
const scaleX = this._core.store.state.viewportWidth / (object.rotatedBoundingBox.width * 1.1);
|
|
537
|
+
const scaleY = this._core.store.state.viewportHeight / (object.rotatedBoundingBox.height * 1.1);
|
|
538
|
+
const newScale = Math.min(scaleX, scaleY, this._core.store.state.scaleMax);
|
|
539
|
+
const clampedScale = Math.max(newScale, this.getEffectiveMinScale());
|
|
540
|
+
if (clampedScale < this._core.store.state.scale) {
|
|
541
|
+
this._core.store.state.scale = clampedScale;
|
|
542
|
+
this.centerInViewport(object);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Smoothly brings a given object into view if it is not fully visible within
|
|
547
|
+
* the current viewport. Zooms out only if the object is too large to fit at
|
|
548
|
+
* the current scale; never zooms in. If the object is already fully visible,
|
|
549
|
+
* the viewport is left unchanged.
|
|
550
|
+
* @param object - The object to bring into view
|
|
551
|
+
*/
|
|
552
|
+
bringIntoViewIfNeeded(object) {
|
|
553
|
+
const { scale, translateX, translateY, viewportWidth, viewportHeight } = this._core.store.state;
|
|
554
|
+
const bounds = object.rotatedBoundingBox;
|
|
555
|
+
// Compute the object's bounding box in screen-space coordinates
|
|
556
|
+
const screenLeft = bounds.x * scale + translateX;
|
|
557
|
+
const screenTop = bounds.y * scale + translateY;
|
|
558
|
+
const screenRight = (bounds.x + bounds.width) * scale + translateX;
|
|
559
|
+
const screenBottom = (bounds.y + bounds.height) * scale + translateY;
|
|
560
|
+
// If fully visible, no adjustment needed
|
|
561
|
+
if (screenLeft >= 0 && screenTop >= 0 && screenRight <= viewportWidth && screenBottom <= viewportHeight) {
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
// Calculate the scale needed to fit the object with padding; never zoom in
|
|
565
|
+
const scaleX = viewportWidth / (bounds.width * 1.1);
|
|
566
|
+
const scaleY = viewportHeight / (bounds.height * 1.1);
|
|
567
|
+
const fitScale = Math.min(scaleX, scaleY, this._core.store.state.scaleMax);
|
|
568
|
+
const clampedFitScale = Math.max(fitScale, this.getEffectiveMinScale());
|
|
569
|
+
const targetScale = Math.min(clampedFitScale, scale);
|
|
570
|
+
// Animate to center the object at the target scale
|
|
571
|
+
const objectCenterX = bounds.x + bounds.width / 2;
|
|
572
|
+
const objectCenterY = bounds.y + bounds.height / 2;
|
|
573
|
+
const targetTranslateX = viewportWidth / 2 - objectCenterX * targetScale;
|
|
574
|
+
const targetTranslateY = viewportHeight / 2 - objectCenterY * targetScale;
|
|
575
|
+
const clamped = this.clampTranslate(targetTranslateX, targetTranslateY);
|
|
576
|
+
this.animateViewportTo(clamped.translateX, clamped.translateY, targetScale);
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Handles zoom operations triggered by wheel events with Ctrl key.
|
|
580
|
+
* Zooms around the cursor position, respecting scale limits.
|
|
581
|
+
* Also handles horizontal pan from trackpad pinch+drag gestures.
|
|
582
|
+
* @param event - The wheel event with Ctrl modifier
|
|
583
|
+
*/
|
|
584
|
+
handleZoom(event) {
|
|
585
|
+
this._core.store.state.isScaling = true;
|
|
586
|
+
const rect = this._core.store.host.getBoundingClientRect();
|
|
587
|
+
const xRelativeToHost = event.clientX - rect.left;
|
|
588
|
+
const yRelativeToHost = event.clientY - rect.top;
|
|
589
|
+
this._core.store.state.pointerX = (xRelativeToHost - this._core.store.state.translateX) / this._core.store.state.scale;
|
|
590
|
+
this._core.store.state.pointerY = (yRelativeToHost - this._core.store.state.translateY) / this._core.store.state.scale;
|
|
591
|
+
this._core.store.objects?.updateCursorPosition(this._core.store.state.pointerX, this._core.store.state.pointerY);
|
|
592
|
+
const rawScaleFactor = 1 + (event.deltaY * -0.012);
|
|
593
|
+
const scaleFactor = Math.max(0.8, Math.min(1.2, rawScaleFactor));
|
|
594
|
+
const effectiveMinScale = this.getEffectiveMinScale();
|
|
595
|
+
const newScale = Math.min(this._core.store.state.scaleMax, Math.max(effectiveMinScale, this._core.store.state.scale * scaleFactor));
|
|
596
|
+
const scaleRatio = newScale / this._core.store.state.scale;
|
|
597
|
+
const translateXAdjustment = (xRelativeToHost - this._core.store.state.translateX) * (scaleRatio - 1);
|
|
598
|
+
const translateYAdjustment = (yRelativeToHost - this._core.store.state.translateY) * (scaleRatio - 1);
|
|
599
|
+
this._core.store.state.scale = newScale;
|
|
600
|
+
let newTranslateX = this._core.store.state.translateX - translateXAdjustment;
|
|
601
|
+
let newTranslateY = this._core.store.state.translateY - translateYAdjustment;
|
|
602
|
+
// Apply horizontal pan from trackpad pinch+drag gestures
|
|
603
|
+
if (event.deltaX !== 0) {
|
|
604
|
+
newTranslateX -= event.deltaX * 0.8;
|
|
605
|
+
}
|
|
606
|
+
const clamped = this.clampTranslate(newTranslateX, newTranslateY);
|
|
607
|
+
this._core.store.state.translateX = clamped.translateX;
|
|
608
|
+
this._core.store.state.translateY = clamped.translateY;
|
|
609
|
+
this._core.store.state.hasViewportChanged = true;
|
|
610
|
+
this._core.rerender();
|
|
611
|
+
this._debounceUpdate();
|
|
612
|
+
this._debounceEndScaling();
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Handles pan operations triggered by wheel events (without Ctrl).
|
|
616
|
+
* Applies a pan speed multiplier and clamps to viewport boundaries.
|
|
617
|
+
* @param event - The wheel event without Ctrl modifier
|
|
618
|
+
*/
|
|
619
|
+
handlePan(event) {
|
|
620
|
+
const panSpeed = 0.8;
|
|
621
|
+
const newTranslateX = this._core.store.state.translateX - event.deltaX * panSpeed;
|
|
622
|
+
const newTranslateY = this._core.store.state.translateY - event.deltaY * panSpeed;
|
|
623
|
+
const clamped = this.clampTranslate(newTranslateX, newTranslateY);
|
|
624
|
+
this._core.store.state.translateX = clamped.translateX;
|
|
625
|
+
this._core.store.state.translateY = clamped.translateY;
|
|
626
|
+
this._core.store.state.hasViewportChanged = true;
|
|
627
|
+
this._core.rerender();
|
|
628
|
+
this._debounceUpdate();
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Cancels any ongoing viewport animation.
|
|
632
|
+
* Called when user interacts with the canvas during animation.
|
|
633
|
+
*/
|
|
634
|
+
cancelViewportAnimation() {
|
|
635
|
+
if (this._animationFrameId !== null) {
|
|
636
|
+
cancelAnimationFrame(this._animationFrameId);
|
|
637
|
+
this._animationFrameId = null;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Animates the viewport to the specified position and scale.
|
|
642
|
+
* @param targetTranslateX - Target X translation
|
|
643
|
+
* @param targetTranslateY - Target Y translation
|
|
644
|
+
* @param targetScale - Target scale
|
|
645
|
+
* @param duration - Animation duration in milliseconds (default 150)
|
|
646
|
+
*/
|
|
647
|
+
animateViewportTo(targetTranslateX, targetTranslateY, targetScale, duration = 150) {
|
|
648
|
+
// Cancel any existing animation
|
|
649
|
+
this.cancelViewportAnimation();
|
|
650
|
+
if (duration <= 0) {
|
|
651
|
+
// Apply immediately; interpolating with duration 0 divides by zero
|
|
652
|
+
// (rAF timestamps can precede performance.now()), producing NaN state.
|
|
653
|
+
this._core.store.state.scale = targetScale;
|
|
654
|
+
const clamped = this.clampTranslate(targetTranslateX, targetTranslateY);
|
|
655
|
+
this._core.store.state.translateX = clamped.translateX;
|
|
656
|
+
this._core.store.state.translateY = clamped.translateY;
|
|
657
|
+
this._core.store.state.hasViewportChanged = true;
|
|
658
|
+
this._core.rerender();
|
|
659
|
+
this._debounceUpdate();
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
const startTranslateX = this._core.store.state.translateX;
|
|
663
|
+
const startTranslateY = this._core.store.state.translateY;
|
|
664
|
+
const startScale = this._core.store.state.scale;
|
|
665
|
+
const startTime = performance.now();
|
|
666
|
+
this._core.store.state.isScaling = true;
|
|
667
|
+
const animate = (currentTime) => {
|
|
668
|
+
const elapsed = currentTime - startTime;
|
|
669
|
+
// Clamp to [0, 1]; rAF timestamps can precede startTime, yielding negative progress
|
|
670
|
+
const progress = Math.min(Math.max(elapsed, 0) / duration, 1);
|
|
671
|
+
// easeOutCubic easing function
|
|
672
|
+
const eased = 1 - Math.pow(1 - progress, 3);
|
|
673
|
+
this._core.store.state.scale = startScale + (targetScale - startScale) * eased;
|
|
674
|
+
const newTranslateX = startTranslateX + (targetTranslateX - startTranslateX) * eased;
|
|
675
|
+
const newTranslateY = startTranslateY + (targetTranslateY - startTranslateY) * eased;
|
|
676
|
+
const clamped = this.clampTranslate(newTranslateX, newTranslateY);
|
|
677
|
+
this._core.store.state.translateX = clamped.translateX;
|
|
678
|
+
this._core.store.state.translateY = clamped.translateY;
|
|
679
|
+
this._core.store.state.hasViewportChanged = true;
|
|
680
|
+
this._core.rerender();
|
|
681
|
+
if (progress < 1) {
|
|
682
|
+
this._animationFrameId = requestAnimationFrame(animate);
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
this._animationFrameId = null;
|
|
686
|
+
this._core.store.state.isScaling = false;
|
|
687
|
+
this._core.rerender();
|
|
688
|
+
this._debounceUpdate();
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
this._animationFrameId = requestAnimationFrame(animate);
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Centers and fits the nearest content objects in the viewport.
|
|
695
|
+
* Finds up to maxObjects closest objects to the current viewport center
|
|
696
|
+
* and animates the viewport to show them.
|
|
697
|
+
* @param maxObjects - Maximum number of objects to consider (default 20)
|
|
698
|
+
* @returns true if content was found and viewport is animating, false if no content exists
|
|
699
|
+
*/
|
|
700
|
+
centerFitNearestContent(maxObjects = 20) {
|
|
701
|
+
const allObjects = this._core.store.allNonSelectionObjects;
|
|
702
|
+
if (allObjects.length === 0) {
|
|
703
|
+
return false;
|
|
704
|
+
}
|
|
705
|
+
// Calculate current viewport center in canvas coordinates
|
|
706
|
+
const viewportCenterX = -this._core.store.state.translateX / this._core.store.state.scale + this._core.store.state.viewportWidth / 2 / this._core.store.state.scale;
|
|
707
|
+
const viewportCenterY = -this._core.store.state.translateY / this._core.store.state.scale + this._core.store.state.viewportHeight / 2 / this._core.store.state.scale;
|
|
708
|
+
// Sort objects by distance from viewport center (using object center)
|
|
709
|
+
const objectsWithDistance = allObjects.map(obj => {
|
|
710
|
+
const bounds = obj.rotatedBoundingBox;
|
|
711
|
+
const objectCenterX = bounds.x + bounds.width / 2;
|
|
712
|
+
const objectCenterY = bounds.y + bounds.height / 2;
|
|
713
|
+
const distance = Math.sqrt(Math.pow(objectCenterX - viewportCenterX, 2) + Math.pow(objectCenterY - viewportCenterY, 2));
|
|
714
|
+
return { obj, distance };
|
|
715
|
+
});
|
|
716
|
+
objectsWithDistance.sort((a, b) => a.distance - b.distance);
|
|
717
|
+
// Take up to maxObjects closest objects
|
|
718
|
+
const nearestObjects = objectsWithDistance.slice(0, maxObjects).map(item => item.obj);
|
|
719
|
+
// Calculate combined bounds of nearest objects
|
|
720
|
+
let minX = Infinity;
|
|
721
|
+
let minY = Infinity;
|
|
722
|
+
let maxX = -Infinity;
|
|
723
|
+
let maxY = -Infinity;
|
|
724
|
+
for (const obj of nearestObjects) {
|
|
725
|
+
const bounds = obj.rotatedBoundingBox;
|
|
726
|
+
minX = Math.min(minX, bounds.x);
|
|
727
|
+
minY = Math.min(minY, bounds.y);
|
|
728
|
+
maxX = Math.max(maxX, bounds.x + bounds.width);
|
|
729
|
+
maxY = Math.max(maxY, bounds.y + bounds.height);
|
|
730
|
+
}
|
|
731
|
+
const contentWidth = maxX - minX;
|
|
732
|
+
const contentHeight = maxY - minY;
|
|
733
|
+
const contentCenterX = minX + contentWidth / 2;
|
|
734
|
+
const contentCenterY = minY + contentHeight / 2;
|
|
735
|
+
// Calculate scale to fit with 10% padding
|
|
736
|
+
const scaleX = this._core.store.state.viewportWidth / (contentWidth * 1.1);
|
|
737
|
+
const scaleY = this._core.store.state.viewportHeight / (contentHeight * 1.1);
|
|
738
|
+
const newScale = Math.min(scaleX, scaleY, this._core.store.state.scaleMax);
|
|
739
|
+
const clampedScale = Math.max(newScale, this.getEffectiveMinScale());
|
|
740
|
+
// Calculate target translation to center content
|
|
741
|
+
const targetTranslateX = this._core.store.state.viewportWidth / 2 - contentCenterX * clampedScale;
|
|
742
|
+
const targetTranslateY = this._core.store.state.viewportHeight / 2 - contentCenterY * clampedScale;
|
|
743
|
+
// Animate to the target position
|
|
744
|
+
this.animateViewportTo(targetTranslateX, targetTranslateY, clampedScale);
|
|
745
|
+
return true;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Centers and fits the provided objects in the viewport.
|
|
749
|
+
* Calculates the combined bounding box of the provided objects and optionally animates the viewport to show them.
|
|
750
|
+
* @param objects - Objects to include in the fit operation.
|
|
751
|
+
* @param animate - Whether to animate the viewport transition (default: true)
|
|
752
|
+
* @returns true if at least one object was provided and viewport was adjusted, false otherwise
|
|
753
|
+
*/
|
|
754
|
+
centerFitObjects(objects, animate = true) {
|
|
755
|
+
if (objects.length === 0) {
|
|
756
|
+
return false;
|
|
757
|
+
}
|
|
758
|
+
let minX = Infinity;
|
|
759
|
+
let minY = Infinity;
|
|
760
|
+
let maxX = -Infinity;
|
|
761
|
+
let maxY = -Infinity;
|
|
762
|
+
for (const obj of objects) {
|
|
763
|
+
const bounds = obj.rotatedBoundingBox;
|
|
764
|
+
minX = Math.min(minX, bounds.x);
|
|
765
|
+
minY = Math.min(minY, bounds.y);
|
|
766
|
+
maxX = Math.max(maxX, bounds.x + bounds.width);
|
|
767
|
+
maxY = Math.max(maxY, bounds.y + bounds.height);
|
|
768
|
+
}
|
|
769
|
+
const contentWidth = maxX - minX;
|
|
770
|
+
const contentHeight = maxY - minY;
|
|
771
|
+
const contentCenterX = minX + contentWidth / 2;
|
|
772
|
+
const contentCenterY = minY + contentHeight / 2;
|
|
773
|
+
// Calculate scale to fit with 10% padding
|
|
774
|
+
const scaleX = this._core.store.state.viewportWidth / (contentWidth * 1.1);
|
|
775
|
+
const scaleY = this._core.store.state.viewportHeight / (contentHeight * 1.1);
|
|
776
|
+
const newScale = Math.min(scaleX, scaleY, this._core.store.state.scaleMax);
|
|
777
|
+
const clampedScale = Math.max(newScale, this.getEffectiveMinScale());
|
|
778
|
+
// Calculate target translation to center content
|
|
779
|
+
const targetTranslateX = this._core.store.state.viewportWidth / 2 - contentCenterX * clampedScale;
|
|
780
|
+
const targetTranslateY = this._core.store.state.viewportHeight / 2 - contentCenterY * clampedScale;
|
|
781
|
+
// Animate or set immediately based on the animate parameter
|
|
782
|
+
this.animateViewportTo(targetTranslateX, targetTranslateY, clampedScale, animate ? 150 : 0);
|
|
783
|
+
return true;
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Centers and fits ALL objects in the viewport, including objects not currently rendered.
|
|
787
|
+
* Calculates the combined bounding box of all objects and optionally animates the viewport to show them.
|
|
788
|
+
* @param animate - Whether to animate the viewport transition (default: true)
|
|
789
|
+
* @returns true if objects were found and viewport was adjusted, false if no objects exist
|
|
790
|
+
*/
|
|
791
|
+
centerFitAllObjects(animate = true) {
|
|
792
|
+
return this.centerFitObjects(this._core.store.allNonSelectionObjects, animate);
|
|
793
|
+
}
|
|
794
|
+
}
|