diff options
Diffstat (limited to 'apps/web')
| -rw-r--r-- | apps/web/src/actions/components/shortcuts-dialog.tsx (renamed from apps/web/src/components/editor/dialogs/shortcuts-dialog.tsx) | 4 | ||||
| -rw-r--r-- | apps/web/src/actions/definitions.ts (renamed from apps/web/src/lib/actions/definitions.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/actions/index.ts (renamed from apps/web/src/lib/actions/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/actions/keybinding.ts (renamed from apps/web/src/lib/actions/keybinding.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/actions/keybindings-store.ts (renamed from apps/web/src/stores/keybindings-store.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/actions/keybindings/migrations/index.ts (renamed from apps/web/src/stores/keybindings/migrations/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/actions/keybindings/migrations/v2-to-v3.ts (renamed from apps/web/src/stores/keybindings/migrations/v2-to-v3.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/actions/keybindings/migrations/v3-to-v4.ts (renamed from apps/web/src/stores/keybindings/migrations/v3-to-v4.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/actions/keybindings/migrations/v4-to-v5.ts (renamed from apps/web/src/stores/keybindings/migrations/v4-to-v5.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/actions/keybindings/migrations/v5-to-v6.ts (renamed from apps/web/src/stores/keybindings/migrations/v5-to-v6.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/actions/keybindings/migrations/v6-to-v7.ts (renamed from apps/web/src/stores/keybindings/migrations/v6-to-v7.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/actions/registry.ts (renamed from apps/web/src/lib/actions/registry.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/actions/types.ts (renamed from apps/web/src/lib/actions/types.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/actions/use-action-handler.ts (renamed from apps/web/src/hooks/actions/use-action-handler.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/actions/use-editor-actions.ts (renamed from apps/web/src/hooks/actions/use-editor-actions.ts) | 22 | ||||
| -rw-r--r-- | apps/web/src/actions/use-keybindings.ts (renamed from apps/web/src/hooks/use-keybindings.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/actions/use-keyboard-shortcuts-help.ts (renamed from apps/web/src/hooks/use-keyboard-shortcuts-help.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/animation/__tests__/binding-values.test.ts (renamed from apps/web/src/lib/animation/__tests__/binding-values.test.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/animation/bezier.ts (renamed from apps/web/src/lib/animation/bezier.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/animation/binding-values.ts (renamed from apps/web/src/lib/animation/binding-values.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/animation/curve-bridge.ts (renamed from apps/web/src/lib/animation/curve-bridge.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/animation/effect-param-channel.ts (renamed from apps/web/src/lib/animation/effect-param-channel.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/animation/graph-channels.ts (renamed from apps/web/src/lib/animation/graph-channels.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/animation/graphic-param-channel.ts (renamed from apps/web/src/lib/animation/graphic-param-channel.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/animation/index.ts (renamed from apps/web/src/lib/animation/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/animation/interpolation.ts (renamed from apps/web/src/lib/animation/interpolation.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/animation/keyframe-query.ts (renamed from apps/web/src/lib/animation/keyframe-query.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/animation/keyframes.ts (renamed from apps/web/src/lib/animation/keyframes.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/animation/property-groups.ts (renamed from apps/web/src/lib/animation/property-groups.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/animation/property-registry.ts (renamed from apps/web/src/lib/animation/property-registry.ts) | 14 | ||||
| -rw-r--r-- | apps/web/src/animation/resolve.ts (renamed from apps/web/src/lib/animation/resolve.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/animation/target-resolver.ts (renamed from apps/web/src/lib/animation/target-resolver.ts) | 16 | ||||
| -rw-r--r-- | apps/web/src/animation/timeline-snap-points.ts | 35 | ||||
| -rw-r--r-- | apps/web/src/animation/transform.ts (renamed from apps/web/src/lib/animation/transform.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/animation/types.ts (renamed from apps/web/src/lib/animation/types.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/app/api/auth/[...all]/route.ts | 2 | ||||
| -rw-r--r-- | apps/web/src/app/api/feedback/route.ts | 4 | ||||
| -rw-r--r-- | apps/web/src/app/api/sounds/search/route.ts | 4 | ||||
| -rw-r--r-- | apps/web/src/app/blog/[slug]/page.tsx | 4 | ||||
| -rw-r--r-- | apps/web/src/app/blog/page.tsx | 4 | ||||
| -rw-r--r-- | apps/web/src/app/changelog/[version]/page.tsx | 6 | ||||
| -rw-r--r-- | apps/web/src/app/changelog/page.tsx | 4 | ||||
| -rw-r--r-- | apps/web/src/app/contributors/page.tsx | 2 | ||||
| -rw-r--r-- | apps/web/src/app/editor/[project_id]/page.tsx | 76 | ||||
| -rw-r--r-- | apps/web/src/app/layout.tsx | 4 | ||||
| -rw-r--r-- | apps/web/src/app/metadata.ts | 2 | ||||
| -rw-r--r-- | apps/web/src/app/page.tsx | 2 | ||||
| -rw-r--r-- | apps/web/src/app/privacy/page.tsx | 2 | ||||
| -rw-r--r-- | apps/web/src/app/projects/page.tsx | 16 | ||||
| -rw-r--r-- | apps/web/src/app/projects/store.ts | 2 | ||||
| -rw-r--r-- | apps/web/src/app/robots.ts | 2 | ||||
| -rw-r--r-- | apps/web/src/app/rss.xml/route.ts | 4 | ||||
| -rw-r--r-- | apps/web/src/app/sitemap.ts | 4 | ||||
| -rw-r--r-- | apps/web/src/app/sponsors/page.tsx | 2 | ||||
| -rw-r--r-- | apps/web/src/app/terms/page.tsx | 2 | ||||
| -rw-r--r-- | apps/web/src/auth/client.ts (renamed from apps/web/src/lib/auth/client.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/auth/rate-limit.ts (renamed from apps/web/src/lib/rate-limit.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/auth/server.ts (renamed from apps/web/src/lib/auth/server.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/background/blur.ts (renamed from apps/web/src/lib/background/blur.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/background/color.ts (renamed from apps/web/src/lib/background/color.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/blog/query.ts (renamed from apps/web/src/lib/blog/query.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/blog/types.ts (renamed from apps/web/src/lib/blog/types.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/canvas/sizes.ts (renamed from apps/web/src/lib/canvas/sizes.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/changelog/components/changelog-notification.tsx (renamed from apps/web/src/lib/changelog/components/changelog-notification.tsx) | 0 | ||||
| -rw-r--r-- | apps/web/src/changelog/components/copy-markdown-button.tsx (renamed from apps/web/src/lib/changelog/components/copy-markdown-button.tsx) | 0 | ||||
| -rw-r--r-- | apps/web/src/changelog/components/release.tsx (renamed from apps/web/src/lib/changelog/components/release.tsx) | 0 | ||||
| -rw-r--r-- | apps/web/src/changelog/entries/0.1.0.md (renamed from apps/web/src/lib/changelog/entries/0.1.0.md) | 0 | ||||
| -rw-r--r-- | apps/web/src/changelog/entries/0.2.0.md (renamed from apps/web/src/lib/changelog/entries/0.2.0.md) | 0 | ||||
| -rw-r--r-- | apps/web/src/changelog/entries/0.3.0.md (renamed from apps/web/src/lib/changelog/entries/0.3.0.md) | 0 | ||||
| -rw-r--r-- | apps/web/src/changelog/entries/0.4.0.md (renamed from apps/web/src/lib/changelog/entries/0.4.0.md) | 0 | ||||
| -rw-r--r-- | apps/web/src/changelog/utils.ts (renamed from apps/web/src/lib/changelog/utils.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/clipboard/handlers/elements.ts (renamed from apps/web/src/lib/clipboard/handlers/elements.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/clipboard/handlers/index.ts (renamed from apps/web/src/lib/clipboard/handlers/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/clipboard/handlers/keyframes.ts (renamed from apps/web/src/lib/clipboard/handlers/keyframes.ts) | 8 | ||||
| -rw-r--r-- | apps/web/src/clipboard/index.ts (renamed from apps/web/src/lib/clipboard/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/clipboard/types.ts (renamed from apps/web/src/lib/clipboard/types.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/commands/base-command.ts (renamed from apps/web/src/lib/commands/base-command.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/commands/batch-command.ts (renamed from apps/web/src/lib/commands/batch-command.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/commands/index.ts (renamed from apps/web/src/lib/commands/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/commands/media/add-media-asset.ts (renamed from apps/web/src/lib/commands/media/add-media-asset.ts) | 10 | ||||
| -rw-r--r-- | apps/web/src/commands/media/index.ts (renamed from apps/web/src/lib/commands/media/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/commands/media/remove-media-asset.ts (renamed from apps/web/src/lib/commands/media/remove-media-asset.ts) | 10 | ||||
| -rw-r--r-- | apps/web/src/commands/preview-tracker.ts (renamed from apps/web/src/lib/commands/preview-tracker.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/commands/project/index.ts (renamed from apps/web/src/lib/commands/project/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/commands/project/update-project-settings.ts (renamed from apps/web/src/lib/commands/project/update-project-settings.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/commands/scene/create-scene.ts (renamed from apps/web/src/lib/commands/scene/create-scene.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/commands/scene/delete-scene.ts (renamed from apps/web/src/lib/commands/scene/delete-scene.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/commands/scene/index.ts (renamed from apps/web/src/lib/commands/scene/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/commands/scene/move-bookmark.ts (renamed from apps/web/src/lib/commands/scene/move-bookmark.ts) | 8 | ||||
| -rw-r--r-- | apps/web/src/commands/scene/remove-bookmark.ts (renamed from apps/web/src/lib/commands/scene/remove-bookmark.ts) | 8 | ||||
| -rw-r--r-- | apps/web/src/commands/scene/rename-scene.ts (renamed from apps/web/src/lib/commands/scene/rename-scene.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/commands/scene/toggle-bookmark.ts (renamed from apps/web/src/lib/commands/scene/toggle-bookmark.ts) | 11 | ||||
| -rw-r--r-- | apps/web/src/commands/scene/update-bookmark.ts (renamed from apps/web/src/lib/commands/scene/update-bookmark.ts) | 11 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/clipboard/index.ts (renamed from apps/web/src/lib/commands/timeline/clipboard/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/clipboard/paste-keyframes.ts (renamed from apps/web/src/lib/commands/timeline/clipboard/paste-keyframes.ts) | 10 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/clipboard/paste.ts | 193 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/delete-elements.ts (renamed from apps/web/src/lib/commands/timeline/element/delete-elements.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/duplicate-elements.ts | 134 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/effects/add-effect.ts (renamed from apps/web/src/lib/commands/timeline/element/effects/add-effect.ts) | 8 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/effects/index.ts (renamed from apps/web/src/lib/commands/timeline/element/effects/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/effects/remove-effect.ts (renamed from apps/web/src/lib/commands/timeline/element/effects/remove-effect.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/effects/reorder-effect.ts (renamed from apps/web/src/lib/commands/timeline/element/effects/reorder-effect.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/effects/toggle-effect.ts (renamed from apps/web/src/lib/commands/timeline/element/effects/toggle-effect.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/effects/update-effect-params.ts (renamed from apps/web/src/lib/commands/timeline/element/effects/update-effect-params.ts) | 8 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/index.ts (renamed from apps/web/src/lib/commands/timeline/element/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/insert-element.ts (renamed from apps/web/src/lib/commands/timeline/element/insert-element.ts) | 16 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/keyframes/index.ts (renamed from apps/web/src/lib/commands/timeline/element/keyframes/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/keyframes/remove-effect-param-keyframe.ts (renamed from apps/web/src/lib/commands/timeline/element/keyframes/remove-effect-param-keyframe.ts) | 10 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/keyframes/remove-keyframe.ts (renamed from apps/web/src/lib/commands/timeline/element/keyframes/remove-keyframe.ts) | 10 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/keyframes/retime-keyframe.ts (renamed from apps/web/src/lib/commands/timeline/element/keyframes/retime-keyframe.ts) | 10 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/keyframes/update-scalar-keyframe-curve.ts (renamed from apps/web/src/lib/commands/timeline/element/keyframes/update-scalar-keyframe-curve.ts) | 10 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/keyframes/upsert-effect-param-keyframe.ts (renamed from apps/web/src/lib/commands/timeline/element/keyframes/upsert-effect-param-keyframe.ts) | 12 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/keyframes/upsert-keyframe.ts (renamed from apps/web/src/lib/commands/timeline/element/keyframes/upsert-keyframe.ts) | 10 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/masks/delete-custom-mask-points.ts (renamed from apps/web/src/lib/commands/timeline/element/masks/delete-custom-mask-points.ts) | 10 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/masks/index.ts (renamed from apps/web/src/lib/commands/timeline/element/masks/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/masks/insert-custom-mask-point.ts (renamed from apps/web/src/lib/commands/timeline/element/masks/insert-custom-mask-point.ts) | 12 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/masks/remove-mask.ts (renamed from apps/web/src/lib/commands/timeline/element/masks/remove-mask.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/masks/toggle-mask-inverted.ts (renamed from apps/web/src/lib/commands/timeline/element/masks/toggle-mask-inverted.ts) | 8 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/move-elements.ts | 183 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/split-elements.ts | 182 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/toggle-source-audio-separation.ts (renamed from apps/web/src/lib/commands/timeline/element/toggle-source-audio-separation.ts) | 10 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/element/update-elements.ts (renamed from apps/web/src/lib/commands/timeline/element/update-elements.ts) | 8 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/index.ts (renamed from apps/web/src/lib/commands/timeline/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/track/add-track.ts (renamed from apps/web/src/lib/commands/timeline/track/add-track.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/track/index.ts (renamed from apps/web/src/lib/commands/timeline/track/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/track/remove-track.ts (renamed from apps/web/src/lib/commands/timeline/track/remove-track.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/track/toggle-track-mute.ts (renamed from apps/web/src/lib/commands/timeline/track/toggle-track-mute.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/track/toggle-track-visibility.ts (renamed from apps/web/src/lib/commands/timeline/track/toggle-track-visibility.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/commands/timeline/tracks-snapshot.ts (renamed from apps/web/src/lib/commands/timeline/tracks-snapshot.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/components/editor/editor-header.tsx | 14 | ||||
| -rw-r--r-- | apps/web/src/components/editor/export-button.tsx | 8 | ||||
| -rw-r--r-- | apps/web/src/components/editor/onboarding.tsx | 4 | ||||
| -rw-r--r-- | apps/web/src/components/editor/panels/assets/assets-panel-store.tsx (renamed from apps/web/src/stores/assets-panel-store.tsx) | 0 | ||||
| -rw-r--r-- | apps/web/src/components/editor/panels/assets/draggable-item.tsx | 6 | ||||
| -rw-r--r-- | apps/web/src/components/editor/panels/assets/index.tsx | 12 | ||||
| -rw-r--r-- | apps/web/src/components/editor/panels/assets/tabbar.tsx | 2 | ||||
| -rw-r--r-- | apps/web/src/components/editor/panels/assets/views/assets.tsx | 24 | ||||
| -rw-r--r-- | apps/web/src/components/editor/panels/assets/views/settings/background.tsx | 6 | ||||
| -rw-r--r-- | apps/web/src/components/editor/panels/assets/views/settings/index.tsx | 10 | ||||
| -rw-r--r-- | apps/web/src/components/editor/panels/preview/bookmark-note-overlay.tsx | 59 | ||||
| -rw-r--r-- | apps/web/src/components/editor/panels/preview/guide-overlay.tsx | 32 | ||||
| -rw-r--r-- | apps/web/src/components/editor/panels/properties/components/property-param-field.tsx | 2 | ||||
| -rw-r--r-- | apps/web/src/components/editor/panels/properties/hooks/use-element-playhead.ts | 4 | ||||
| -rw-r--r-- | apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts | 8 | ||||
| -rw-r--r-- | apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts | 8 | ||||
| -rw-r--r-- | apps/web/src/components/editor/panels/properties/hooks/use-keyframed-param-property.ts | 10 | ||||
| -rw-r--r-- | apps/web/src/components/editor/panels/properties/index.tsx | 4 | ||||
| -rw-r--r-- | apps/web/src/components/editor/panels/properties/registry.tsx | 20 | ||||
| -rw-r--r-- | apps/web/src/components/editor/scenes-view.tsx | 4 | ||||
| -rw-r--r-- | apps/web/src/components/footer.tsx | 4 | ||||
| -rw-r--r-- | apps/web/src/components/gitHub-contribute-section.tsx | 2 | ||||
| -rw-r--r-- | apps/web/src/components/header.tsx | 4 | ||||
| -rw-r--r-- | apps/web/src/components/providers/editor-provider.tsx | 12 | ||||
| -rw-r--r-- | apps/web/src/components/storage-provider.tsx | 2 | ||||
| -rw-r--r-- | apps/web/src/components/ui/font-picker.tsx | 8 | ||||
| -rw-r--r-- | apps/web/src/components/ui/use-overlay-open-change.ts | 2 | ||||
| -rw-r--r-- | apps/web/src/core/index.ts | 6 | ||||
| -rw-r--r-- | apps/web/src/core/managers/audio-manager.ts | 14 | ||||
| -rw-r--r-- | apps/web/src/core/managers/clipboard-manager.ts | 2 | ||||
| -rw-r--r-- | apps/web/src/core/managers/commands.ts | 8 | ||||
| -rw-r--r-- | apps/web/src/core/managers/diagnostics-manager.ts | 2 | ||||
| -rw-r--r-- | apps/web/src/core/managers/media-manager.ts | 4 | ||||
| -rw-r--r-- | apps/web/src/core/managers/playback-manager.ts | 2 | ||||
| -rw-r--r-- | apps/web/src/core/managers/project-manager.ts | 24 | ||||
| -rw-r--r-- | apps/web/src/core/managers/renderer-manager.ts | 6 | ||||
| -rw-r--r-- | apps/web/src/core/managers/scenes-manager.ts | 640 | ||||
| -rw-r--r-- | apps/web/src/core/managers/selection-manager.ts | 6 | ||||
| -rw-r--r-- | apps/web/src/core/managers/timeline-manager.ts | 24 | ||||
| -rw-r--r-- | apps/web/src/db/index.ts (renamed from apps/web/src/lib/db/index.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/db/schema.ts (renamed from apps/web/src/lib/db/schema.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/diagnostics/types.ts (renamed from apps/web/src/lib/diagnostics/types.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/editor/cancel-interaction.ts (renamed from apps/web/src/lib/cancel-interaction.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/editor/editor-store.ts (renamed from apps/web/src/stores/editor-store.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/editor/panel-store.ts (renamed from apps/web/src/stores/panel-store.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/editor/use-editor.ts (renamed from apps/web/src/hooks/use-editor.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/editor/use-menu-preview.ts (renamed from apps/web/src/hooks/use-menu-preview.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/effects/components/assets-view.tsx (renamed from apps/web/src/components/editor/panels/assets/views/effects.tsx) | 8 | ||||
| -rw-r--r-- | apps/web/src/effects/components/effects-tab.tsx (renamed from apps/web/src/components/editor/panels/properties/tabs/effects-tab.tsx) | 16 | ||||
| -rw-r--r-- | apps/web/src/effects/definitions/blur.ts (renamed from apps/web/src/lib/effects/definitions/blur.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/effects/definitions/index.ts (renamed from apps/web/src/lib/effects/definitions/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/effects/index.ts (renamed from apps/web/src/lib/effects/index.ts) | 8 | ||||
| -rw-r--r-- | apps/web/src/effects/registry.ts (renamed from apps/web/src/lib/effects/registry.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/effects/types.ts (renamed from apps/web/src/lib/effects/types.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/env/web.ts (renamed from apps/web/src/lib/env/web.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/export/defaults.ts (renamed from apps/web/src/lib/export/defaults.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/export/index.ts (renamed from apps/web/src/lib/export/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/export/mime-types.ts (renamed from apps/web/src/lib/export/mime-types.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/feedback/components/feedback-popover.tsx (renamed from apps/web/src/lib/feedback/components/feedback-popover.tsx) | 0 | ||||
| -rw-r--r-- | apps/web/src/feedback/index.ts (renamed from apps/web/src/lib/feedback/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/feedback/queries.ts (renamed from apps/web/src/lib/feedback/queries.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/feedback/types.ts (renamed from apps/web/src/lib/feedback/types.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/fonts/google-fonts.ts (renamed from apps/web/src/lib/fonts/google-fonts.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/fonts/system-fonts.ts (renamed from apps/web/src/lib/fonts/system-fonts.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/fonts/types.ts (renamed from apps/web/src/lib/fonts/types.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/fonts/use-font-atlas.ts (renamed from apps/web/src/hooks/use-font-atlas.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/fps/__tests__/fps.test.ts (renamed from apps/web/src/lib/fps/__tests__/fps.test.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/fps/defaults.ts (renamed from apps/web/src/lib/fps/defaults.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/fps/presets.ts (renamed from apps/web/src/lib/fps/presets.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/fps/utils.ts (renamed from apps/web/src/lib/fps/utils.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/gradients/canvas.ts (renamed from apps/web/src/lib/gradients/canvas.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/gradients/index.ts (renamed from apps/web/src/lib/gradients/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/gradients/parser.ts (renamed from apps/web/src/lib/gradients/parser.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/graphics/components/graphic-tab.tsx (renamed from apps/web/src/components/editor/panels/properties/tabs/graphic-tab.tsx) | 54 | ||||
| -rw-r--r-- | apps/web/src/graphics/definitions/ellipse.ts (renamed from apps/web/src/lib/graphics/definitions/ellipse.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/graphics/definitions/index.ts (renamed from apps/web/src/lib/graphics/definitions/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/graphics/definitions/polygon.ts (renamed from apps/web/src/lib/graphics/definitions/polygon.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/graphics/definitions/rectangle.ts (renamed from apps/web/src/lib/graphics/definitions/rectangle.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/graphics/definitions/shared.ts (renamed from apps/web/src/lib/graphics/definitions/shared.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/graphics/definitions/star.ts (renamed from apps/web/src/lib/graphics/definitions/star.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/graphics/index.ts (renamed from apps/web/src/lib/graphics/index.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/graphics/registry.ts (renamed from apps/web/src/lib/graphics/registry.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/graphics/stroke.ts (renamed from apps/web/src/lib/graphics/stroke.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/graphics/types.ts (renamed from apps/web/src/lib/graphics/types.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/guides/definitions/custom.tsx (renamed from apps/web/src/lib/guides/definitions/custom.tsx) | 2 | ||||
| -rw-r--r-- | apps/web/src/guides/definitions/grid.tsx (renamed from apps/web/src/lib/guides/definitions/grid.tsx) | 6 | ||||
| -rw-r--r-- | apps/web/src/guides/definitions/platforms.tsx (renamed from apps/web/src/lib/guides/definitions/platforms.tsx) | 2 | ||||
| -rw-r--r-- | apps/web/src/guides/definitions/tiktok-layout.tsx (renamed from apps/web/src/lib/guides/definitions/tiktok-layout.tsx) | 0 | ||||
| -rw-r--r-- | apps/web/src/guides/grid.ts (renamed from apps/web/src/lib/guides/grid.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/guides/index.ts (renamed from apps/web/src/lib/guides/index.ts) | 27 | ||||
| -rw-r--r-- | apps/web/src/guides/preview-overlay.tsx | 33 | ||||
| -rw-r--r-- | apps/web/src/guides/registry.tsx (renamed from apps/web/src/lib/guides/registry.tsx) | 4 | ||||
| -rw-r--r-- | apps/web/src/guides/types.ts (renamed from apps/web/src/lib/guides/types.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/lib/selection/index.ts | 4 | ||||
| -rw-r--r-- | apps/web/src/lib/timeline/snap-utils.ts | 182 | ||||
| -rw-r--r-- | apps/web/src/masks/__tests__/snap.test.ts (renamed from apps/web/src/lib/masks/__tests__/snap.test.ts) | 16 | ||||
| -rw-r--r-- | apps/web/src/masks/components/masks-tab.tsx (renamed from apps/web/src/components/editor/panels/properties/tabs/masks-tab.tsx) | 21 | ||||
| -rw-r--r-- | apps/web/src/masks/custom-path.ts (renamed from apps/web/src/lib/masks/custom-path.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/masks/definitions/box-like.ts (renamed from apps/web/src/lib/masks/definitions/box-like.ts) | 10 | ||||
| -rw-r--r-- | apps/web/src/masks/definitions/cinematic-bars.ts (renamed from apps/web/src/lib/masks/definitions/cinematic-bars.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/masks/definitions/custom.ts (renamed from apps/web/src/lib/masks/definitions/custom.ts) | 18 | ||||
| -rw-r--r-- | apps/web/src/masks/definitions/diamond.ts (renamed from apps/web/src/lib/masks/definitions/diamond.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/masks/definitions/ellipse.ts (renamed from apps/web/src/lib/masks/definitions/ellipse.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/masks/definitions/heart.ts (renamed from apps/web/src/lib/masks/definitions/heart.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/masks/definitions/index.ts (renamed from apps/web/src/lib/masks/definitions/index.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/masks/definitions/rectangle.ts (renamed from apps/web/src/lib/masks/definitions/rectangle.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/masks/definitions/split.ts (renamed from apps/web/src/lib/masks/definitions/split.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/masks/definitions/star.ts (renamed from apps/web/src/lib/masks/definitions/star.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/masks/definitions/text.ts (renamed from apps/web/src/lib/masks/definitions/text.ts) | 22 | ||||
| -rw-r--r-- | apps/web/src/masks/dimensions.ts (renamed from apps/web/src/lib/masks/dimensions.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/masks/feather.ts (renamed from apps/web/src/lib/masks/feather.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/masks/geometry.ts (renamed from apps/web/src/lib/masks/geometry.ts) | 8 | ||||
| -rw-r--r-- | apps/web/src/masks/handle-positions.ts (renamed from apps/web/src/lib/masks/handle-positions.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/masks/index.ts (renamed from apps/web/src/lib/masks/index.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/masks/param-update.ts (renamed from apps/web/src/lib/masks/param-update.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/masks/registry.ts (renamed from apps/web/src/lib/masks/registry.ts) | 8 | ||||
| -rw-r--r-- | apps/web/src/masks/snap.ts (renamed from apps/web/src/lib/masks/snap.ts) | 8 | ||||
| -rw-r--r-- | apps/web/src/masks/types.ts (renamed from apps/web/src/lib/masks/types.ts) | 10 | ||||
| -rw-r--r-- | apps/web/src/masks/use-mask-handles.ts (renamed from apps/web/src/hooks/use-mask-handles.ts) | 22 | ||||
| -rw-r--r-- | apps/web/src/masks/utils.ts (renamed from apps/web/src/lib/masks/utils.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/media/audio-mastering.ts (renamed from apps/web/src/lib/media/audio-mastering.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/media/audio.ts (renamed from apps/web/src/lib/media/audio.ts) | 26 | ||||
| -rw-r--r-- | apps/web/src/media/media-utils.ts (renamed from apps/web/src/lib/media/media-utils.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/media/mediabunny.ts (renamed from apps/web/src/lib/media/mediabunny.ts) | 8 | ||||
| -rw-r--r-- | apps/web/src/media/processing.ts (renamed from apps/web/src/lib/media/processing.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/media/types.ts (renamed from apps/web/src/lib/media/types.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/media/upload-toast.ts (renamed from apps/web/src/lib/media/upload-toast.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/media/use-file-upload.ts (renamed from apps/web/src/hooks/use-file-upload.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/media/use-paste-media.ts (renamed from apps/web/src/hooks/use-paste-media.ts) | 20 | ||||
| -rw-r--r-- | apps/web/src/media/waveform-summary.ts (renamed from apps/web/src/lib/media/waveform-summary.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/panels/layout.ts (renamed from apps/web/src/lib/panels/layout.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/params/index.ts (renamed from apps/web/src/lib/params.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/params/registry.ts (renamed from apps/web/src/lib/registry.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/preview/components/context-menu.tsx (renamed from apps/web/src/components/editor/panels/preview/context-menu.tsx) | 158 | ||||
| -rw-r--r-- | apps/web/src/preview/components/cursors.ts (renamed from apps/web/src/components/editor/panels/preview/cursors.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/preview/components/guide-popover.tsx (renamed from apps/web/src/components/editor/panels/preview/guide-popover.tsx) | 4 | ||||
| -rw-r--r-- | apps/web/src/preview/components/handle-primitives.tsx (renamed from apps/web/src/components/editor/panels/preview/handle-primitives.tsx) | 0 | ||||
| -rw-r--r-- | apps/web/src/preview/components/index.tsx (renamed from apps/web/src/components/editor/panels/preview/index.tsx) | 68 | ||||
| -rw-r--r-- | apps/web/src/preview/components/mask-handles.tsx (renamed from apps/web/src/components/editor/panels/preview/mask-handles.tsx) | 8 | ||||
| -rw-r--r-- | apps/web/src/preview/components/overlay-layer.tsx | 197 | ||||
| -rw-r--r-- | apps/web/src/preview/components/preview-interaction-overlay.tsx (renamed from apps/web/src/components/editor/panels/preview/preview-interaction-overlay.tsx) | 10 | ||||
| -rw-r--r-- | apps/web/src/preview/components/preview-viewport.tsx (renamed from apps/web/src/components/editor/panels/preview/preview-viewport.tsx) | 4 | ||||
| -rw-r--r-- | apps/web/src/preview/components/snap-guides.tsx (renamed from apps/web/src/components/editor/panels/preview/snap-guides.tsx) | 4 | ||||
| -rw-r--r-- | apps/web/src/preview/components/text-edit-overlay.tsx (renamed from apps/web/src/components/editor/panels/preview/text-edit-overlay.tsx) | 12 | ||||
| -rw-r--r-- | apps/web/src/preview/components/toolbar.tsx (renamed from apps/web/src/components/editor/panels/preview/toolbar.tsx) | 10 | ||||
| -rw-r--r-- | apps/web/src/preview/components/transform-handles.tsx (renamed from apps/web/src/components/editor/panels/preview/transform-handles.tsx) | 10 | ||||
| -rw-r--r-- | apps/web/src/preview/element-bounds.ts (renamed from apps/web/src/lib/preview/element-bounds.ts) | 12 | ||||
| -rw-r--r-- | apps/web/src/preview/hit-test.ts (renamed from apps/web/src/lib/preview/hit-test.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/preview/hooks/use-preview-interaction.ts (renamed from apps/web/src/hooks/use-preview-interaction.ts) | 18 | ||||
| -rw-r--r-- | apps/web/src/preview/hooks/use-transform-handles.ts (renamed from apps/web/src/hooks/use-transform-handles.ts) | 64 | ||||
| -rw-r--r-- | apps/web/src/preview/overlays.ts | 108 | ||||
| -rw-r--r-- | apps/web/src/preview/preview-coords.ts (renamed from apps/web/src/lib/preview/preview-coords.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/preview/preview-snap.ts (renamed from apps/web/src/lib/preview/preview-snap.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/preview/preview-store.ts (renamed from apps/web/src/stores/preview-store.ts) | 216 | ||||
| -rw-r--r-- | apps/web/src/preview/zoom.ts (renamed from apps/web/src/lib/preview/zoom.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/project/components/delete-project-dialog.tsx (renamed from apps/web/src/components/editor/dialogs/delete-project-dialog.tsx) | 0 | ||||
| -rw-r--r-- | apps/web/src/project/components/migration-dialog.tsx (renamed from apps/web/src/components/editor/dialogs/migration-dialog.tsx) | 2 | ||||
| -rw-r--r-- | apps/web/src/project/components/project-info-dialog.tsx (renamed from apps/web/src/components/editor/dialogs/project-info-dialog.tsx) | 2 | ||||
| -rw-r--r-- | apps/web/src/project/components/rename-project-dialog.tsx (renamed from apps/web/src/components/editor/dialogs/rename-project-dialog.tsx) | 0 | ||||
| -rw-r--r-- | apps/web/src/project/types.ts (renamed from apps/web/src/lib/project/types.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/rendering/components/blending-tab.tsx (renamed from apps/web/src/components/editor/panels/properties/tabs/blending-tab.tsx) | 20 | ||||
| -rw-r--r-- | apps/web/src/rendering/components/transform-tab.tsx (renamed from apps/web/src/components/editor/panels/properties/tabs/transform-tab.tsx) | 16 | ||||
| -rw-r--r-- | apps/web/src/rendering/index.ts (renamed from apps/web/src/lib/rendering.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/retime/__tests__/resolve.test.ts (renamed from apps/web/src/lib/retime/__tests__/resolve.test.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/retime/__tests__/split.test.ts (renamed from apps/web/src/lib/retime/__tests__/split.test.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/retime/audio-stretch.ts (renamed from apps/web/src/lib/retime/audio-stretch.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/retime/index.ts (renamed from apps/web/src/lib/retime/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/retime/presets.ts (renamed from apps/web/src/lib/retime/presets.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/retime/rate.ts (renamed from apps/web/src/lib/retime/rate.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/retime/resolve.ts (renamed from apps/web/src/lib/retime/resolve.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/retime/split.ts (renamed from apps/web/src/lib/retime/split.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/ripple/apply.ts (renamed from apps/web/src/lib/ripple/apply.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/ripple/diff.ts (renamed from apps/web/src/lib/ripple/diff.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/ripple/index.ts (renamed from apps/web/src/lib/ripple/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/ripple/shift.ts (renamed from apps/web/src/lib/ripple/shift.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/selection/attributes.ts (renamed from apps/web/src/lib/selection/attributes.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/selection/context.tsx (renamed from apps/web/src/lib/selection/context.tsx) | 0 | ||||
| -rw-r--r-- | apps/web/src/selection/editor-selection.ts (renamed from apps/web/src/lib/selection/editor-selection.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/selection/hit-testing.ts (renamed from apps/web/src/lib/selection/hit-testing.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/selection/hooks/use-box-select.ts (renamed from apps/web/src/lib/selection/hooks/use-box-select.ts) | 340 | ||||
| -rw-r--r-- | apps/web/src/selection/hooks/use-selection-scope.ts (renamed from apps/web/src/lib/selection/hooks/use-selection-scope.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/selection/index.ts | 4 | ||||
| -rw-r--r-- | apps/web/src/selection/scope.ts (renamed from apps/web/src/lib/selection/scope.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/selection/selectable-item.tsx (renamed from apps/web/src/lib/selection/selectable-item.tsx) | 6 | ||||
| -rw-r--r-- | apps/web/src/selection/selectable-surface.tsx (renamed from apps/web/src/lib/selection/selectable-surface.tsx) | 14 | ||||
| -rw-r--r-- | apps/web/src/selection/selection-box.tsx (renamed from apps/web/src/lib/selection/selection-box.tsx) | 0 | ||||
| -rw-r--r-- | apps/web/src/selection/state.ts (renamed from apps/web/src/lib/selection/state.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/selection/types.ts (renamed from apps/web/src/lib/selection/types.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/services/renderer/compositor/frame-descriptor.ts | 6 | ||||
| -rw-r--r-- | apps/web/src/services/renderer/compositor/types.ts | 4 | ||||
| -rw-r--r-- | apps/web/src/services/renderer/effect-preview.ts | 6 | ||||
| -rw-r--r-- | apps/web/src/services/renderer/gpu-renderer.ts | 2 | ||||
| -rw-r--r-- | apps/web/src/services/renderer/nodes/blur-background-node.ts | 4 | ||||
| -rw-r--r-- | apps/web/src/services/renderer/nodes/effect-layer-node.ts | 4 | ||||
| -rw-r--r-- | apps/web/src/services/renderer/nodes/graphic-node.ts | 4 | ||||
| -rw-r--r-- | apps/web/src/services/renderer/nodes/sticker-node.ts | 2 | ||||
| -rw-r--r-- | apps/web/src/services/renderer/nodes/text-node.ts | 10 | ||||
| -rw-r--r-- | apps/web/src/services/renderer/nodes/visual-node.ts | 8 | ||||
| -rw-r--r-- | apps/web/src/services/renderer/resolve.ts | 16 | ||||
| -rw-r--r-- | apps/web/src/services/renderer/scene-builder.ts | 8 | ||||
| -rw-r--r-- | apps/web/src/services/renderer/scene-exporter.ts | 6 | ||||
| -rw-r--r-- | apps/web/src/services/storage/components/storage-persistence-dialog.tsx (renamed from apps/web/src/components/editor/dialogs/storage-persistence-dialog.tsx) | 2 | ||||
| -rw-r--r-- | apps/web/src/services/storage/migrations/__tests__/v1-to-v2.test.ts | 6 | ||||
| -rw-r--r-- | apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts | 6 | ||||
| -rw-r--r-- | apps/web/src/services/storage/migrations/transformers/v14-to-v15.ts | 2 | ||||
| -rw-r--r-- | apps/web/src/services/storage/migrations/transformers/v17-to-v18.ts | 4 | ||||
| -rw-r--r-- | apps/web/src/services/storage/migrations/transformers/v20-to-v21.ts | 2 | ||||
| -rw-r--r-- | apps/web/src/services/storage/migrations/transformers/v22-to-v23.ts | 2 | ||||
| -rw-r--r-- | apps/web/src/services/storage/migrations/transformers/v26-to-v27.ts | 2 | ||||
| -rw-r--r-- | apps/web/src/services/storage/service.ts | 10 | ||||
| -rw-r--r-- | apps/web/src/services/storage/types.ts | 6 | ||||
| -rw-r--r-- | apps/web/src/services/storage/use-local-storage.ts (renamed from apps/web/src/hooks/storage/use-local-storage.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/services/storage/use-storage-persistence.ts (renamed from apps/web/src/hooks/use-storage-persistence.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/services/transcription/service.ts | 4 | ||||
| -rw-r--r-- | apps/web/src/services/transcription/worker.ts | 4 | ||||
| -rw-r--r-- | apps/web/src/services/waveform-cache/service.ts | 4 | ||||
| -rw-r--r-- | apps/web/src/site/brand.ts (renamed from apps/web/src/lib/site/brand.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/site/external-tools.ts (renamed from apps/web/src/lib/site/external-tools.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/site/social.ts (renamed from apps/web/src/lib/site/social.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/site/sponsors.ts (renamed from apps/web/src/lib/site/sponsors.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/sounds/components/assets-view.tsx (renamed from apps/web/src/components/editor/panels/assets/views/sounds.tsx) | 6 | ||||
| -rw-r--r-- | apps/web/src/sounds/sounds-store.ts (renamed from apps/web/src/stores/sounds-store.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/sounds/types.ts (renamed from apps/web/src/lib/sounds/types.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/sounds/use-sound-search.ts (renamed from apps/web/src/hooks/use-sound-search.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/speed/components/speed-tab.tsx (renamed from apps/web/src/components/editor/panels/properties/tabs/speed-tab.tsx) | 10 | ||||
| -rw-r--r-- | apps/web/src/stickers/__tests__/sticker-id.test.ts (renamed from apps/web/src/lib/stickers/__tests__/sticker-id.test.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/stickers/categories.ts (renamed from apps/web/src/lib/stickers/categories.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/stickers/components/assets-view.tsx (renamed from apps/web/src/components/editor/panels/assets/views/stickers.tsx) | 18 | ||||
| -rw-r--r-- | apps/web/src/stickers/index.ts (renamed from apps/web/src/lib/stickers/index.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/stickers/intrinsic-size.ts (renamed from apps/web/src/lib/stickers/intrinsic-size.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/stickers/providers/countries-data.ts (renamed from apps/web/src/lib/stickers/providers/countries-data.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/stickers/providers/flags.ts (renamed from apps/web/src/lib/stickers/providers/flags.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/stickers/providers/index.ts (renamed from apps/web/src/lib/stickers/providers/index.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/stickers/providers/logos.ts (renamed from apps/web/src/lib/stickers/providers/logos.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/stickers/providers/shapes.ts (renamed from apps/web/src/lib/stickers/providers/shapes.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/stickers/registry.ts (renamed from apps/web/src/lib/stickers/registry.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/stickers/resolver.ts (renamed from apps/web/src/lib/stickers/resolver.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/stickers/sticker-id.ts (renamed from apps/web/src/lib/stickers/sticker-id.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/stickers/stickers-store.ts (renamed from apps/web/src/stores/stickers-store.ts) | 14 | ||||
| -rw-r--r-- | apps/web/src/stickers/types.ts (renamed from apps/web/src/lib/stickers/types.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/stores/properties-store.ts | 19 | ||||
| -rw-r--r-- | apps/web/src/subtitles/ass.ts (renamed from apps/web/src/lib/subtitles/ass.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/subtitles/build-subtitle-text-element.ts (renamed from apps/web/src/lib/subtitles/build-subtitle-text-element.ts) | 10 | ||||
| -rw-r--r-- | apps/web/src/subtitles/components/assets-view.tsx (renamed from apps/web/src/components/editor/panels/assets/views/captions.tsx) | 22 | ||||
| -rw-r--r-- | apps/web/src/subtitles/insert.ts (renamed from apps/web/src/lib/subtitles/insert.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/subtitles/parse.ts (renamed from apps/web/src/lib/subtitles/parse.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/subtitles/srt.ts (renamed from apps/web/src/lib/subtitles/srt.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/subtitles/types.ts (renamed from apps/web/src/lib/subtitles/types.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/text/background.ts (renamed from apps/web/src/lib/text/background.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/text/components/assets-view.tsx (renamed from apps/web/src/components/editor/panels/assets/views/text.tsx) | 6 | ||||
| -rw-r--r-- | apps/web/src/text/components/text-tab.tsx (renamed from apps/web/src/components/editor/panels/properties/tabs/text-tab.tsx) | 27 | ||||
| -rw-r--r-- | apps/web/src/text/layout.ts (renamed from apps/web/src/lib/text/layout.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/text/measure-element.ts (renamed from apps/web/src/lib/text/measure-element.ts) | 8 | ||||
| -rw-r--r-- | apps/web/src/text/primitives.ts (renamed from apps/web/src/lib/text/primitives.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/text/typography.ts (renamed from apps/web/src/lib/text/typography.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/timeline/audio-constants.ts (renamed from apps/web/src/lib/timeline/audio-constants.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/timeline/audio-display.ts (renamed from apps/web/src/lib/timeline/audio-display.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/timeline/audio-separation/index.ts (renamed from apps/web/src/lib/timeline/audio-separation/index.ts) | 8 | ||||
| -rw-r--r-- | apps/web/src/timeline/audio-state.ts (renamed from apps/web/src/lib/timeline/audio-state.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/timeline/bookmarks/components/bookmarks.tsx (renamed from apps/web/src/components/editor/panels/timeline/bookmarks.tsx) | 24 | ||||
| -rw-r--r-- | apps/web/src/timeline/bookmarks/hooks/use-bookmark-drag.ts (renamed from apps/web/src/hooks/timeline/use-bookmark-drag.ts) | 58 | ||||
| -rw-r--r-- | apps/web/src/timeline/bookmarks/index.ts | 19 | ||||
| -rw-r--r-- | apps/web/src/timeline/bookmarks/preview-overlay-source.tsx | 85 | ||||
| -rw-r--r-- | apps/web/src/timeline/bookmarks/snap-source.ts | 20 | ||||
| -rw-r--r-- | apps/web/src/timeline/bookmarks/utils.ts (renamed from apps/web/src/lib/timeline/bookmarks.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/audio-tab.tsx (renamed from apps/web/src/components/editor/panels/properties/tabs/audio-tab.tsx) | 18 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/audio-volume-line.tsx (renamed from apps/web/src/components/editor/panels/timeline/audio-volume-line.tsx) | 12 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/audio-waveform.tsx (renamed from apps/web/src/components/editor/panels/timeline/audio-waveform.tsx) | 6 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/drag-line.tsx (renamed from apps/web/src/components/editor/panels/timeline/drag-line.tsx) | 2 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/drop-target.ts (renamed from apps/web/src/components/editor/panels/timeline/drop-target.ts) | 8 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/expanded-layout.ts (renamed from apps/web/src/components/editor/panels/timeline/expanded-layout.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/graph-editor/bezier-graph.tsx (renamed from apps/web/src/components/editor/panels/timeline/graph-editor/bezier-graph.tsx) | 4 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/graph-editor/custom-presets-store.ts (renamed from apps/web/src/components/editor/panels/timeline/graph-editor/custom-presets-store.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/graph-editor/easing-presets.ts (renamed from apps/web/src/components/editor/panels/timeline/graph-editor/easing-presets.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/graph-editor/popover.tsx (renamed from apps/web/src/components/editor/panels/timeline/graph-editor/popover.tsx) | 4 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/graph-editor/session.ts (renamed from apps/web/src/components/editor/panels/timeline/graph-editor/session.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/graph-editor/use-controller.ts (renamed from apps/web/src/components/editor/panels/timeline/graph-editor/use-controller.ts) | 8 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/index.tsx (renamed from apps/web/src/components/editor/panels/timeline/index.tsx) | 44 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/interaction.ts (renamed from apps/web/src/components/editor/panels/timeline/interaction.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/layers.ts (renamed from apps/web/src/components/editor/panels/timeline/layers.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/layout.ts (renamed from apps/web/src/components/editor/panels/timeline/layout.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/selection-hit-testing.ts (renamed from apps/web/src/components/editor/panels/timeline/selection-hit-testing.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/snap-indicator.tsx (renamed from apps/web/src/components/editor/panels/timeline/snap-indicator.tsx) | 100 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/theme.ts (renamed from apps/web/src/components/editor/panels/timeline/theme.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/timeline-element.tsx (renamed from apps/web/src/components/editor/panels/timeline/timeline-element.tsx) | 51 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/timeline-playhead.tsx (renamed from apps/web/src/components/editor/panels/timeline/timeline-playhead.tsx) | 21 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/timeline-ruler.tsx (renamed from apps/web/src/components/editor/panels/timeline/timeline-ruler.tsx) | 27 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/timeline-tick.tsx (renamed from apps/web/src/components/editor/panels/timeline/timeline-tick.tsx) | 4 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/timeline-toolbar.tsx (renamed from apps/web/src/components/editor/panels/timeline/timeline-toolbar.tsx) | 16 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/timeline-track.tsx (renamed from apps/web/src/components/editor/panels/timeline/timeline-track.tsx) | 14 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/track-layout.ts (renamed from apps/web/src/components/editor/panels/timeline/track-layout.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/timeline/creation.ts (renamed from apps/web/src/lib/timeline/creation.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/timeline/defaults.ts (renamed from apps/web/src/lib/timeline/defaults.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/timeline/drag-data.ts (renamed from apps/web/src/lib/drag-data.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/timeline/drag-utils.ts (renamed from apps/web/src/lib/timeline/drag-utils.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/timeline/drag.ts (renamed from apps/web/src/lib/timeline/drag.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/timeline/element-snap-source.ts | 38 | ||||
| -rw-r--r-- | apps/web/src/timeline/element-utils.ts (renamed from apps/web/src/lib/timeline/element-utils.ts) | 14 | ||||
| -rw-r--r-- | apps/web/src/timeline/group-move/build-group.ts (renamed from apps/web/src/lib/timeline/group-move/build-group.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/timeline/group-move/index.ts (renamed from apps/web/src/lib/timeline/group-move/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/timeline/group-move/resolve-move.ts (renamed from apps/web/src/lib/timeline/group-move/resolve-move.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/timeline/group-move/snap.ts (renamed from apps/web/src/lib/timeline/group-move/snap.ts) | 38 | ||||
| -rw-r--r-- | apps/web/src/timeline/group-move/track-placement.ts (renamed from apps/web/src/lib/timeline/group-move/track-placement.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/timeline/group-move/types.ts (renamed from apps/web/src/lib/timeline/group-move/types.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/timeline/group-resize/compute-resize.ts (renamed from apps/web/src/lib/timeline/group-resize/compute-resize.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/timeline/group-resize/index.ts (renamed from apps/web/src/lib/timeline/group-resize/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/timeline/group-resize/types.ts (renamed from apps/web/src/lib/timeline/group-resize/types.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/timeline/hooks/element/use-element-interaction.ts (renamed from apps/web/src/hooks/timeline/element/use-element-interaction.ts) | 22 | ||||
| -rw-r--r-- | apps/web/src/timeline/hooks/element/use-element-selection.ts (renamed from apps/web/src/hooks/timeline/element/use-element-selection.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/timeline/hooks/element/use-keyframe-box-select.ts (renamed from apps/web/src/hooks/timeline/element/use-keyframe-box-select.ts) | 21 | ||||
| -rw-r--r-- | apps/web/src/timeline/hooks/element/use-keyframe-drag.ts (renamed from apps/web/src/hooks/timeline/element/use-keyframe-drag.ts) | 84 | ||||
| -rw-r--r-- | apps/web/src/timeline/hooks/element/use-keyframe-selection.ts (renamed from apps/web/src/hooks/timeline/element/use-keyframe-selection.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/timeline/hooks/use-edge-auto-scroll.ts (renamed from apps/web/src/hooks/timeline/use-edge-auto-scroll.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/timeline/hooks/use-element-preview.ts (renamed from apps/web/src/hooks/use-element-preview.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/timeline/hooks/use-initial-scroll-bottom.ts (renamed from apps/web/src/hooks/timeline/use-initial-scroll-bottom.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/timeline/hooks/use-scroll-position.ts (renamed from apps/web/src/hooks/timeline/use-scroll-position.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/timeline/hooks/use-scroll-sync.ts (renamed from apps/web/src/hooks/timeline/use-scroll-sync.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/timeline/hooks/use-snap-indicator-position.ts (renamed from apps/web/src/hooks/timeline/use-snap-indicator-position.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/timeline/hooks/use-timeline-drag-drop.ts (renamed from apps/web/src/hooks/timeline/use-timeline-drag-drop.ts) | 26 | ||||
| -rw-r--r-- | apps/web/src/timeline/hooks/use-timeline-playhead.ts (renamed from apps/web/src/hooks/timeline/use-timeline-playhead.ts) | 77 | ||||
| -rw-r--r-- | apps/web/src/timeline/hooks/use-timeline-resize.ts (renamed from apps/web/src/hooks/timeline/use-timeline-resize.ts) | 58 | ||||
| -rw-r--r-- | apps/web/src/timeline/hooks/use-timeline-seek.ts (renamed from apps/web/src/hooks/timeline/use-timeline-seek.ts) | 31 | ||||
| -rw-r--r-- | apps/web/src/timeline/hooks/use-timeline-zoom.ts (renamed from apps/web/src/hooks/timeline/use-timeline-zoom.ts) | 72 | ||||
| -rw-r--r-- | apps/web/src/timeline/index.ts (renamed from apps/web/src/lib/timeline/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/timeline/pixel-utils.ts (renamed from apps/web/src/lib/timeline/pixel-utils.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/timeline/placement/__tests__/resolve.test.ts (renamed from apps/web/src/lib/timeline/placement/__tests__/resolve.test.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/timeline/placement/apply.ts (renamed from apps/web/src/lib/timeline/placement/apply.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/timeline/placement/compatibility.ts (renamed from apps/web/src/lib/timeline/placement/compatibility.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/timeline/placement/index.ts (renamed from apps/web/src/lib/timeline/placement/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/timeline/placement/insert-index.ts (renamed from apps/web/src/lib/timeline/placement/insert-index.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/timeline/placement/main-track.ts (renamed from apps/web/src/lib/timeline/placement/main-track.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/timeline/placement/overlap.ts (renamed from apps/web/src/lib/timeline/placement/overlap.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/timeline/placement/resolve.ts (renamed from apps/web/src/lib/timeline/placement/resolve.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/timeline/placement/track-factory.ts (renamed from apps/web/src/lib/timeline/placement/track-factory.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/timeline/placement/types.ts (renamed from apps/web/src/lib/timeline/placement/types.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/timeline/playhead-snap-source.ts | 9 | ||||
| -rw-r--r-- | apps/web/src/timeline/ruler-utils.ts (renamed from apps/web/src/lib/timeline/ruler-utils.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/timeline/scale.ts (renamed from apps/web/src/lib/timeline/scale.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/timeline/scenes.ts (renamed from apps/web/src/lib/scenes.ts) | 6 | ||||
| -rw-r--r-- | apps/web/src/timeline/snapping/build.ts | 17 | ||||
| -rw-r--r-- | apps/web/src/timeline/snapping/index.ts | 9 | ||||
| -rw-r--r-- | apps/web/src/timeline/snapping/resolve.ts | 28 | ||||
| -rw-r--r-- | apps/web/src/timeline/snapping/threshold.ts | 15 | ||||
| -rw-r--r-- | apps/web/src/timeline/snapping/types.ts | 21 | ||||
| -rw-r--r-- | apps/web/src/timeline/timeline-store.ts (renamed from apps/web/src/stores/timeline-store.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/timeline/track-capabilities.ts (renamed from apps/web/src/lib/timeline/track-capabilities.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/timeline/track-element-update.ts (renamed from apps/web/src/lib/timeline/track-element-update.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/timeline/tracks.ts (renamed from apps/web/src/lib/timeline/tracks.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/timeline/types.ts (renamed from apps/web/src/lib/timeline/types.ts) | 10 | ||||
| -rw-r--r-- | apps/web/src/timeline/update-pipeline.ts (renamed from apps/web/src/lib/timeline/update-pipeline.ts) | 8 | ||||
| -rw-r--r-- | apps/web/src/timeline/zoom-utils.ts (renamed from apps/web/src/lib/timeline/zoom-utils.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/transcription/audio.ts (renamed from apps/web/src/lib/transcription/audio.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/transcription/caption-defaults.ts (renamed from apps/web/src/lib/transcription/caption-defaults.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/transcription/caption.ts (renamed from apps/web/src/lib/transcription/caption.ts) | 4 | ||||
| -rw-r--r-- | apps/web/src/transcription/diagnostics.ts (renamed from apps/web/src/lib/transcription/diagnostics.ts) | 2 | ||||
| -rw-r--r-- | apps/web/src/transcription/languages.ts (renamed from apps/web/src/lib/transcription/languages.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/transcription/models.ts (renamed from apps/web/src/lib/transcription/models.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/transcription/supported-languages.ts (renamed from apps/web/src/lib/transcription/supported-languages.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/transcription/types.ts (renamed from apps/web/src/lib/transcription/types.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/wasm/index.ts (renamed from apps/web/src/lib/wasm/index.ts) | 0 | ||||
| -rw-r--r-- | apps/web/src/wasm/ticks.ts (renamed from apps/web/src/lib/wasm/ticks.ts) | 0 |
490 files changed, 3564 insertions, 2371 deletions
diff --git a/apps/web/src/components/editor/dialogs/shortcuts-dialog.tsx b/apps/web/src/actions/components/shortcuts-dialog.tsx index 70802b5b..cd9c0242 100644 --- a/apps/web/src/components/editor/dialogs/shortcuts-dialog.tsx +++ b/apps/web/src/actions/components/shortcuts-dialog.tsx @@ -5,8 +5,8 @@ import { toast } from "sonner"; import {
type KeyboardShortcut,
useKeyboardShortcutsHelp,
-} from "@/hooks/use-keyboard-shortcuts-help";
-import { useKeybindingsStore } from "@/stores/keybindings-store";
+} from "@/actions/use-keyboard-shortcuts-help";
+import { useKeybindingsStore } from "@/actions/keybindings-store";
import { Button } from "@/components/ui/button";
import {
Dialog,
diff --git a/apps/web/src/lib/actions/definitions.ts b/apps/web/src/actions/definitions.ts index f6846846..6f0ba700 100644 --- a/apps/web/src/lib/actions/definitions.ts +++ b/apps/web/src/actions/definitions.ts @@ -1,7 +1,7 @@ import type { KeybindingConfig, ShortcutKey, -} from "@/lib/actions/keybinding"; +} from "@/actions/keybinding"; import type { TActionWithOptionalArgs } from "./types"; export type TActionCategory = diff --git a/apps/web/src/lib/actions/index.ts b/apps/web/src/actions/index.ts index 801a846a..801a846a 100644 --- a/apps/web/src/lib/actions/index.ts +++ b/apps/web/src/actions/index.ts diff --git a/apps/web/src/lib/actions/keybinding.ts b/apps/web/src/actions/keybinding.ts index 65ef4538..65ef4538 100644 --- a/apps/web/src/lib/actions/keybinding.ts +++ b/apps/web/src/actions/keybinding.ts diff --git a/apps/web/src/stores/keybindings-store.ts b/apps/web/src/actions/keybindings-store.ts index ff77e0b4..c0a1a5e2 100644 --- a/apps/web/src/stores/keybindings-store.ts +++ b/apps/web/src/actions/keybindings-store.ts @@ -2,11 +2,11 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; -import type { TActionWithOptionalArgs } from "@/lib/actions"; -import { getDefaultShortcuts } from "@/lib/actions"; +import type { TActionWithOptionalArgs } from "@/actions"; +import { getDefaultShortcuts } from "@/actions"; import { isTypableDOMElement } from "@/utils/browser"; import { isAppleDevice } from "@/utils/platform"; -import type { KeybindingConfig, ShortcutKey } from "@/lib/actions/keybinding"; +import type { KeybindingConfig, ShortcutKey } from "@/actions/keybinding"; import { runMigrations, CURRENT_VERSION } from "./keybindings/migrations"; const defaultKeybindings: KeybindingConfig = getDefaultShortcuts(); diff --git a/apps/web/src/stores/keybindings/migrations/index.ts b/apps/web/src/actions/keybindings/migrations/index.ts index 71fd81a6..71fd81a6 100644 --- a/apps/web/src/stores/keybindings/migrations/index.ts +++ b/apps/web/src/actions/keybindings/migrations/index.ts diff --git a/apps/web/src/stores/keybindings/migrations/v2-to-v3.ts b/apps/web/src/actions/keybindings/migrations/v2-to-v3.ts index 6ebff92c..0171c5a5 100644 --- a/apps/web/src/stores/keybindings/migrations/v2-to-v3.ts +++ b/apps/web/src/actions/keybindings/migrations/v2-to-v3.ts @@ -1,5 +1,5 @@ -import type { KeybindingConfig, ShortcutKey } from "@/lib/actions/keybinding";
-import type { TActionWithOptionalArgs } from "@/lib/actions";
+import type { KeybindingConfig, ShortcutKey } from "@/actions/keybinding";
+import type { TActionWithOptionalArgs } from "@/actions";
interface V2State {
keybindings: KeybindingConfig;
diff --git a/apps/web/src/stores/keybindings/migrations/v3-to-v4.ts b/apps/web/src/actions/keybindings/migrations/v3-to-v4.ts index 0f9b35da..8541071b 100644 --- a/apps/web/src/stores/keybindings/migrations/v3-to-v4.ts +++ b/apps/web/src/actions/keybindings/migrations/v3-to-v4.ts @@ -1,6 +1,6 @@ -import type { TActionWithOptionalArgs } from "@/lib/actions";
-import type { ShortcutKey } from "@/lib/actions/keybinding";
-import type { KeybindingConfig } from "@/lib/actions/keybinding";
+import type { TActionWithOptionalArgs } from "@/actions";
+import type { ShortcutKey } from "@/actions/keybinding";
+import type { KeybindingConfig } from "@/actions/keybinding";
interface V3State {
keybindings: KeybindingConfig;
diff --git a/apps/web/src/stores/keybindings/migrations/v4-to-v5.ts b/apps/web/src/actions/keybindings/migrations/v4-to-v5.ts index 880df1dc..f6937c39 100644 --- a/apps/web/src/stores/keybindings/migrations/v4-to-v5.ts +++ b/apps/web/src/actions/keybindings/migrations/v4-to-v5.ts @@ -1,4 +1,4 @@ -import type { KeybindingConfig } from "@/lib/actions/keybinding";
+import type { KeybindingConfig } from "@/actions/keybinding";
interface V4State {
keybindings: KeybindingConfig;
diff --git a/apps/web/src/stores/keybindings/migrations/v5-to-v6.ts b/apps/web/src/actions/keybindings/migrations/v5-to-v6.ts index ebabb7c0..09b44307 100644 --- a/apps/web/src/stores/keybindings/migrations/v5-to-v6.ts +++ b/apps/web/src/actions/keybindings/migrations/v5-to-v6.ts @@ -1,4 +1,4 @@ -import type { KeybindingConfig } from "@/lib/actions/keybinding";
+import type { KeybindingConfig } from "@/actions/keybinding";
interface V5State {
keybindings: KeybindingConfig;
diff --git a/apps/web/src/stores/keybindings/migrations/v6-to-v7.ts b/apps/web/src/actions/keybindings/migrations/v6-to-v7.ts index 8c5fd96c..46a99ac1 100644 --- a/apps/web/src/stores/keybindings/migrations/v6-to-v7.ts +++ b/apps/web/src/actions/keybindings/migrations/v6-to-v7.ts @@ -1,4 +1,4 @@ -import type { KeybindingConfig } from "@/lib/actions/keybinding"; +import type { KeybindingConfig } from "@/actions/keybinding"; interface V6State { keybindings: KeybindingConfig; diff --git a/apps/web/src/lib/actions/registry.ts b/apps/web/src/actions/registry.ts index e69b3b9f..e69b3b9f 100644 --- a/apps/web/src/lib/actions/registry.ts +++ b/apps/web/src/actions/registry.ts diff --git a/apps/web/src/lib/actions/types.ts b/apps/web/src/actions/types.ts index 41258c7b..41258c7b 100644 --- a/apps/web/src/lib/actions/types.ts +++ b/apps/web/src/actions/types.ts diff --git a/apps/web/src/hooks/actions/use-action-handler.ts b/apps/web/src/actions/use-action-handler.ts index 3f6dbd21..a8bb0054 100644 --- a/apps/web/src/hooks/actions/use-action-handler.ts +++ b/apps/web/src/actions/use-action-handler.ts @@ -5,8 +5,8 @@ import type { TActionHandlerOptions, TArgOfAction, TInvocationTrigger, -} from "@/lib/actions"; -import { bindAction, unbindAction } from "@/lib/actions"; +} from "@/actions"; +import { bindAction, unbindAction } from "@/actions"; export function useActionHandler<A extends TAction>( action: A, diff --git a/apps/web/src/hooks/actions/use-editor-actions.ts b/apps/web/src/actions/use-editor-actions.ts index 873fd33a..e62a8ce0 100644 --- a/apps/web/src/hooks/actions/use-editor-actions.ts +++ b/apps/web/src/actions/use-editor-actions.ts @@ -1,21 +1,21 @@ "use client"; import { useEffect, useRef } from "react"; -import { useTimelineStore } from "@/stores/timeline-store"; -import { useActionHandler } from "@/hooks/actions/use-action-handler"; -import { useEditor } from "../use-editor"; -import { useElementSelection } from "../timeline/element/use-element-selection"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; -import { useKeyframeSelection } from "../timeline/element/use-keyframe-selection"; -import { getElementsAtTime, hasMediaId } from "@/lib/timeline"; -import { cancelInteraction } from "@/lib/cancel-interaction"; -import { invokeAction } from "@/lib/actions"; -import { canToggleSourceAudio } from "@/lib/timeline/audio-separation"; +import { useTimelineStore } from "@/timeline/timeline-store"; +import { useActionHandler } from "@/actions/use-action-handler"; +import { useEditor } from "@/editor/use-editor"; +import { useElementSelection } from "@/timeline/hooks/element/use-element-selection"; +import { TICKS_PER_SECOND } from "@/wasm"; +import { useKeyframeSelection } from "@/timeline/hooks/element/use-keyframe-selection"; +import { getElementsAtTime, hasMediaId } from "@/timeline"; +import { cancelInteraction } from "@/editor/cancel-interaction"; +import { invokeAction } from "@/actions"; +import { canToggleSourceAudio } from "@/timeline/audio-separation"; import { activateScope, clearActiveScope, type ScopeEntry, -} from "@/lib/selection/scope"; +} from "@/selection/scope"; export function useEditorActions() { const editor = useEditor(); diff --git a/apps/web/src/hooks/use-keybindings.ts b/apps/web/src/actions/use-keybindings.ts index 0c524214..c129795c 100644 --- a/apps/web/src/hooks/use-keybindings.ts +++ b/apps/web/src/actions/use-keybindings.ts @@ -1,7 +1,7 @@ import { useEffect } from "react"; -import { invokeAction } from "@/lib/actions"; -import { useEditor } from "@/hooks/use-editor"; -import { useKeybindingsStore } from "@/stores/keybindings-store"; +import { invokeAction } from "@/actions"; +import { useEditor } from "@/editor/use-editor"; +import { useKeybindingsStore } from "@/actions/keybindings-store"; import { isTypableDOMElement } from "@/utils/browser"; /** diff --git a/apps/web/src/hooks/use-keyboard-shortcuts-help.ts b/apps/web/src/actions/use-keyboard-shortcuts-help.ts index 9a2f6cf5..7681e664 100644 --- a/apps/web/src/hooks/use-keyboard-shortcuts-help.ts +++ b/apps/web/src/actions/use-keyboard-shortcuts-help.ts @@ -1,8 +1,8 @@ "use client"; import { useMemo } from "react"; -import { useKeybindingsStore } from "@/stores/keybindings-store"; -import { ACTIONS, type TActionWithOptionalArgs } from "@/lib/actions"; +import { useKeybindingsStore } from "@/actions/keybindings-store"; +import { ACTIONS, type TActionWithOptionalArgs } from "@/actions"; import { getPlatformAlternateKey, getPlatformSpecialKey, diff --git a/apps/web/src/lib/animation/__tests__/binding-values.test.ts b/apps/web/src/animation/__tests__/binding-values.test.ts index c5a354e4..a1f2010c 100644 --- a/apps/web/src/lib/animation/__tests__/binding-values.test.ts +++ b/apps/web/src/animation/__tests__/binding-values.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"; import { composeAnimationValue, createAnimationBinding, -} from "@/lib/animation/binding-values"; +} from "@/animation/binding-values"; describe("binding values", () => { test("formats composed animated colors as hex", () => { diff --git a/apps/web/src/lib/animation/bezier.ts b/apps/web/src/animation/bezier.ts index 5a9758ed..6f9a1d18 100644 --- a/apps/web/src/lib/animation/bezier.ts +++ b/apps/web/src/animation/bezier.ts @@ -1,4 +1,4 @@ -import type { ScalarAnimationKey } from "@/lib/animation/types"; +import type { ScalarAnimationKey } from "@/animation/types"; const BEZIER_SOLVE_ITERATIONS = 20; diff --git a/apps/web/src/lib/animation/binding-values.ts b/apps/web/src/animation/binding-values.ts index 66fb2b5d..a46aa9a6 100644 --- a/apps/web/src/lib/animation/binding-values.ts +++ b/apps/web/src/animation/binding-values.ts @@ -12,7 +12,7 @@ import type { DiscreteValue, Vector2AnimationBinding, VectorValue, -} from "@/lib/animation/types"; +} from "@/animation/types"; import { clamp } from "@/utils/math"; interface LinearRgba { diff --git a/apps/web/src/lib/animation/curve-bridge.ts b/apps/web/src/animation/curve-bridge.ts index 385fb448..af411dd1 100644 --- a/apps/web/src/lib/animation/curve-bridge.ts +++ b/apps/web/src/animation/curve-bridge.ts @@ -1,12 +1,12 @@ import { getDefaultLeftHandle, getDefaultRightHandle, -} from "@/lib/animation/bezier"; +} from "@/animation/bezier"; import type { CurveHandle, NormalizedCubicBezier, ScalarAnimationKey, -} from "@/lib/animation/types"; +} from "@/animation/types"; const VALUE_EPSILON = 1e-6; diff --git a/apps/web/src/lib/animation/effect-param-channel.ts b/apps/web/src/animation/effect-param-channel.ts index 3080e6ff..c635bb82 100644 --- a/apps/web/src/lib/animation/effect-param-channel.ts +++ b/apps/web/src/animation/effect-param-channel.ts @@ -1,9 +1,9 @@ -import type { ParamValues } from "@/lib/params"; -import type { Effect } from "@/lib/effects/types"; +import type { ParamValues } from "@/params"; +import type { Effect } from "@/effects/types"; import type { ElementAnimations, EffectParamPath, -} from "@/lib/animation/types"; +} from "@/animation/types"; import { removeElementKeyframe } from "./keyframes"; import { resolveAnimationPathValueAtTime } from "./resolve"; diff --git a/apps/web/src/lib/animation/graph-channels.ts b/apps/web/src/animation/graph-channels.ts index 19d53e27..d6482602 100644 --- a/apps/web/src/lib/animation/graph-channels.ts +++ b/apps/web/src/animation/graph-channels.ts @@ -5,7 +5,7 @@ import type { ScalarAnimationChannel, ScalarGraphChannel, ScalarGraphKeyframeContext, -} from "@/lib/animation/types"; +} from "@/animation/types"; export interface EditableScalarChannels { binding: AnimationBindingInstance; diff --git a/apps/web/src/lib/animation/graphic-param-channel.ts b/apps/web/src/animation/graphic-param-channel.ts index de595060..ebf0f940 100644 --- a/apps/web/src/lib/animation/graphic-param-channel.ts +++ b/apps/web/src/animation/graphic-param-channel.ts @@ -1,12 +1,12 @@ import type { ElementAnimations, GraphicParamPath, -} from "@/lib/animation/types"; -import type { ParamValues } from "@/lib/params"; +} from "@/animation/types"; +import type { ParamValues } from "@/params"; import { getGraphicDefinition, resolveGraphicParams, -} from "@/lib/graphics"; +} from "@/graphics"; import { resolveAnimationPathValueAtTime } from "./resolve"; export const GRAPHIC_PARAM_PATH_PREFIX = "params."; diff --git a/apps/web/src/lib/animation/index.ts b/apps/web/src/animation/index.ts index 384f3d17..384f3d17 100644 --- a/apps/web/src/lib/animation/index.ts +++ b/apps/web/src/animation/index.ts diff --git a/apps/web/src/lib/animation/interpolation.ts b/apps/web/src/animation/interpolation.ts index 1a3c0fed..277bfbb7 100644 --- a/apps/web/src/lib/animation/interpolation.ts +++ b/apps/web/src/animation/interpolation.ts @@ -7,7 +7,7 @@ import type { ScalarAnimationChannel, ScalarAnimationKey, ScalarSegmentType, -} from "@/lib/animation/types"; +} from "@/animation/types"; import { clamp } from "@/utils/math"; import { getBezierPoint, diff --git a/apps/web/src/lib/animation/keyframe-query.ts b/apps/web/src/animation/keyframe-query.ts index dbcd041c..231aa5f2 100644 --- a/apps/web/src/lib/animation/keyframe-query.ts +++ b/apps/web/src/animation/keyframe-query.ts @@ -4,7 +4,7 @@ import type { AnimationPath, ElementAnimations, ElementKeyframe, -} from "@/lib/animation/types"; +} from "@/animation/types"; import { type AnimationComponentValue, composeAnimationValue, diff --git a/apps/web/src/lib/animation/keyframes.ts b/apps/web/src/animation/keyframes.ts index a5efb6ee..12dc777d 100644 --- a/apps/web/src/lib/animation/keyframes.ts +++ b/apps/web/src/animation/keyframes.ts @@ -13,7 +13,7 @@ import type { ScalarAnimationKey, ScalarCurveKeyframePatch, ScalarSegmentType, -} from "@/lib/animation/types"; +} from "@/animation/types"; import { generateUUID } from "@/utils/id"; import { cloneAnimationBinding, diff --git a/apps/web/src/lib/animation/property-groups.ts b/apps/web/src/animation/property-groups.ts index 1e475dac..7b71af15 100644 --- a/apps/web/src/lib/animation/property-groups.ts +++ b/apps/web/src/animation/property-groups.ts @@ -2,8 +2,8 @@ import type { AnimationPropertyGroup,
AnimationPropertyPath,
ElementAnimations,
-} from "@/lib/animation/types";
-import { ANIMATION_PROPERTY_GROUPS } from "@/lib/animation/types";
+} from "@/animation/types";
+import { ANIMATION_PROPERTY_GROUPS } from "@/animation/types";
import { getKeyframeAtTime } from "./keyframe-query";
export interface GroupKeyframeRef {
diff --git a/apps/web/src/lib/animation/property-registry.ts b/apps/web/src/animation/property-registry.ts index 56340fd8..aacb9972 100644 --- a/apps/web/src/lib/animation/property-registry.ts +++ b/apps/web/src/animation/property-registry.ts @@ -3,20 +3,20 @@ import type { AnimationInterpolation, AnimationPropertyPath, AnimationValue, -} from "@/lib/animation/types"; +} from "@/animation/types"; import { parseColorToLinearRgba } from "./binding-values"; -import type { TimelineElement } from "@/lib/timeline"; -import { MIN_TRANSFORM_SCALE } from "@/lib/animation/transform"; +import type { TimelineElement } from "@/timeline"; +import { MIN_TRANSFORM_SCALE } from "@/animation/transform"; import { CORNER_RADIUS_MAX, CORNER_RADIUS_MIN, -} from "@/lib/text/background"; +} from "@/text/background"; import { canElementHaveAudio, isVisualElement, -} from "@/lib/timeline/element-utils"; -import { VOLUME_DB_MAX, VOLUME_DB_MIN } from "@/lib/timeline/audio-constants"; -import { DEFAULTS } from "@/lib/timeline/defaults"; +} from "@/timeline/element-utils"; +import { VOLUME_DB_MAX, VOLUME_DB_MIN } from "@/timeline/audio-constants"; +import { DEFAULTS } from "@/timeline/defaults"; import { snapToStep } from "@/utils/math"; export interface NumericSpec { diff --git a/apps/web/src/lib/animation/resolve.ts b/apps/web/src/animation/resolve.ts index 42c1c73c..1b51c126 100644 --- a/apps/web/src/lib/animation/resolve.ts +++ b/apps/web/src/animation/resolve.ts @@ -5,8 +5,8 @@ import type { AnimationPropertyPath, AnimationValueForPath, ElementAnimations, -} from "@/lib/animation/types"; -import type { Transform } from "@/lib/rendering"; +} from "@/animation/types"; +import type { Transform } from "@/rendering"; import { type AnimationComponentValue, composeAnimationValue, diff --git a/apps/web/src/lib/animation/target-resolver.ts b/apps/web/src/animation/target-resolver.ts index 64bb8709..32e7917c 100644 --- a/apps/web/src/lib/animation/target-resolver.ts +++ b/apps/web/src/animation/target-resolver.ts @@ -3,20 +3,20 @@ import type { AnimationInterpolation, AnimationPath, AnimationValue, -} from "@/lib/animation/types"; +} from "@/animation/types"; import { parseEffectParamPath, isEffectParamPath, -} from "@/lib/animation/effect-param-channel"; +} from "@/animation/effect-param-channel"; import { isGraphicParamPath, parseGraphicParamPath, -} from "@/lib/animation/graphic-param-channel"; -import type { ParamDefinition } from "@/lib/params"; -import { effectsRegistry, registerDefaultEffects } from "@/lib/effects"; -import { getGraphicDefinition } from "@/lib/graphics"; -import type { TimelineElement } from "@/lib/timeline"; -import { isVisualElement } from "@/lib/timeline/element-utils"; +} from "@/animation/graphic-param-channel"; +import type { ParamDefinition } from "@/params"; +import { effectsRegistry, registerDefaultEffects } from "@/effects"; +import { getGraphicDefinition } from "@/graphics"; +import type { TimelineElement } from "@/timeline"; +import { isVisualElement } from "@/timeline/element-utils"; import { snapToStep } from "@/utils/math"; import { coerceAnimationValueForProperty, diff --git a/apps/web/src/animation/timeline-snap-points.ts b/apps/web/src/animation/timeline-snap-points.ts new file mode 100644 index 00000000..f7bf549a --- /dev/null +++ b/apps/web/src/animation/timeline-snap-points.ts @@ -0,0 +1,35 @@ +import { getElementKeyframes } from "@/animation"; +import type { SceneTracks } from "@/timeline"; +import type { SnapPoint } from "@/timeline/snapping"; + +export function getAnimationKeyframeSnapPointsForTimeline({ + tracks, + excludeElementIds, +}: { + tracks: SceneTracks; + excludeElementIds?: Set<string>; +}): SnapPoint[] { + const snapPoints: SnapPoint[] = []; + const orderedTracks = [...tracks.overlay, tracks.main, ...tracks.audio]; + + for (const track of orderedTracks) { + for (const element of track.elements) { + if (excludeElementIds?.has(element.id)) { + continue; + } + + for (const keyframe of getElementKeyframes({ + animations: element.animations, + })) { + snapPoints.push({ + time: element.startTime + keyframe.time, + type: "keyframe", + elementId: element.id, + trackId: track.id, + }); + } + } + } + + return snapPoints; +} diff --git a/apps/web/src/lib/animation/transform.ts b/apps/web/src/animation/transform.ts index ab932ea2..ab932ea2 100644 --- a/apps/web/src/lib/animation/transform.ts +++ b/apps/web/src/animation/transform.ts diff --git a/apps/web/src/lib/animation/types.ts b/apps/web/src/animation/types.ts index 115b4dbf..3afddfa1 100644 --- a/apps/web/src/lib/animation/types.ts +++ b/apps/web/src/animation/types.ts @@ -1,4 +1,4 @@ -import type { ParamValues } from "@/lib/params"; +import type { ParamValues } from "@/params"; export const ANIMATION_PROPERTY_PATHS = [ "transform.positionX", diff --git a/apps/web/src/app/api/auth/[...all]/route.ts b/apps/web/src/app/api/auth/[...all]/route.ts index 544457e2..edf0d0c2 100644 --- a/apps/web/src/app/api/auth/[...all]/route.ts +++ b/apps/web/src/app/api/auth/[...all]/route.ts @@ -1,4 +1,4 @@ -import { auth } from "@/lib/auth/server";
+import { auth } from "@/auth/server";
import { toNextJsHandler } from "better-auth/next-js";
export const { POST, GET } = toNextJsHandler(auth);
diff --git a/apps/web/src/app/api/feedback/route.ts b/apps/web/src/app/api/feedback/route.ts index b13ec2bf..cd853eea 100644 --- a/apps/web/src/app/api/feedback/route.ts +++ b/apps/web/src/app/api/feedback/route.ts @@ -1,7 +1,7 @@ import { type NextRequest, NextResponse } from "next/server"; import { z } from "zod"; -import { checkRateLimit } from "@/lib/rate-limit"; -import { submitFeedback, MAX_MESSAGE_LENGTH } from "@/lib/feedback"; +import { checkRateLimit } from "@/auth/rate-limit"; +import { submitFeedback, MAX_MESSAGE_LENGTH } from "@/feedback"; const submitSchema = z.object({ message: z diff --git a/apps/web/src/app/api/sounds/search/route.ts b/apps/web/src/app/api/sounds/search/route.ts index c4d065a9..aa7a1b60 100644 --- a/apps/web/src/app/api/sounds/search/route.ts +++ b/apps/web/src/app/api/sounds/search/route.ts @@ -1,7 +1,7 @@ -import { webEnv } from "@/lib/env/web"; +import { webEnv } from "@/env/web"; import { type NextRequest, NextResponse } from "next/server"; import { z } from "zod"; -import { checkRateLimit } from "@/lib/rate-limit"; +import { checkRateLimit } from "@/auth/rate-limit"; const searchParamsSchema = z.object({ q: z.string().max(500, "Query too long").optional(), diff --git a/apps/web/src/app/blog/[slug]/page.tsx b/apps/web/src/app/blog/[slug]/page.tsx index b581de95..dba9b504 100644 --- a/apps/web/src/app/blog/[slug]/page.tsx +++ b/apps/web/src/app/blog/[slug]/page.tsx @@ -4,8 +4,8 @@ import { notFound } from "next/navigation"; import { BasePage } from "@/app/base-page";
import Prose from "@/components/ui/prose";
import { Separator } from "@/components/ui/separator";
-import { getPosts, getSinglePost, processHtmlContent } from "@/lib/blog/query";
-import type { Author, Post } from "@/lib/blog/types";
+import { getPosts, getSinglePost, processHtmlContent } from "@/blog/query";
+import type { Author, Post } from "@/blog/types";
type PageProps = {
params: Promise<{ slug: string }>;
diff --git a/apps/web/src/app/blog/page.tsx b/apps/web/src/app/blog/page.tsx index 9f5a8972..b40bc2fc 100644 --- a/apps/web/src/app/blog/page.tsx +++ b/apps/web/src/app/blog/page.tsx @@ -2,8 +2,8 @@ import type { Metadata } from "next"; import Link from "next/link";
import { BasePage } from "@/app/base-page";
import { Separator } from "@/components/ui/separator";
-import { getPosts } from "@/lib/blog/query";
-import type { Post } from "@/lib/blog/types";
+import { getPosts } from "@/blog/query";
+import type { Post } from "@/blog/types";
export const metadata: Metadata = {
title: "Blog - OpenCut",
diff --git a/apps/web/src/app/changelog/[version]/page.tsx b/apps/web/src/app/changelog/[version]/page.tsx index f069832b..6c529ce1 100644 --- a/apps/web/src/app/changelog/[version]/page.tsx +++ b/apps/web/src/app/changelog/[version]/page.tsx @@ -3,15 +3,15 @@ import Link from "next/link"; import { notFound } from "next/navigation";
import { BasePage } from "@/app/base-page";
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
-import { getReleaseByVersion, getSortedReleases } from "@/lib/changelog/utils";
+import { getReleaseByVersion, getSortedReleases } from "@/changelog/utils";
import {
ReleaseArticle,
ReleaseMeta,
ReleaseTitle,
ReleaseDescription,
ReleaseChanges,
-} from "@/lib/changelog/components/release";
-import { CopyMarkdownButton } from "@/lib/changelog/components/copy-markdown-button";
+} from "@/changelog/components/release";
+import { CopyMarkdownButton } from "@/changelog/components/copy-markdown-button";
type Props = { params: Promise<{ version: string }> };
diff --git a/apps/web/src/app/changelog/page.tsx b/apps/web/src/app/changelog/page.tsx index 8440eaa2..25ae25db 100644 --- a/apps/web/src/app/changelog/page.tsx +++ b/apps/web/src/app/changelog/page.tsx @@ -4,14 +4,14 @@ import { Separator } from "@/components/ui/separator"; import { type Release as ReleaseType, getSortedReleases, -} from "@/lib/changelog/utils"; +} from "@/changelog/utils"; import { ReleaseArticle, ReleaseMeta, ReleaseTitle, ReleaseDescription, ReleaseChanges, -} from "@/lib/changelog/components/release"; +} from "@/changelog/components/release"; export const metadata: Metadata = { title: "Changelog - OpenCut", diff --git a/apps/web/src/app/contributors/page.tsx b/apps/web/src/app/contributors/page.tsx index b15d9f27..f4d13732 100644 --- a/apps/web/src/app/contributors/page.tsx +++ b/apps/web/src/app/contributors/page.tsx @@ -3,7 +3,7 @@ import Link from "next/link"; import { GitHubContributeSection } from "@/components/gitHub-contribute-section";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Card, CardContent } from "@/components/ui/card";
-import { EXTERNAL_TOOLS } from "@/lib/site/external-tools";
+import { EXTERNAL_TOOLS } from "@/site/external-tools";
import { BasePage } from "../base-page";
export const metadata: Metadata = {
diff --git a/apps/web/src/app/editor/[project_id]/page.tsx b/apps/web/src/app/editor/[project_id]/page.tsx index a3a7afa5..b3d40a37 100644 --- a/apps/web/src/app/editor/[project_id]/page.tsx +++ b/apps/web/src/app/editor/[project_id]/page.tsx @@ -8,21 +8,32 @@ import { } from "@/components/ui/resizable"; import { AssetsPanel } from "@/components/editor/panels/assets"; import { PropertiesPanel } from "@/components/editor/panels/properties"; -import { Timeline } from "@/components/editor/panels/timeline"; -import { PreviewPanel } from "@/components/editor/panels/preview"; +import { Timeline } from "@/timeline/components"; +import { PreviewPanel } from "@/preview/components"; import { EditorHeader } from "@/components/editor/editor-header"; import { EditorProvider } from "@/components/providers/editor-provider"; import { Onboarding } from "@/components/editor/onboarding"; -import { MigrationDialog } from "@/components/editor/dialogs/migration-dialog"; -import { usePanelStore } from "@/stores/panel-store"; -import { usePasteMedia } from "@/hooks/use-paste-media"; +import { MigrationDialog } from "@/project/components/migration-dialog"; +import { usePanelStore } from "@/editor/panel-store"; +import { usePasteMedia } from "@/media/use-paste-media"; import { MobileGate } from "@/components/editor/mobile-gate"; -import { useState } from "react"; -import { useEditor } from "@/hooks/use-editor"; +import { useMemo, useState } from "react"; +import { useEditor } from "@/editor/use-editor"; import { Cancel01Icon } from "@hugeicons/core-free-icons"; import { HugeiconsIcon } from "@hugeicons/react"; import { Button } from "@/components/ui/button"; -import { ChangelogNotification } from "@/lib/changelog/components/changelog-notification"; +import { ChangelogNotification } from "@/changelog/components/changelog-notification"; +import { + createPreviewOverlayControl, + isPreviewOverlayVisible, + mergePreviewOverlaySources, +} from "@/preview/overlays"; +import { usePreviewStore } from "@/preview/preview-store"; +import { getGuidePreviewOverlaySource } from "@/guides"; +import { + bookmarkNotesPreviewOverlay, + getBookmarkPreviewOverlaySource, +} from "@/timeline/bookmarks/index"; export default function Editor() { const params = useParams(); @@ -70,6 +81,49 @@ function DegradedRendererBanner() { function EditorLayout() { usePasteMedia(); const { panels, setPanel } = usePanelStore(); + const activeScene = useEditor((editor) => + editor.scenes.getActiveSceneOrNull(), + ); + const currentTime = useEditor((editor) => editor.playback.getCurrentTime()); + const activeGuide = usePreviewStore((state) => state.activeGuide); + const overlays = usePreviewStore((state) => state.overlays); + const setOverlayVisibility = usePreviewStore( + (state) => state.setOverlayVisibility, + ); + const showBookmarkNotes = isPreviewOverlayVisible({ + overlay: bookmarkNotesPreviewOverlay, + overlays, + }); + + const overlaySource = useMemo( + () => + mergePreviewOverlaySources({ + sources: [ + getGuidePreviewOverlaySource({ + guideId: activeGuide, + }), + activeScene + ? getBookmarkPreviewOverlaySource({ + bookmarks: activeScene.bookmarks, + time: currentTime, + isVisible: showBookmarkNotes, + }) + : { + definitions: [bookmarkNotesPreviewOverlay], + instances: [], + }, + ], + }), + [activeGuide, activeScene, currentTime, showBookmarkNotes], + ); + + const overlayControls = useMemo( + () => + overlaySource.definitions.map((overlay) => + createPreviewOverlayControl({ overlay, overlays }), + ), + [overlaySource.definitions, overlays], + ); return ( <ResizablePanelGroup @@ -111,7 +165,11 @@ function EditorLayout() { minSize={30} className="min-h-0 min-w-0 flex-1" > - <PreviewPanel /> + <PreviewPanel + overlayControls={overlayControls} + overlayInstances={overlaySource.instances} + onOverlayVisibilityChange={setOverlayVisibility} + /> </ResizablePanel> <ResizableHandle withHandle /> diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 6d4878c5..558ae538 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -2,11 +2,11 @@ import { ThemeProvider } from "next-themes"; import Script from "next/script"; import "./globals.css"; import { Toaster } from "../components/ui/sonner"; -import { ChangelogNotification } from "@/lib/changelog/components/changelog-notification"; +import { ChangelogNotification } from "@/changelog/components/changelog-notification"; import { TooltipProvider } from "../components/ui/tooltip"; import { baseMetaData } from "./metadata"; import { BotIdClient } from "botid/client"; -import { webEnv } from "@/lib/env/web"; +import { webEnv } from "@/env/web"; import { Inter } from "next/font/google"; const siteFont = Inter({ subsets: ["latin"] }); diff --git a/apps/web/src/app/metadata.ts b/apps/web/src/app/metadata.ts index 06276baa..aa11c43e 100644 --- a/apps/web/src/app/metadata.ts +++ b/apps/web/src/app/metadata.ts @@ -1,5 +1,5 @@ import type { Metadata } from "next";
-import { SITE_INFO, SITE_URL } from "@/lib/site/brand";
+import { SITE_INFO, SITE_URL } from "@/site/brand";
export const baseMetaData: Metadata = {
metadataBase: new URL(SITE_URL),
diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx index 460e8bc3..6084a912 100644 --- a/apps/web/src/app/page.tsx +++ b/apps/web/src/app/page.tsx @@ -2,7 +2,7 @@ import { Hero } from "@/components/landing/hero"; import { Header } from "@/components/header";
import { Footer } from "@/components/footer";
import type { Metadata } from "next";
-import { SITE_URL } from "@/lib/site/brand";
+import { SITE_URL } from "@/site/brand";
export const metadata: Metadata = {
alternates: {
diff --git a/apps/web/src/app/privacy/page.tsx b/apps/web/src/app/privacy/page.tsx index 4963e2ab..6362146e 100644 --- a/apps/web/src/app/privacy/page.tsx +++ b/apps/web/src/app/privacy/page.tsx @@ -7,7 +7,7 @@ import { AccordionTrigger, } from "@/components/ui/accordion"; import { Separator } from "@/components/ui/separator"; -import { SOCIAL_LINKS } from "@/lib/site/social"; +import { SOCIAL_LINKS } from "@/site/social"; export const metadata: Metadata = { title: "Privacy Policy - OpenCut", diff --git a/apps/web/src/app/projects/page.tsx b/apps/web/src/app/projects/page.tsx index e8b09291..c39c4fd9 100644 --- a/apps/web/src/app/projects/page.tsx +++ b/apps/web/src/app/projects/page.tsx @@ -7,20 +7,20 @@ import type { KeyboardEvent, MouseEvent } from "react"; import { useEffect, useState } from "react"; import { toast } from "sonner"; import type { EditorCore } from "@/core"; -import { MigrationDialog } from "@/components/editor/dialogs/migration-dialog"; -import { StoragePersistenceDialog } from "@/components/editor/dialogs/storage-persistence-dialog"; +import { MigrationDialog } from "@/project/components/migration-dialog"; +import { StoragePersistenceDialog } from "@/services/storage/components/storage-persistence-dialog"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Checkbox } from "@/components/ui/checkbox"; import { Input } from "@/components/ui/input"; import { Skeleton } from "@/components/ui/skeleton"; -import { useEditor } from "@/hooks/use-editor"; +import { useEditor } from "@/editor/use-editor"; import { useProjectsStore } from "./store"; import type { TProjectMetadata, TProjectSortKey, TProjectSortOption, -} from "@/lib/project/types"; +} from "@/project/types"; import { formatTimecode, mediaTimeToSeconds } from "opencut-wasm"; import { formatDate } from "@/utils/date"; import { HugeiconsIcon } from "@hugeicons/react"; @@ -62,11 +62,11 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -import { DeleteProjectDialog } from "@/components/editor/dialogs/delete-project-dialog"; -import { ProjectInfoDialog } from "@/components/editor/dialogs/project-info-dialog"; -import { RenameProjectDialog } from "@/components/editor/dialogs/rename-project-dialog"; +import { DeleteProjectDialog } from "@/project/components/delete-project-dialog"; +import { ProjectInfoDialog } from "@/project/components/project-info-dialog"; +import { RenameProjectDialog } from "@/project/components/rename-project-dialog"; import { cn } from "@/utils/ui"; -import { ChangelogNotification } from "@/lib/changelog/components/changelog-notification"; +import { ChangelogNotification } from "@/changelog/components/changelog-notification"; const formatProjectDuration = ({ duration, }: { diff --git a/apps/web/src/app/projects/store.ts b/apps/web/src/app/projects/store.ts index 71f89b4a..82d2744e 100644 --- a/apps/web/src/app/projects/store.ts +++ b/apps/web/src/app/projects/store.ts @@ -1,6 +1,6 @@ import { create } from "zustand";
import { persist } from "zustand/middleware";
-import type { TProjectSortKey } from "@/lib/project/types";
+import type { TProjectSortKey } from "@/project/types";
export type ProjectsViewMode = "grid" | "list";
diff --git a/apps/web/src/app/robots.ts b/apps/web/src/app/robots.ts index 38f6f487..1b1ca865 100644 --- a/apps/web/src/app/robots.ts +++ b/apps/web/src/app/robots.ts @@ -1,5 +1,5 @@ import type { MetadataRoute } from "next";
-import { SITE_URL } from "@/lib/site/brand";
+import { SITE_URL } from "@/site/brand";
export default function robots(): MetadataRoute.Robots {
return {
diff --git a/apps/web/src/app/rss.xml/route.ts b/apps/web/src/app/rss.xml/route.ts index 4dfccd59..d4e9570a 100644 --- a/apps/web/src/app/rss.xml/route.ts +++ b/apps/web/src/app/rss.xml/route.ts @@ -1,6 +1,6 @@ import { Feed } from "feed";
-import { getPosts } from "@/lib/blog/query";
-import { SITE_INFO, SITE_URL } from "@/lib/site/brand";
+import { getPosts } from "@/blog/query";
+import { SITE_INFO, SITE_URL } from "@/site/brand";
export async function GET() {
try {
diff --git a/apps/web/src/app/sitemap.ts b/apps/web/src/app/sitemap.ts index 7b404642..73b4180f 100644 --- a/apps/web/src/app/sitemap.ts +++ b/apps/web/src/app/sitemap.ts @@ -1,5 +1,5 @@ -import { SITE_URL } from "@/lib/site/brand";
-import { getPosts } from "@/lib/blog/query";
+import { SITE_URL } from "@/site/brand";
+import { getPosts } from "@/blog/query";
import type { MetadataRoute } from "next";
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
diff --git a/apps/web/src/app/sponsors/page.tsx b/apps/web/src/app/sponsors/page.tsx index 0de78c85..510419f0 100644 --- a/apps/web/src/app/sponsors/page.tsx +++ b/apps/web/src/app/sponsors/page.tsx @@ -3,7 +3,7 @@ import Image from "next/image"; import Link from "next/link";
import { BasePage } from "@/app/base-page";
import { Card, CardContent } from "@/components/ui/card";
-import { SPONSORS, type Sponsor } from "@/lib/site/sponsors";
+import { SPONSORS, type Sponsor } from "@/site/sponsors";
import { HugeiconsIcon } from "@hugeicons/react";
import { LinkSquare02Icon } from "@hugeicons/core-free-icons";
import { cn } from "@/utils/ui";
diff --git a/apps/web/src/app/terms/page.tsx b/apps/web/src/app/terms/page.tsx index 73a6cce1..0845892c 100644 --- a/apps/web/src/app/terms/page.tsx +++ b/apps/web/src/app/terms/page.tsx @@ -7,7 +7,7 @@ import { AccordionTrigger, } from "@/components/ui/accordion"; import { Separator } from "@/components/ui/separator"; -import { SOCIAL_LINKS } from "@/lib/site/social"; +import { SOCIAL_LINKS } from "@/site/social"; export const metadata: Metadata = { title: "Terms of Service - OpenCut", diff --git a/apps/web/src/lib/auth/client.ts b/apps/web/src/auth/client.ts index 3bec5072..58be51c3 100644 --- a/apps/web/src/lib/auth/client.ts +++ b/apps/web/src/auth/client.ts @@ -1,5 +1,5 @@ import { createAuthClient } from "better-auth/react"; -import { webEnv } from "@/lib/env/web"; +import { webEnv } from "@/env/web"; export const { signIn, signUp, useSession } = createAuthClient({ baseURL: webEnv.NEXT_PUBLIC_SITE_URL, diff --git a/apps/web/src/lib/rate-limit.ts b/apps/web/src/auth/rate-limit.ts index 234bf48f..c10c3819 100644 --- a/apps/web/src/lib/rate-limit.ts +++ b/apps/web/src/auth/rate-limit.ts @@ -1,6 +1,6 @@ import { Ratelimit } from "@upstash/ratelimit"; import { Redis } from "@upstash/redis"; -import { webEnv } from "@/lib/env/web"; +import { webEnv } from "@/env/web"; const redis = new Redis({ url: webEnv.UPSTASH_REDIS_REST_URL, diff --git a/apps/web/src/lib/auth/server.ts b/apps/web/src/auth/server.ts index 39fce38f..5e49387a 100644 --- a/apps/web/src/lib/auth/server.ts +++ b/apps/web/src/auth/server.ts @@ -1,8 +1,8 @@ import { betterAuth, type RateLimit } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { Redis } from "@upstash/redis"; -import { db } from "@/lib/db"; -import { webEnv } from "@/lib/env/web"; +import { db } from "@/db"; +import { webEnv } from "@/env/web"; const redis = new Redis({ url: webEnv.UPSTASH_REDIS_REST_URL, diff --git a/apps/web/src/lib/background/blur.ts b/apps/web/src/background/blur.ts index 3b111482..3b111482 100644 --- a/apps/web/src/lib/background/blur.ts +++ b/apps/web/src/background/blur.ts diff --git a/apps/web/src/lib/background/color.ts b/apps/web/src/background/color.ts index 7b86faf5..7b86faf5 100644 --- a/apps/web/src/lib/background/color.ts +++ b/apps/web/src/background/color.ts diff --git a/apps/web/src/lib/blog/query.ts b/apps/web/src/blog/query.ts index 91c2a514..a7742db2 100644 --- a/apps/web/src/lib/blog/query.ts +++ b/apps/web/src/blog/query.ts @@ -5,7 +5,7 @@ import type { MarblePost,
MarblePostList,
MarbleTagList,
-} from "@/lib/blog/types";
+} from "@/blog/types";
import { unified } from "unified";
import rehypeParse from "rehype-parse";
import rehypeStringify from "rehype-stringify";
diff --git a/apps/web/src/lib/blog/types.ts b/apps/web/src/blog/types.ts index 7b2f450a..7b2f450a 100644 --- a/apps/web/src/lib/blog/types.ts +++ b/apps/web/src/blog/types.ts diff --git a/apps/web/src/lib/canvas/sizes.ts b/apps/web/src/canvas/sizes.ts index dce75bcb..76361336 100644 --- a/apps/web/src/lib/canvas/sizes.ts +++ b/apps/web/src/canvas/sizes.ts @@ -1,4 +1,4 @@ -import type { TCanvasSize } from "@/lib/project/types"; +import type { TCanvasSize } from "@/project/types"; export const DEFAULT_CANVAS_PRESETS: TCanvasSize[] = [ { width: 1920, height: 1080 }, diff --git a/apps/web/src/lib/changelog/components/changelog-notification.tsx b/apps/web/src/changelog/components/changelog-notification.tsx index 4433df30..4433df30 100644 --- a/apps/web/src/lib/changelog/components/changelog-notification.tsx +++ b/apps/web/src/changelog/components/changelog-notification.tsx diff --git a/apps/web/src/lib/changelog/components/copy-markdown-button.tsx b/apps/web/src/changelog/components/copy-markdown-button.tsx index bc7e94bf..bc7e94bf 100644 --- a/apps/web/src/lib/changelog/components/copy-markdown-button.tsx +++ b/apps/web/src/changelog/components/copy-markdown-button.tsx diff --git a/apps/web/src/lib/changelog/components/release.tsx b/apps/web/src/changelog/components/release.tsx index 475b0a6d..475b0a6d 100644 --- a/apps/web/src/lib/changelog/components/release.tsx +++ b/apps/web/src/changelog/components/release.tsx diff --git a/apps/web/src/lib/changelog/entries/0.1.0.md b/apps/web/src/changelog/entries/0.1.0.md index 33c5413c..33c5413c 100644 --- a/apps/web/src/lib/changelog/entries/0.1.0.md +++ b/apps/web/src/changelog/entries/0.1.0.md diff --git a/apps/web/src/lib/changelog/entries/0.2.0.md b/apps/web/src/changelog/entries/0.2.0.md index 261b47d2..261b47d2 100644 --- a/apps/web/src/lib/changelog/entries/0.2.0.md +++ b/apps/web/src/changelog/entries/0.2.0.md diff --git a/apps/web/src/lib/changelog/entries/0.3.0.md b/apps/web/src/changelog/entries/0.3.0.md index 86c62cff..86c62cff 100644 --- a/apps/web/src/lib/changelog/entries/0.3.0.md +++ b/apps/web/src/changelog/entries/0.3.0.md diff --git a/apps/web/src/lib/changelog/entries/0.4.0.md b/apps/web/src/changelog/entries/0.4.0.md index 141df133..141df133 100644 --- a/apps/web/src/lib/changelog/entries/0.4.0.md +++ b/apps/web/src/changelog/entries/0.4.0.md diff --git a/apps/web/src/lib/changelog/utils.ts b/apps/web/src/changelog/utils.ts index 682d376a..682d376a 100644 --- a/apps/web/src/lib/changelog/utils.ts +++ b/apps/web/src/changelog/utils.ts diff --git a/apps/web/src/lib/clipboard/handlers/elements.ts b/apps/web/src/clipboard/handlers/elements.ts index 1b58ac78..b2f5f2c3 100644 --- a/apps/web/src/lib/clipboard/handlers/elements.ts +++ b/apps/web/src/clipboard/handlers/elements.ts @@ -1,4 +1,4 @@ -import { PasteCommand } from "@/lib/commands/timeline"; +import { PasteCommand } from "@/commands/timeline"; import type { ClipboardHandler } from "../types"; export const ElementsClipboardHandler = { diff --git a/apps/web/src/lib/clipboard/handlers/index.ts b/apps/web/src/clipboard/handlers/index.ts index 29d4d1d0..29d4d1d0 100644 --- a/apps/web/src/lib/clipboard/handlers/index.ts +++ b/apps/web/src/clipboard/handlers/index.ts diff --git a/apps/web/src/lib/clipboard/handlers/keyframes.ts b/apps/web/src/clipboard/handlers/keyframes.ts index b746c179..ad156e12 100644 --- a/apps/web/src/lib/clipboard/handlers/keyframes.ts +++ b/apps/web/src/clipboard/handlers/keyframes.ts @@ -1,7 +1,7 @@ -import { getKeyframeById } from "@/lib/animation"; -import type { SelectedKeyframeRef } from "@/lib/animation/types"; -import type { TimelineElement } from "@/lib/timeline"; -import { PasteKeyframesCommand } from "@/lib/commands/timeline"; +import { getKeyframeById } from "@/animation"; +import type { SelectedKeyframeRef } from "@/animation/types"; +import type { TimelineElement } from "@/timeline"; +import { PasteKeyframesCommand } from "@/commands/timeline"; import type { ClipboardHandler, KeyframeClipboardCurvePatch, diff --git a/apps/web/src/lib/clipboard/index.ts b/apps/web/src/clipboard/index.ts index 76d5bddf..76d5bddf 100644 --- a/apps/web/src/lib/clipboard/index.ts +++ b/apps/web/src/clipboard/index.ts diff --git a/apps/web/src/lib/clipboard/types.ts b/apps/web/src/clipboard/types.ts index 41e107ef..0d249c9d 100644 --- a/apps/web/src/lib/clipboard/types.ts +++ b/apps/web/src/clipboard/types.ts @@ -5,13 +5,13 @@ import type { AnimationValue, ScalarCurveKeyframePatch, SelectedKeyframeRef, -} from "@/lib/animation/types"; -import type { Command } from "@/lib/commands/base-command"; +} from "@/animation/types"; +import type { Command } from "@/commands/base-command"; import type { CreateTimelineElement, ElementRef, TrackType, -} from "@/lib/timeline"; +} from "@/timeline"; export interface ElementClipboardItem { trackId: string; diff --git a/apps/web/src/lib/commands/base-command.ts b/apps/web/src/commands/base-command.ts index b244f2f3..2ab9af09 100644 --- a/apps/web/src/lib/commands/base-command.ts +++ b/apps/web/src/commands/base-command.ts @@ -1,5 +1,5 @@ -import type { EditorSelectionPatch } from "@/lib/selection/editor-selection"; -import type { ElementRef } from "@/lib/timeline/types"; +import type { EditorSelectionPatch } from "@/selection/editor-selection"; +import type { ElementRef } from "@/timeline/types"; export interface CommandResult { selection?: EditorSelectionPatch; diff --git a/apps/web/src/lib/commands/batch-command.ts b/apps/web/src/commands/batch-command.ts index c4ee7e0b..c4ee7e0b 100644 --- a/apps/web/src/lib/commands/batch-command.ts +++ b/apps/web/src/commands/batch-command.ts diff --git a/apps/web/src/lib/commands/index.ts b/apps/web/src/commands/index.ts index 78927ca9..78927ca9 100644 --- a/apps/web/src/lib/commands/index.ts +++ b/apps/web/src/commands/index.ts diff --git a/apps/web/src/lib/commands/media/add-media-asset.ts b/apps/web/src/commands/media/add-media-asset.ts index 12e0906e..d22241a2 100644 --- a/apps/web/src/lib/commands/media/add-media-asset.ts +++ b/apps/web/src/commands/media/add-media-asset.ts @@ -1,13 +1,13 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; +import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; import { toast } from "sonner"; -import type { MediaAsset } from "@/lib/media/types"; +import type { MediaAsset } from "@/media/types"; import { generateUUID } from "@/utils/id"; import { storageService } from "@/services/storage/service"; import type { FrameRate } from "opencut-wasm"; -import { hasMediaId } from "@/lib/timeline/element-utils"; -import { frameRatesEqual, getHighestImportedVideoFps } from "@/lib/fps/utils"; -import { UpdateProjectSettingsCommand } from "@/lib/commands/project"; +import { hasMediaId } from "@/timeline/element-utils"; +import { frameRatesEqual, getHighestImportedVideoFps } from "@/fps/utils"; +import { UpdateProjectSettingsCommand } from "@/commands/project"; export class AddMediaAssetCommand extends Command { private assetId: string; diff --git a/apps/web/src/lib/commands/media/index.ts b/apps/web/src/commands/media/index.ts index 466222f2..466222f2 100644 --- a/apps/web/src/lib/commands/media/index.ts +++ b/apps/web/src/commands/media/index.ts diff --git a/apps/web/src/lib/commands/media/remove-media-asset.ts b/apps/web/src/commands/media/remove-media-asset.ts index 85d187d9..8da62181 100644 --- a/apps/web/src/lib/commands/media/remove-media-asset.ts +++ b/apps/web/src/commands/media/remove-media-asset.ts @@ -1,12 +1,12 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; +import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import type { MediaAsset } from "@/lib/media/types"; -import { buildWaveformSourceKey } from "@/lib/media/waveform-summary"; +import type { MediaAsset } from "@/media/types"; +import { buildWaveformSourceKey } from "@/media/waveform-summary"; import { storageService } from "@/services/storage/service"; import { videoCache } from "@/services/video-cache/service"; import { waveformCache } from "@/services/waveform-cache/service"; -import { hasMediaId } from "@/lib/timeline/element-utils"; -import type { SceneTracks } from "@/lib/timeline"; +import { hasMediaId } from "@/timeline/element-utils"; +import type { SceneTracks } from "@/timeline"; export class RemoveMediaAssetCommand extends Command { private savedAssets: MediaAsset[] | null = null; diff --git a/apps/web/src/lib/commands/preview-tracker.ts b/apps/web/src/commands/preview-tracker.ts index 68c8dee3..68c8dee3 100644 --- a/apps/web/src/lib/commands/preview-tracker.ts +++ b/apps/web/src/commands/preview-tracker.ts diff --git a/apps/web/src/lib/commands/project/index.ts b/apps/web/src/commands/project/index.ts index a9f26edc..a9f26edc 100644 --- a/apps/web/src/lib/commands/project/index.ts +++ b/apps/web/src/commands/project/index.ts diff --git a/apps/web/src/lib/commands/project/update-project-settings.ts b/apps/web/src/commands/project/update-project-settings.ts index 32258e12..8d653db2 100644 --- a/apps/web/src/lib/commands/project/update-project-settings.ts +++ b/apps/web/src/commands/project/update-project-settings.ts @@ -1,6 +1,6 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; +import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import type { TProject, TProjectSettings } from "@/lib/project/types"; +import type { TProject, TProjectSettings } from "@/project/types"; export class UpdateProjectSettingsCommand extends Command { private savedSettings: TProjectSettings | null = null; diff --git a/apps/web/src/lib/commands/scene/create-scene.ts b/apps/web/src/commands/scene/create-scene.ts index 1cc72c0f..b554fe90 100644 --- a/apps/web/src/lib/commands/scene/create-scene.ts +++ b/apps/web/src/commands/scene/create-scene.ts @@ -1,7 +1,7 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; +import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import type { TScene } from "@/lib/timeline"; -import { buildDefaultScene } from "@/lib/scenes"; +import type { TScene } from "@/timeline"; +import { buildDefaultScene } from "@/timeline/scenes"; export class CreateSceneCommand extends Command { private savedScenes: TScene[] | null = null; diff --git a/apps/web/src/lib/commands/scene/delete-scene.ts b/apps/web/src/commands/scene/delete-scene.ts index ab269b12..92de518f 100644 --- a/apps/web/src/lib/commands/scene/delete-scene.ts +++ b/apps/web/src/commands/scene/delete-scene.ts @@ -1,7 +1,7 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; +import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import type { TScene } from "@/lib/timeline"; -import { canDeleteScene, getFallbackSceneAfterDelete } from "@/lib/scenes"; +import type { TScene } from "@/timeline"; +import { canDeleteScene, getFallbackSceneAfterDelete } from "@/timeline/scenes"; export class DeleteSceneCommand extends Command { private savedScenes: TScene[] | null = null; diff --git a/apps/web/src/lib/commands/scene/index.ts b/apps/web/src/commands/scene/index.ts index 2b843c99..2b843c99 100644 --- a/apps/web/src/lib/commands/scene/index.ts +++ b/apps/web/src/commands/scene/index.ts diff --git a/apps/web/src/lib/commands/scene/move-bookmark.ts b/apps/web/src/commands/scene/move-bookmark.ts index 103caa74..a1624eb0 100644 --- a/apps/web/src/lib/commands/scene/move-bookmark.ts +++ b/apps/web/src/commands/scene/move-bookmark.ts @@ -1,8 +1,8 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; +import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import type { TScene } from "@/lib/timeline"; -import { updateSceneInArray } from "@/lib/scenes"; -import { getFrameTime, moveBookmarkInArray } from "@/lib/timeline/bookmarks"; +import type { TScene } from "@/timeline"; +import { updateSceneInArray } from "@/timeline/scenes"; +import { getFrameTime, moveBookmarkInArray } from "@/timeline/bookmarks/index"; export class MoveBookmarkCommand extends Command { private savedScenes: TScene[] | null = null; diff --git a/apps/web/src/lib/commands/scene/remove-bookmark.ts b/apps/web/src/commands/scene/remove-bookmark.ts index 5eac920f..ea592556 100644 --- a/apps/web/src/lib/commands/scene/remove-bookmark.ts +++ b/apps/web/src/commands/scene/remove-bookmark.ts @@ -1,11 +1,11 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; +import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import type { TScene } from "@/lib/timeline"; -import { updateSceneInArray } from "@/lib/scenes"; +import type { TScene } from "@/timeline"; +import { updateSceneInArray } from "@/timeline/scenes"; import { getFrameTime, removeBookmarkFromArray, -} from "@/lib/timeline/bookmarks"; +} from "@/timeline/bookmarks/index"; export class RemoveBookmarkCommand extends Command { private savedScenes: TScene[] | null = null; diff --git a/apps/web/src/lib/commands/scene/rename-scene.ts b/apps/web/src/commands/scene/rename-scene.ts index ccdc85d6..19d6bfd8 100644 --- a/apps/web/src/lib/commands/scene/rename-scene.ts +++ b/apps/web/src/commands/scene/rename-scene.ts @@ -1,7 +1,7 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; +import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import type { TScene } from "@/lib/timeline"; -import { updateSceneInArray } from "@/lib/scenes"; +import type { TScene } from "@/timeline"; +import { updateSceneInArray } from "@/timeline/scenes"; export class RenameSceneCommand extends Command { private savedScenes: TScene[] | null = null; diff --git a/apps/web/src/lib/commands/scene/toggle-bookmark.ts b/apps/web/src/commands/scene/toggle-bookmark.ts index cb4fa655..4193c76e 100644 --- a/apps/web/src/lib/commands/scene/toggle-bookmark.ts +++ b/apps/web/src/commands/scene/toggle-bookmark.ts @@ -1,8 +1,11 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; +import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import type { TScene } from "@/lib/timeline"; -import { updateSceneInArray } from "@/lib/scenes"; -import { getFrameTime, toggleBookmarkInArray } from "@/lib/timeline/bookmarks"; +import type { TScene } from "@/timeline"; +import { updateSceneInArray } from "@/timeline/scenes"; +import { + getFrameTime, + toggleBookmarkInArray, +} from "@/timeline/bookmarks/index"; export class ToggleBookmarkCommand extends Command { private savedScenes: TScene[] | null = null; diff --git a/apps/web/src/lib/commands/scene/update-bookmark.ts b/apps/web/src/commands/scene/update-bookmark.ts index 1d1c7283..0d6a0193 100644 --- a/apps/web/src/lib/commands/scene/update-bookmark.ts +++ b/apps/web/src/commands/scene/update-bookmark.ts @@ -1,8 +1,11 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; +import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import type { Bookmark, TScene } from "@/lib/timeline"; -import { updateSceneInArray } from "@/lib/scenes"; -import { getFrameTime, updateBookmarkInArray } from "@/lib/timeline/bookmarks"; +import type { Bookmark, TScene } from "@/timeline"; +import { updateSceneInArray } from "@/timeline/scenes"; +import { + getFrameTime, + updateBookmarkInArray, +} from "@/timeline/bookmarks/index"; export class UpdateBookmarkCommand extends Command { private savedScenes: TScene[] | null = null; diff --git a/apps/web/src/lib/commands/timeline/clipboard/index.ts b/apps/web/src/commands/timeline/clipboard/index.ts index e893b62b..e893b62b 100644 --- a/apps/web/src/lib/commands/timeline/clipboard/index.ts +++ b/apps/web/src/commands/timeline/clipboard/index.ts diff --git a/apps/web/src/lib/commands/timeline/clipboard/paste-keyframes.ts b/apps/web/src/commands/timeline/clipboard/paste-keyframes.ts index 210afbd3..e05b49fa 100644 --- a/apps/web/src/lib/commands/timeline/clipboard/paste-keyframes.ts +++ b/apps/web/src/commands/timeline/clipboard/paste-keyframes.ts @@ -4,11 +4,11 @@ import { resolveAnimationTarget, updateScalarKeyframeCurve, upsertPathKeyframe, -} from "@/lib/animation"; -import { Command, type CommandResult } from "@/lib/commands/base-command"; -import type { KeyframeClipboardItem } from "@/lib/clipboard"; -import type { SceneTracks, TimelineElement } from "@/lib/timeline"; -import { updateElementInSceneTracks } from "@/lib/timeline"; +} from "@/animation"; +import { Command, type CommandResult } from "@/commands/base-command"; +import type { KeyframeClipboardItem } from "@/clipboard"; +import type { SceneTracks, TimelineElement } from "@/timeline"; +import { updateElementInSceneTracks } from "@/timeline"; import { generateUUID } from "@/utils/id"; function pasteKeyframesIntoElement({ diff --git a/apps/web/src/commands/timeline/clipboard/paste.ts b/apps/web/src/commands/timeline/clipboard/paste.ts new file mode 100644 index 00000000..4523566e --- /dev/null +++ b/apps/web/src/commands/timeline/clipboard/paste.ts @@ -0,0 +1,193 @@ +import { + Command, + createElementSelectionResult, + type CommandResult, +} from "@/commands/base-command"; +import { EditorCore } from "@/core"; +import type { SceneTracks, TimelineElement } from "@/timeline"; +import type { ElementClipboardItem } from "@/clipboard"; +import { generateUUID } from "@/utils/id"; +import { + applyPlacement, + resolveTrackPlacement, + enforceMainTrackStart, +} from "@/timeline/placement"; +import { cloneAnimations } from "@/animation"; + +export class PasteCommand extends Command { + private savedState: SceneTracks | null = null; + private pastedElements: { trackId: string; elementId: string }[] = []; + private readonly time: number; + private readonly clipboardItems: ElementClipboardItem[]; + + constructor({ + time, + clipboardItems, + }: { + time: number; + clipboardItems: ElementClipboardItem[]; + }) { + super(); + this.time = time; + this.clipboardItems = clipboardItems; + } + + execute(): CommandResult | undefined { + if (this.clipboardItems.length === 0) return undefined; + + const editor = EditorCore.getInstance(); + this.savedState = editor.scenes.getActiveScene().tracks; + this.pastedElements = []; + + const minStart = Math.min( + ...this.clipboardItems.map((item) => item.element.startTime), + ); + + let updatedTracks = this.savedState; + const itemsByTrackId = groupClipboardItemsByTrackId({ + clipboardItems: this.clipboardItems, + }); + + for (const [trackId, items] of itemsByTrackId) { + const elementsToAdd = buildPastedElements({ + items, + minStart, + time: this.time, + }); + + if (elementsToAdd.length === 0) { + continue; + } + + const trackType = items[0].trackType; + const sourceTrackIndex = [ + ...updatedTracks.overlay, + updatedTracks.main, + ...updatedTracks.audio, + ].findIndex((track) => track.id === trackId); + const placementResult = resolveTrackPlacement({ + tracks: updatedTracks, + trackType, + timeSpans: elementsToAdd.map((element) => ({ + startTime: element.startTime, + duration: element.duration, + })), + strategy: { type: "aboveSource", sourceTrackIndex }, + }); + if (!placementResult) { + continue; + } + + let elementsForPlacement = elementsToAdd; + if (placementResult.kind === "existingTrack") { + const targetTrack = + placementResult.trackIndex < updatedTracks.overlay.length + ? updatedTracks.overlay[placementResult.trackIndex] + : placementResult.trackIndex === updatedTracks.overlay.length + ? updatedTracks.main + : updatedTracks.audio[ + placementResult.trackIndex - updatedTracks.overlay.length - 1 + ]; + if (targetTrack?.id === updatedTracks.main.id) { + const earliestElement = elementsToAdd.reduce((earliest, element) => + element.startTime < earliest.startTime ? element : earliest, + ); + const adjustedEarliestStartTime = enforceMainTrackStart({ + tracks: updatedTracks, + targetTrackId: targetTrack.id, + requestedStartTime: earliestElement.startTime, + }); + const delta = adjustedEarliestStartTime - earliestElement.startTime; + + if (delta !== 0) { + elementsForPlacement = elementsToAdd.map((element) => ({ + ...element, + startTime: Math.max(0, element.startTime + delta), + })); + } + } + } + + const applied = applyPlacement({ + tracks: updatedTracks, + placementResult, + elements: elementsForPlacement, + }); + if (!applied) { + continue; + } + + updatedTracks = applied.updatedTracks; + + for (const element of elementsForPlacement) { + this.pastedElements.push({ + trackId: applied.targetTrackId, + elementId: element.id, + }); + } + } + + editor.timeline.updateTracks(updatedTracks); + + if (this.pastedElements.length > 0) { + return createElementSelectionResult(this.pastedElements); + } + return undefined; + } + + undo(): void { + if (this.savedState) { + const editor = EditorCore.getInstance(); + editor.timeline.updateTracks(this.savedState); + } + } + + getPastedElements(): { trackId: string; elementId: string }[] { + return this.pastedElements; + } +} + +function groupClipboardItemsByTrackId({ + clipboardItems, +}: { + clipboardItems: ElementClipboardItem[]; +}): Map<string, ElementClipboardItem[]> { + const groupedItems = new Map<string, ElementClipboardItem[]>(); + + for (const item of clipboardItems) { + const existingItems = groupedItems.get(item.trackId) ?? []; + groupedItems.set(item.trackId, [...existingItems, item]); + } + + return groupedItems; +} + +function buildPastedElements({ + items, + minStart, + time, +}: { + items: ElementClipboardItem[]; + minStart: number; + time: number; +}): TimelineElement[] { + const elementsToAdd: TimelineElement[] = []; + + for (const item of items) { + const relativeOffset = item.element.startTime - minStart; + const startTime = Math.max(0, time + relativeOffset); + const newElementId = generateUUID(); + + elementsToAdd.push({ + ...item.element, + id: newElementId, + startTime, + animations: cloneAnimations({ + animations: item.element.animations, + shouldRegenerateKeyframeIds: true, + }), + } as TimelineElement); + } + + return elementsToAdd; +} diff --git a/apps/web/src/lib/commands/timeline/element/delete-elements.ts b/apps/web/src/commands/timeline/element/delete-elements.ts index 0fc7948f..ad0267b7 100644 --- a/apps/web/src/lib/commands/timeline/element/delete-elements.ts +++ b/apps/web/src/commands/timeline/element/delete-elements.ts @@ -1,7 +1,7 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; -import type { SceneTracks } from "@/lib/timeline"; +import { Command, type CommandResult } from "@/commands/base-command"; +import type { SceneTracks } from "@/timeline"; import { EditorCore } from "@/core"; -import type { TimelineTrack } from "@/lib/timeline"; +import type { TimelineTrack } from "@/timeline"; function removeTrackElements<TTrack extends TimelineTrack>({ track, diff --git a/apps/web/src/commands/timeline/element/duplicate-elements.ts b/apps/web/src/commands/timeline/element/duplicate-elements.ts new file mode 100644 index 00000000..8f65f7c7 --- /dev/null +++ b/apps/web/src/commands/timeline/element/duplicate-elements.ts @@ -0,0 +1,134 @@ +import { + Command, + createElementSelectionResult, + type CommandResult, +} from "@/commands/base-command"; +import type { SceneTracks, TimelineElement } from "@/timeline"; +import { generateUUID } from "@/utils/id"; +import { EditorCore } from "@/core"; +import { applyPlacement, resolveTrackPlacement } from "@/timeline/placement"; +import { cloneAnimations } from "@/animation"; + +interface DuplicateElementsParams { + elements: { trackId: string; elementId: string }[]; +} + +export class DuplicateElementsCommand extends Command { + private duplicatedElements: { trackId: string; elementId: string }[] = []; + private savedState: SceneTracks | null = null; + private elements: DuplicateElementsParams["elements"]; + + constructor({ elements }: DuplicateElementsParams) { + super(); + this.elements = elements; + } + + execute(): CommandResult | undefined { + const editor = EditorCore.getInstance(); + this.savedState = editor.scenes.getActiveScene().tracks; + this.duplicatedElements = []; + + let updatedTracks = this.savedState; + + for (const track of [ + ...this.savedState.overlay, + this.savedState.main, + ...this.savedState.audio, + ]) { + const elementsToDuplicate = this.elements.filter( + (elementEntry) => elementEntry.trackId === track.id, + ); + + if (elementsToDuplicate.length === 0) { + continue; + } + + const elementIdsToDuplicate = new Set( + elementsToDuplicate.map((element) => element.elementId), + ); + const newTrackElements: TimelineElement[] = []; + + for (const element of track.elements) { + if (!elementIdsToDuplicate.has(element.id)) { + continue; + } + + const newId = generateUUID(); + newTrackElements.push( + buildDuplicateElement({ + element, + id: newId, + startTime: element.startTime, + }), + ); + } + + const placementResult = resolveTrackPlacement({ + tracks: updatedTracks, + trackType: track.type, + timeSpans: [], + strategy: { type: "alwaysNew", position: "highest" }, + }); + if (!placementResult || placementResult.kind !== "newTrack") { + continue; + } + + const applied = applyPlacement({ + tracks: updatedTracks, + placementResult, + elements: newTrackElements, + }); + if (!applied) { + continue; + } + + updatedTracks = applied.updatedTracks; + + for (const element of newTrackElements) { + this.duplicatedElements.push({ + trackId: applied.targetTrackId, + elementId: element.id, + }); + } + } + + editor.timeline.updateTracks(updatedTracks); + + if (this.duplicatedElements.length > 0) { + return createElementSelectionResult(this.duplicatedElements); + } + return undefined; + } + + undo(): void { + if (this.savedState) { + const editor = EditorCore.getInstance(); + editor.timeline.updateTracks(this.savedState); + } + } + + getDuplicatedElements(): { trackId: string; elementId: string }[] { + return this.duplicatedElements; + } +} + +function buildDuplicateElement({ + element, + id, + startTime, +}: { + element: TimelineElement; + id: string; + startTime: number; +}): TimelineElement { + return { + ...element, + id, + name: `${element.name} (copy)`, + startTime, + animations: cloneAnimations({ + animations: element.animations, + shouldRegenerateKeyframeIds: true, + }), + }; +} diff --git a/apps/web/src/lib/commands/timeline/element/effects/add-effect.ts b/apps/web/src/commands/timeline/element/effects/add-effect.ts index 902ca341..be08b433 100644 --- a/apps/web/src/lib/commands/timeline/element/effects/add-effect.ts +++ b/apps/web/src/commands/timeline/element/effects/add-effect.ts @@ -1,8 +1,8 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; +import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import { isVisualElement, updateElementInSceneTracks } from "@/lib/timeline"; -import type { SceneTracks, VisualElement } from "@/lib/timeline"; -import { buildDefaultEffectInstance } from "@/lib/effects"; +import { isVisualElement, updateElementInSceneTracks } from "@/timeline"; +import type { SceneTracks, VisualElement } from "@/timeline"; +import { buildDefaultEffectInstance } from "@/effects"; function addEffectToElement({ element, diff --git a/apps/web/src/lib/commands/timeline/element/effects/index.ts b/apps/web/src/commands/timeline/element/effects/index.ts index 133ce661..133ce661 100644 --- a/apps/web/src/lib/commands/timeline/element/effects/index.ts +++ b/apps/web/src/commands/timeline/element/effects/index.ts diff --git a/apps/web/src/lib/commands/timeline/element/effects/remove-effect.ts b/apps/web/src/commands/timeline/element/effects/remove-effect.ts index f453f2cc..e6ef9934 100644 --- a/apps/web/src/lib/commands/timeline/element/effects/remove-effect.ts +++ b/apps/web/src/commands/timeline/element/effects/remove-effect.ts @@ -1,7 +1,7 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; +import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import { isVisualElement, updateElementInSceneTracks } from "@/lib/timeline"; -import type { SceneTracks, VisualElement } from "@/lib/timeline"; +import { isVisualElement, updateElementInSceneTracks } from "@/timeline"; +import type { SceneTracks, VisualElement } from "@/timeline"; function removeEffectFromElement({ element, diff --git a/apps/web/src/lib/commands/timeline/element/effects/reorder-effect.ts b/apps/web/src/commands/timeline/element/effects/reorder-effect.ts index 28b7c4e1..554804e2 100644 --- a/apps/web/src/lib/commands/timeline/element/effects/reorder-effect.ts +++ b/apps/web/src/commands/timeline/element/effects/reorder-effect.ts @@ -1,7 +1,7 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; +import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import { isVisualElement, updateElementInSceneTracks } from "@/lib/timeline"; -import type { SceneTracks, VisualElement } from "@/lib/timeline"; +import { isVisualElement, updateElementInSceneTracks } from "@/timeline"; +import type { SceneTracks, VisualElement } from "@/timeline"; function reorderEffectsOnElement({ element, diff --git a/apps/web/src/lib/commands/timeline/element/effects/toggle-effect.ts b/apps/web/src/commands/timeline/element/effects/toggle-effect.ts index a703cd94..89149a79 100644 --- a/apps/web/src/lib/commands/timeline/element/effects/toggle-effect.ts +++ b/apps/web/src/commands/timeline/element/effects/toggle-effect.ts @@ -1,7 +1,7 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; +import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import { isVisualElement, updateElementInSceneTracks } from "@/lib/timeline"; -import type { SceneTracks, VisualElement } from "@/lib/timeline"; +import { isVisualElement, updateElementInSceneTracks } from "@/timeline"; +import type { SceneTracks, VisualElement } from "@/timeline"; export function toggleEffectOnElement({ element, diff --git a/apps/web/src/lib/commands/timeline/element/effects/update-effect-params.ts b/apps/web/src/commands/timeline/element/effects/update-effect-params.ts index 5e75c1f0..62b3c937 100644 --- a/apps/web/src/lib/commands/timeline/element/effects/update-effect-params.ts +++ b/apps/web/src/commands/timeline/element/effects/update-effect-params.ts @@ -1,8 +1,8 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; +import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import { isVisualElement, updateElementInSceneTracks } from "@/lib/timeline"; -import type { ParamValues } from "@/lib/params"; -import type { SceneTracks, VisualElement } from "@/lib/timeline"; +import { isVisualElement, updateElementInSceneTracks } from "@/timeline"; +import type { ParamValues } from "@/params"; +import type { SceneTracks, VisualElement } from "@/timeline"; function updateEffectParamsOnElement({ element, diff --git a/apps/web/src/lib/commands/timeline/element/index.ts b/apps/web/src/commands/timeline/element/index.ts index ff365801..ff365801 100644 --- a/apps/web/src/lib/commands/timeline/element/index.ts +++ b/apps/web/src/commands/timeline/element/index.ts diff --git a/apps/web/src/lib/commands/timeline/element/insert-element.ts b/apps/web/src/commands/timeline/element/insert-element.ts index ed05b8c6..49ac6f2d 100644 --- a/apps/web/src/lib/commands/timeline/element/insert-element.ts +++ b/apps/web/src/commands/timeline/element/insert-element.ts @@ -1,23 +1,23 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; +import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; import type { CreateTimelineElement, SceneTracks, TimelineElement, TrackType, -} from "@/lib/timeline"; +} from "@/timeline"; import { generateUUID } from "@/utils/id"; -import { requiresMediaId } from "@/lib/timeline/element-utils"; -import type { MediaAsset } from "@/lib/media/types"; -import { DEFAULT_NEW_ELEMENT_DURATION } from "@/lib/timeline/creation"; -import { floatToFrameRate } from "@/lib/fps/utils"; -import { graphicsRegistry, registerDefaultGraphics } from "@/lib/graphics"; +import { requiresMediaId } from "@/timeline/element-utils"; +import type { MediaAsset } from "@/media/types"; +import { DEFAULT_NEW_ELEMENT_DURATION } from "@/timeline/creation"; +import { floatToFrameRate } from "@/fps/utils"; +import { graphicsRegistry, registerDefaultGraphics } from "@/graphics"; import { applyPlacement, canElementGoOnTrack, resolveTrackPlacement, validateElementTrackCompatibility, -} from "@/lib/timeline/placement"; +} from "@/timeline/placement"; type InsertElementPlacement = | { mode: "explicit"; trackId: string } diff --git a/apps/web/src/lib/commands/timeline/element/keyframes/index.ts b/apps/web/src/commands/timeline/element/keyframes/index.ts index 319a827b..319a827b 100644 --- a/apps/web/src/lib/commands/timeline/element/keyframes/index.ts +++ b/apps/web/src/commands/timeline/element/keyframes/index.ts diff --git a/apps/web/src/lib/commands/timeline/element/keyframes/remove-effect-param-keyframe.ts b/apps/web/src/commands/timeline/element/keyframes/remove-effect-param-keyframe.ts index b1ef4aef..c049d5bb 100644 --- a/apps/web/src/lib/commands/timeline/element/keyframes/remove-effect-param-keyframe.ts +++ b/apps/web/src/commands/timeline/element/keyframes/remove-effect-param-keyframe.ts @@ -1,9 +1,9 @@ import { EditorCore } from "@/core"; -import { Command, type CommandResult } from "@/lib/commands/base-command"; -import { removeEffectParamKeyframe } from "@/lib/animation/effect-param-channel"; -import { updateElementInSceneTracks } from "@/lib/timeline"; -import { isVisualElement } from "@/lib/timeline/element-utils"; -import type { SceneTracks } from "@/lib/timeline"; +import { Command, type CommandResult } from "@/commands/base-command"; +import { removeEffectParamKeyframe } from "@/animation/effect-param-channel"; +import { updateElementInSceneTracks } from "@/timeline"; +import { isVisualElement } from "@/timeline/element-utils"; +import type { SceneTracks } from "@/timeline"; export class RemoveEffectParamKeyframeCommand extends Command { private savedState: SceneTracks | null = null; diff --git a/apps/web/src/lib/commands/timeline/element/keyframes/remove-keyframe.ts b/apps/web/src/commands/timeline/element/keyframes/remove-keyframe.ts index 3c3d1c6b..ed917406 100644 --- a/apps/web/src/lib/commands/timeline/element/keyframes/remove-keyframe.ts +++ b/apps/web/src/commands/timeline/element/keyframes/remove-keyframe.ts @@ -3,11 +3,11 @@ import { hasKeyframesForPath, removeElementKeyframe, resolveAnimationTarget, -} from "@/lib/animation"; -import { Command, type CommandResult } from "@/lib/commands/base-command"; -import { updateElementInSceneTracks } from "@/lib/timeline"; -import type { AnimationPath, AnimationValue } from "@/lib/animation/types"; -import type { SceneTracks, TimelineElement } from "@/lib/timeline"; +} from "@/animation"; +import { Command, type CommandResult } from "@/commands/base-command"; +import { updateElementInSceneTracks } from "@/timeline"; +import type { AnimationPath, AnimationValue } from "@/animation/types"; +import type { SceneTracks, TimelineElement } from "@/timeline"; function removeKeyframeAndPersist({ element, diff --git a/apps/web/src/lib/commands/timeline/element/keyframes/retime-keyframe.ts b/apps/web/src/commands/timeline/element/keyframes/retime-keyframe.ts index ad7f5651..ccbdbc5d 100644 --- a/apps/web/src/lib/commands/timeline/element/keyframes/retime-keyframe.ts +++ b/apps/web/src/commands/timeline/element/keyframes/retime-keyframe.ts @@ -1,9 +1,9 @@ import { EditorCore } from "@/core"; -import { resolveAnimationTarget, retimeElementKeyframe } from "@/lib/animation"; -import { Command, type CommandResult } from "@/lib/commands/base-command"; -import { updateElementInSceneTracks } from "@/lib/timeline"; -import type { AnimationPath } from "@/lib/animation/types"; -import type { SceneTracks } from "@/lib/timeline"; +import { resolveAnimationTarget, retimeElementKeyframe } from "@/animation"; +import { Command, type CommandResult } from "@/commands/base-command"; +import { updateElementInSceneTracks } from "@/timeline"; +import type { AnimationPath } from "@/animation/types"; +import type { SceneTracks } from "@/timeline"; export class RetimeKeyframeCommand extends Command { private savedState: SceneTracks | null = null; diff --git a/apps/web/src/lib/commands/timeline/element/keyframes/update-scalar-keyframe-curve.ts b/apps/web/src/commands/timeline/element/keyframes/update-scalar-keyframe-curve.ts index 9a4c2304..4a3db39d 100644 --- a/apps/web/src/lib/commands/timeline/element/keyframes/update-scalar-keyframe-curve.ts +++ b/apps/web/src/commands/timeline/element/keyframes/update-scalar-keyframe-curve.ts @@ -2,14 +2,14 @@ import { EditorCore } from "@/core"; import { resolveAnimationTarget, updateScalarKeyframeCurve, -} from "@/lib/animation"; -import { Command, type CommandResult } from "@/lib/commands/base-command"; -import { updateElementInSceneTracks } from "@/lib/timeline"; +} from "@/animation"; +import { Command, type CommandResult } from "@/commands/base-command"; +import { updateElementInSceneTracks } from "@/timeline"; import type { AnimationPath, ScalarCurveKeyframePatch, -} from "@/lib/animation/types"; -import type { SceneTracks } from "@/lib/timeline"; +} from "@/animation/types"; +import type { SceneTracks } from "@/timeline"; export class UpdateScalarKeyframeCurveCommand extends Command { private savedState: SceneTracks | null = null; diff --git a/apps/web/src/lib/commands/timeline/element/keyframes/upsert-effect-param-keyframe.ts b/apps/web/src/commands/timeline/element/keyframes/upsert-effect-param-keyframe.ts index f436721a..eae8f039 100644 --- a/apps/web/src/lib/commands/timeline/element/keyframes/upsert-effect-param-keyframe.ts +++ b/apps/web/src/commands/timeline/element/keyframes/upsert-effect-param-keyframe.ts @@ -1,14 +1,14 @@ import { EditorCore } from "@/core"; -import { Command, type CommandResult } from "@/lib/commands/base-command"; +import { Command, type CommandResult } from "@/commands/base-command"; import { buildEffectParamPath, resolveAnimationTarget, upsertPathKeyframe, -} from "@/lib/animation"; -import { updateElementInSceneTracks } from "@/lib/timeline"; -import { isVisualElement } from "@/lib/timeline/element-utils"; -import type { AnimationInterpolation } from "@/lib/animation/types"; -import type { SceneTracks } from "@/lib/timeline"; +} from "@/animation"; +import { updateElementInSceneTracks } from "@/timeline"; +import { isVisualElement } from "@/timeline/element-utils"; +import type { AnimationInterpolation } from "@/animation/types"; +import type { SceneTracks } from "@/timeline"; export class UpsertEffectParamKeyframeCommand extends Command { private savedState: SceneTracks | null = null; diff --git a/apps/web/src/lib/commands/timeline/element/keyframes/upsert-keyframe.ts b/apps/web/src/commands/timeline/element/keyframes/upsert-keyframe.ts index f77e69b8..30514146 100644 --- a/apps/web/src/lib/commands/timeline/element/keyframes/upsert-keyframe.ts +++ b/apps/web/src/commands/timeline/element/keyframes/upsert-keyframe.ts @@ -1,13 +1,13 @@ import { EditorCore } from "@/core"; -import { Command, type CommandResult } from "@/lib/commands/base-command"; -import { resolveAnimationTarget, upsertPathKeyframe } from "@/lib/animation"; -import { updateElementInSceneTracks } from "@/lib/timeline"; -import type { SceneTracks } from "@/lib/timeline"; +import { Command, type CommandResult } from "@/commands/base-command"; +import { resolveAnimationTarget, upsertPathKeyframe } from "@/animation"; +import { updateElementInSceneTracks } from "@/timeline"; +import type { SceneTracks } from "@/timeline"; import type { AnimationPath, AnimationInterpolation, AnimationValue, -} from "@/lib/animation/types"; +} from "@/animation/types"; export class UpsertKeyframeCommand extends Command { private savedState: SceneTracks | null = null; diff --git a/apps/web/src/lib/commands/timeline/element/masks/delete-custom-mask-points.ts b/apps/web/src/commands/timeline/element/masks/delete-custom-mask-points.ts index 346eb8d9..1445b1f8 100644 --- a/apps/web/src/lib/commands/timeline/element/masks/delete-custom-mask-points.ts +++ b/apps/web/src/commands/timeline/element/masks/delete-custom-mask-points.ts @@ -1,12 +1,12 @@ import { EditorCore } from "@/core"; -import { Command, type CommandResult } from "@/lib/commands/base-command"; +import { Command, type CommandResult } from "@/commands/base-command"; import { getCustomMaskClosedStateAfterPointRemoval, removeCustomMaskPoints, -} from "@/lib/masks/custom-path"; -import type { CustomMask } from "@/lib/masks/types"; -import { isMaskableElement, updateElementInSceneTracks } from "@/lib/timeline"; -import type { MaskableElement, SceneTracks } from "@/lib/timeline"; +} from "@/masks/custom-path"; +import type { CustomMask } from "@/masks/types"; +import { isMaskableElement, updateElementInSceneTracks } from "@/timeline"; +import type { MaskableElement, SceneTracks } from "@/timeline"; function deletePointsFromCustomMask({ mask, diff --git a/apps/web/src/lib/commands/timeline/element/masks/index.ts b/apps/web/src/commands/timeline/element/masks/index.ts index dd6373b1..dd6373b1 100644 --- a/apps/web/src/lib/commands/timeline/element/masks/index.ts +++ b/apps/web/src/commands/timeline/element/masks/index.ts diff --git a/apps/web/src/lib/commands/timeline/element/masks/insert-custom-mask-point.ts b/apps/web/src/commands/timeline/element/masks/insert-custom-mask-point.ts index 9f837836..58559af6 100644 --- a/apps/web/src/lib/commands/timeline/element/masks/insert-custom-mask-point.ts +++ b/apps/web/src/commands/timeline/element/masks/insert-custom-mask-point.ts @@ -1,10 +1,10 @@ import { EditorCore } from "@/core"; -import { Command, type CommandResult } from "@/lib/commands/base-command"; -import { insertPointOnCustomMaskSegment } from "@/lib/masks/definitions/custom"; -import type { ElementBounds } from "@/lib/preview/element-bounds"; -import type { CustomMask } from "@/lib/masks/types"; -import { isMaskableElement, updateElementInSceneTracks } from "@/lib/timeline"; -import type { MaskableElement, SceneTracks } from "@/lib/timeline"; +import { Command, type CommandResult } from "@/commands/base-command"; +import { insertPointOnCustomMaskSegment } from "@/masks/definitions/custom"; +import type { ElementBounds } from "@/preview/element-bounds"; +import type { CustomMask } from "@/masks/types"; +import { isMaskableElement, updateElementInSceneTracks } from "@/timeline"; +import type { MaskableElement, SceneTracks } from "@/timeline"; function insertPointIntoCustomMask({ mask, diff --git a/apps/web/src/lib/commands/timeline/element/masks/remove-mask.ts b/apps/web/src/commands/timeline/element/masks/remove-mask.ts index 9f6c5f77..b543bce1 100644 --- a/apps/web/src/lib/commands/timeline/element/masks/remove-mask.ts +++ b/apps/web/src/commands/timeline/element/masks/remove-mask.ts @@ -1,7 +1,7 @@ import { EditorCore } from "@/core"; -import { Command, type CommandResult } from "@/lib/commands/base-command"; -import { isMaskableElement, updateElementInSceneTracks } from "@/lib/timeline"; -import type { SceneTracks, MaskableElement } from "@/lib/timeline"; +import { Command, type CommandResult } from "@/commands/base-command"; +import { isMaskableElement, updateElementInSceneTracks } from "@/timeline"; +import type { SceneTracks, MaskableElement } from "@/timeline"; function removeMaskFromElement({ element, diff --git a/apps/web/src/lib/commands/timeline/element/masks/toggle-mask-inverted.ts b/apps/web/src/commands/timeline/element/masks/toggle-mask-inverted.ts index 2b5c1694..fec9b923 100644 --- a/apps/web/src/lib/commands/timeline/element/masks/toggle-mask-inverted.ts +++ b/apps/web/src/commands/timeline/element/masks/toggle-mask-inverted.ts @@ -1,8 +1,8 @@ import { EditorCore } from "@/core"; -import { Command, type CommandResult } from "@/lib/commands/base-command"; -import { isMaskableElement, updateElementInSceneTracks } from "@/lib/timeline"; -import type { Mask } from "@/lib/masks/types"; -import type { SceneTracks, MaskableElement } from "@/lib/timeline"; +import { Command, type CommandResult } from "@/commands/base-command"; +import { isMaskableElement, updateElementInSceneTracks } from "@/timeline"; +import type { Mask } from "@/masks/types"; +import type { SceneTracks, MaskableElement } from "@/timeline"; export function toggleMaskInvertedOnElement({ element, diff --git a/apps/web/src/commands/timeline/element/move-elements.ts b/apps/web/src/commands/timeline/element/move-elements.ts new file mode 100644 index 00000000..3ed31d42 --- /dev/null +++ b/apps/web/src/commands/timeline/element/move-elements.ts @@ -0,0 +1,183 @@ +import { + Command, + createElementSelectionResult, + type CommandResult, +} from "@/commands/base-command"; +import { EditorCore } from "@/core"; +import type { SceneTracks, TimelineElement, TimelineTrack } from "@/timeline"; +import { + buildEmptyTrack, + validateElementTrackCompatibility, +} from "@/timeline/placement"; +import type { + PlannedElementMove, + PlannedTrackCreation, +} from "@/timeline/group-move"; +import { findTrackInSceneTracks } from "@/timeline/track-element-update"; + +export class MoveElementCommand extends Command { + private savedState: SceneTracks | null = null; + + constructor({ + moves, + createTracks = [], + }: { + moves: PlannedElementMove[]; + createTracks?: PlannedTrackCreation[]; + }) { + super(); + this.moves = moves; + this.createTracks = createTracks; + } + + private readonly moves: PlannedElementMove[]; + private readonly createTracks: PlannedTrackCreation[]; + + execute(): CommandResult | undefined { + const editor = EditorCore.getInstance(); + this.savedState = editor.scenes.getActiveScene().tracks; + + let tracksToUpdate = this.savedState; + for (const createTrack of [...this.createTracks].sort( + (firstTrack, secondTrack) => firstTrack.index - secondTrack.index, + )) { + tracksToUpdate = insertTrackAtDisplayIndex({ + tracks: tracksToUpdate, + track: buildEmptyTrack({ + id: createTrack.id, + type: createTrack.type, + }), + insertIndex: createTrack.index, + }); + } + + const movedElementsById = new Map<string, TimelineElement>(); + for (const move of this.moves) { + const sourceTrack = findTrackInSceneTracks({ + tracks: this.savedState, + trackId: move.sourceTrackId, + }); + const sourceElement = sourceTrack?.elements.find( + (trackElement) => trackElement.id === move.elementId, + ); + if (!sourceTrack || !sourceElement) { + throw new Error("Source track or element not found"); + } + + const targetTrack = findTrackInSceneTracks({ + tracks: tracksToUpdate, + trackId: move.targetTrackId, + }); + if (!targetTrack) { + throw new Error("Target track not found"); + } + + const validation = validateElementTrackCompatibility({ + element: sourceElement, + track: targetTrack, + }); + if (!validation.isValid) { + throw new Error(validation.errorMessage); + } + + movedElementsById.set(move.elementId, { + ...sourceElement, + startTime: move.newStartTime, + }); + } + + const movedElementIds = new Set(this.moves.map((move) => move.elementId)); + const movedElementsByTargetTrackId = new Map<string, TimelineElement[]>(); + for (const move of this.moves) { + const movedElement = movedElementsById.get(move.elementId); + if (!movedElement) { + continue; + } + + const nextTargetElements = + movedElementsByTargetTrackId.get(move.targetTrackId) ?? []; + nextTargetElements.push(movedElement); + movedElementsByTargetTrackId.set(move.targetTrackId, nextTargetElements); + } + + const updatedTracks = mapSceneTracks({ + tracks: tracksToUpdate, + update: (track) => ({ + ...track, + elements: [ + ...track.elements.filter( + (element) => !movedElementIds.has(element.id), + ), + ...(movedElementsByTargetTrackId.get(track.id) ?? []), + ], + }), + }); + + editor.timeline.updateTracks(updatedTracks); + return createElementSelectionResult( + this.moves.map(({ elementId, targetTrackId }) => ({ + trackId: targetTrackId, + elementId, + })), + ); + } + + undo(): void { + if (this.savedState) { + const editor = EditorCore.getInstance(); + editor.timeline.updateTracks(this.savedState); + } + } +} + +function mapSceneTracks({ + tracks, + update, +}: { + tracks: SceneTracks; + update: <TTrack extends TimelineTrack>(track: TTrack) => TTrack; +}): SceneTracks { + return { + overlay: tracks.overlay.map((track) => update(track)), + main: update(tracks.main), + audio: tracks.audio.map((track) => update(track)), + }; +} + +function insertTrackAtDisplayIndex({ + tracks, + track, + insertIndex, +}: { + tracks: SceneTracks; + track: TimelineTrack; + insertIndex: number; +}): SceneTracks { + if (track.type === "audio") { + const audioInsertIndex = Math.max( + 0, + Math.min(insertIndex - tracks.overlay.length - 1, tracks.audio.length), + ); + return { + ...tracks, + audio: [ + ...tracks.audio.slice(0, audioInsertIndex), + track, + ...tracks.audio.slice(audioInsertIndex), + ], + }; + } + + const overlayInsertIndex = Math.max( + 0, + Math.min(insertIndex, tracks.overlay.length), + ); + return { + ...tracks, + overlay: [ + ...tracks.overlay.slice(0, overlayInsertIndex), + track, + ...tracks.overlay.slice(overlayInsertIndex), + ], + }; +} diff --git a/apps/web/src/commands/timeline/element/split-elements.ts b/apps/web/src/commands/timeline/element/split-elements.ts new file mode 100644 index 00000000..11c317f4 --- /dev/null +++ b/apps/web/src/commands/timeline/element/split-elements.ts @@ -0,0 +1,182 @@ +import { + Command, + createElementSelectionResult, + type CommandResult, +} from "@/commands/base-command"; +import type { SceneTracks, TimelineElement } from "@/timeline"; +import { generateUUID } from "@/utils/id"; +import { EditorCore } from "@/core"; +import { isRetimableElement } from "@/timeline"; +import { splitAnimationsAtTime } from "@/animation"; +import { getSourceSpanAtClipTime } from "@/retime"; + +export class SplitElementsCommand extends Command { + private savedState: SceneTracks | null = null; + private rightSideElements: { trackId: string; elementId: string }[] = []; + private readonly elements: { trackId: string; elementId: string }[]; + private readonly splitTime: number; + private readonly retainSide: "both" | "left" | "right"; + + constructor({ + elements, + splitTime, + retainSide = "both", + }: { + elements: { trackId: string; elementId: string }[]; + splitTime: number; + retainSide?: "both" | "left" | "right"; + }) { + super(); + this.elements = elements; + this.splitTime = splitTime; + this.retainSide = retainSide; + } + + getRightSideElements(): { trackId: string; elementId: string }[] { + return this.rightSideElements; + } + + execute(): CommandResult | undefined { + const editor = EditorCore.getInstance(); + this.savedState = editor.scenes.getActiveScene().tracks; + this.rightSideElements = []; + + const splitTrack = < + TTrack extends { id: string; elements: TimelineElement[] }, + >( + track: TTrack, + ): TTrack => { + const elementsToSplit = this.elements.filter( + (target) => target.trackId === track.id, + ); + + if (elementsToSplit.length === 0) { + return track; + } + + const elements = track.elements.flatMap((element) => { + const shouldSplit = elementsToSplit.some( + (target) => target.elementId === element.id, + ); + + if (!shouldSplit) { + return [element]; + } + + const effectiveStart = element.startTime; + const effectiveEnd = element.startTime + element.duration; + + if ( + this.splitTime <= effectiveStart || + this.splitTime >= effectiveEnd + ) { + return [element]; + } + + const relativeTime = this.splitTime - element.startTime; + const leftVisibleDuration = relativeTime; + const rightVisibleDuration = element.duration - relativeTime; + const retimeRef = isRetimableElement(element) + ? element.retime + : undefined; + const leftSourceSpan = getSourceSpanAtClipTime({ + clipTime: leftVisibleDuration, + retime: retimeRef, + }); + const totalSourceSpan = getSourceSpanAtClipTime({ + clipTime: element.duration, + retime: retimeRef, + }); + const rightSourceSpan = totalSourceSpan - leftSourceSpan; + const { leftAnimations, rightAnimations } = splitAnimationsAtTime({ + animations: element.animations, + splitTime: relativeTime, + shouldIncludeSplitBoundary: true, + }); + let splitResult: TimelineElement[]; + + if (this.retainSide === "left") { + splitResult = [ + { + ...element, + duration: leftVisibleDuration, + trimEnd: element.trimEnd + rightSourceSpan, + name: `${element.name} (left)`, + animations: leftAnimations, + ...(retimeRef !== undefined ? { retime: retimeRef } : {}), + }, + ]; + } else if (this.retainSide === "right") { + const newId = generateUUID(); + this.rightSideElements.push({ + trackId: track.id, + elementId: newId, + }); + splitResult = [ + { + ...element, + id: newId, + startTime: this.splitTime, + duration: rightVisibleDuration, + trimStart: element.trimStart + leftSourceSpan, + name: `${element.name} (right)`, + animations: rightAnimations, + ...(retimeRef !== undefined ? { retime: retimeRef } : {}), + }, + ]; + } else { + // "both" - split into two pieces + const secondElementId = generateUUID(); + this.rightSideElements.push({ + trackId: track.id, + elementId: secondElementId, + }); + splitResult = [ + { + ...element, + duration: leftVisibleDuration, + trimEnd: element.trimEnd + rightSourceSpan, + name: `${element.name} (left)`, + animations: leftAnimations, + ...(retimeRef !== undefined ? { retime: retimeRef } : {}), + }, + { + ...element, + id: secondElementId, + startTime: this.splitTime, + duration: rightVisibleDuration, + trimStart: element.trimStart + leftSourceSpan, + name: `${element.name} (right)`, + animations: rightAnimations, + ...(retimeRef !== undefined ? { retime: retimeRef } : {}), + }, + ]; + } + + return splitResult; + }); + + return { ...track, elements } as TTrack; + }; + + const updatedTracks: SceneTracks = { + overlay: this.savedState.overlay.map((track) => splitTrack(track)), + main: splitTrack(this.savedState.main), + audio: this.savedState.audio.map((track) => splitTrack(track)), + }; + + editor.timeline.updateTracks(updatedTracks); + + if (this.rightSideElements.length > 0) { + return createElementSelectionResult(this.rightSideElements); + } + return undefined; + } + + undo(): void { + if (this.savedState) { + const editor = EditorCore.getInstance(); + editor.timeline.updateTracks(this.savedState); + } + } +} diff --git a/apps/web/src/lib/commands/timeline/element/toggle-source-audio-separation.ts b/apps/web/src/commands/timeline/element/toggle-source-audio-separation.ts index 7300f540..36d4cd87 100644 --- a/apps/web/src/lib/commands/timeline/element/toggle-source-audio-separation.ts +++ b/apps/web/src/commands/timeline/element/toggle-source-audio-separation.ts @@ -1,20 +1,20 @@ import { EditorCore } from "@/core"; -import { Command, type CommandResult } from "@/lib/commands/base-command"; +import { Command, type CommandResult } from "@/commands/base-command"; import { buildSeparatedAudioElement, canExtractSourceAudio, isSourceAudioSeparated, -} from "@/lib/timeline/audio-separation"; +} from "@/timeline/audio-separation"; import { applyPlacement, resolveTrackPlacement, -} from "@/lib/timeline/placement"; -import { updateElementInSceneTracks } from "@/lib/timeline/track-element-update"; +} from "@/timeline/placement"; +import { updateElementInSceneTracks } from "@/timeline/track-element-update"; import type { SceneTracks, TimelineElement, VideoElement, -} from "@/lib/timeline/types"; +} from "@/timeline/types"; import { generateUUID } from "@/utils/id"; export class ToggleSourceAudioSeparationCommand extends Command { diff --git a/apps/web/src/lib/commands/timeline/element/update-elements.ts b/apps/web/src/commands/timeline/element/update-elements.ts index 54d1ca88..f03f2498 100644 --- a/apps/web/src/lib/commands/timeline/element/update-elements.ts +++ b/apps/web/src/commands/timeline/element/update-elements.ts @@ -1,11 +1,11 @@ import { EditorCore } from "@/core"; -import { Command, type CommandResult } from "@/lib/commands/base-command"; -import type { SceneTracks, TimelineElement } from "@/lib/timeline"; +import { Command, type CommandResult } from "@/commands/base-command"; +import type { SceneTracks, TimelineElement } from "@/timeline"; import { findTrackInSceneTracks, updateElementInSceneTracks, -} from "@/lib/timeline"; -import { applyElementUpdate } from "@/lib/timeline/update-pipeline"; +} from "@/timeline"; +import { applyElementUpdate } from "@/timeline/update-pipeline"; export class UpdateElementsCommand extends Command { private savedState: SceneTracks | null = null; diff --git a/apps/web/src/lib/commands/timeline/index.ts b/apps/web/src/commands/timeline/index.ts index cb285d94..cb285d94 100644 --- a/apps/web/src/lib/commands/timeline/index.ts +++ b/apps/web/src/commands/timeline/index.ts diff --git a/apps/web/src/lib/commands/timeline/track/add-track.ts b/apps/web/src/commands/timeline/track/add-track.ts index a577c693..6b58b687 100644 --- a/apps/web/src/lib/commands/timeline/track/add-track.ts +++ b/apps/web/src/commands/timeline/track/add-track.ts @@ -1,11 +1,11 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; -import type { SceneTracks, TrackType } from "@/lib/timeline"; +import { Command, type CommandResult } from "@/commands/base-command"; +import type { SceneTracks, TrackType } from "@/timeline"; import { generateUUID } from "@/utils/id"; import { EditorCore } from "@/core"; import { buildEmptyTrack, getDefaultInsertIndexForTrack, -} from "@/lib/timeline/placement"; +} from "@/timeline/placement"; export class AddTrackCommand extends Command { private trackId: string; diff --git a/apps/web/src/lib/commands/timeline/track/index.ts b/apps/web/src/commands/timeline/track/index.ts index 9d4bd9c3..9d4bd9c3 100644 --- a/apps/web/src/lib/commands/timeline/track/index.ts +++ b/apps/web/src/commands/timeline/track/index.ts diff --git a/apps/web/src/lib/commands/timeline/track/remove-track.ts b/apps/web/src/commands/timeline/track/remove-track.ts index 5fcaa898..13a9ff4e 100644 --- a/apps/web/src/lib/commands/timeline/track/remove-track.ts +++ b/apps/web/src/commands/timeline/track/remove-track.ts @@ -1,6 +1,6 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; +import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import type { SceneTracks } from "@/lib/timeline"; +import type { SceneTracks } from "@/timeline"; export class RemoveTrackCommand extends Command { private savedState: SceneTracks | null = null; diff --git a/apps/web/src/lib/commands/timeline/track/toggle-track-mute.ts b/apps/web/src/commands/timeline/track/toggle-track-mute.ts index 7ffe2fd7..0d1f41cd 100644 --- a/apps/web/src/lib/commands/timeline/track/toggle-track-mute.ts +++ b/apps/web/src/commands/timeline/track/toggle-track-mute.ts @@ -1,7 +1,7 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; -import type { SceneTracks } from "@/lib/timeline"; +import { Command, type CommandResult } from "@/commands/base-command"; +import type { SceneTracks } from "@/timeline"; import { EditorCore } from "@/core"; -import { canTrackHaveAudio, findTrackInSceneTracks, updateTrackInSceneTracks } from "@/lib/timeline"; +import { canTrackHaveAudio, findTrackInSceneTracks, updateTrackInSceneTracks } from "@/timeline"; export class ToggleTrackMuteCommand extends Command { private savedState: SceneTracks | null = null; diff --git a/apps/web/src/lib/commands/timeline/track/toggle-track-visibility.ts b/apps/web/src/commands/timeline/track/toggle-track-visibility.ts index 2292c83a..f4160e3f 100644 --- a/apps/web/src/lib/commands/timeline/track/toggle-track-visibility.ts +++ b/apps/web/src/commands/timeline/track/toggle-track-visibility.ts @@ -1,7 +1,7 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; -import type { SceneTracks } from "@/lib/timeline"; +import { Command, type CommandResult } from "@/commands/base-command"; +import type { SceneTracks } from "@/timeline"; import { EditorCore } from "@/core"; -import { canTrackBeHidden, findTrackInSceneTracks, updateTrackInSceneTracks } from "@/lib/timeline"; +import { canTrackBeHidden, findTrackInSceneTracks, updateTrackInSceneTracks } from "@/timeline"; export class ToggleTrackVisibilityCommand extends Command { private savedState: SceneTracks | null = null; diff --git a/apps/web/src/lib/commands/timeline/tracks-snapshot.ts b/apps/web/src/commands/timeline/tracks-snapshot.ts index daed6a9e..5b6d871a 100644 --- a/apps/web/src/lib/commands/timeline/tracks-snapshot.ts +++ b/apps/web/src/commands/timeline/tracks-snapshot.ts @@ -1,5 +1,5 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; -import type { SceneTracks } from "@/lib/timeline"; +import { Command, type CommandResult } from "@/commands/base-command"; +import type { SceneTracks } from "@/timeline"; import { EditorCore } from "@/core"; export class TracksSnapshotCommand extends Command { diff --git a/apps/web/src/components/editor/editor-header.tsx b/apps/web/src/components/editor/editor-header.tsx index 44e8af68..6404dc01 100644 --- a/apps/web/src/components/editor/editor-header.tsx +++ b/apps/web/src/components/editor/editor-header.tsx @@ -10,20 +10,20 @@ import { DropdownMenuTrigger,
} from "../ui/dropdown-menu";
import Link from "next/link";
-import { RenameProjectDialog } from "./dialogs/rename-project-dialog";
-import { DeleteProjectDialog } from "./dialogs/delete-project-dialog";
+import { RenameProjectDialog } from "@/project/components/rename-project-dialog";
+import { DeleteProjectDialog } from "@/project/components/delete-project-dialog";
import { useRouter } from "next/navigation";
import { FaDiscord } from "react-icons/fa6";
import { ExportButton } from "./export-button";
-import { FeedbackPopover } from "@/lib/feedback/components/feedback-popover";
+import { FeedbackPopover } from "@/feedback/components/feedback-popover";
import { ThemeToggle } from "../theme-toggle";
-import { DEFAULT_LOGO_URL } from "@/lib/site/brand";
-import { SOCIAL_LINKS } from "@/lib/site/social";
+import { DEFAULT_LOGO_URL } from "@/site/brand";
+import { SOCIAL_LINKS } from "@/site/social";
import { toast } from "sonner";
-import { useEditor } from "@/hooks/use-editor";
+import { useEditor } from "@/editor/use-editor";
import { CommandIcon, Logout05Icon } from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";
-import { ShortcutsDialog } from "./dialogs/shortcuts-dialog";
+import { ShortcutsDialog } from "@/actions/components/shortcuts-dialog";
import Image from "next/image";
import { cn } from "@/utils/ui";
diff --git a/apps/web/src/components/editor/export-button.tsx b/apps/web/src/components/editor/export-button.tsx index e66067da..e738fb68 100644 --- a/apps/web/src/components/editor/export-button.tsx +++ b/apps/web/src/components/editor/export-button.tsx @@ -18,22 +18,22 @@ import { getExportMimeType,
getExportFileExtension,
downloadBuffer,
-} from "@/lib/export";
+} from "@/export";
import { Check, Copy, Download, RotateCcw } from "lucide-react";
import {
EXPORT_FORMAT_VALUES,
EXPORT_QUALITY_VALUES,
type ExportFormat,
type ExportQuality,
-} from "@/lib/export";
+} from "@/export";
import {
Section,
SectionContent,
SectionHeader,
SectionTitle,
} from "@/components/section";
-import { useEditor } from "@/hooks/use-editor";
-import { DEFAULT_EXPORT_OPTIONS } from "@/lib/export/defaults";
+import { useEditor } from "@/editor/use-editor";
+import { DEFAULT_EXPORT_OPTIONS } from "@/export/defaults";
function isExportFormat(value: string): value is ExportFormat {
return EXPORT_FORMAT_VALUES.some((formatValue) => formatValue === value);
diff --git a/apps/web/src/components/editor/onboarding.tsx b/apps/web/src/components/editor/onboarding.tsx index 54196c76..bc5533ce 100644 --- a/apps/web/src/components/editor/onboarding.tsx +++ b/apps/web/src/components/editor/onboarding.tsx @@ -3,8 +3,8 @@ import { ArrowRightIcon } from "lucide-react"; import { useState } from "react"; import ReactMarkdown from "react-markdown"; -import { SOCIAL_LINKS } from "@/lib/site/social"; -import { useLocalStorage } from "@/hooks/storage/use-local-storage"; +import { SOCIAL_LINKS } from "@/site/social"; +import { useLocalStorage } from "@/services/storage/use-local-storage"; import { Button } from "../ui/button"; import { Dialog, DialogBody, DialogContent, DialogTitle } from "../ui/dialog"; diff --git a/apps/web/src/stores/assets-panel-store.tsx b/apps/web/src/components/editor/panels/assets/assets-panel-store.tsx index 1539d305..1539d305 100644 --- a/apps/web/src/stores/assets-panel-store.tsx +++ b/apps/web/src/components/editor/panels/assets/assets-panel-store.tsx diff --git a/apps/web/src/components/editor/panels/assets/draggable-item.tsx b/apps/web/src/components/editor/panels/assets/draggable-item.tsx index 166c81db..2f8bc64a 100644 --- a/apps/web/src/components/editor/panels/assets/draggable-item.tsx +++ b/apps/web/src/components/editor/panels/assets/draggable-item.tsx @@ -10,9 +10,9 @@ import { TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
-import { useEditor } from "@/hooks/use-editor";
-import { clearDragData, setDragData } from "@/lib/drag-data";
-import type { TimelineDragData } from "@/lib/timeline/drag";
+import { useEditor } from "@/editor/use-editor";
+import { clearDragData, setDragData } from "@/timeline/drag-data";
+import type { TimelineDragData } from "@/timeline/drag";
import { cn } from "@/utils/ui";
export interface DraggableItemProps {
diff --git a/apps/web/src/components/editor/panels/assets/index.tsx b/apps/web/src/components/editor/panels/assets/index.tsx index 1cdbf295..bae73299 100644 --- a/apps/web/src/components/editor/panels/assets/index.tsx +++ b/apps/web/src/components/editor/panels/assets/index.tsx @@ -1,15 +1,15 @@ "use client"; import { Separator } from "@/components/ui/separator"; -import { type Tab, useAssetsPanelStore } from "@/stores/assets-panel-store"; +import { type Tab, useAssetsPanelStore } from "@/components/editor/panels/assets/assets-panel-store"; import { TabBar } from "./tabbar"; -import { Captions } from "./views/captions"; +import { Captions } from "@/subtitles/components/assets-view"; import { MediaView } from "./views/assets"; import { SettingsView } from "./views/settings"; -import { SoundsView } from "./views/sounds"; -import { StickersView } from "./views/stickers"; -import { TextView } from "./views/text"; -import { EffectsView } from "./views/effects"; +import { SoundsView } from "@/sounds/components/assets-view"; +import { StickersView } from "@/stickers/components/assets-view"; +import { TextView } from "@/text/components/assets-view"; +import { EffectsView } from "@/effects/components/assets-view"; export function AssetsPanel() { const { activeTab } = useAssetsPanelStore(); diff --git a/apps/web/src/components/editor/panels/assets/tabbar.tsx b/apps/web/src/components/editor/panels/assets/tabbar.tsx index da53bed0..3b084c0d 100644 --- a/apps/web/src/components/editor/panels/assets/tabbar.tsx +++ b/apps/web/src/components/editor/panels/assets/tabbar.tsx @@ -12,7 +12,7 @@ import { TAB_KEYS,
tabs,
useAssetsPanelStore,
-} from "@/stores/assets-panel-store";
+} from "@/components/editor/panels/assets/assets-panel-store";
export function TabBar() {
const { activeTab, setActiveTab } = useAssetsPanelStore();
diff --git a/apps/web/src/components/editor/panels/assets/views/assets.tsx b/apps/web/src/components/editor/panels/assets/views/assets.tsx index 33b47c67..4afd22b7 100644 --- a/apps/web/src/components/editor/panels/assets/views/assets.tsx +++ b/apps/web/src/components/editor/panels/assets/views/assets.tsx @@ -25,28 +25,28 @@ import { TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; -import { DEFAULT_NEW_ELEMENT_DURATION } from "@/lib/timeline/creation"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; -import { useEditor } from "@/hooks/use-editor"; -import { useFileUpload } from "@/hooks/use-file-upload"; -import { invokeAction } from "@/lib/actions"; -import { processMediaAssets } from "@/lib/media/processing"; -import { showMediaUploadToast } from "@/lib/media/upload-toast"; +import { DEFAULT_NEW_ELEMENT_DURATION } from "@/timeline/creation"; +import { TICKS_PER_SECOND } from "@/wasm"; +import { useEditor } from "@/editor/use-editor"; +import { useFileUpload } from "@/media/use-file-upload"; +import { invokeAction } from "@/actions"; +import { processMediaAssets } from "@/media/processing"; +import { showMediaUploadToast } from "@/media/upload-toast"; import { SelectableItem, SelectableSurface, useSelection, useSelectionScope, -} from "@/lib/selection"; -import { buildElementFromMedia } from "@/lib/timeline/element-utils"; +} from "@/selection"; +import { buildElementFromMedia } from "@/timeline/element-utils"; import { type MediaSortKey, type MediaSortOrder, type MediaViewMode, useAssetsPanelStore, -} from "@/stores/assets-panel-store"; -import { MASKABLE_ELEMENT_TYPES } from "@/lib/timeline"; -import type { MediaAsset } from "@/lib/media/types"; +} from "@/components/editor/panels/assets/assets-panel-store"; +import { MASKABLE_ELEMENT_TYPES } from "@/timeline"; +import type { MediaAsset } from "@/media/types"; import { cn } from "@/utils/ui"; import { CloudUploadIcon, diff --git a/apps/web/src/components/editor/panels/assets/views/settings/background.tsx b/apps/web/src/components/editor/panels/assets/views/settings/background.tsx index 099e335a..9ce34ebb 100644 --- a/apps/web/src/components/editor/panels/assets/views/settings/background.tsx +++ b/apps/web/src/components/editor/panels/assets/views/settings/background.tsx @@ -12,12 +12,12 @@ import { Popover, PopoverTrigger } from "@/components/ui/popover"; import { BACKGROUND_BLUR_INTENSITY_PRESETS, DEFAULT_BACKGROUND_BLUR_INTENSITY, -} from "@/lib/background/blur"; -import { DEFAULT_BACKGROUND_COLOR } from "@/lib/background/color"; +} from "@/background/blur"; +import { DEFAULT_BACKGROUND_COLOR } from "@/background/color"; import { patternCraftGradients } from "@/data/colors/pattern-craft"; import { colors } from "@/data/colors/solid"; import { syntaxUIGradients } from "@/data/colors/syntax-ui"; -import { useEditor } from "@/hooks/use-editor"; +import { useEditor } from "@/editor/use-editor"; import { effectPreviewService } from "@/services/renderer/effect-preview"; import { cn } from "@/utils/ui"; diff --git a/apps/web/src/components/editor/panels/assets/views/settings/index.tsx b/apps/web/src/components/editor/panels/assets/views/settings/index.tsx index 4d87493a..bf3a1c16 100644 --- a/apps/web/src/components/editor/panels/assets/views/settings/index.tsx +++ b/apps/web/src/components/editor/panels/assets/views/settings/index.tsx @@ -9,9 +9,9 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { FPS_PRESETS } from "@/lib/fps/presets"; -import { floatToFrameRate, frameRateToFloat } from "@/lib/fps/utils"; -import { useEditor } from "@/hooks/use-editor"; +import { FPS_PRESETS } from "@/fps/presets"; +import { floatToFrameRate, frameRateToFloat } from "@/fps/utils"; +import { useEditor } from "@/editor/use-editor"; import { Section, SectionContent, @@ -22,7 +22,7 @@ import { BackgroundContent } from "./background"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Button } from "@/components/ui/button"; import { NumberField } from "@/components/ui/number-field"; -import { useEditorStore } from "@/stores/editor-store"; +import { useEditorStore } from "@/editor/editor-store"; import { usePropertyDraft } from "@/components/editor/panels/properties/hooks/use-property-draft"; import { HugeiconsIcon } from "@hugeicons/react"; import { Tick02Icon } from "@hugeicons/core-free-icons"; @@ -30,7 +30,7 @@ import { cn } from "@/utils/ui"; import { dimensionToAspectRatio } from "@/utils/geometry"; import { formatNumberForDisplay } from "@/utils/math"; import { OcSquarePlusIcon } from "@/components/icons"; -import type { TCanvasSize } from "@/lib/project/types"; +import type { TCanvasSize } from "@/project/types"; type SettingsView = "project-info" | "background"; diff --git a/apps/web/src/components/editor/panels/preview/bookmark-note-overlay.tsx b/apps/web/src/components/editor/panels/preview/bookmark-note-overlay.tsx deleted file mode 100644 index 3d96f762..00000000 --- a/apps/web/src/components/editor/panels/preview/bookmark-note-overlay.tsx +++ /dev/null @@ -1,59 +0,0 @@ -"use client";
-
-import { useEditor } from "@/hooks/use-editor";
-import { findCurrentScene } from "@/lib/scenes";
-import { getBookmarksActiveAtTime } from "@/lib/timeline/bookmarks";
-
-export function BookmarkNoteOverlay() {
- const editor = useEditor();
- const currentTime = editor.playback.getCurrentTime();
- const activeProject = editor.project.getActive();
-
- if (!activeProject) {
- return null;
- }
-
- const activeScene = findCurrentScene({
- scenes: activeProject.scenes,
- currentSceneId: activeProject.currentSceneId,
- });
-
- if (!activeScene) {
- return null;
- }
-
- const bookmarks = activeScene.bookmarks;
- const activeBookmarks = getBookmarksActiveAtTime({
- bookmarks,
- time: currentTime,
- });
- const bookmarksWithNotes = activeBookmarks.filter(
- (bookmark) => bookmark.note != null && bookmark.note.trim() !== "",
- );
-
- if (bookmarksWithNotes.length === 0) {
- return null;
- }
-
- return (
- <div
- className="pointer-events-none absolute top-2 left-2 flex flex-col gap-1.5"
- aria-live="polite"
- >
- {bookmarksWithNotes.map((bookmark) => (
- <div
- key={bookmark.time}
- className="flex max-w-[min(200px,50vw)] px-2.5 py-1.5 text-left text-white text-xs shadow-md backdrop-blur-sm"
- style={{
- backgroundColor: "rgb(0 0 0 / 0.5)",
- borderLeft: bookmark.color
- ? `3px solid ${bookmark.color}`
- : "3px solid var(--primary)",
- }}
- >
- {bookmark.note}
- </div>
- ))}
- </div>
- );
-}
diff --git a/apps/web/src/components/editor/panels/preview/guide-overlay.tsx b/apps/web/src/components/editor/panels/preview/guide-overlay.tsx deleted file mode 100644 index 4270fcb3..00000000 --- a/apps/web/src/components/editor/panels/preview/guide-overlay.tsx +++ /dev/null @@ -1,32 +0,0 @@ -"use client";
-
-import { getGuideById } from "@/lib/guides";
-import { usePreviewStore } from "@/stores/preview-store";
-import { usePreviewViewport } from "./preview-viewport";
-
-export function GuideOverlay() {
- const activeGuide = usePreviewStore((state) => state.activeGuide);
- const viewport = usePreviewViewport();
- const guide = getGuideById(activeGuide);
-
- if (!guide) {
- return null;
- }
-
- return (
- <div
- className="pointer-events-none absolute"
- style={{
- left: viewport.sceneLeft,
- top: viewport.sceneTop,
- width: viewport.sceneWidth,
- height: viewport.sceneHeight,
- }}
- >
- {guide.renderOverlay({
- width: viewport.sceneWidth,
- height: viewport.sceneHeight,
- })}
- </div>
- );
-}
diff --git a/apps/web/src/components/editor/panels/properties/components/property-param-field.tsx b/apps/web/src/components/editor/panels/properties/components/property-param-field.tsx index 9cc2b02e..c9a9c22e 100644 --- a/apps/web/src/components/editor/panels/properties/components/property-param-field.tsx +++ b/apps/web/src/components/editor/panels/properties/components/property-param-field.tsx @@ -1,6 +1,6 @@ "use client"; -import type { ParamDefinition, NumberParamDefinition } from "@/lib/params"; +import type { ParamDefinition, NumberParamDefinition } from "@/params"; import { formatNumberForDisplay, getFractionDigitsForStep, diff --git a/apps/web/src/components/editor/panels/properties/hooks/use-element-playhead.ts b/apps/web/src/components/editor/panels/properties/hooks/use-element-playhead.ts index 27300784..967156b3 100644 --- a/apps/web/src/components/editor/panels/properties/hooks/use-element-playhead.ts +++ b/apps/web/src/components/editor/panels/properties/hooks/use-element-playhead.ts @@ -1,5 +1,5 @@ -import { useEditor } from "@/hooks/use-editor"; -import { getElementLocalTime } from "@/lib/animation"; +import { useEditor } from "@/editor/use-editor"; +import { getElementLocalTime } from "@/animation"; export function useElementPlayhead({ startTime, diff --git a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts index a42e422f..734b514f 100644 --- a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts +++ b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts @@ -1,11 +1,11 @@ -import { useEditor } from "@/hooks/use-editor";
+import { useEditor } from "@/editor/use-editor";
import {
getKeyframeAtTime,
hasKeyframesForPath,
upsertElementKeyframe,
-} from "@/lib/animation";
-import type { AnimationPropertyPath, ElementAnimations } from "@/lib/animation/types";
-import type { TimelineElement } from "@/lib/timeline";
+} from "@/animation";
+import type { AnimationPropertyPath, ElementAnimations } from "@/animation/types";
+import type { TimelineElement } from "@/timeline";
export function useKeyframedColorProperty({
trackId,
diff --git a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts index 872d55c0..9dd39768 100644 --- a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts +++ b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts @@ -1,11 +1,11 @@ -import { useEditor } from "@/hooks/use-editor";
+import { useEditor } from "@/editor/use-editor";
import {
getKeyframeAtTime,
hasKeyframesForPath,
upsertElementKeyframe,
-} from "@/lib/animation";
-import type { AnimationPropertyPath, ElementAnimations } from "@/lib/animation/types";
-import type { TimelineElement } from "@/lib/timeline";
+} from "@/animation";
+import type { AnimationPropertyPath, ElementAnimations } from "@/animation/types";
+import type { TimelineElement } from "@/timeline";
import { snapToStep } from "@/utils/math";
import { usePropertyDraft } from "./use-property-draft";
diff --git a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-param-property.ts b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-param-property.ts index 29b290d9..b0b6f01a 100644 --- a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-param-property.ts +++ b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-param-property.ts @@ -1,6 +1,6 @@ "use client"; -import { useEditor } from "@/hooks/use-editor"; +import { useEditor } from "@/editor/use-editor"; import { buildGraphicParamPath, coerceAnimationValueForParam, @@ -9,12 +9,12 @@ import { getParamValueKind, hasKeyframesForPath, upsertPathKeyframe, -} from "@/lib/animation"; +} from "@/animation"; import type { ElementAnimations, -} from "@/lib/animation/types"; -import type { ParamDefinition } from "@/lib/params"; -import type { TimelineElement } from "@/lib/timeline"; +} from "@/animation/types"; +import type { ParamDefinition } from "@/params"; +import type { TimelineElement } from "@/timeline"; export interface KeyframedParamPropertyResult { hasAnimatedKeyframes: boolean; diff --git a/apps/web/src/components/editor/panels/properties/index.tsx b/apps/web/src/components/editor/panels/properties/index.tsx index 75319ef3..52fa42b5 100644 --- a/apps/web/src/components/editor/panels/properties/index.tsx +++ b/apps/web/src/components/editor/panels/properties/index.tsx @@ -8,8 +8,8 @@ import { TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; -import { useEditor } from "@/hooks/use-editor"; -import { useElementSelection } from "@/hooks/timeline/element/use-element-selection"; +import { useEditor } from "@/editor/use-editor"; +import { useElementSelection } from "@/timeline/hooks/element/use-element-selection"; import { usePropertiesStore } from "./stores/properties-store"; import { getPropertiesConfig } from "./registry"; import { cn } from "@/utils/ui"; diff --git a/apps/web/src/components/editor/panels/properties/registry.tsx b/apps/web/src/components/editor/panels/properties/registry.tsx index df6c0e52..f9e40ab3 100644 --- a/apps/web/src/components/editor/panels/properties/registry.tsx +++ b/apps/web/src/components/editor/panels/properties/registry.tsx @@ -11,8 +11,8 @@ import type { VideoElement, AudioElement, TimelineElement, -} from "@/lib/timeline"; -import type { MediaAsset } from "@/lib/media/types"; +} from "@/timeline"; +import type { MediaAsset } from "@/media/types"; import { HugeiconsIcon } from "@hugeicons/react"; import { TextFontIcon, @@ -22,14 +22,14 @@ import { MagicWand05Icon, DashboardSpeed02Icon, } from "@hugeicons/core-free-icons"; -import { TransformTab } from "./tabs/transform-tab"; -import { BlendingTab } from "./tabs/blending-tab"; -import { AudioTab } from "./tabs/audio-tab"; -import { TextTab } from "./tabs/text-tab"; -import { ClipEffectsTab, StandaloneEffectTab } from "./tabs/effects-tab"; -import { MasksTab } from "./tabs/masks-tab"; -import { SpeedTab } from "./tabs/speed-tab"; -import { GraphicTab } from "./tabs/graphic-tab"; +import { TransformTab } from "@/rendering/components/transform-tab"; +import { BlendingTab } from "@/rendering/components/blending-tab"; +import { AudioTab } from "@/timeline/components/audio-tab"; +import { TextTab } from "@/text/components/text-tab"; +import { ClipEffectsTab, StandaloneEffectTab } from "@/effects/components/effects-tab"; +import { MasksTab } from "@/masks/components/masks-tab"; +import { SpeedTab } from "@/speed/components/speed-tab"; +import { GraphicTab } from "@/graphics/components/graphic-tab"; import { OcShapesIcon } from "@/components/icons"; export type TabContentProps = { diff --git a/apps/web/src/components/editor/scenes-view.tsx b/apps/web/src/components/editor/scenes-view.tsx index 34efeaff..b0f487a4 100644 --- a/apps/web/src/components/editor/scenes-view.tsx +++ b/apps/web/src/components/editor/scenes-view.tsx @@ -21,9 +21,9 @@ import { DialogFooter,
DialogTrigger,
} from "@/components/ui/dialog";
-import { canDeleteScene, getMainScene } from "@/lib/scenes";
+import { canDeleteScene, getMainScene } from "@/timeline/scenes";
import { toast } from "sonner";
-import { useEditor } from "@/hooks/use-editor";
+import { useEditor } from "@/editor/use-editor";
export function ScenesView({ children }: { children: React.ReactNode }) {
const editor = useEditor();
diff --git a/apps/web/src/components/footer.tsx b/apps/web/src/components/footer.tsx index 21334267..b56f8c6c 100644 --- a/apps/web/src/components/footer.tsx +++ b/apps/web/src/components/footer.tsx @@ -2,8 +2,8 @@ import Link from "next/link"; import { RiDiscordFill, RiTwitterXLine } from "react-icons/ri";
import { FaGithub } from "react-icons/fa6";
import Image from "next/image";
-import { DEFAULT_LOGO_URL } from "@/lib/site/brand";
-import { SOCIAL_LINKS } from "@/lib/site/social";
+import { DEFAULT_LOGO_URL } from "@/site/brand";
+import { SOCIAL_LINKS } from "@/site/social";
import { capitalizeFirstLetter } from "@/utils/string";
type Category = "resources" | "company";
diff --git a/apps/web/src/components/gitHub-contribute-section.tsx b/apps/web/src/components/gitHub-contribute-section.tsx index 0239e366..6978b81a 100644 --- a/apps/web/src/components/gitHub-contribute-section.tsx +++ b/apps/web/src/components/gitHub-contribute-section.tsx @@ -1,6 +1,6 @@ import { Button } from "./ui/button";
import Link from "next/link";
-import { SOCIAL_LINKS } from "@/lib/site/social";
+import { SOCIAL_LINKS } from "@/site/social";
import { GithubIcon, Link04Icon } from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";
diff --git a/apps/web/src/components/header.tsx b/apps/web/src/components/header.tsx index 66fec0ea..99ba1730 100644 --- a/apps/web/src/components/header.tsx +++ b/apps/web/src/components/header.tsx @@ -16,8 +16,8 @@ import { } from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";
import { cn } from "@/utils/ui";
-import { DEFAULT_LOGO_URL, SITE_URL } from "@/lib/site/brand";
-import { SOCIAL_LINKS } from "@/lib/site/social";
+import { DEFAULT_LOGO_URL, SITE_URL } from "@/site/brand";
+import { SOCIAL_LINKS } from "@/site/social";
import {
ContextMenu,
ContextMenuContent,
diff --git a/apps/web/src/components/providers/editor-provider.tsx b/apps/web/src/components/providers/editor-provider.tsx index 98e6d4c6..fdc84af0 100644 --- a/apps/web/src/components/providers/editor-provider.tsx +++ b/apps/web/src/components/providers/editor-provider.tsx @@ -4,12 +4,12 @@ import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import { Loader2 } from "lucide-react"; import { EditorCore } from "@/core"; -import { useEditor } from "@/hooks/use-editor"; -import { useKeybindingsListener } from "@/hooks/use-keybindings"; -import { useKeybindingsStore } from "@/stores/keybindings-store"; -import { useTimelineStore } from "@/stores/timeline-store"; -import { useEditorActions } from "@/hooks/actions/use-editor-actions"; -import { loadFontAtlas } from "@/lib/fonts/google-fonts"; +import { useEditor } from "@/editor/use-editor"; +import { useKeybindingsListener } from "@/actions/use-keybindings"; +import { useKeybindingsStore } from "@/actions/keybindings-store"; +import { useTimelineStore } from "@/timeline/timeline-store"; +import { useEditorActions } from "@/actions/use-editor-actions"; +import { loadFontAtlas } from "@/fonts/google-fonts"; import { initializeGpuRenderer, isGpuAvailable, diff --git a/apps/web/src/components/storage-provider.tsx b/apps/web/src/components/storage-provider.tsx index fc7ae121..185a44ef 100644 --- a/apps/web/src/components/storage-provider.tsx +++ b/apps/web/src/components/storage-provider.tsx @@ -2,7 +2,7 @@ import { createContext, useContext, useEffect, useRef, useState } from "react";
import { toast } from "sonner";
-import { useEditor } from "@/hooks/use-editor";
+import { useEditor } from "@/editor/use-editor";
import { storageService } from "@/services/storage/service";
interface StorageContextType {
diff --git a/apps/web/src/components/ui/font-picker.tsx b/apps/web/src/components/ui/font-picker.tsx index 297d58a7..27ea4bdb 100644 --- a/apps/web/src/components/ui/font-picker.tsx +++ b/apps/web/src/components/ui/font-picker.tsx @@ -9,10 +9,10 @@ import { } from "@/components/ui/popover";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
-import { loadFullFont } from "@/lib/fonts/google-fonts";
-import { SYSTEM_FONTS } from "@/lib/fonts/system-fonts";
-import type { FontAtlas, FontAtlasEntry } from "@/lib/fonts/types";
-import { useFontAtlas } from "@/hooks/use-font-atlas";
+import { loadFullFont } from "@/fonts/google-fonts";
+import { SYSTEM_FONTS } from "@/fonts/system-fonts";
+import type { FontAtlas, FontAtlasEntry } from "@/fonts/types";
+import { useFontAtlas } from "@/fonts/use-font-atlas";
import { cn } from "@/utils/ui";
import { ChevronDown, Search } from "lucide-react";
import { HugeiconsIcon } from "@hugeicons/react";
diff --git a/apps/web/src/components/ui/use-overlay-open-change.ts b/apps/web/src/components/ui/use-overlay-open-change.ts index dbc46cfc..b9cb87d8 100644 --- a/apps/web/src/components/ui/use-overlay-open-change.ts +++ b/apps/web/src/components/ui/use-overlay-open-change.ts @@ -1,5 +1,5 @@ import { useCallback, useEffect, useId, useRef } from "react"; -import { useKeybindingsStore } from "@/stores/keybindings-store"; +import { useKeybindingsStore } from "@/actions/keybindings-store"; export function useOverlayOpenChange({ open, diff --git a/apps/web/src/core/index.ts b/apps/web/src/core/index.ts index 483a26c1..3e55e1d6 100644 --- a/apps/web/src/core/index.ts +++ b/apps/web/src/core/index.ts @@ -10,9 +10,9 @@ import { AudioManager } from "./managers/audio-manager"; import { SelectionManager } from "./managers/selection-manager"; import { ClipboardManager } from "./managers/clipboard-manager"; import { DiagnosticsManager } from "./managers/diagnostics-manager"; -import { registerDefaultEffects } from "@/lib/effects"; -import { registerDefaultMasks } from "@/lib/masks"; -import { registerTranscriptionDiagnostics } from "@/lib/transcription/diagnostics"; +import { registerDefaultEffects } from "@/effects"; +import { registerDefaultMasks } from "@/masks"; +import { registerTranscriptionDiagnostics } from "@/transcription/diagnostics"; export class EditorCore { private static instance: EditorCore | null = null; diff --git a/apps/web/src/core/managers/audio-manager.ts b/apps/web/src/core/managers/audio-manager.ts index 076b998d..545565be 100644 --- a/apps/web/src/core/managers/audio-manager.ts +++ b/apps/web/src/core/managers/audio-manager.ts @@ -1,21 +1,21 @@ import type { EditorCore } from "@/core"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; +import { TICKS_PER_SECOND } from "@/wasm"; import { clampRetimeRate, shouldMaintainPitch, -} from "@/lib/retime/rate"; -import type { AudioClipSource } from "@/lib/media/audio"; -import { createAudioContext, collectAudioClips } from "@/lib/media/audio"; +} from "@/retime/rate"; +import type { AudioClipSource } from "@/media/audio"; +import { createAudioContext, collectAudioClips } from "@/media/audio"; import { buildAudioGainAutomation, hasAnimatedVolume, -} from "@/lib/timeline/audio-state"; -import { createAudioMasteringChain } from "@/lib/media/audio-mastering"; +} from "@/timeline/audio-state"; +import { createAudioMasteringChain } from "@/media/audio-mastering"; import { getClipTimeAtSourceTime, getSourceTimeAtClipTime, renderRetimedBuffer, -} from "@/lib/retime"; +} from "@/retime"; import { ALL_FORMATS, AudioBufferSink, diff --git a/apps/web/src/core/managers/clipboard-manager.ts b/apps/web/src/core/managers/clipboard-manager.ts index b19343df..8a2a51c9 100644 --- a/apps/web/src/core/managers/clipboard-manager.ts +++ b/apps/web/src/core/managers/clipboard-manager.ts @@ -5,7 +5,7 @@ import { type ClipboardEntry, type CopyContext, type PasteContext, -} from "@/lib/clipboard"; +} from "@/clipboard"; export class ClipboardManager { private entry: ClipboardEntry | null = null; diff --git a/apps/web/src/core/managers/commands.ts b/apps/web/src/core/managers/commands.ts index 7cdd6373..9082db10 100644 --- a/apps/web/src/core/managers/commands.ts +++ b/apps/web/src/core/managers/commands.ts @@ -1,8 +1,8 @@ import type { EditorCore } from "@/core"; -import type { Command, CommandResult } from "@/lib/commands"; -import type { EditorSelectionSnapshot } from "@/lib/selection/editor-selection"; -import { applyRippleAdjustments, computeRippleAdjustments } from "@/lib/ripple"; -import type { SceneTracks } from "@/lib/timeline/types"; +import type { Command, CommandResult } from "@/commands"; +import type { EditorSelectionSnapshot } from "@/selection/editor-selection"; +import { applyRippleAdjustments, computeRippleAdjustments } from "@/ripple"; +import type { SceneTracks } from "@/timeline/types"; interface CommandHistoryEntry { command: Command; diff --git a/apps/web/src/core/managers/diagnostics-manager.ts b/apps/web/src/core/managers/diagnostics-manager.ts index d022a350..0a32fab5 100644 --- a/apps/web/src/core/managers/diagnostics-manager.ts +++ b/apps/web/src/core/managers/diagnostics-manager.ts @@ -1,5 +1,5 @@ import type { EditorCore } from "@/core"; -import type { DiagnosticDefinition } from "@/lib/diagnostics/types"; +import type { DiagnosticDefinition } from "@/diagnostics/types"; interface DiagnosticRegistration extends DiagnosticDefinition { check: (editor: EditorCore) => boolean; diff --git a/apps/web/src/core/managers/media-manager.ts b/apps/web/src/core/managers/media-manager.ts index e43340a7..553d1557 100644 --- a/apps/web/src/core/managers/media-manager.ts +++ b/apps/web/src/core/managers/media-manager.ts @@ -1,11 +1,11 @@ import type { EditorCore } from "@/core"; import { toast } from "sonner"; -import type { MediaAsset } from "@/lib/media/types"; +import type { MediaAsset } from "@/media/types"; import { storageService } from "@/services/storage/service"; import { generateUUID } from "@/utils/id"; import { videoCache } from "@/services/video-cache/service"; import { waveformCache } from "@/services/waveform-cache/service"; -import { BatchCommand, RemoveMediaAssetCommand } from "@/lib/commands"; +import { BatchCommand, RemoveMediaAssetCommand } from "@/commands"; export class MediaManager { private assets: MediaAsset[] = []; diff --git a/apps/web/src/core/managers/playback-manager.ts b/apps/web/src/core/managers/playback-manager.ts index 341a0eae..1ea09826 100644 --- a/apps/web/src/core/managers/playback-manager.ts +++ b/apps/web/src/core/managers/playback-manager.ts @@ -1,5 +1,5 @@ import type { EditorCore } from "@/core"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; +import { TICKS_PER_SECOND } from "@/wasm"; import { roundToFrame } from "opencut-wasm"; export class PlaybackManager { diff --git a/apps/web/src/core/managers/project-manager.ts b/apps/web/src/core/managers/project-manager.ts index 8555786d..b38fd50d 100644 --- a/apps/web/src/core/managers/project-manager.ts +++ b/apps/web/src/core/managers/project-manager.ts @@ -6,16 +6,16 @@ import type { TProjectSortOption, TProjectSettings, TTimelineViewState, -} from "@/lib/project/types"; -import type { ExportOptions, ExportResult, ExportState } from "@/lib/export"; +} from "@/project/types"; +import type { ExportOptions, ExportResult, ExportState } from "@/export"; import { storageService } from "@/services/storage/service"; import { toast } from "sonner"; import { generateUUID } from "@/utils/id"; -import { UpdateProjectSettingsCommand } from "@/lib/commands/project"; -import { DEFAULT_BACKGROUND_COLOR } from "@/lib/background/color"; -import { DEFAULT_CANVAS_SIZE } from "@/lib/canvas/sizes"; -import { DEFAULT_FPS } from "@/lib/fps/defaults"; -import { buildDefaultScene, getProjectDurationFromScenes } from "@/lib/scenes"; +import { UpdateProjectSettingsCommand } from "@/commands/project"; +import { DEFAULT_BACKGROUND_COLOR } from "@/background/color"; +import { DEFAULT_CANVAS_SIZE } from "@/canvas/sizes"; +import { DEFAULT_FPS } from "@/fps/defaults"; +import { buildDefaultScene, getProjectDurationFromScenes } from "@/timeline/scenes"; import { buildScene } from "@/services/renderer/scene-builder"; import { CanvasRenderer } from "@/services/renderer/canvas-renderer"; import { @@ -24,11 +24,11 @@ import { runStorageMigrations, type MigrationProgress, } from "@/services/storage/migrations"; -import { loadFonts } from "@/lib/fonts/google-fonts"; -import { DEFAULTS } from "@/lib/timeline/defaults"; -import { getElementFontFamilies } from "@/lib/timeline/element-utils"; -import { getRaisedProjectFpsForImportedMedia } from "@/lib/fps/utils"; -import type { MediaAsset } from "@/lib/media/types"; +import { loadFonts } from "@/fonts/google-fonts"; +import { DEFAULTS } from "@/timeline/defaults"; +import { getElementFontFamilies } from "@/timeline/element-utils"; +import { getRaisedProjectFpsForImportedMedia } from "@/fps/utils"; +import type { MediaAsset } from "@/media/types"; export interface MigrationState { isMigrating: boolean; diff --git a/apps/web/src/core/managers/renderer-manager.ts b/apps/web/src/core/managers/renderer-manager.ts index 495292e6..7984655c 100644 --- a/apps/web/src/core/managers/renderer-manager.ts +++ b/apps/web/src/core/managers/renderer-manager.ts @@ -1,12 +1,12 @@ import type { EditorCore } from "@/core"; import type { RootNode } from "@/services/renderer/nodes/root-node"; -import type { ExportOptions, ExportResult } from "@/lib/export"; +import type { ExportOptions, ExportResult } from "@/export"; import { CanvasRenderer } from "@/services/renderer/canvas-renderer"; import { SceneExporter } from "@/services/renderer/scene-exporter"; import { buildScene } from "@/services/renderer/scene-builder"; -import { createTimelineAudioBuffer } from "@/lib/media/audio"; +import { createTimelineAudioBuffer } from "@/media/audio"; import { formatTimecode } from "opencut-wasm"; -import { frameRateToFloat } from "@/lib/fps/utils"; +import { frameRateToFloat } from "@/fps/utils"; import { downloadBlob } from "@/utils/browser"; type SnapshotResult = diff --git a/apps/web/src/core/managers/scenes-manager.ts b/apps/web/src/core/managers/scenes-manager.ts index 3508a066..13f251a1 100644 --- a/apps/web/src/core/managers/scenes-manager.ts +++ b/apps/web/src/core/managers/scenes-manager.ts @@ -1,320 +1,320 @@ -import type { EditorCore } from "@/core";
-import type { SceneTracks, TScene } from "@/lib/timeline";
-import { storageService } from "@/services/storage/service";
-import {
- getMainScene,
- ensureMainScene,
- canDeleteScene,
- findCurrentScene,
-} from "@/lib/scenes";
-import {
- getBookmarkAtTime,
- getFrameTime,
- isBookmarkAtTime,
-} from "@/lib/timeline/bookmarks";
-import {
- CreateSceneCommand,
- DeleteSceneCommand,
- MoveBookmarkCommand,
- RemoveBookmarkCommand,
- RenameSceneCommand,
- ToggleBookmarkCommand,
- UpdateBookmarkCommand,
-} from "@/lib/commands/scene";
-
-export class ScenesManager {
- private active: TScene | null = null;
- private list: TScene[] = [];
- private listeners = new Set<() => void>();
-
- constructor(private editor: EditorCore) {}
-
- async createScene({
- name,
- isMain = false,
- }: {
- name: string;
- isMain: boolean;
- }): Promise<string> {
- if (!this.editor.project.getActive()) {
- throw new Error("No active project");
- }
-
- const command = new CreateSceneCommand(name, isMain);
- this.editor.command.execute({ command });
- return command.getSceneId();
- }
-
- async deleteScene({ sceneId }: { sceneId: string }): Promise<void> {
- const sceneToDelete = this.list.find((s) => s.id === sceneId);
-
- if (!sceneToDelete) {
- throw new Error("Scene not found");
- }
-
- const { canDelete, reason } = canDeleteScene({ scene: sceneToDelete });
- if (!canDelete) {
- throw new Error(reason);
- }
-
- if (!this.editor.project.getActive()) {
- throw new Error("No active project");
- }
-
- const command = new DeleteSceneCommand(sceneId);
- this.editor.command.execute({ command });
- }
-
- async renameScene({
- sceneId,
- name,
- }: {
- sceneId: string;
- name: string;
- }): Promise<void> {
- if (!this.editor.project.getActive()) {
- throw new Error("No active project");
- }
-
- const command = new RenameSceneCommand(sceneId, name);
- this.editor.command.execute({ command });
- }
-
- async switchToScene({ sceneId }: { sceneId: string }): Promise<void> {
- const targetScene = this.list.find((s) => s.id === sceneId);
-
- if (!targetScene) {
- throw new Error("Scene not found");
- }
-
- const activeProject = this.editor.project.getActive();
-
- if (activeProject) {
- const updatedProject = {
- ...activeProject,
- currentSceneId: sceneId,
- metadata: {
- ...activeProject.metadata,
- updatedAt: new Date(),
- },
- };
-
- this.editor.project.setActiveProject({ project: updatedProject });
- }
-
- this.active = targetScene;
- this.notify();
- }
-
- async toggleBookmark({ time }: { time: number }): Promise<void> {
- const command = new ToggleBookmarkCommand(time);
- this.editor.command.execute({ command });
- }
-
- isBookmarked({ time }: { time: number }): boolean {
- const activeScene = this.getActiveScene();
- const activeProject = this.editor.project.getActive();
-
- if (!activeScene || !this.active || !activeProject) return false;
-
- const frameTime = getFrameTime({
- time,
- fps: activeProject.settings.fps,
- });
-
- return isBookmarkAtTime({ bookmarks: activeScene.bookmarks, frameTime });
- }
-
- async removeBookmark({ time }: { time: number }): Promise<void> {
- const command = new RemoveBookmarkCommand(time);
- this.editor.command.execute({ command });
- }
-
- async updateBookmark({
- time,
- updates,
- }: {
- time: number;
- updates: Partial<{ note: string; color: string; duration: number }>;
- }): Promise<void> {
- const command = new UpdateBookmarkCommand(time, updates);
- this.editor.command.execute({ command });
- }
-
- async moveBookmark({
- fromTime,
- toTime,
- }: {
- fromTime: number;
- toTime: number;
- }): Promise<void> {
- const command = new MoveBookmarkCommand(fromTime, toTime);
- this.editor.command.execute({ command });
- }
-
- getBookmarkAtTime({ time }: { time: number }) {
- const activeScene = this.active;
- const activeProject = this.editor.project.getActive();
-
- if (!activeScene || !activeProject) return null;
-
- const frameTime = getFrameTime({
- time,
- fps: activeProject.settings.fps,
- });
-
- return getBookmarkAtTime({
- bookmarks: activeScene.bookmarks,
- frameTime,
- });
- }
-
- async loadProjectScenes({ projectId }: { projectId: string }): Promise<void> {
- try {
- const result = await storageService.loadProject({ id: projectId });
- if (result?.project.scenes) {
- const ensuredScenes = result.project.scenes ?? [];
- const currentScene = findCurrentScene({
- scenes: ensuredScenes,
- currentSceneId: result.project.currentSceneId,
- });
-
- this.list = ensuredScenes;
- this.active = currentScene;
- this.notify();
- }
- } catch (error) {
- console.error("Failed to load project scenes:", error);
- this.list = [];
- this.active = null;
- this.notify();
- }
- }
-
- initializeScenes({
- scenes,
- currentSceneId,
- }: {
- scenes: TScene[];
- currentSceneId?: string;
- }): void {
- const ensuredScenes = ensureMainScene({ scenes });
- const currentScene = currentSceneId
- ? ensuredScenes.find((s) => s.id === currentSceneId)
- : null;
-
- const fallbackScene = getMainScene({ scenes: ensuredScenes });
-
- this.list = ensuredScenes;
- this.active = currentScene || fallbackScene;
- this.notify();
-
- const hasAddedMainScene = ensuredScenes.length > scenes.length;
- if (hasAddedMainScene) {
- const activeProject = this.editor.project.getActive();
-
- if (activeProject) {
- const updatedProject = {
- ...activeProject,
- scenes: ensuredScenes,
- metadata: {
- ...activeProject.metadata,
- updatedAt: new Date(),
- },
- };
-
- this.editor.project.setActiveProject({ project: updatedProject });
- this.editor.save.markDirty({ force: true });
- }
- }
- }
-
- clearScenes(): void {
- this.list = [];
- this.active = null;
- this.notify();
- }
-
- getActiveScene(): TScene {
- if (!this.active) {
- throw new Error("No active scene.");
- }
- return this.active;
- }
-
- getActiveSceneOrNull(): TScene | null {
- return this.active;
- }
-
- getScenes(): TScene[] {
- return this.list;
- }
-
- setScenes({
- scenes,
- activeSceneId,
- }: {
- scenes: TScene[];
- activeSceneId?: string;
- }): void {
- this.list = scenes;
- const nextActiveSceneId = activeSceneId ?? this.active?.id ?? null;
- this.active = nextActiveSceneId
- ? (scenes.find((scene) => scene.id === nextActiveSceneId) ?? null)
- : null;
- this.notify();
-
- const activeProject = this.editor.project.getActive();
- if (activeProject) {
- const updatedProject = {
- ...activeProject,
- scenes,
- metadata: {
- ...activeProject.metadata,
- updatedAt: new Date(),
- },
- };
- this.editor.project.setActiveProject({ project: updatedProject });
- }
- }
-
- subscribe(listener: () => void): () => void {
- this.listeners.add(listener);
- return () => this.listeners.delete(listener);
- }
-
- private notify(): void {
- this.listeners.forEach((fn) => {
- fn();
- });
- }
-
- updateSceneTracks({ tracks }: { tracks: SceneTracks }): void {
- if (!this.active) return;
-
- const updatedScene: TScene = {
- ...this.active,
- tracks,
- updatedAt: new Date(),
- };
-
- this.list = this.list.map((s) =>
- s.id === this.active?.id ? updatedScene : s,
- );
- this.active = updatedScene;
- this.notify();
-
- const activeProject = this.editor.project.getActive();
- if (activeProject) {
- const updatedProject = {
- ...activeProject,
- scenes: this.list,
- metadata: {
- ...activeProject.metadata,
- updatedAt: new Date(),
- },
- };
- this.editor.project.setActiveProject({ project: updatedProject });
- }
- }
-}
+import type { EditorCore } from "@/core"; +import type { SceneTracks, TScene } from "@/timeline"; +import { storageService } from "@/services/storage/service"; +import { + getMainScene, + ensureMainScene, + canDeleteScene, + findCurrentScene, +} from "@/timeline/scenes"; +import { + getBookmarkAtTime, + getFrameTime, + isBookmarkAtTime, +} from "@/timeline/bookmarks/index"; +import { + CreateSceneCommand, + DeleteSceneCommand, + MoveBookmarkCommand, + RemoveBookmarkCommand, + RenameSceneCommand, + ToggleBookmarkCommand, + UpdateBookmarkCommand, +} from "@/commands/scene"; + +export class ScenesManager { + private active: TScene | null = null; + private list: TScene[] = []; + private listeners = new Set<() => void>(); + + constructor(private editor: EditorCore) {} + + async createScene({ + name, + isMain = false, + }: { + name: string; + isMain: boolean; + }): Promise<string> { + if (!this.editor.project.getActive()) { + throw new Error("No active project"); + } + + const command = new CreateSceneCommand(name, isMain); + this.editor.command.execute({ command }); + return command.getSceneId(); + } + + async deleteScene({ sceneId }: { sceneId: string }): Promise<void> { + const sceneToDelete = this.list.find((s) => s.id === sceneId); + + if (!sceneToDelete) { + throw new Error("Scene not found"); + } + + const { canDelete, reason } = canDeleteScene({ scene: sceneToDelete }); + if (!canDelete) { + throw new Error(reason); + } + + if (!this.editor.project.getActive()) { + throw new Error("No active project"); + } + + const command = new DeleteSceneCommand(sceneId); + this.editor.command.execute({ command }); + } + + async renameScene({ + sceneId, + name, + }: { + sceneId: string; + name: string; + }): Promise<void> { + if (!this.editor.project.getActive()) { + throw new Error("No active project"); + } + + const command = new RenameSceneCommand(sceneId, name); + this.editor.command.execute({ command }); + } + + async switchToScene({ sceneId }: { sceneId: string }): Promise<void> { + const targetScene = this.list.find((s) => s.id === sceneId); + + if (!targetScene) { + throw new Error("Scene not found"); + } + + const activeProject = this.editor.project.getActive(); + + if (activeProject) { + const updatedProject = { + ...activeProject, + currentSceneId: sceneId, + metadata: { + ...activeProject.metadata, + updatedAt: new Date(), + }, + }; + + this.editor.project.setActiveProject({ project: updatedProject }); + } + + this.active = targetScene; + this.notify(); + } + + async toggleBookmark({ time }: { time: number }): Promise<void> { + const command = new ToggleBookmarkCommand(time); + this.editor.command.execute({ command }); + } + + isBookmarked({ time }: { time: number }): boolean { + const activeScene = this.getActiveScene(); + const activeProject = this.editor.project.getActive(); + + if (!activeScene || !this.active || !activeProject) return false; + + const frameTime = getFrameTime({ + time, + fps: activeProject.settings.fps, + }); + + return isBookmarkAtTime({ bookmarks: activeScene.bookmarks, frameTime }); + } + + async removeBookmark({ time }: { time: number }): Promise<void> { + const command = new RemoveBookmarkCommand(time); + this.editor.command.execute({ command }); + } + + async updateBookmark({ + time, + updates, + }: { + time: number; + updates: Partial<{ note: string; color: string; duration: number }>; + }): Promise<void> { + const command = new UpdateBookmarkCommand(time, updates); + this.editor.command.execute({ command }); + } + + async moveBookmark({ + fromTime, + toTime, + }: { + fromTime: number; + toTime: number; + }): Promise<void> { + const command = new MoveBookmarkCommand(fromTime, toTime); + this.editor.command.execute({ command }); + } + + getBookmarkAtTime({ time }: { time: number }) { + const activeScene = this.active; + const activeProject = this.editor.project.getActive(); + + if (!activeScene || !activeProject) return null; + + const frameTime = getFrameTime({ + time, + fps: activeProject.settings.fps, + }); + + return getBookmarkAtTime({ + bookmarks: activeScene.bookmarks, + frameTime, + }); + } + + async loadProjectScenes({ projectId }: { projectId: string }): Promise<void> { + try { + const result = await storageService.loadProject({ id: projectId }); + if (result?.project.scenes) { + const ensuredScenes = result.project.scenes ?? []; + const currentScene = findCurrentScene({ + scenes: ensuredScenes, + currentSceneId: result.project.currentSceneId, + }); + + this.list = ensuredScenes; + this.active = currentScene; + this.notify(); + } + } catch (error) { + console.error("Failed to load project scenes:", error); + this.list = []; + this.active = null; + this.notify(); + } + } + + initializeScenes({ + scenes, + currentSceneId, + }: { + scenes: TScene[]; + currentSceneId?: string; + }): void { + const ensuredScenes = ensureMainScene({ scenes }); + const currentScene = currentSceneId + ? ensuredScenes.find((s) => s.id === currentSceneId) + : null; + + const fallbackScene = getMainScene({ scenes: ensuredScenes }); + + this.list = ensuredScenes; + this.active = currentScene || fallbackScene; + this.notify(); + + const hasAddedMainScene = ensuredScenes.length > scenes.length; + if (hasAddedMainScene) { + const activeProject = this.editor.project.getActive(); + + if (activeProject) { + const updatedProject = { + ...activeProject, + scenes: ensuredScenes, + metadata: { + ...activeProject.metadata, + updatedAt: new Date(), + }, + }; + + this.editor.project.setActiveProject({ project: updatedProject }); + this.editor.save.markDirty({ force: true }); + } + } + } + + clearScenes(): void { + this.list = []; + this.active = null; + this.notify(); + } + + getActiveScene(): TScene { + if (!this.active) { + throw new Error("No active scene."); + } + return this.active; + } + + getActiveSceneOrNull(): TScene | null { + return this.active; + } + + getScenes(): TScene[] { + return this.list; + } + + setScenes({ + scenes, + activeSceneId, + }: { + scenes: TScene[]; + activeSceneId?: string; + }): void { + this.list = scenes; + const nextActiveSceneId = activeSceneId ?? this.active?.id ?? null; + this.active = nextActiveSceneId + ? (scenes.find((scene) => scene.id === nextActiveSceneId) ?? null) + : null; + this.notify(); + + const activeProject = this.editor.project.getActive(); + if (activeProject) { + const updatedProject = { + ...activeProject, + scenes, + metadata: { + ...activeProject.metadata, + updatedAt: new Date(), + }, + }; + this.editor.project.setActiveProject({ project: updatedProject }); + } + } + + subscribe(listener: () => void): () => void { + this.listeners.add(listener); + return () => this.listeners.delete(listener); + } + + private notify(): void { + this.listeners.forEach((fn) => { + fn(); + }); + } + + updateSceneTracks({ tracks }: { tracks: SceneTracks }): void { + if (!this.active) return; + + const updatedScene: TScene = { + ...this.active, + tracks, + updatedAt: new Date(), + }; + + this.list = this.list.map((s) => + s.id === this.active?.id ? updatedScene : s, + ); + this.active = updatedScene; + this.notify(); + + const activeProject = this.editor.project.getActive(); + if (activeProject) { + const updatedProject = { + ...activeProject, + scenes: this.list, + metadata: { + ...activeProject.metadata, + updatedAt: new Date(), + }, + }; + this.editor.project.setActiveProject({ project: updatedProject }); + } + } +} diff --git a/apps/web/src/core/managers/selection-manager.ts b/apps/web/src/core/managers/selection-manager.ts index 5da4fa60..cfc46ccc 100644 --- a/apps/web/src/core/managers/selection-manager.ts +++ b/apps/web/src/core/managers/selection-manager.ts @@ -1,12 +1,12 @@ import type { EditorCore } from "@/core"; -import type { SelectedKeyframeRef } from "@/lib/animation/types"; +import type { SelectedKeyframeRef } from "@/animation/types"; import type { EditorSelectionKind, EditorSelectionPatch, EditorSelectionSnapshot, SelectedMaskPointSelection, -} from "@/lib/selection/editor-selection"; -import type { ElementRef } from "@/lib/timeline/types"; +} from "@/selection/editor-selection"; +import type { ElementRef } from "@/timeline/types"; export class SelectionManager { private selectedElements: ElementRef[] = []; diff --git a/apps/web/src/core/managers/timeline-manager.ts b/apps/web/src/core/managers/timeline-manager.ts index c58128fb..dbc3332b 100644 --- a/apps/web/src/core/managers/timeline-manager.ts +++ b/apps/web/src/core/managers/timeline-manager.ts @@ -1,33 +1,33 @@ import type { EditorCore } from "@/core"; -import type { ElementBounds } from "@/lib/preview/element-bounds"; -import type { ParamValues } from "@/lib/params"; +import type { ElementBounds } from "@/preview/element-bounds"; +import type { ParamValues } from "@/params"; import type { SceneTracks, TrackType, TimelineTrack, TimelineElement, RetimeConfig, -} from "@/lib/timeline"; -import { calculateTotalDuration } from "@/lib/timeline"; -import { findTrackInSceneTracks } from "@/lib/timeline/track-element-update"; +} from "@/timeline"; +import { calculateTotalDuration } from "@/timeline"; +import { findTrackInSceneTracks } from "@/timeline/track-element-update"; import { canElementBeHidden, canElementHaveAudio, -} from "@/lib/timeline/element-utils"; +} from "@/timeline/element-utils"; import type { AnimationPath, AnimationInterpolation, AnimationValue, AnimationValueForPath, ScalarCurveKeyframePatch, -} from "@/lib/animation/types"; +} from "@/animation/types"; import { getElementLocalTime, resolveAnimationTarget, resolveAnimationPathValueAtTime, -} from "@/lib/animation"; +} from "@/animation"; import { lastFrameTime } from "opencut-wasm"; -import { BatchCommand } from "@/lib/commands"; +import { BatchCommand } from "@/commands"; import { AddTrackCommand, RemoveTrackCommand, @@ -56,12 +56,12 @@ import { UpsertEffectParamKeyframeCommand, RemoveEffectParamKeyframeCommand, ToggleSourceAudioSeparationCommand, -} from "@/lib/commands/timeline"; -import type { InsertElementParams } from "@/lib/commands/timeline/element/insert-element"; +} from "@/commands/timeline"; +import type { InsertElementParams } from "@/commands/timeline/element/insert-element"; import type { PlannedElementMove, PlannedTrackCreation, -} from "@/lib/timeline/group-move"; +} from "@/timeline/group-move"; export class TimelineManager { private listeners = new Set<() => void>(); diff --git a/apps/web/src/lib/db/index.ts b/apps/web/src/db/index.ts index b1ffd119..29e3ffda 100644 --- a/apps/web/src/lib/db/index.ts +++ b/apps/web/src/db/index.ts @@ -1,7 +1,7 @@ import { drizzle } from "drizzle-orm/postgres-js"; import postgres from "postgres"; import * as schema from "./schema"; -import { webEnv } from "@/lib/env/web"; +import { webEnv } from "@/env/web"; let _db: ReturnType<typeof drizzle> | null = null; diff --git a/apps/web/src/lib/db/schema.ts b/apps/web/src/db/schema.ts index 8f8be248..8f8be248 100644 --- a/apps/web/src/lib/db/schema.ts +++ b/apps/web/src/db/schema.ts diff --git a/apps/web/src/lib/diagnostics/types.ts b/apps/web/src/diagnostics/types.ts index 486f362c..486f362c 100644 --- a/apps/web/src/lib/diagnostics/types.ts +++ b/apps/web/src/diagnostics/types.ts diff --git a/apps/web/src/lib/cancel-interaction.ts b/apps/web/src/editor/cancel-interaction.ts index 297a2062..297a2062 100644 --- a/apps/web/src/lib/cancel-interaction.ts +++ b/apps/web/src/editor/cancel-interaction.ts diff --git a/apps/web/src/stores/editor-store.ts b/apps/web/src/editor/editor-store.ts index 07cb433e..6b05e8bd 100644 --- a/apps/web/src/stores/editor-store.ts +++ b/apps/web/src/editor/editor-store.ts @@ -1,6 +1,6 @@ import { create } from "zustand";
-import { DEFAULT_CANVAS_PRESETS } from "@/lib/canvas/sizes";
-import type { TCanvasSize } from "@/lib/project/types";
+import { DEFAULT_CANVAS_PRESETS } from "@/canvas/sizes";
+import type { TCanvasSize } from "@/project/types";
interface EditorState {
isInitializing: boolean;
diff --git a/apps/web/src/stores/panel-store.ts b/apps/web/src/editor/panel-store.ts index 757a37a6..2cb768a1 100644 --- a/apps/web/src/stores/panel-store.ts +++ b/apps/web/src/editor/panel-store.ts @@ -1,6 +1,6 @@ import { create } from "zustand";
import { persist } from "zustand/middleware";
-import { PANEL_CONFIG } from "@/lib/panels/layout";
+import { PANEL_CONFIG } from "@/panels/layout";
export interface PanelSizes {
tools: number;
diff --git a/apps/web/src/hooks/use-editor.ts b/apps/web/src/editor/use-editor.ts index ce48e036..ce48e036 100644 --- a/apps/web/src/hooks/use-editor.ts +++ b/apps/web/src/editor/use-editor.ts diff --git a/apps/web/src/hooks/use-menu-preview.ts b/apps/web/src/editor/use-menu-preview.ts index a3c14489..ca42124e 100644 --- a/apps/web/src/hooks/use-menu-preview.ts +++ b/apps/web/src/editor/use-menu-preview.ts @@ -1,5 +1,5 @@ import { useRef } from "react";
-import { useEditor } from "@/hooks/use-editor";
+import { useEditor } from "@/editor/use-editor";
export function useMenuPreview() {
const editor = useEditor();
diff --git a/apps/web/src/components/editor/panels/assets/views/effects.tsx b/apps/web/src/effects/components/assets-view.tsx index 74128df6..000af7e6 100644 --- a/apps/web/src/components/editor/panels/assets/views/effects.tsx +++ b/apps/web/src/effects/components/assets-view.tsx @@ -3,11 +3,11 @@ import { useEffect, useRef, useCallback } from "react"; import { PanelView } from "@/components/editor/panels/assets/views/base-panel"; import { DraggableItem } from "@/components/editor/panels/assets/draggable-item"; -import { effectsRegistry, EFFECT_TARGET_ELEMENT_TYPES } from "@/lib/effects"; +import { effectsRegistry, EFFECT_TARGET_ELEMENT_TYPES } from "@/effects"; import { effectPreviewService } from "@/services/renderer/effect-preview"; -import { useEditor } from "@/hooks/use-editor"; -import { buildEffectElement } from "@/lib/timeline/element-utils"; -import type { EffectDefinition } from "@/lib/effects/types"; +import { useEditor } from "@/editor/use-editor"; +import { buildEffectElement } from "@/timeline/element-utils"; +import type { EffectDefinition } from "@/effects/types"; export function EffectsView() { const effects = effectsRegistry.getAll(); diff --git a/apps/web/src/components/editor/panels/properties/tabs/effects-tab.tsx b/apps/web/src/effects/components/effects-tab.tsx index 1b94ee2d..102d818a 100644 --- a/apps/web/src/components/editor/panels/properties/tabs/effects-tab.tsx +++ b/apps/web/src/effects/components/effects-tab.tsx @@ -1,12 +1,12 @@ "use client"; import { useState } from "react"; -import type { ParamValues } from "@/lib/params"; -import type { Effect } from "@/lib/effects/types"; -import type { EffectElement, VisualElement } from "@/lib/timeline"; -import { effectsRegistry } from "@/lib/effects"; -import { useEditor } from "@/hooks/use-editor"; -import { useElementPreview } from "@/hooks/use-element-preview"; +import type { ParamValues } from "@/params"; +import type { Effect } from "@/effects/types"; +import type { EffectElement, VisualElement } from "@/timeline"; +import { effectsRegistry } from "@/effects"; +import { useEditor } from "@/editor/use-editor"; +import { useElementPreview } from "@/timeline/hooks/use-element-preview"; import { Section, SectionContent, @@ -14,7 +14,7 @@ import { SectionTitle, SectionFields, } from "@/components/section"; -import { PropertyParamField } from "../components/property-param-field"; +import { PropertyParamField } from "@/components/editor/panels/properties/components/property-param-field"; import { Button } from "@/components/ui/button"; import { HugeiconsIcon } from "@hugeicons/react"; import { @@ -25,7 +25,7 @@ import { } from "@hugeicons/core-free-icons"; import { cn } from "@/utils/ui"; import { Separator } from "@/components/ui/separator"; -import { useAssetsPanelStore } from "@/stores/assets-panel-store"; +import { useAssetsPanelStore } from "@/components/editor/panels/assets/assets-panel-store"; export function StandaloneEffectTab({ element, diff --git a/apps/web/src/lib/effects/definitions/blur.ts b/apps/web/src/effects/definitions/blur.ts index b464db3a..194196e9 100644 --- a/apps/web/src/lib/effects/definitions/blur.ts +++ b/apps/web/src/effects/definitions/blur.ts @@ -1,4 +1,4 @@ -import type { EffectDefinition, EffectPass } from "@/lib/effects/types"; +import type { EffectDefinition, EffectPass } from "@/effects/types"; export const GAUSSIAN_BLUR_SHADER = "gaussian-blur"; diff --git a/apps/web/src/lib/effects/definitions/index.ts b/apps/web/src/effects/definitions/index.ts index edc8c5e2..edc8c5e2 100644 --- a/apps/web/src/lib/effects/definitions/index.ts +++ b/apps/web/src/effects/definitions/index.ts diff --git a/apps/web/src/lib/effects/index.ts b/apps/web/src/effects/index.ts index 92484ee2..eebb8cbc 100644 --- a/apps/web/src/lib/effects/index.ts +++ b/apps/web/src/effects/index.ts @@ -1,9 +1,9 @@ import { generateUUID } from "@/utils/id"; -import { buildDefaultParamValues } from "@/lib/registry"; +import { buildDefaultParamValues } from "@/params/registry"; import { effectsRegistry } from "./registry"; -import type { ParamValues } from "@/lib/params"; -import type { Effect, EffectDefinition, EffectPass } from "@/lib/effects/types"; -import { VISUAL_ELEMENT_TYPES } from "@/lib/timeline"; +import type { ParamValues } from "@/params"; +import type { Effect, EffectDefinition, EffectPass } from "@/effects/types"; +import { VISUAL_ELEMENT_TYPES } from "@/timeline"; export { effectsRegistry } from "./registry"; export { registerDefaultEffects } from "./definitions"; diff --git a/apps/web/src/lib/effects/registry.ts b/apps/web/src/effects/registry.ts index 29cc3202..610bca02 100644 --- a/apps/web/src/lib/effects/registry.ts +++ b/apps/web/src/effects/registry.ts @@ -1,5 +1,5 @@ -import { DefinitionRegistry } from "@/lib/registry";
-import type { EffectDefinition } from "@/lib/effects/types";
+import { DefinitionRegistry } from "@/params/registry";
+import type { EffectDefinition } from "@/effects/types";
export class EffectsRegistry extends DefinitionRegistry<string, EffectDefinition> {
constructor() {
diff --git a/apps/web/src/lib/effects/types.ts b/apps/web/src/effects/types.ts index 1862b440..6b85e743 100644 --- a/apps/web/src/lib/effects/types.ts +++ b/apps/web/src/effects/types.ts @@ -1,4 +1,4 @@ -import type { ParamDefinition, ParamValues } from "@/lib/params"; +import type { ParamDefinition, ParamValues } from "@/params"; export interface Effect { id: string; diff --git a/apps/web/src/lib/env/web.ts b/apps/web/src/env/web.ts index aa051615..aa051615 100644 --- a/apps/web/src/lib/env/web.ts +++ b/apps/web/src/env/web.ts diff --git a/apps/web/src/lib/export/defaults.ts b/apps/web/src/export/defaults.ts index 0247f8a8..0247f8a8 100644 --- a/apps/web/src/lib/export/defaults.ts +++ b/apps/web/src/export/defaults.ts diff --git a/apps/web/src/lib/export/index.ts b/apps/web/src/export/index.ts index c5c522b3..c5c522b3 100644 --- a/apps/web/src/lib/export/index.ts +++ b/apps/web/src/export/index.ts diff --git a/apps/web/src/lib/export/mime-types.ts b/apps/web/src/export/mime-types.ts index b98900bf..b98900bf 100644 --- a/apps/web/src/lib/export/mime-types.ts +++ b/apps/web/src/export/mime-types.ts diff --git a/apps/web/src/lib/feedback/components/feedback-popover.tsx b/apps/web/src/feedback/components/feedback-popover.tsx index f94b7253..f94b7253 100644 --- a/apps/web/src/lib/feedback/components/feedback-popover.tsx +++ b/apps/web/src/feedback/components/feedback-popover.tsx diff --git a/apps/web/src/lib/feedback/index.ts b/apps/web/src/feedback/index.ts index dfd0743c..dfd0743c 100644 --- a/apps/web/src/lib/feedback/index.ts +++ b/apps/web/src/feedback/index.ts diff --git a/apps/web/src/lib/feedback/queries.ts b/apps/web/src/feedback/queries.ts index 6962e71f..121c6fe9 100644 --- a/apps/web/src/lib/feedback/queries.ts +++ b/apps/web/src/feedback/queries.ts @@ -1,4 +1,4 @@ -import { db, feedback } from "@/lib/db"; +import { db, feedback } from "@/db"; import { generateUUID } from "@/utils/id"; import type { FeedbackEntry, SubmitFeedbackInput } from "./types"; diff --git a/apps/web/src/lib/feedback/types.ts b/apps/web/src/feedback/types.ts index 358b1354..358b1354 100644 --- a/apps/web/src/lib/feedback/types.ts +++ b/apps/web/src/feedback/types.ts diff --git a/apps/web/src/lib/fonts/google-fonts.ts b/apps/web/src/fonts/google-fonts.ts index 8ac55a08..36fca068 100644 --- a/apps/web/src/lib/fonts/google-fonts.ts +++ b/apps/web/src/fonts/google-fonts.ts @@ -1,5 +1,5 @@ -import type { FontAtlas } from "@/lib/fonts/types"; -import { SYSTEM_FONTS } from "@/lib/fonts/system-fonts"; +import type { FontAtlas } from "@/fonts/types"; +import { SYSTEM_FONTS } from "@/fonts/system-fonts"; const GOOGLE_FONTS_CSS = "https://fonts.googleapis.com/css2"; const FONT_ATLAS_PATH = "/fonts/font-atlas.json"; diff --git a/apps/web/src/lib/fonts/system-fonts.ts b/apps/web/src/fonts/system-fonts.ts index 9cc363a3..9cc363a3 100644 --- a/apps/web/src/lib/fonts/system-fonts.ts +++ b/apps/web/src/fonts/system-fonts.ts diff --git a/apps/web/src/lib/fonts/types.ts b/apps/web/src/fonts/types.ts index 89ebce08..89ebce08 100644 --- a/apps/web/src/lib/fonts/types.ts +++ b/apps/web/src/fonts/types.ts diff --git a/apps/web/src/hooks/use-font-atlas.ts b/apps/web/src/fonts/use-font-atlas.ts index aa48f3b4..153eebd4 100644 --- a/apps/web/src/hooks/use-font-atlas.ts +++ b/apps/web/src/fonts/use-font-atlas.ts @@ -3,9 +3,9 @@ import { getCachedFontAtlas,
loadFontAtlas,
clearFontAtlasCache,
-} from "@/lib/fonts/google-fonts";
-import type { FontAtlas } from "@/lib/fonts/types";
-import { SYSTEM_FONTS } from "@/lib/fonts/system-fonts";
+} from "@/fonts/google-fonts";
+import type { FontAtlas } from "@/fonts/types";
+import { SYSTEM_FONTS } from "@/fonts/system-fonts";
type Status = "idle" | "loading" | "error";
diff --git a/apps/web/src/lib/fps/__tests__/fps.test.ts b/apps/web/src/fps/__tests__/fps.test.ts index 10eed8d9..e025a188 100644 --- a/apps/web/src/lib/fps/__tests__/fps.test.ts +++ b/apps/web/src/fps/__tests__/fps.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"; import { getHighestImportedVideoFps, getRaisedProjectFpsForImportedMedia, -} from "@/lib/fps/utils"; +} from "@/fps/utils"; describe("getHighestImportedVideoFps", () => { test("returns the highest valid video fps", () => { diff --git a/apps/web/src/lib/fps/defaults.ts b/apps/web/src/fps/defaults.ts index 66bf0b1c..66bf0b1c 100644 --- a/apps/web/src/lib/fps/defaults.ts +++ b/apps/web/src/fps/defaults.ts diff --git a/apps/web/src/lib/fps/presets.ts b/apps/web/src/fps/presets.ts index b09920d4..b09920d4 100644 --- a/apps/web/src/lib/fps/presets.ts +++ b/apps/web/src/fps/presets.ts diff --git a/apps/web/src/lib/fps/utils.ts b/apps/web/src/fps/utils.ts index 932788c4..e30e263a 100644 --- a/apps/web/src/lib/fps/utils.ts +++ b/apps/web/src/fps/utils.ts @@ -1,5 +1,5 @@ import type { FrameRate } from "opencut-wasm"; -import type { MediaAsset } from "@/lib/media/types"; +import type { MediaAsset } from "@/media/types"; type MediaAssetFpsInput = Pick<MediaAsset, "type" | "fps">; diff --git a/apps/web/src/lib/gradients/canvas.ts b/apps/web/src/gradients/canvas.ts index e2287333..e2287333 100644 --- a/apps/web/src/lib/gradients/canvas.ts +++ b/apps/web/src/gradients/canvas.ts diff --git a/apps/web/src/lib/gradients/index.ts b/apps/web/src/gradients/index.ts index 490a8fbf..490a8fbf 100644 --- a/apps/web/src/lib/gradients/index.ts +++ b/apps/web/src/gradients/index.ts diff --git a/apps/web/src/lib/gradients/parser.ts b/apps/web/src/gradients/parser.ts index 342780b4..342780b4 100644 --- a/apps/web/src/lib/gradients/parser.ts +++ b/apps/web/src/gradients/parser.ts diff --git a/apps/web/src/components/editor/panels/properties/tabs/graphic-tab.tsx b/apps/web/src/graphics/components/graphic-tab.tsx index c61c9cd7..60599772 100644 --- a/apps/web/src/components/editor/panels/properties/tabs/graphic-tab.tsx +++ b/apps/web/src/graphics/components/graphic-tab.tsx @@ -1,17 +1,17 @@ "use client";
import { useRef } from "react";
-import { useElementPlayhead } from "../hooks/use-element-playhead";
+import { useElementPlayhead } from "@/components/editor/panels/properties/hooks/use-element-playhead";
import {
useKeyframedParamProperty,
type KeyframedParamPropertyResult,
-} from "../hooks/use-keyframed-param-property";
-import { resolveGraphicParamsAtTime } from "@/lib/animation";
-import type { ParamDefinition, ParamValues } from "@/lib/params";
-import type { GraphicElement } from "@/lib/timeline";
-import { graphicsRegistry, registerDefaultGraphics } from "@/lib/graphics";
-import { useElementPreview } from "@/hooks/use-element-preview";
-import { useEditor } from "@/hooks/use-editor";
+} from "@/components/editor/panels/properties/hooks/use-keyframed-param-property";
+import { resolveGraphicParamsAtTime } from "@/animation";
+import type { ParamDefinition, ParamValues } from "@/params";
+import type { GraphicElement } from "@/timeline";
+import { graphicsRegistry, registerDefaultGraphics } from "@/graphics";
+import { useElementPreview } from "@/timeline/hooks/use-element-preview";
+import { useEditor } from "@/editor/use-editor";
import {
Section,
SectionContent,
@@ -19,7 +19,7 @@ import { SectionHeader,
SectionTitle,
} from "@/components/section";
-import { PropertyParamField } from "../components/property-param-field";
+import { PropertyParamField } from "@/components/editor/panels/properties/components/property-param-field";
import { Button } from "@/components/ui/button";
import { HugeiconsIcon } from "@hugeicons/react";
import { MinusSignIcon, PlusSignIcon } from "@hugeicons/core-free-icons";
@@ -78,9 +78,7 @@ export function GraphicTab({ </SectionFields>
</SectionContent>
</Section>
- {hasStrokeParams && (
- <StrokeSection element={element} trackId={trackId} />
- )}
+ {hasStrokeParams && <StrokeSection element={element} trackId={trackId} />}
</div>
);
}
@@ -206,21 +204,23 @@ function AnimatedGraphicParamField({ isPlayheadWithinElementRange: boolean;
resolvedParams: ParamValues;
}) {
- const animatedParam: KeyframedParamPropertyResult = useKeyframedParamProperty({
- param,
- trackId,
- elementId: element.id,
- animations: element.animations,
- localTime,
- isPlayheadWithinElementRange,
- resolvedValue: resolvedParams[param.key] ?? param.default,
- buildBaseUpdates: ({ value }) => ({
- params: {
- ...element.params,
- [param.key]: value,
- },
- }),
- });
+ const animatedParam: KeyframedParamPropertyResult = useKeyframedParamProperty(
+ {
+ param,
+ trackId,
+ elementId: element.id,
+ animations: element.animations,
+ localTime,
+ isPlayheadWithinElementRange,
+ resolvedValue: resolvedParams[param.key] ?? param.default,
+ buildBaseUpdates: ({ value }) => ({
+ params: {
+ ...element.params,
+ [param.key]: value,
+ },
+ }),
+ },
+ );
return (
<PropertyParamField
diff --git a/apps/web/src/lib/graphics/definitions/ellipse.ts b/apps/web/src/graphics/definitions/ellipse.ts index 04b93c27..7ae9e8e3 100644 --- a/apps/web/src/lib/graphics/definitions/ellipse.ts +++ b/apps/web/src/graphics/definitions/ellipse.ts @@ -1,4 +1,4 @@ -import type { ParamDefinition } from "@/lib/params";
+import type { ParamDefinition } from "@/params";
import { applyAlignedStroke } from "../stroke";
import { STROKE_ALIGN_PARAM, type GraphicStrokeAlign } from "./shared";
import type { GraphicDefinition } from "../types";
diff --git a/apps/web/src/lib/graphics/definitions/index.ts b/apps/web/src/graphics/definitions/index.ts index 6fd9e446..6fd9e446 100644 --- a/apps/web/src/lib/graphics/definitions/index.ts +++ b/apps/web/src/graphics/definitions/index.ts diff --git a/apps/web/src/lib/graphics/definitions/polygon.ts b/apps/web/src/graphics/definitions/polygon.ts index deecb6ff..11273fed 100644 --- a/apps/web/src/lib/graphics/definitions/polygon.ts +++ b/apps/web/src/graphics/definitions/polygon.ts @@ -1,4 +1,4 @@ -import type { ParamDefinition } from "@/lib/params";
+import type { ParamDefinition } from "@/params";
import { applyAlignedStroke } from "../stroke";
import { STROKE_ALIGN_PARAM, type GraphicStrokeAlign } from "./shared";
import type { GraphicDefinition } from "../types";
diff --git a/apps/web/src/lib/graphics/definitions/rectangle.ts b/apps/web/src/graphics/definitions/rectangle.ts index fb52c03a..3cd0a449 100644 --- a/apps/web/src/lib/graphics/definitions/rectangle.ts +++ b/apps/web/src/graphics/definitions/rectangle.ts @@ -1,4 +1,4 @@ -import type { ParamDefinition } from "@/lib/params";
+import type { ParamDefinition } from "@/params";
import { applyAlignedStroke } from "../stroke";
import { STROKE_ALIGN_PARAM, type GraphicStrokeAlign } from "./shared";
import type { GraphicDefinition } from "../types";
diff --git a/apps/web/src/lib/graphics/definitions/shared.ts b/apps/web/src/graphics/definitions/shared.ts index 4f56c77c..2bd92875 100644 --- a/apps/web/src/lib/graphics/definitions/shared.ts +++ b/apps/web/src/graphics/definitions/shared.ts @@ -1,4 +1,4 @@ -import type { ParamDefinition } from "@/lib/params";
+import type { ParamDefinition } from "@/params";
export type GraphicStrokeAlign = "inside" | "center" | "outside";
diff --git a/apps/web/src/lib/graphics/definitions/star.ts b/apps/web/src/graphics/definitions/star.ts index 2160c125..3aa40fee 100644 --- a/apps/web/src/lib/graphics/definitions/star.ts +++ b/apps/web/src/graphics/definitions/star.ts @@ -1,4 +1,4 @@ -import type { ParamDefinition } from "@/lib/params";
+import type { ParamDefinition } from "@/params";
import { applyAlignedStroke } from "../stroke";
import { STROKE_ALIGN_PARAM, type GraphicStrokeAlign } from "./shared";
import type { GraphicDefinition } from "../types";
diff --git a/apps/web/src/lib/graphics/index.ts b/apps/web/src/graphics/index.ts index 9002a151..c69a4ab0 100644 --- a/apps/web/src/lib/graphics/index.ts +++ b/apps/web/src/graphics/index.ts @@ -1,5 +1,5 @@ -import { buildDefaultParamValues } from "@/lib/registry";
-import type { ParamValues } from "@/lib/params";
+import { buildDefaultParamValues } from "@/params/registry";
+import type { ParamValues } from "@/params";
import { graphicsRegistry } from "./registry";
import {
registerDefaultGraphics,
diff --git a/apps/web/src/lib/graphics/registry.ts b/apps/web/src/graphics/registry.ts index 2f6a5850..ea40a43a 100644 --- a/apps/web/src/lib/graphics/registry.ts +++ b/apps/web/src/graphics/registry.ts @@ -1,4 +1,4 @@ -import { DefinitionRegistry } from "@/lib/registry";
+import { DefinitionRegistry } from "@/params/registry";
import type { GraphicDefinition } from "./types";
export class GraphicsRegistry extends DefinitionRegistry<string, GraphicDefinition> {
diff --git a/apps/web/src/lib/graphics/stroke.ts b/apps/web/src/graphics/stroke.ts index 230d9904..230d9904 100644 --- a/apps/web/src/lib/graphics/stroke.ts +++ b/apps/web/src/graphics/stroke.ts diff --git a/apps/web/src/lib/graphics/types.ts b/apps/web/src/graphics/types.ts index 22bfc5ac..1c6cc10e 100644 --- a/apps/web/src/lib/graphics/types.ts +++ b/apps/web/src/graphics/types.ts @@ -1,4 +1,4 @@ -import type { ParamDefinition, ParamValues } from "@/lib/params";
+import type { ParamDefinition, ParamValues } from "@/params";
export const DEFAULT_GRAPHIC_SOURCE_SIZE = 512;
diff --git a/apps/web/src/lib/guides/definitions/custom.tsx b/apps/web/src/guides/definitions/custom.tsx index fe7944cb..a5bede0b 100644 --- a/apps/web/src/lib/guides/definitions/custom.tsx +++ b/apps/web/src/guides/definitions/custom.tsx @@ -1,7 +1,7 @@ import { PlusSignIcon, RulerIcon } from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";
import { Button } from "@/components/ui/button";
-import type { GuideDefinition } from "@/lib/guides/types";
+import type { GuideDefinition } from "@/guides/types";
function CustomGuideOptions() {
return (
diff --git a/apps/web/src/lib/guides/definitions/grid.tsx b/apps/web/src/guides/definitions/grid.tsx index 7677a1d9..7df6425d 100644 --- a/apps/web/src/lib/guides/definitions/grid.tsx +++ b/apps/web/src/guides/definitions/grid.tsx @@ -9,11 +9,11 @@ import { GRID_MIN, GRID_MAX, DEFAULT_GRID_CONFIG, -} from "@/lib/guides/grid"; -import { usePreviewStore } from "@/stores/preview-store"; +} from "@/guides/grid"; +import { usePreviewStore } from "@/preview/preview-store"; import { clampRound } from "@/utils/math"; import { cn } from "@/utils/ui"; -import type { GuideDefinition } from "@/lib/guides/types"; +import type { GuideDefinition } from "@/guides/types"; function GridLines({ rows, diff --git a/apps/web/src/lib/guides/definitions/platforms.tsx b/apps/web/src/guides/definitions/platforms.tsx index 8c403eb1..b2471a74 100644 --- a/apps/web/src/lib/guides/definitions/platforms.tsx +++ b/apps/web/src/guides/definitions/platforms.tsx @@ -1,5 +1,5 @@ import Image from "next/image";
-import type { GuideDefinition } from "@/lib/guides/types";
+import type { GuideDefinition } from "@/guides/types";
import { TikTokLayout } from "./tiktok-layout";
function PlatformLogo({
diff --git a/apps/web/src/lib/guides/definitions/tiktok-layout.tsx b/apps/web/src/guides/definitions/tiktok-layout.tsx index b7513bac..b7513bac 100644 --- a/apps/web/src/lib/guides/definitions/tiktok-layout.tsx +++ b/apps/web/src/guides/definitions/tiktok-layout.tsx diff --git a/apps/web/src/lib/guides/grid.ts b/apps/web/src/guides/grid.ts index 0b707de7..0b707de7 100644 --- a/apps/web/src/lib/guides/grid.ts +++ b/apps/web/src/guides/grid.ts diff --git a/apps/web/src/lib/guides/index.ts b/apps/web/src/guides/index.ts index 9ea75631..a01d3d24 100644 --- a/apps/web/src/lib/guides/index.ts +++ b/apps/web/src/guides/index.ts @@ -1,13 +1,14 @@ -import { GUIDE_REGISTRY } from "./registry";
-import type { GuideDefinition } from "@/lib/guides/types";
-
-export { GUIDE_REGISTRY, isGuideId } from "./registry";
-export type { GuideDefinition, GuideId, GuideRenderProps } from "./registry";
-
-export function getGuideById(guideId: string | null): GuideDefinition | null {
- if (!guideId) {
- return null;
- }
-
- return GUIDE_REGISTRY.find((guide) => guide.id === guideId) ?? null;
-}
+import { GUIDE_REGISTRY } from "./registry"; +import type { GuideDefinition } from "@/guides/types"; + +export { GUIDE_REGISTRY, isGuideId } from "./registry"; +export type { GuideDefinition, GuideId, GuideRenderProps } from "./registry"; +export { getGuidePreviewOverlaySource } from "./preview-overlay"; + +export function getGuideById(guideId: string | null): GuideDefinition | null { + if (!guideId) { + return null; + } + + return GUIDE_REGISTRY.find((guide) => guide.id === guideId) ?? null; +} diff --git a/apps/web/src/guides/preview-overlay.tsx b/apps/web/src/guides/preview-overlay.tsx new file mode 100644 index 00000000..d71adb12 --- /dev/null +++ b/apps/web/src/guides/preview-overlay.tsx @@ -0,0 +1,33 @@ +import { getGuideById, type GuideId } from "@/guides"; +import { + EMPTY_PREVIEW_OVERLAY_SOURCE_RESULT, + type PreviewOverlaySourceResult, +} from "@/preview/overlays"; + +export function getGuidePreviewOverlaySource({ + guideId, +}: { + guideId: GuideId | null; +}): PreviewOverlaySourceResult { + const guide = getGuideById(guideId); + if (!guide) { + return EMPTY_PREVIEW_OVERLAY_SOURCE_RESULT; + } + + return { + definitions: [], + instances: [ + { + id: `guide-${guide.id}`, + mount: { kind: "scene" }, + plane: "under-interaction", + pointerEvents: "none", + render: ({ sceneHeight, sceneWidth }) => + guide.renderOverlay({ + width: sceneWidth, + height: sceneHeight, + }), + }, + ], + }; +} diff --git a/apps/web/src/lib/guides/registry.tsx b/apps/web/src/guides/registry.tsx index 8000d989..d1c6d652 100644 --- a/apps/web/src/lib/guides/registry.tsx +++ b/apps/web/src/guides/registry.tsx @@ -1,4 +1,4 @@ -import type { GuideDefinition } from "@/lib/guides/types";
+import type { GuideDefinition } from "@/guides/types";
import { gridGuide } from "./definitions/grid";
// import { customGuide } from "./definitions/custom";
import {
@@ -8,7 +8,7 @@ import { spotlightGuide,
} from "./definitions/platforms";
-export type { GuideDefinition, GuideRenderProps } from "@/lib/guides/types";
+export type { GuideDefinition, GuideRenderProps } from "@/guides/types";
export const GUIDE_REGISTRY = [
gridGuide,
diff --git a/apps/web/src/lib/guides/types.ts b/apps/web/src/guides/types.ts index 5b776090..5b776090 100644 --- a/apps/web/src/lib/guides/types.ts +++ b/apps/web/src/guides/types.ts diff --git a/apps/web/src/lib/selection/index.ts b/apps/web/src/lib/selection/index.ts deleted file mode 100644 index b1ded820..00000000 --- a/apps/web/src/lib/selection/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { SelectableItem } from "@/lib/selection/selectable-item";
-export { SelectableSurface } from "@/lib/selection/selectable-surface";
-export { useSelection } from "@/lib/selection/context";
-export { useSelectionScope } from "@/lib/selection/hooks/use-selection-scope";
diff --git a/apps/web/src/lib/timeline/snap-utils.ts b/apps/web/src/lib/timeline/snap-utils.ts deleted file mode 100644 index b2be95a9..00000000 --- a/apps/web/src/lib/timeline/snap-utils.ts +++ /dev/null @@ -1,182 +0,0 @@ -import type { Bookmark, SceneTracks } from "@/lib/timeline"; -import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/lib/timeline/scale"; -import { getElementKeyframes } from "@/lib/animation"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; - -export interface SnapPoint { - time: number; - type: "element-start" | "element-end" | "playhead" | "bookmark" | "keyframe"; - elementId?: string; - trackId?: string; -} - -export interface SnapResult { - snappedTime: number; - snapPoint: SnapPoint | null; - snapDistance: number; -} - -const DEFAULT_SNAP_THRESHOLD_PX = 10; - -export function findSnapPoints({ - tracks, - playheadTime, - excludeElementId, - excludeElementIds, - bookmarks = [], - excludeBookmarkTime, - enableElementSnapping = true, - enablePlayheadSnapping = true, - enableBookmarkSnapping = true, - enableKeyframeSnapping = true, -}: { - tracks: SceneTracks; - playheadTime: number; - excludeElementId?: string; - excludeElementIds?: Set<string>; - bookmarks?: Array<Bookmark>; - excludeBookmarkTime?: number; - enableElementSnapping?: boolean; - enablePlayheadSnapping?: boolean; - enableBookmarkSnapping?: boolean; - enableKeyframeSnapping?: boolean; -}): SnapPoint[] { - const snapPoints: SnapPoint[] = []; - const orderedTracks = [...tracks.overlay, tracks.main, ...tracks.audio]; - const blockedElementIds = new Set(excludeElementIds); - if (excludeElementId) { - blockedElementIds.add(excludeElementId); - } - - for (const track of orderedTracks) { - for (const element of track.elements) { - if (blockedElementIds.has(element.id)) continue; - - if (enableElementSnapping) { - snapPoints.push( - { - time: element.startTime, - type: "element-start", - elementId: element.id, - trackId: track.id, - }, - { - time: element.startTime + element.duration, - type: "element-end", - elementId: element.id, - trackId: track.id, - }, - ); - } - - if (enableKeyframeSnapping) { - for (const keyframe of getElementKeyframes({ - animations: element.animations, - })) { - snapPoints.push({ - time: element.startTime + keyframe.time, - type: "keyframe", - elementId: element.id, - trackId: track.id, - }); - } - } - } - } - - if (enablePlayheadSnapping) { - snapPoints.push({ time: playheadTime, type: "playhead" }); - } - - if (enableBookmarkSnapping) { - for (const bookmark of bookmarks) { - if ( - excludeBookmarkTime != null && - bookmark.time === excludeBookmarkTime - ) { - continue; - } - snapPoints.push({ time: bookmark.time, type: "bookmark" }); - } - } - - return snapPoints; -} - -export function snapToNearestPoint({ - targetTime, - snapPoints, - zoomLevel, - snapThreshold = DEFAULT_SNAP_THRESHOLD_PX, -}: { - targetTime: number; - snapPoints: Array<SnapPoint>; - zoomLevel: number; - snapThreshold?: number; -}): SnapResult { - const pixelsPerSecond = BASE_TIMELINE_PIXELS_PER_SECOND * zoomLevel; - const thresholdInTicks = (snapThreshold / pixelsPerSecond) * TICKS_PER_SECOND; - - let closestSnapPoint: SnapPoint | null = null; - let closestDistance = Infinity; - - for (const snapPoint of snapPoints) { - const distance = Math.abs(targetTime - snapPoint.time); - if (distance < thresholdInTicks && distance < closestDistance) { - closestDistance = distance; - closestSnapPoint = snapPoint; - } - } - - return { - snappedTime: closestSnapPoint ? closestSnapPoint.time : targetTime, - snapPoint: closestSnapPoint, - snapDistance: closestDistance, - }; -} - -export function snapElementEdge({ - targetTime, - elementDuration, - tracks, - playheadTime, - zoomLevel, - excludeElementId, - excludeElementIds, - snapToStart = true, - bookmarks = [], -}: { - targetTime: number; - elementDuration: number; - tracks: SceneTracks; - playheadTime: number; - zoomLevel: number; - excludeElementId?: string; - excludeElementIds?: Set<string>; - snapToStart?: boolean; - bookmarks?: Array<Bookmark>; -}): SnapResult { - const snapPoints = findSnapPoints({ - tracks, - playheadTime, - excludeElementId, - excludeElementIds, - bookmarks, - }); - - const effectiveTargetTime = snapToStart - ? targetTime - : targetTime + elementDuration; - - const snapResult = snapToNearestPoint({ - targetTime: effectiveTargetTime, - snapPoints, - zoomLevel, - }); - - if (!snapToStart && snapResult.snapPoint) { - snapResult.snappedTime = snapResult.snappedTime - elementDuration; - } - - return snapResult; -} diff --git a/apps/web/src/lib/masks/__tests__/snap.test.ts b/apps/web/src/masks/__tests__/snap.test.ts index e2f9cafb..2f33615f 100644 --- a/apps/web/src/lib/masks/__tests__/snap.test.ts +++ b/apps/web/src/masks/__tests__/snap.test.ts @@ -4,23 +4,23 @@ import { getCustomMaskClosedStateAfterPointRemoval, insertPointIntoCustomMaskSegment, removeCustomMaskPoints, -} from "@/lib/masks/custom-path"; +} from "@/masks/custom-path"; import { appendPointToCustomMask, customMaskDefinition, insertPointOnCustomMaskSegment, -} from "@/lib/masks/definitions/custom"; -import { getSplitMaskStrokeSegment } from "@/lib/masks/definitions/split"; -import { textMaskDefinition } from "@/lib/masks/definitions/text"; -import { getMaskSnapGeometry } from "@/lib/masks/geometry"; -import { snapMaskInteraction } from "@/lib/masks/snap"; -import type { ElementBounds } from "@/lib/preview/element-bounds"; +} from "@/masks/definitions/custom"; +import { getSplitMaskStrokeSegment } from "@/masks/definitions/split"; +import { textMaskDefinition } from "@/masks/definitions/text"; +import { getMaskSnapGeometry } from "@/masks/geometry"; +import { snapMaskInteraction } from "@/masks/snap"; +import type { ElementBounds } from "@/preview/element-bounds"; import type { CustomMaskParams, RectangleMaskParams, SplitMaskParams, TextMaskParams, -} from "@/lib/masks/types"; +} from "@/masks/types"; const bounds: ElementBounds = { cx: 200, diff --git a/apps/web/src/components/editor/panels/properties/tabs/masks-tab.tsx b/apps/web/src/masks/components/masks-tab.tsx index 090fce64..97fba3ab 100644 --- a/apps/web/src/components/editor/panels/properties/tabs/masks-tab.tsx +++ b/apps/web/src/masks/components/masks-tab.tsx @@ -1,16 +1,13 @@ "use client"; -import type { MaskableElement } from "@/lib/timeline"; -import type { Mask, MaskType, TextMask } from "@/lib/masks/types"; -import type { - NumberParamDefinition, - SelectParamDefinition, -} from "@/lib/params"; -import { masksRegistry, buildDefaultMaskInstance } from "@/lib/masks"; -import { useEditor } from "@/hooks/use-editor"; -import { useElementPreview } from "@/hooks/use-element-preview"; -import { useMenuPreview } from "@/hooks/use-menu-preview"; -import { getVisibleElementsWithBounds } from "@/lib/preview/element-bounds"; +import type { MaskableElement } from "@/timeline"; +import type { Mask, MaskType, TextMask } from "@/masks/types"; +import type { NumberParamDefinition, SelectParamDefinition } from "@/params"; +import { masksRegistry, buildDefaultMaskInstance } from "@/masks"; +import { useEditor } from "@/editor/use-editor"; +import { useElementPreview } from "@/timeline/hooks/use-element-preview"; +import { useMenuPreview } from "@/editor/use-menu-preview"; +import { getVisibleElementsWithBounds } from "@/preview/element-bounds"; import { HugeiconsIcon } from "@hugeicons/react"; import { ArrowExpandIcon, @@ -58,7 +55,7 @@ import { SectionHeader, SectionTitle, } from "@/components/section"; -import { usePropertyDraft } from "../hooks/use-property-draft"; +import { usePropertyDraft } from "@/components/editor/panels/properties/hooks/use-property-draft"; import { OcMirrorIcon, OcShapesIcon, diff --git a/apps/web/src/lib/masks/custom-path.ts b/apps/web/src/masks/custom-path.ts index 94a9043c..0349df63 100644 --- a/apps/web/src/lib/masks/custom-path.ts +++ b/apps/web/src/masks/custom-path.ts @@ -1,4 +1,4 @@ -import type { ElementBounds } from "@/lib/preview/element-bounds"; +import type { ElementBounds } from "@/preview/element-bounds"; export interface CustomMaskPathPoint { id: string; diff --git a/apps/web/src/lib/masks/definitions/box-like.ts b/apps/web/src/masks/definitions/box-like.ts index fe0f1a85..7db51df7 100644 --- a/apps/web/src/lib/masks/definitions/box-like.ts +++ b/apps/web/src/masks/definitions/box-like.ts @@ -1,7 +1,7 @@ import { DEFAULT_SHAPE_MASK_SHORT_SIDE_RATIO, MIN_MASK_DIMENSION, -} from "@/lib/masks/dimensions"; +} from "@/masks/dimensions"; import { computeFeatherUpdate } from "../param-update"; import type { BaseMaskParams, @@ -10,13 +10,13 @@ import type { MaskInteractionDefinition, MaskParamUpdateArgs, RectangleMaskParams, -} from "@/lib/masks/types"; -import type { NumberParamDefinition, ParamDefinition } from "@/lib/params"; +} from "@/masks/types"; +import type { NumberParamDefinition, ParamDefinition } from "@/params"; import { getBoxMaskHandlePositions, getBoxMaskOverlays, -} from "@/lib/masks/handle-positions"; -import { snapMaskInteraction } from "@/lib/masks/snap"; +} from "@/masks/handle-positions"; +import { snapMaskInteraction } from "@/masks/snap"; const PERCENTAGE_DISPLAY: Pick< NumberParamDefinition, diff --git a/apps/web/src/lib/masks/definitions/cinematic-bars.ts b/apps/web/src/masks/definitions/cinematic-bars.ts index ef9c6e00..36b31328 100644 --- a/apps/web/src/lib/masks/definitions/cinematic-bars.ts +++ b/apps/web/src/masks/definitions/cinematic-bars.ts @@ -2,7 +2,7 @@ import type { MaskDefaultContext, MaskDefinition, RectangleMaskParams, -} from "@/lib/masks/types"; +} from "@/masks/types"; import { BOX_LIKE_MASK_PARAMS, buildBoxMaskInteraction, diff --git a/apps/web/src/lib/masks/definitions/custom.ts b/apps/web/src/masks/definitions/custom.ts index 0fc69929..cd6e2055 100644 --- a/apps/web/src/lib/masks/definitions/custom.ts +++ b/apps/web/src/masks/definitions/custom.ts @@ -1,7 +1,7 @@ import { generateUUID } from "@/utils/id"; -import type { ParamDefinition } from "@/lib/params"; -import { PEN_CURSOR } from "@/components/editor/panels/preview/cursors"; -import type { ElementBounds } from "@/lib/preview/element-bounds"; +import type { ParamDefinition } from "@/params"; +import { PEN_CURSOR } from "@/preview/components/cursors"; +import type { ElementBounds } from "@/preview/element-bounds"; import type { CustomMask, CustomMaskParams, @@ -9,7 +9,7 @@ import type { MaskHandlePosition, MaskOverlay, MaskParamUpdateArgs, -} from "@/lib/masks/types"; +} from "@/masks/types"; import { buildCustomMaskPath2D, buildCustomMaskSvgPath, @@ -23,18 +23,18 @@ import { parseCustomMaskHandleId, recenterCustomMaskPath, type CustomMaskPathPoint, -} from "@/lib/masks/custom-path"; -import { getBoxMaskHandlePositions } from "@/lib/masks/handle-positions"; -import { computeFeatherUpdate } from "@/lib/masks/param-update"; +} from "@/masks/custom-path"; +import { getBoxMaskHandlePositions } from "@/masks/handle-positions"; +import { computeFeatherUpdate } from "@/masks/param-update"; import { setMaskLocalCenter, toGlobalMaskSnapLines, -} from "@/lib/masks/geometry"; +} from "@/masks/geometry"; import { snapPosition, snapRotation, snapScale, -} from "@/lib/preview/preview-snap"; +} from "@/preview/preview-snap"; const PERCENTAGE_DISPLAY = { displayMultiplier: 100, diff --git a/apps/web/src/lib/masks/definitions/diamond.ts b/apps/web/src/masks/definitions/diamond.ts index 56930678..037aee70 100644 --- a/apps/web/src/lib/masks/definitions/diamond.ts +++ b/apps/web/src/masks/definitions/diamond.ts @@ -1,4 +1,4 @@ -import type { MaskDefinition, RectangleMaskParams } from "@/lib/masks/types"; +import type { MaskDefinition, RectangleMaskParams } from "@/masks/types"; import { BOX_LIKE_MASK_PARAMS, buildBoxMaskInteraction, diff --git a/apps/web/src/lib/masks/definitions/ellipse.ts b/apps/web/src/masks/definitions/ellipse.ts index 2eb85f0c..d0567817 100644 --- a/apps/web/src/lib/masks/definitions/ellipse.ts +++ b/apps/web/src/masks/definitions/ellipse.ts @@ -1,4 +1,4 @@ -import type { MaskDefinition, RectangleMaskParams } from "@/lib/masks/types"; +import type { MaskDefinition, RectangleMaskParams } from "@/masks/types"; import { BOX_LIKE_MASK_PARAMS, buildBoxMaskInteraction, diff --git a/apps/web/src/lib/masks/definitions/heart.ts b/apps/web/src/masks/definitions/heart.ts index 429c5d98..d15c09e6 100644 --- a/apps/web/src/lib/masks/definitions/heart.ts +++ b/apps/web/src/masks/definitions/heart.ts @@ -1,4 +1,4 @@ -import type { MaskDefinition, RectangleMaskParams } from "@/lib/masks/types"; +import type { MaskDefinition, RectangleMaskParams } from "@/masks/types"; import { BOX_LIKE_MASK_PARAMS, buildBoxMaskInteraction, diff --git a/apps/web/src/lib/masks/definitions/index.ts b/apps/web/src/masks/definitions/index.ts index fd00cab1..9cee1020 100644 --- a/apps/web/src/lib/masks/definitions/index.ts +++ b/apps/web/src/masks/definitions/index.ts @@ -1,4 +1,4 @@ -import type { BaseMaskParams, MaskDefinition } from "@/lib/masks/types"; +import type { BaseMaskParams, MaskDefinition } from "@/masks/types"; import { masksRegistry, type MaskIconProps } from "../registry"; import { cinematicBarsMaskDefinition } from "./cinematic-bars"; import { customMaskDefinition } from "./custom"; diff --git a/apps/web/src/lib/masks/definitions/rectangle.ts b/apps/web/src/masks/definitions/rectangle.ts index 832b8307..7bf1a7fd 100644 --- a/apps/web/src/lib/masks/definitions/rectangle.ts +++ b/apps/web/src/masks/definitions/rectangle.ts @@ -1,4 +1,4 @@ -import type { MaskDefinition, RectangleMaskParams } from "@/lib/masks/types"; +import type { MaskDefinition, RectangleMaskParams } from "@/masks/types"; import { BOX_LIKE_MASK_PARAMS, buildBoxMaskInteraction, diff --git a/apps/web/src/lib/masks/definitions/split.ts b/apps/web/src/masks/definitions/split.ts index c1a14d90..d50a2571 100644 --- a/apps/web/src/lib/masks/definitions/split.ts +++ b/apps/web/src/masks/definitions/split.ts @@ -3,13 +3,13 @@ import type { MaskDefinition, MaskParamUpdateArgs, SplitMaskParams, -} from "@/lib/masks/types"; +} from "@/masks/types"; import { halfPlaneSign, lineEdgeIntersection } from "../utils"; import { getLineMaskHandlePositions, getLineMaskOverlay, -} from "@/lib/masks/handle-positions"; -import { snapMaskInteraction } from "@/lib/masks/snap"; +} from "@/masks/handle-positions"; +import { snapMaskInteraction } from "@/masks/snap"; // cos(Ï€/2) returns ~6e-17 in JS, not 0. Values below this threshold are snapped // to exactly 0 to prevent opposite-sign float noise on canvas corners that lie diff --git a/apps/web/src/lib/masks/definitions/star.ts b/apps/web/src/masks/definitions/star.ts index d311ab80..a7f8819c 100644 --- a/apps/web/src/lib/masks/definitions/star.ts +++ b/apps/web/src/masks/definitions/star.ts @@ -1,4 +1,4 @@ -import type { MaskDefinition, RectangleMaskParams } from "@/lib/masks/types"; +import type { MaskDefinition, RectangleMaskParams } from "@/masks/types"; import { BOX_LIKE_MASK_PARAMS, buildBoxMaskInteraction, diff --git a/apps/web/src/lib/masks/definitions/text.ts b/apps/web/src/masks/definitions/text.ts index b774ed28..c285f1c8 100644 --- a/apps/web/src/lib/masks/definitions/text.ts +++ b/apps/web/src/masks/definitions/text.ts @@ -1,34 +1,34 @@ -import type { ParamDefinition } from "@/lib/params"; +import type { ParamDefinition } from "@/params"; import type { MaskDefinition, MaskParamUpdateArgs, TextMask, TextMaskParams, -} from "@/lib/masks/types"; -import { DEFAULTS } from "@/lib/timeline/defaults"; -import { MIN_FONT_SIZE, MAX_FONT_SIZE } from "@/lib/text/typography"; +} from "@/masks/types"; +import { DEFAULTS } from "@/timeline/defaults"; +import { MIN_FONT_SIZE, MAX_FONT_SIZE } from "@/text/typography"; import { drawMeasuredTextLayout, measureTextLayout, strokeMeasuredTextLayout, -} from "@/lib/text/primitives"; -import { getTextMeasurementContext } from "@/lib/text/measure-element"; -import { getTextVisualRect } from "@/lib/text/layout"; +} from "@/text/primitives"; +import { getTextMeasurementContext } from "@/text/measure-element"; +import { getTextVisualRect } from "@/text/layout"; import { getBoxMaskHandlePositions, getBoxMaskRectOverlay, -} from "@/lib/masks/handle-positions"; -import { computeFeatherUpdate } from "@/lib/masks/param-update"; +} from "@/masks/handle-positions"; +import { computeFeatherUpdate } from "@/masks/param-update"; import { setMaskLocalCenter, toGlobalMaskSnapLines, -} from "@/lib/masks/geometry"; +} from "@/masks/geometry"; import { snapPosition, snapRotation, snapScale, type ScaleEdgePreference, -} from "@/lib/preview/preview-snap"; +} from "@/preview/preview-snap"; const PERCENTAGE_DISPLAY = { displayMultiplier: 100, diff --git a/apps/web/src/lib/masks/dimensions.ts b/apps/web/src/masks/dimensions.ts index dcfcdc0c..dcfcdc0c 100644 --- a/apps/web/src/lib/masks/dimensions.ts +++ b/apps/web/src/masks/dimensions.ts diff --git a/apps/web/src/lib/masks/feather.ts b/apps/web/src/masks/feather.ts index 83e80a0c..83e80a0c 100644 --- a/apps/web/src/lib/masks/feather.ts +++ b/apps/web/src/masks/feather.ts diff --git a/apps/web/src/lib/masks/geometry.ts b/apps/web/src/masks/geometry.ts index a5b05030..007dfa49 100644 --- a/apps/web/src/lib/masks/geometry.ts +++ b/apps/web/src/masks/geometry.ts @@ -1,7 +1,7 @@ -import type { ElementBounds } from "@/lib/preview/element-bounds"; -import type { SnapLine } from "@/lib/preview/preview-snap"; -import { MIN_MASK_DIMENSION } from "@/lib/masks/dimensions"; -import type { RectangleMaskParams } from "@/lib/masks/types"; +import type { ElementBounds } from "@/preview/element-bounds"; +import type { SnapLine } from "@/preview/preview-snap"; +import { MIN_MASK_DIMENSION } from "@/masks/dimensions"; +import type { RectangleMaskParams } from "@/masks/types"; type CenterMaskParams = { centerX: number; diff --git a/apps/web/src/lib/masks/handle-positions.ts b/apps/web/src/masks/handle-positions.ts index d32b8424..75668959 100644 --- a/apps/web/src/lib/masks/handle-positions.ts +++ b/apps/web/src/masks/handle-positions.ts @@ -1,5 +1,5 @@ -import { FEATHER_HANDLE_SCALE } from "@/lib/masks/feather"; -import type { ElementBounds } from "@/lib/preview/element-bounds"; +import { FEATHER_HANDLE_SCALE } from "@/masks/feather"; +import type { ElementBounds } from "@/preview/element-bounds"; import type { MaskFeatures, MaskHandlePosition, @@ -8,7 +8,7 @@ import type { MaskRectOverlay, RectangleMaskParams, MaskShapeOverlay, -} from "@/lib/masks/types"; +} from "@/masks/types"; const LINE_HANDLE_OFFSET_SCREEN_PX = 20; const BOX_HANDLE_OFFSET_SCREEN_PX = 20; diff --git a/apps/web/src/lib/masks/index.ts b/apps/web/src/masks/index.ts index 43871b44..3544e239 100644 --- a/apps/web/src/lib/masks/index.ts +++ b/apps/web/src/masks/index.ts @@ -1,4 +1,4 @@ -import type { Mask, MaskDefaultContext, MaskType } from "@/lib/masks/types";
+import type { Mask, MaskDefaultContext, MaskType } from "@/masks/types";
import { masksRegistry } from "./registry";
import { generateUUID } from "@/utils/id";
diff --git a/apps/web/src/lib/masks/param-update.ts b/apps/web/src/masks/param-update.ts index 4f8693eb..e5c7f5b7 100644 --- a/apps/web/src/lib/masks/param-update.ts +++ b/apps/web/src/masks/param-update.ts @@ -1,4 +1,4 @@ -import { FEATHER_HANDLE_SCALE, MAX_FEATHER } from "@/lib/masks/feather"; +import { FEATHER_HANDLE_SCALE, MAX_FEATHER } from "@/masks/feather"; export function computeFeatherUpdate({ startFeather, diff --git a/apps/web/src/lib/masks/registry.ts b/apps/web/src/masks/registry.ts index ae8ddd20..00b276c8 100644 --- a/apps/web/src/lib/masks/registry.ts +++ b/apps/web/src/masks/registry.ts @@ -1,5 +1,5 @@ -import { MAX_FEATHER } from "@/lib/masks/feather"; -import type { ParamDefinition } from "@/lib/params"; +import { MAX_FEATHER } from "@/masks/feather"; +import type { ParamDefinition } from "@/params"; import type { BaseMaskParams, MaskDefaultContext, @@ -8,9 +8,9 @@ import type { MaskSnapArgs, MaskSnapResult, MaskType, -} from "@/lib/masks/types"; +} from "@/masks/types"; import type { HugeiconsIconProps } from "@hugeicons/react"; -import { DefinitionRegistry } from "@/lib/registry"; +import { DefinitionRegistry } from "@/params/registry"; export type MaskIconProps = { icon: HugeiconsIconProps["icon"]; diff --git a/apps/web/src/lib/masks/snap.ts b/apps/web/src/masks/snap.ts index 7396dd82..cff62ebb 100644 --- a/apps/web/src/lib/masks/snap.ts +++ b/apps/web/src/masks/snap.ts @@ -1,5 +1,5 @@ -import type { ElementBounds } from "@/lib/preview/element-bounds"; -import { MIN_MASK_DIMENSION } from "@/lib/masks/dimensions"; +import type { ElementBounds } from "@/preview/element-bounds"; +import { MIN_MASK_DIMENSION } from "@/masks/dimensions"; import { snapPosition, snapRotation, @@ -7,8 +7,8 @@ import { snapScaleAxes, type ScaleEdgePreference, type SnapLine, -} from "@/lib/preview/preview-snap"; -import type { RectangleMaskParams, SplitMaskParams } from "@/lib/masks/types"; +} from "@/preview/preview-snap"; +import type { RectangleMaskParams, SplitMaskParams } from "@/masks/types"; import { isRectangleMaskParams, getMaskSnapGeometry, diff --git a/apps/web/src/lib/masks/types.ts b/apps/web/src/masks/types.ts index 5a0d126d..4f6c90aa 100644 --- a/apps/web/src/lib/masks/types.ts +++ b/apps/web/src/masks/types.ts @@ -1,12 +1,12 @@ -import type { ElementBounds } from "@/lib/preview/element-bounds"; -import type { SnapLine } from "@/lib/preview/preview-snap"; -import type { ParamDefinition } from "@/lib/params"; -import type { CustomMaskPathPoint } from "@/lib/masks/custom-path"; +import type { ElementBounds } from "@/preview/element-bounds"; +import type { SnapLine } from "@/preview/preview-snap"; +import type { ParamDefinition } from "@/params"; +import type { CustomMaskPathPoint } from "@/masks/custom-path"; import type { TextDecoration, TextFontStyle, TextFontWeight, -} from "@/lib/text/primitives"; +} from "@/text/primitives"; export type MaskType = | "split" diff --git a/apps/web/src/hooks/use-mask-handles.ts b/apps/web/src/masks/use-mask-handles.ts index 917a0677..b81de4f4 100644 --- a/apps/web/src/hooks/use-mask-handles.ts +++ b/apps/web/src/masks/use-mask-handles.ts @@ -1,25 +1,25 @@ import { useCallback, useEffect, useRef, useState } from "react"; -import { usePreviewViewport } from "@/components/editor/panels/preview/preview-viewport"; -import { useEditor } from "@/hooks/use-editor"; +import { usePreviewViewport } from "@/preview/components/preview-viewport"; +import { useEditor } from "@/editor/use-editor"; import { useShiftKey } from "@/hooks/use-shift-key"; -import { masksRegistry } from "@/lib/masks"; +import { masksRegistry } from "@/masks"; import { parseCustomMaskHandleId, parseCustomMaskSegmentHandleId, -} from "@/lib/masks/custom-path"; -import { appendPointToCustomMask } from "@/lib/masks/definitions/custom"; +} from "@/masks/custom-path"; +import { appendPointToCustomMask } from "@/masks/definitions/custom"; import { getVisibleElementsWithBounds, type ElementBounds, -} from "@/lib/preview/element-bounds"; +} from "@/preview/element-bounds"; import { SNAP_THRESHOLD_SCREEN_PIXELS, type SnapLine, -} from "@/lib/preview/preview-snap"; -import type { SelectedMaskPointSelection } from "@/lib/selection/editor-selection"; -import type { Mask, MaskInteractionResult } from "@/lib/masks/types"; -import type { MaskableElement } from "@/lib/timeline"; -import { registerCanceller } from "@/lib/cancel-interaction"; +} from "@/preview/preview-snap"; +import type { SelectedMaskPointSelection } from "@/selection/editor-selection"; +import type { Mask, MaskInteractionResult } from "@/masks/types"; +import type { MaskableElement } from "@/timeline"; +import { registerCanceller } from "@/editor/cancel-interaction"; interface DragState { trackId: string; diff --git a/apps/web/src/lib/masks/utils.ts b/apps/web/src/masks/utils.ts index b1ac2c39..b1ac2c39 100644 --- a/apps/web/src/lib/masks/utils.ts +++ b/apps/web/src/masks/utils.ts diff --git a/apps/web/src/lib/media/audio-mastering.ts b/apps/web/src/media/audio-mastering.ts index f901c3bf..f901c3bf 100644 --- a/apps/web/src/lib/media/audio-mastering.ts +++ b/apps/web/src/media/audio-mastering.ts diff --git a/apps/web/src/lib/media/audio.ts b/apps/web/src/media/audio.ts index 675cc3a5..42756d49 100644 --- a/apps/web/src/lib/media/audio.ts +++ b/apps/web/src/media/audio.ts @@ -4,26 +4,26 @@ import type { LibraryAudioElement, RetimeConfig, SceneTracks, -} from "@/lib/timeline"; -import { shouldMaintainPitch } from "@/lib/retime/rate"; -import type { MediaAsset } from "@/lib/media/types"; -import { applyAudioMasteringToBuffer } from "@/lib/media/audio-mastering"; -import type { AudioCapableElement } from "@/lib/timeline/audio-state"; +} from "@/timeline"; +import { shouldMaintainPitch } from "@/retime/rate"; +import type { MediaAsset } from "@/media/types"; +import { applyAudioMasteringToBuffer } from "@/media/audio-mastering"; +import type { AudioCapableElement } from "@/timeline/audio-state"; import { hasAnimatedVolume, resolveEffectiveAudioGain, -} from "@/lib/timeline/audio-state"; -import { doesElementHaveEnabledAudio } from "@/lib/timeline/audio-separation"; -import { canElementHaveAudio, hasMediaId } from "@/lib/timeline/element-utils"; -import { canTrackHaveAudio } from "@/lib/timeline"; -import { mediaSupportsAudio } from "@/lib/media/media-utils"; -import { getSourceTimeAtClipTime, renderRetimedBuffer } from "@/lib/retime"; +} from "@/timeline/audio-state"; +import { doesElementHaveEnabledAudio } from "@/timeline/audio-separation"; +import { canElementHaveAudio, hasMediaId } from "@/timeline/element-utils"; +import { canTrackHaveAudio } from "@/timeline"; +import { mediaSupportsAudio } from "@/media/media-utils"; +import { getSourceTimeAtClipTime, renderRetimedBuffer } from "@/retime"; import { Input, ALL_FORMATS, BlobSource, AudioBufferSink } from "mediabunny"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; +import { TICKS_PER_SECOND } from "@/wasm"; import { computeRmsBuckets, type SampleBucket, -} from "@/lib/media/waveform-summary"; +} from "@/media/waveform-summary"; const MAX_AUDIO_CHANNELS = 2; const EXPORT_SAMPLE_RATE = 44100; diff --git a/apps/web/src/lib/media/media-utils.ts b/apps/web/src/media/media-utils.ts index dc73da23..dd59bf31 100644 --- a/apps/web/src/lib/media/media-utils.ts +++ b/apps/web/src/media/media-utils.ts @@ -1,4 +1,4 @@ -import type { MediaAsset, MediaType } from "@/lib/media/types";
+import type { MediaAsset, MediaType } from "@/media/types";
export const SUPPORTS_AUDIO: readonly MediaType[] = ["audio", "video"];
diff --git a/apps/web/src/lib/media/mediabunny.ts b/apps/web/src/media/mediabunny.ts index 1e97fc42..47ffaeb1 100644 --- a/apps/web/src/lib/media/mediabunny.ts +++ b/apps/web/src/media/mediabunny.ts @@ -1,8 +1,8 @@ import { Input, ALL_FORMATS, BlobSource } from "mediabunny"; -import { createTimelineAudioBuffer } from "@/lib/media/audio"; -import type { SceneTracks } from "@/lib/timeline"; -import type { MediaAsset } from "@/lib/media/types"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; +import { createTimelineAudioBuffer } from "@/media/audio"; +import type { SceneTracks } from "@/timeline"; +import type { MediaAsset } from "@/media/types"; +import { TICKS_PER_SECOND } from "@/wasm"; export async function getVideoInfo({ videoFile, diff --git a/apps/web/src/lib/media/processing.ts b/apps/web/src/media/processing.ts index 547e7ed8..d8d483f8 100644 --- a/apps/web/src/lib/media/processing.ts +++ b/apps/web/src/media/processing.ts @@ -1,9 +1,9 @@ import { Input, ALL_FORMATS, BlobSource, VideoSampleSink } from "mediabunny"; import { toast } from "sonner"; -import { getMediaTypeFromFile } from "@/lib/media/media-utils"; +import { getMediaTypeFromFile } from "@/media/media-utils"; import { formatStorageBytes } from "@/services/storage/quota"; import { storageService } from "@/services/storage/service"; -import type { MediaAsset } from "@/lib/media/types"; +import type { MediaAsset } from "@/media/types"; import { getVideoInfo } from "./mediabunny"; export interface ProcessedMediaAsset extends Omit<MediaAsset, "id"> {} diff --git a/apps/web/src/lib/media/types.ts b/apps/web/src/media/types.ts index 4d3e4106..4d3e4106 100644 --- a/apps/web/src/lib/media/types.ts +++ b/apps/web/src/media/types.ts diff --git a/apps/web/src/lib/media/upload-toast.ts b/apps/web/src/media/upload-toast.ts index 45846206..45846206 100644 --- a/apps/web/src/lib/media/upload-toast.ts +++ b/apps/web/src/media/upload-toast.ts diff --git a/apps/web/src/hooks/use-file-upload.ts b/apps/web/src/media/use-file-upload.ts index d174e002..1ee30ad9 100644 --- a/apps/web/src/hooks/use-file-upload.ts +++ b/apps/web/src/media/use-file-upload.ts @@ -1,5 +1,5 @@ import { useState, useRef } from "react"; -import { hasDragData } from "@/lib/drag-data"; +import { hasDragData } from "@/timeline/drag-data"; interface UseFileUploadOptions { accept?: string; diff --git a/apps/web/src/hooks/use-paste-media.ts b/apps/web/src/media/use-paste-media.ts index 95020a04..3055920e 100644 --- a/apps/web/src/hooks/use-paste-media.ts +++ b/apps/web/src/media/use-paste-media.ts @@ -1,15 +1,15 @@ import { useEffect } from "react"; -import { useEditor } from "@/hooks/use-editor"; -import { processMediaAssets } from "@/lib/media/processing"; -import { showMediaUploadToast } from "@/lib/media/upload-toast"; -import { buildElementFromMedia } from "@/lib/timeline/element-utils"; -import { AddMediaAssetCommand } from "@/lib/commands/media"; -import { InsertElementCommand } from "@/lib/commands/timeline"; -import { BatchCommand } from "@/lib/commands"; -import { DEFAULT_NEW_ELEMENT_DURATION } from "@/lib/timeline/creation"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; +import { useEditor } from "@/editor/use-editor"; +import { processMediaAssets } from "@/media/processing"; +import { showMediaUploadToast } from "@/media/upload-toast"; +import { buildElementFromMedia } from "@/timeline/element-utils"; +import { AddMediaAssetCommand } from "@/commands/media"; +import { InsertElementCommand } from "@/commands/timeline"; +import { BatchCommand } from "@/commands"; +import { DEFAULT_NEW_ELEMENT_DURATION } from "@/timeline/creation"; +import { TICKS_PER_SECOND } from "@/wasm"; import { isTypableDOMElement } from "@/utils/browser"; -import type { MediaType } from "@/lib/media/types"; +import type { MediaType } from "@/media/types"; const MEDIA_MIME_PREFIXES: MediaType[] = ["image", "video", "audio"]; diff --git a/apps/web/src/lib/media/waveform-summary.ts b/apps/web/src/media/waveform-summary.ts index 522d6e04..03a0059c 100644 --- a/apps/web/src/lib/media/waveform-summary.ts +++ b/apps/web/src/media/waveform-summary.ts @@ -1,7 +1,7 @@ "use client"; -import { getSourceTimeAtClipTime } from "@/lib/retime"; -import type { RetimeConfig } from "@/lib/timeline"; +import { getSourceTimeAtClipTime } from "@/retime"; +import type { RetimeConfig } from "@/timeline"; const RMS_ANALYSIS_WINDOW_SECONDS = 0.02; const DEFAULT_SOURCE_WAVEFORM_BUCKET_SIZE = 128; diff --git a/apps/web/src/lib/panels/layout.ts b/apps/web/src/panels/layout.ts index 5a570469..5a570469 100644 --- a/apps/web/src/lib/panels/layout.ts +++ b/apps/web/src/panels/layout.ts diff --git a/apps/web/src/lib/params.ts b/apps/web/src/params/index.ts index 633bfbd0..633bfbd0 100644 --- a/apps/web/src/lib/params.ts +++ b/apps/web/src/params/index.ts diff --git a/apps/web/src/lib/registry.ts b/apps/web/src/params/registry.ts index f1c92f67..d9fcc266 100644 --- a/apps/web/src/lib/registry.ts +++ b/apps/web/src/params/registry.ts @@ -1,4 +1,4 @@ -import type { ParamDefinition, ParamValues } from "@/lib/params";
+import type { ParamDefinition, ParamValues } from "@/params";
export function buildDefaultParamValues(
params: ParamDefinition[],
diff --git a/apps/web/src/components/editor/panels/preview/context-menu.tsx b/apps/web/src/preview/components/context-menu.tsx index 677f35db..fca55d71 100644 --- a/apps/web/src/components/editor/panels/preview/context-menu.tsx +++ b/apps/web/src/preview/components/context-menu.tsx @@ -1,73 +1,85 @@ -"use client";
-
-import {
- ContextMenuCheckboxItem,
- ContextMenuContent,
- ContextMenuItem,
- ContextMenuSeparator,
-} from "@/components/ui/context-menu";
-import { usePreviewViewport } from "@/components/editor/panels/preview/preview-viewport";
-import { useEditor } from "@/hooks/use-editor";
-import { usePreviewStore } from "@/stores/preview-store";
-import { toast } from "sonner";
-
-export function PreviewContextMenu({
- onToggleFullscreen,
- containerRef,
-}: {
- onToggleFullscreen: () => void;
- containerRef: React.RefObject<HTMLElement | null>;
-}) {
- const editor = useEditor();
- const viewport = usePreviewViewport();
- const { overlays, setOverlayVisibility } = usePreviewStore();
-
- const handleCopySnapshot = async () => {
- const result = await editor.renderer.copySnapshot();
-
- if (!result.success) {
- toast.error("Failed to copy snapshot", {
- description: result.error ?? "Please try again",
- });
- return;
- }
- };
-
- const handleSaveSnapshot = async () => {
- const result = await editor.renderer.saveSnapshot();
-
- if (!result.success) {
- toast.error("Failed to save snapshot", {
- description: result.error ?? "Please try again",
- });
- return;
- }
- };
-
- return (
- <ContextMenuContent className="w-56" container={containerRef.current}>
- <ContextMenuItem onClick={viewport.fitToScreen} inset>
- Fit to screen
- </ContextMenuItem>
- <ContextMenuSeparator />
- <ContextMenuItem onClick={onToggleFullscreen} inset>
- Full screen
- </ContextMenuItem>
- <ContextMenuItem onClick={handleSaveSnapshot} inset>
- Save snapshot
- </ContextMenuItem>
- <ContextMenuItem onClick={handleCopySnapshot} inset>
- Copy snapshot
- </ContextMenuItem>
- <ContextMenuSeparator />
- <ContextMenuCheckboxItem
- checked={overlays.bookmarks}
- onCheckedChange={(checked) =>
- setOverlayVisibility({ overlay: "bookmarks", isVisible: !!checked })
- }
- >
- Show bookmark notes
- </ContextMenuCheckboxItem>
- </ContextMenuContent>
- );
-}
+"use client"; + +import { + ContextMenuCheckboxItem, + ContextMenuContent, + ContextMenuItem, + ContextMenuSeparator, +} from "@/components/ui/context-menu"; +import { usePreviewViewport } from "@/preview/components/preview-viewport"; +import { useEditor } from "@/editor/use-editor"; +import type { PreviewOverlayControl } from "@/preview/overlays"; +import { toast } from "sonner"; + +export function PreviewContextMenu({ + onToggleFullscreen, + containerRef, + overlayControls, + onOverlayVisibilityChange, +}: { + onToggleFullscreen: () => void; + containerRef: React.RefObject<HTMLElement | null>; + overlayControls: PreviewOverlayControl[]; + onOverlayVisibilityChange: (params: { + overlayId: string; + isVisible: boolean; + }) => void; +}) { + const editor = useEditor(); + const viewport = usePreviewViewport(); + + const handleCopySnapshot = async () => { + const result = await editor.renderer.copySnapshot(); + + if (!result.success) { + toast.error("Failed to copy snapshot", { + description: result.error ?? "Please try again", + }); + return; + } + }; + + const handleSaveSnapshot = async () => { + const result = await editor.renderer.saveSnapshot(); + + if (!result.success) { + toast.error("Failed to save snapshot", { + description: result.error ?? "Please try again", + }); + return; + } + }; + + return ( + <ContextMenuContent className="w-56" container={containerRef.current}> + <ContextMenuItem onClick={viewport.fitToScreen} inset> + Fit to screen + </ContextMenuItem> + <ContextMenuSeparator /> + <ContextMenuItem onClick={onToggleFullscreen} inset> + Full screen + </ContextMenuItem> + <ContextMenuItem onClick={handleSaveSnapshot} inset> + Save snapshot + </ContextMenuItem> + <ContextMenuItem onClick={handleCopySnapshot} inset> + Copy snapshot + </ContextMenuItem> + {overlayControls.length > 0 ? <ContextMenuSeparator /> : null} + {overlayControls.map((overlayControl) => ( + <ContextMenuCheckboxItem + key={overlayControl.id} + checked={overlayControl.isVisible} + onCheckedChange={(checked) => + onOverlayVisibilityChange({ + overlayId: overlayControl.id, + isVisible: !!checked, + }) + } + > + {overlayControl.label} + </ContextMenuCheckboxItem> + ))} + </ContextMenuContent> + ); +} diff --git a/apps/web/src/components/editor/panels/preview/cursors.ts b/apps/web/src/preview/components/cursors.ts index f04dbef0..f04dbef0 100644 --- a/apps/web/src/components/editor/panels/preview/cursors.ts +++ b/apps/web/src/preview/components/cursors.ts diff --git a/apps/web/src/components/editor/panels/preview/guide-popover.tsx b/apps/web/src/preview/components/guide-popover.tsx index bcac8075..5bfa92ce 100644 --- a/apps/web/src/components/editor/panels/preview/guide-popover.tsx +++ b/apps/web/src/preview/components/guide-popover.tsx @@ -1,8 +1,8 @@ "use client";
import { AnimatePresence, motion } from "motion/react";
-import { GUIDE_REGISTRY, getGuideById } from "@/lib/guides";
-import { usePreviewStore } from "@/stores/preview-store";
+import { GUIDE_REGISTRY, getGuideById } from "@/guides";
+import { usePreviewStore } from "@/preview/preview-store";
import {
Popover,
PopoverContent,
diff --git a/apps/web/src/components/editor/panels/preview/handle-primitives.tsx b/apps/web/src/preview/components/handle-primitives.tsx index 33baf9fc..33baf9fc 100644 --- a/apps/web/src/components/editor/panels/preview/handle-primitives.tsx +++ b/apps/web/src/preview/components/handle-primitives.tsx diff --git a/apps/web/src/components/editor/panels/preview/index.tsx b/apps/web/src/preview/components/index.tsx index 369d854c..d3cb2a49 100644 --- a/apps/web/src/components/editor/panels/preview/index.tsx +++ b/apps/web/src/preview/components/index.tsx @@ -2,19 +2,21 @@ import { useCallback, useEffect, useMemo, useRef } from "react"; import useDeepCompareEffect from "use-deep-compare-effect"; -import { useEditor } from "@/hooks/use-editor"; +import { useEditor } from "@/editor/use-editor"; import { useRafLoop } from "@/hooks/use-raf-loop"; import { useContainerSize } from "@/hooks/use-container-size"; import { useFullscreen } from "@/hooks/use-fullscreen"; import { CanvasRenderer } from "@/services/renderer/canvas-renderer"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; +import { TICKS_PER_SECOND } from "@/wasm"; import type { RootNode } from "@/services/renderer/nodes/root-node"; import { buildScene } from "@/services/renderer/scene-builder"; +import { PreviewOverlayLayer } from "./overlay-layer"; import { PreviewInteractionOverlay } from "./preview-interaction-overlay"; -import { BookmarkNoteOverlay } from "./bookmark-note-overlay"; -import { GuideOverlay } from "./guide-overlay"; import { ContextMenu, ContextMenuTrigger } from "@/components/ui/context-menu"; -import { usePreviewStore } from "@/stores/preview-store"; +import type { + PreviewOverlayControl, + PreviewOverlayInstance, +} from "@/preview/overlays"; import { PreviewContextMenu } from "./context-menu"; import { PreviewToolbar } from "./toolbar"; import { @@ -53,7 +55,18 @@ function normalizeWheelDelta({ return delta; } -export function PreviewPanel() { +export function PreviewPanel({ + overlayControls, + overlayInstances, + onOverlayVisibilityChange, +}: { + overlayControls: PreviewOverlayControl[]; + overlayInstances: PreviewOverlayInstance[]; + onOverlayVisibilityChange: (params: { + overlayId: string; + isVisible: boolean; + }) => void; +}) { const containerRef = useRef<HTMLDivElement>(null); const { toggleFullscreen } = useFullscreen({ containerRef }); @@ -65,6 +78,9 @@ export function PreviewPanel() { <PreviewCanvas containerRef={containerRef} onToggleFullscreen={toggleFullscreen} + overlayControls={overlayControls} + overlayInstances={overlayInstances} + onOverlayVisibilityChange={onOverlayVisibilityChange} /> <RenderTreeController /> </div> @@ -103,9 +119,18 @@ function RenderTreeController() { function PreviewCanvas({ containerRef, onToggleFullscreen, + overlayControls, + overlayInstances, + onOverlayVisibilityChange, }: { containerRef: React.RefObject<HTMLElement | null>; onToggleFullscreen: () => void; + overlayControls: PreviewOverlayControl[]; + overlayInstances: PreviewOverlayInstance[]; + onOverlayVisibilityChange: (params: { + overlayId: string; + isVisible: boolean; + }) => void; }) { const canvasRef = useRef<HTMLCanvasElement>(null); const viewportRef = useRef<HTMLDivElement>(null); @@ -117,7 +142,6 @@ function PreviewCanvas({ const editor = useEditor(); const activeProject = useEditor((e) => e.project.getActive()); const renderTree = useEditor((e) => e.renderer.getRenderTree()); - const { overlays } = usePreviewStore(); const viewport = usePreviewViewportState({ canvasHeight: nativeHeight, canvasWidth: nativeWidth, @@ -132,16 +156,18 @@ function PreviewCanvas({ height: nativeHeight, fps: activeProject.settings.fps, }); - }, [nativeWidth, nativeHeight, activeProject.settings.fps.numerator, activeProject.settings.fps.denominator]); + }, [nativeWidth, nativeHeight, activeProject.settings.fps]); const render = useCallback(() => { if (canvasRef.current && renderTree && !renderingRef.current) { - const renderTime = Math.min( - editor.playback.getCurrentTime(), - editor.timeline.getLastFrameTime(), - ); - const ticksPerFrame = Math.round(TICKS_PER_SECOND * renderer.fps.denominator / renderer.fps.numerator); - const frame = Math.floor(renderTime / ticksPerFrame); + const renderTime = Math.min( + editor.playback.getCurrentTime(), + editor.timeline.getLastFrameTime(), + ); + const ticksPerFrame = Math.round( + (TICKS_PER_SECOND * renderer.fps.denominator) / renderer.fps.numerator, + ); + const frame = Math.floor(renderTime / ticksPerFrame); if ( frame !== lastFrameRef.current || @@ -161,7 +187,7 @@ function PreviewCanvas({ }); } } - }, [renderer, renderTree, editor.playback]); + }, [renderer, renderTree, editor.playback, editor.timeline]); useRafLoop(render); @@ -276,14 +302,22 @@ function PreviewCanvas({ : activeProject?.settings.background.color, }} /> - <GuideOverlay /> + <PreviewOverlayLayer + instances={overlayInstances} + plane="under-interaction" + /> <PreviewInteractionOverlay /> - {overlays.bookmarks && <BookmarkNoteOverlay />} + <PreviewOverlayLayer + instances={overlayInstances} + plane="over-interaction" + /> </div> </ContextMenuTrigger> <PreviewContextMenu onToggleFullscreen={onToggleFullscreen} containerRef={containerRef} + overlayControls={overlayControls} + onOverlayVisibilityChange={onOverlayVisibilityChange} /> </ContextMenu> </div> diff --git a/apps/web/src/components/editor/panels/preview/mask-handles.tsx b/apps/web/src/preview/components/mask-handles.tsx index d32b6ff9..340cab0a 100644 --- a/apps/web/src/components/editor/panels/preview/mask-handles.tsx +++ b/apps/web/src/preview/components/mask-handles.tsx @@ -1,9 +1,9 @@ "use client"; -import { PEN_CURSOR } from "@/components/editor/panels/preview/cursors"; -import { usePreviewViewport } from "@/components/editor/panels/preview/preview-viewport"; -import { useMaskHandles } from "@/hooks/use-mask-handles"; -import type { SnapLine } from "@/lib/preview/preview-snap"; +import { PEN_CURSOR } from "@/preview/components/cursors"; +import { usePreviewViewport } from "@/preview/components/preview-viewport"; +import { useMaskHandles } from "@/masks/use-mask-handles"; +import type { SnapLine } from "@/preview/preview-snap"; import { CornerHandle, CircleHandle, diff --git a/apps/web/src/preview/components/overlay-layer.tsx b/apps/web/src/preview/components/overlay-layer.tsx new file mode 100644 index 00000000..eb730f6e --- /dev/null +++ b/apps/web/src/preview/components/overlay-layer.tsx @@ -0,0 +1,197 @@ +"use client"; + +import type { + PreviewOverlayHudAnchor, + PreviewOverlayInstance, + PreviewOverlayPlane, +} from "@/preview/overlays"; +import { usePreviewViewport } from "./preview-viewport"; + +const HUD_ANCHOR_CLASS_NAMES: Record<PreviewOverlayHudAnchor, string> = { + "top-left": "absolute top-2 left-2 flex flex-col items-start gap-1.5", + "top-right": "absolute top-2 right-2 flex flex-col items-end gap-1.5", + "bottom-left": + "absolute bottom-2 left-2 flex flex-col-reverse items-start gap-1.5", + "bottom-right": + "absolute right-2 bottom-2 flex flex-col-reverse items-end gap-1.5", +}; + +function getPositionedMountStyle({ + instance, + baseStyle, + sceneLeft, + sceneTop, + sceneWidth, + sceneHeight, +}: { + instance: PreviewOverlayInstance; + baseStyle: { + pointerEvents: "none" | "auto"; + zIndex: number | undefined; + }; + sceneLeft: number; + sceneTop: number; + sceneWidth: number; + sceneHeight: number; +}) { + if (instance.mount.kind === "hud") { + return baseStyle; + } + + const isSceneMount = instance.mount.kind === "scene"; + const mount = instance.mount; + const hasExplicitBounds = + mount.x != null || + mount.y != null || + mount.width != null || + mount.height != null; + + return { + ...baseStyle, + left: (isSceneMount ? sceneLeft : 0) + (mount.x ?? 0), + top: (isSceneMount ? sceneTop : 0) + (mount.y ?? 0), + ...(hasExplicitBounds + ? { + ...(mount.width != null ? { width: mount.width } : {}), + ...(mount.height != null ? { height: mount.height } : {}), + } + : { + width: isSceneMount ? sceneWidth : "100%", + height: isSceneMount ? sceneHeight : "100%", + }), + } as const; +} + +export function PreviewOverlayLayer({ + instances, + plane, +}: { + instances: PreviewOverlayInstance[]; + plane: PreviewOverlayPlane; +}) { + const viewport = usePreviewViewport(); + const visibleInstances = instances + .filter((instance) => (instance.plane ?? "over-interaction") === plane) + .sort((left, right) => (left.zIndex ?? 0) - (right.zIndex ?? 0)); + + if (visibleInstances.length === 0) { + return null; + } + + const hudInstancesByAnchor = visibleInstances.reduce< + Record<PreviewOverlayHudAnchor, PreviewOverlayInstance[]> + >( + (groups, instance) => { + if (instance.mount.kind === "hud") { + groups[instance.mount.anchor].push(instance); + } + return groups; + }, + { + "top-left": [], + "top-right": [], + "bottom-left": [], + "bottom-right": [], + }, + ); + + return ( + <> + {visibleInstances.map((instance) => { + if (instance.mount.kind === "hud") { + return null; + } + + const pointerEvents = instance.pointerEvents ?? "none"; + const baseStyle = { + pointerEvents, + zIndex: instance.zIndex, + } as const; + const content = instance.render({ + sceneHeight: viewport.sceneHeight, + sceneWidth: viewport.sceneWidth, + }); + + switch (instance.mount.kind) { + case "scene": + return ( + <div + key={instance.id} + className="absolute" + style={getPositionedMountStyle({ + instance, + baseStyle, + sceneLeft: viewport.sceneLeft, + sceneTop: viewport.sceneTop, + sceneWidth: viewport.sceneWidth, + sceneHeight: viewport.sceneHeight, + })} + > + {content} + </div> + ); + + case "viewport": + return ( + <div + key={instance.id} + className="absolute" + style={getPositionedMountStyle({ + instance, + baseStyle, + sceneLeft: viewport.sceneLeft, + sceneTop: viewport.sceneTop, + sceneWidth: viewport.sceneWidth, + sceneHeight: viewport.sceneHeight, + })} + > + {content} + </div> + ); + + default: + return null; + } + })} + {( + Object.entries(hudInstancesByAnchor) as Array< + [PreviewOverlayHudAnchor, PreviewOverlayInstance[]] + > + ).map(([anchor, anchorInstances]) => { + if (anchorInstances.length === 0) { + return null; + } + + const sortedAnchorInstances = [...anchorInstances].sort( + (left, right) => + (left.mount.kind === "hud" ? (left.mount.order ?? 0) : 0) - + (right.mount.kind === "hud" ? (right.mount.order ?? 0) : 0), + ); + + return ( + <div key={anchor} className={HUD_ANCHOR_CLASS_NAMES[anchor]}> + {sortedAnchorInstances.map((instance) => { + const pointerEvents = instance.pointerEvents ?? "none"; + const content = instance.render({ + sceneHeight: viewport.sceneHeight, + sceneWidth: viewport.sceneWidth, + }); + + return ( + <div + key={instance.id} + style={{ + pointerEvents, + zIndex: instance.zIndex, + }} + > + {content} + </div> + ); + })} + </div> + ); + })} + </> + ); +} diff --git a/apps/web/src/components/editor/panels/preview/preview-interaction-overlay.tsx b/apps/web/src/preview/components/preview-interaction-overlay.tsx index 6ee4b584..e16a31d2 100644 --- a/apps/web/src/components/editor/panels/preview/preview-interaction-overlay.tsx +++ b/apps/web/src/preview/components/preview-interaction-overlay.tsx @@ -1,13 +1,13 @@ import { useState } from "react"; -import { usePreviewViewport } from "@/components/editor/panels/preview/preview-viewport"; -import { usePreviewInteraction } from "@/hooks/use-preview-interaction"; -import type { SnapLine } from "@/lib/preview/preview-snap"; +import { usePreviewViewport } from "@/preview/components/preview-viewport"; +import { usePreviewInteraction } from "@/preview/hooks/use-preview-interaction"; +import type { SnapLine } from "@/preview/preview-snap"; import { TransformHandles } from "./transform-handles"; import { MaskHandles } from "./mask-handles"; import { SnapGuides } from "./snap-guides"; import { TextEditOverlay } from "./text-edit-overlay"; -import { usePropertiesStore } from "../properties/stores/properties-store"; -import { useEditor } from "@/hooks/use-editor"; +import { usePropertiesStore } from "@/components/editor/panels/properties/stores/properties-store"; +import { useEditor } from "@/editor/use-editor"; export function PreviewInteractionOverlay() { const [snapLines, setSnapLines] = useState<SnapLine[]>([]); diff --git a/apps/web/src/components/editor/panels/preview/preview-viewport.tsx b/apps/web/src/preview/components/preview-viewport.tsx index 2ac6bd92..6dc9f084 100644 --- a/apps/web/src/components/editor/panels/preview/preview-viewport.tsx +++ b/apps/web/src/preview/components/preview-viewport.tsx @@ -15,9 +15,9 @@ import { positionToOverlay,
screenPixelsToLogicalThreshold,
screenToCanvas,
-} from "@/lib/preview/preview-coords";
+} from "@/preview/preview-coords";
import { clamp, isNearlyEqual } from "@/utils/math";
-import { PREVIEW_ZOOM } from "@/lib/preview/zoom";
+import { PREVIEW_ZOOM } from "@/preview/zoom";
const MIDDLE_MOUSE_BUTTON = 1;
const IS_AT_FIT_EPSILON = 0.001;
diff --git a/apps/web/src/components/editor/panels/preview/snap-guides.tsx b/apps/web/src/preview/components/snap-guides.tsx index ced5e921..5c2e1b9f 100644 --- a/apps/web/src/components/editor/panels/preview/snap-guides.tsx +++ b/apps/web/src/preview/components/snap-guides.tsx @@ -1,7 +1,7 @@ "use client";
-import { usePreviewViewport } from "@/components/editor/panels/preview/preview-viewport";
-import type { SnapLine } from "@/lib/preview/preview-snap";
+import { usePreviewViewport } from "@/preview/components/preview-viewport";
+import type { SnapLine } from "@/preview/preview-snap";
export function SnapGuides({ lines }: { lines: SnapLine[] }) {
const viewport = usePreviewViewport();
diff --git a/apps/web/src/components/editor/panels/preview/text-edit-overlay.tsx b/apps/web/src/preview/components/text-edit-overlay.tsx index eabbde8a..bc27c724 100644 --- a/apps/web/src/components/editor/panels/preview/text-edit-overlay.tsx +++ b/apps/web/src/preview/components/text-edit-overlay.tsx @@ -1,15 +1,15 @@ "use client"; import { useCallback, useEffect, useRef } from "react"; -import { usePreviewViewport } from "@/components/editor/panels/preview/preview-viewport"; -import { useEditor } from "@/hooks/use-editor"; -import type { TextElement } from "@/lib/timeline"; -import { DEFAULTS } from "@/lib/timeline/defaults"; +import { usePreviewViewport } from "@/preview/components/preview-viewport"; +import { useEditor } from "@/editor/use-editor"; +import type { TextElement } from "@/timeline"; +import { DEFAULTS } from "@/timeline/defaults"; import { getElementLocalTime, resolveTransformAtTime, -} from "@/lib/animation"; -import { resolveTextLayout } from "@/lib/text/primitives"; +} from "@/animation"; +import { resolveTextLayout } from "@/text/primitives"; export function TextEditOverlay({ trackId, diff --git a/apps/web/src/components/editor/panels/preview/toolbar.tsx b/apps/web/src/preview/components/toolbar.tsx index 9ee6c822..b44e42ef 100644 --- a/apps/web/src/components/editor/panels/preview/toolbar.tsx +++ b/apps/web/src/preview/components/toolbar.tsx @@ -1,9 +1,9 @@ "use client"; import { useState, useEffect } from "react"; -import { useEditor } from "@/hooks/use-editor"; +import { useEditor } from "@/editor/use-editor"; import { formatTimecode } from "opencut-wasm"; -import { invokeAction } from "@/lib/actions"; +import { invokeAction } from "@/actions"; import { EditableTimecode } from "@/components/editable-timecode"; import { Button } from "@/components/ui/button"; import { @@ -13,7 +13,7 @@ import { PlayIcon, } from "@hugeicons/core-free-icons"; import { HugeiconsIcon } from "@hugeicons/react"; -import { getGuideById } from "@/lib/guides"; +import { getGuideById } from "@/guides"; import { Separator } from "@/components/ui/separator"; import { Select, @@ -22,10 +22,10 @@ import { SelectItem, SelectSeparator, } from "@/components/ui/select"; -import { PREVIEW_ZOOM_PRESETS } from "@/lib/preview/zoom"; +import { PREVIEW_ZOOM_PRESETS } from "@/preview/zoom"; import { usePreviewViewport } from "./preview-viewport"; import { GridPopover } from "./guide-popover"; -import { usePreviewStore } from "@/stores/preview-store"; +import { usePreviewStore } from "@/preview/preview-store"; export function PreviewToolbar({ onToggleFullscreen, diff --git a/apps/web/src/components/editor/panels/preview/transform-handles.tsx b/apps/web/src/preview/components/transform-handles.tsx index 2b81b557..ad496cbc 100644 --- a/apps/web/src/components/editor/panels/preview/transform-handles.tsx +++ b/apps/web/src/preview/components/transform-handles.tsx @@ -1,16 +1,16 @@ "use client"; -import { usePreviewViewport } from "@/components/editor/panels/preview/preview-viewport"; -import { useTransformHandles } from "@/hooks/use-transform-handles"; -import { isVisualElement } from "@/lib/timeline/element-utils"; +import { usePreviewViewport } from "@/preview/components/preview-viewport"; +import { useTransformHandles } from "@/preview/hooks/use-transform-handles"; +import { isVisualElement } from "@/timeline/element-utils"; import { getCornerPosition, getEdgeHandlePosition, ROTATION_HANDLE_OFFSET, type Corner, type Edge, -} from "@/lib/preview/element-bounds"; -import type { OnSnapLinesChange } from "@/hooks/use-preview-interaction"; +} from "@/preview/element-bounds"; +import type { OnSnapLinesChange } from "@/preview/hooks/use-preview-interaction"; import { BoundingBoxOutline, CornerHandle, diff --git a/apps/web/src/lib/preview/element-bounds.ts b/apps/web/src/preview/element-bounds.ts index 39595c33..ac556181 100644 --- a/apps/web/src/lib/preview/element-bounds.ts +++ b/apps/web/src/preview/element-bounds.ts @@ -1,12 +1,12 @@ -import type { SceneTracks, TimelineElement } from "@/lib/timeline";
-import type { MediaAsset } from "@/lib/media/types";
-import { STICKER_INTRINSIC_SIZE_FALLBACK } from "@/lib/stickers/intrinsic-size";
-import { DEFAULT_GRAPHIC_SOURCE_SIZE } from "@/lib/graphics";
-import { measureTextElement } from "@/lib/text/measure-element";
+import type { SceneTracks, TimelineElement } from "@/timeline";
+import type { MediaAsset } from "@/media/types";
+import { STICKER_INTRINSIC_SIZE_FALLBACK } from "@/stickers/intrinsic-size";
+import { DEFAULT_GRAPHIC_SOURCE_SIZE } from "@/graphics";
+import { measureTextElement } from "@/text/measure-element";
import {
getElementLocalTime,
resolveTransformAtTime,
-} from "@/lib/animation";
+} from "@/animation";
export interface ElementBounds {
cx: number;
diff --git a/apps/web/src/lib/preview/hit-test.ts b/apps/web/src/preview/hit-test.ts index 2552922b..0f5fd45e 100644 --- a/apps/web/src/lib/preview/hit-test.ts +++ b/apps/web/src/preview/hit-test.ts @@ -1,5 +1,5 @@ import type { ElementWithBounds } from "./element-bounds"; -import type { ElementRef } from "@/lib/timeline/types"; +import type { ElementRef } from "@/timeline/types"; function pointInRotatedRect({ px, diff --git a/apps/web/src/hooks/use-preview-interaction.ts b/apps/web/src/preview/hooks/use-preview-interaction.ts index cfb3d527..78212e74 100644 --- a/apps/web/src/hooks/use-preview-interaction.ts +++ b/apps/web/src/preview/hooks/use-preview-interaction.ts @@ -1,25 +1,25 @@ import { useCallback, useEffect, useRef, useState } from "react"; -import { useEditor } from "@/hooks/use-editor"; +import { useEditor } from "@/editor/use-editor"; import { useShiftKey } from "@/hooks/use-shift-key"; -import { usePreviewViewport } from "@/components/editor/panels/preview/preview-viewport"; -import type { Transform } from "@/lib/rendering"; -import type { ElementRef, TextElement } from "@/lib/timeline"; +import { usePreviewViewport } from "@/preview/components/preview-viewport"; +import type { Transform } from "@/rendering"; +import type { ElementRef, TextElement } from "@/timeline"; import { getVisibleElementsWithBounds, type ElementWithBounds, -} from "@/lib/preview/element-bounds"; +} from "@/preview/element-bounds"; import { getHitElements, hitTest, resolvePreferredHit, -} from "@/lib/preview/hit-test"; -import { isVisualElement } from "@/lib/timeline/element-utils"; +} from "@/preview/hit-test"; +import { isVisualElement } from "@/timeline/element-utils"; import { SNAP_THRESHOLD_SCREEN_PIXELS, snapPosition, type SnapLine, -} from "@/lib/preview/preview-snap"; -import { registerCanceller } from "@/lib/cancel-interaction"; +} from "@/preview/preview-snap"; +import { registerCanceller } from "@/editor/cancel-interaction"; export type OnSnapLinesChange = (lines: SnapLine[]) => void; diff --git a/apps/web/src/hooks/use-transform-handles.ts b/apps/web/src/preview/hooks/use-transform-handles.ts index 7495a387..030e026c 100644 --- a/apps/web/src/hooks/use-transform-handles.ts +++ b/apps/web/src/preview/hooks/use-transform-handles.ts @@ -1,12 +1,12 @@ import { useCallback, useEffect, useRef, useState } from "react"; -import { usePreviewViewport } from "@/components/editor/panels/preview/preview-viewport"; -import type { OnSnapLinesChange } from "@/hooks/use-preview-interaction"; -import { useEditor } from "@/hooks/use-editor"; +import { usePreviewViewport } from "@/preview/components/preview-viewport"; +import type { OnSnapLinesChange } from "@/preview/hooks/use-preview-interaction"; +import { useEditor } from "@/editor/use-editor"; import { useShiftKey } from "@/hooks/use-shift-key"; import { getVisibleElementsWithBounds, type ElementWithBounds, -} from "@/lib/preview/element-bounds"; +} from "@/preview/element-bounds"; import { MIN_SCALE, SNAP_THRESHOLD_SCREEN_PIXELS, @@ -15,27 +15,23 @@ import { snapScaleAxes, type ScaleEdgePreference, type SnapLine, -} from "@/lib/preview/preview-snap"; -import { isVisualElement } from "@/lib/timeline/element-utils"; +} from "@/preview/preview-snap"; +import { isVisualElement } from "@/timeline/element-utils"; import { getElementLocalTime, hasKeyframesForPath, resolveTransformAtTime, setChannel, -} from "@/lib/animation"; -import type { Transform } from "@/lib/rendering"; -import type { ElementAnimations } from "@/lib/animation/types"; -import { registerCanceller } from "@/lib/cancel-interaction"; +} from "@/animation"; +import type { Transform } from "@/rendering"; +import type { ElementAnimations } from "@/animation/types"; +import { registerCanceller } from "@/editor/cancel-interaction"; type Corner = "top-left" | "top-right" | "bottom-left" | "bottom-right"; type Edge = "right" | "left" | "bottom"; type HandleType = Corner | Edge | "rotation"; -function getPreferredEdge({ - edge, -}: { - edge: Edge; -}): ScaleEdgePreference { +function getPreferredEdge({ edge }: { edge: Edge }): ScaleEdgePreference { return edge === "right" ? { right: true } : edge === "left" @@ -191,7 +187,12 @@ export function useTransformHandles({ releaseCapturedPointer(); }, }); - }, [activeHandle, clearActiveHandleState, editor.timeline, releaseCapturedPointer]); + }, [ + activeHandle, + clearActiveHandleState, + editor.timeline, + releaseCapturedPointer, + ]); const handleCornerPointerDown = useCallback( ({ event, corner }: { event: React.PointerEvent; corner: Corner }) => { @@ -333,11 +334,10 @@ export function useTransformHandles({ edge === "right" || edge === "left" ? "transform.scaleX" : "transform.scaleY"; - const shouldClearScaleAnimation = - hasKeyframesForPath({ - animations: element.animations, - propertyPath, - }); + const shouldClearScaleAnimation = hasKeyframesForPath({ + animations: element.animations, + propertyPath, + }); const animationsWithoutScale = shouldClearScaleAnimation ? setChannel({ animations: element.animations, @@ -605,18 +605,16 @@ export function useTransformHandles({ ); const handlePointerUp = useCallback(() => { - if ( - scaleStateRef.current || - rotationStateRef.current || - edgeScaleStateRef.current - ) { - editor.timeline.commitPreview(); - clearActiveHandleState(); - } - releaseCapturedPointer(); - }, - [clearActiveHandleState, editor, releaseCapturedPointer], - ); + if ( + scaleStateRef.current || + rotationStateRef.current || + edgeScaleStateRef.current + ) { + editor.timeline.commitPreview(); + clearActiveHandleState(); + } + releaseCapturedPointer(); + }, [clearActiveHandleState, editor, releaseCapturedPointer]); return { selectedWithBounds, diff --git a/apps/web/src/preview/overlays.ts b/apps/web/src/preview/overlays.ts new file mode 100644 index 00000000..6e5b0816 --- /dev/null +++ b/apps/web/src/preview/overlays.ts @@ -0,0 +1,108 @@ +import type { ReactNode } from "react"; + +export type PreviewOverlayHudAnchor = + | "top-left" + | "top-right" + | "bottom-left" + | "bottom-right"; + +export type PreviewOverlayMount = + | { + kind: "hud"; + anchor: PreviewOverlayHudAnchor; + order?: number; + } + | { + kind: "scene"; + x?: number; + y?: number; + width?: number; + height?: number; + } + | { + kind: "viewport"; + x?: number; + y?: number; + width?: number; + height?: number; + }; + +export type PreviewOverlayPlane = "under-interaction" | "over-interaction"; + +export interface PreviewOverlayRenderContext { + sceneHeight: number; + sceneWidth: number; +} + +export interface PreviewOverlayInstance { + id: string; + mount: PreviewOverlayMount; + plane?: PreviewOverlayPlane; + pointerEvents?: "none" | "auto"; + zIndex?: number; + render: (context: PreviewOverlayRenderContext) => ReactNode; +} + +export interface PreviewOverlayDefinition { + id: string; + label: string; + defaultVisible?: boolean; +} + +export interface PreviewOverlayControl extends PreviewOverlayDefinition { + isVisible: boolean; +} + +export interface PreviewOverlaySourceResult { + definitions: PreviewOverlayDefinition[]; + instances: PreviewOverlayInstance[]; +} + +export const EMPTY_PREVIEW_OVERLAY_SOURCE_RESULT: PreviewOverlaySourceResult = { + definitions: [], + instances: [], +}; + +export function isPreviewOverlayVisible({ + overlay, + overlays, +}: { + overlay: PreviewOverlayDefinition; + overlays: Record<string, boolean>; +}): boolean { + return overlays[overlay.id] ?? overlay.defaultVisible ?? true; +} + +export function createPreviewOverlayControl({ + overlay, + overlays, +}: { + overlay: PreviewOverlayDefinition; + overlays: Record<string, boolean>; +}): PreviewOverlayControl { + return { + ...overlay, + isVisible: isPreviewOverlayVisible({ overlay, overlays }), + }; +} + +export function mergePreviewOverlaySources({ + sources, +}: { + sources: PreviewOverlaySourceResult[]; +}): PreviewOverlaySourceResult { + const definitionsById = new Map<string, PreviewOverlayDefinition>(); + const instances: PreviewOverlayInstance[] = []; + + for (const source of sources) { + for (const definition of source.definitions) { + definitionsById.set(definition.id, definition); + } + instances.push(...source.instances); + } + + return { + definitions: [...definitionsById.values()], + instances, + }; +} diff --git a/apps/web/src/lib/preview/preview-coords.ts b/apps/web/src/preview/preview-coords.ts index 64184990..64184990 100644 --- a/apps/web/src/lib/preview/preview-coords.ts +++ b/apps/web/src/preview/preview-coords.ts diff --git a/apps/web/src/lib/preview/preview-snap.ts b/apps/web/src/preview/preview-snap.ts index 86d35390..86d35390 100644 --- a/apps/web/src/lib/preview/preview-snap.ts +++ b/apps/web/src/preview/preview-snap.ts diff --git a/apps/web/src/stores/preview-store.ts b/apps/web/src/preview/preview-store.ts index b5957cb5..0cfcbdff 100644 --- a/apps/web/src/stores/preview-store.ts +++ b/apps/web/src/preview/preview-store.ts @@ -1,112 +1,104 @@ -import { create } from "zustand";
-import { persist } from "zustand/middleware";
-import { isGuideId, type GuideId } from "@/lib/guides";
-import { DEFAULT_GRID_CONFIG } from "@/lib/guides/grid";
-import type { GridConfig } from "@/lib/guides/types";
-
-interface PreviewOverlaysState {
- bookmarks: boolean;
-}
-
-interface PersistedPreviewState {
- activeGuide?: string | null;
- layoutGuide?: {
- platform?: string | null;
- };
- overlays?: PreviewOverlaysState;
- gridConfig?: GridConfig;
-}
-
-interface PreviewState {
- activeGuide: GuideId | null;
- overlays: PreviewOverlaysState;
- gridConfig: GridConfig;
- toggleGuide: (guideId: GuideId) => void;
- setGridConfig: (config: Partial<GridConfig>) => void;
- setOverlayVisibility: ({
- overlay,
- isVisible,
- }: {
- overlay: keyof PreviewOverlaysState;
- isVisible: boolean;
- }) => void;
- toggleOverlayVisibility: ({
- overlay,
- }: {
- overlay: keyof PreviewOverlaysState;
- }) => void;
-}
-
-const DEFAULT_PREVIEW_OVERLAYS: PreviewOverlaysState = {
- bookmarks: true,
-};
-
-function getPersistedActiveGuide(
- state: PersistedPreviewState | undefined,
-): GuideId | null {
- const persistedGuide =
- state?.activeGuide ?? state?.layoutGuide?.platform ?? null;
-
- if (typeof persistedGuide !== "string") {
- return null;
- }
-
- return isGuideId(persistedGuide) ? persistedGuide : null;
-}
-
-export const usePreviewStore = create<PreviewState>()(
- persist(
- (set) => ({
- activeGuide: null,
- overlays: DEFAULT_PREVIEW_OVERLAYS,
- gridConfig: DEFAULT_GRID_CONFIG,
- toggleGuide: (guideId) => {
- set((state) => ({
- activeGuide: state.activeGuide === guideId ? null : guideId,
- }));
- },
- setGridConfig: (config) => {
- set((state) => ({
- gridConfig: { ...state.gridConfig, ...config },
- }));
- },
- setOverlayVisibility: ({ overlay, isVisible }) => {
- set((state) => ({
- overlays: {
- ...state.overlays,
- [overlay]: isVisible,
- },
- }));
- },
- toggleOverlayVisibility: ({ overlay }) => {
- set((state) => ({
- overlays: {
- ...state.overlays,
- [overlay]: !state.overlays[overlay],
- },
- }));
- },
- }),
- {
- name: "preview-settings",
- version: 4,
- migrate: (persistedState) => {
- const state = persistedState as PersistedPreviewState | undefined;
-
- return {
- activeGuide: getPersistedActiveGuide(state),
- overlays: state?.overlays ?? DEFAULT_PREVIEW_OVERLAYS,
- gridConfig: {
- rows: state?.gridConfig?.rows ?? DEFAULT_GRID_CONFIG.rows,
- cols: state?.gridConfig?.cols ?? DEFAULT_GRID_CONFIG.cols,
- },
- };
- },
- partialize: (state) => ({
- activeGuide: state.activeGuide,
- overlays: state.overlays,
- gridConfig: state.gridConfig,
- }),
- },
- ),
-);
+import { create } from "zustand"; +import { persist } from "zustand/middleware"; +import { isGuideId, type GuideId } from "@/guides"; +import { DEFAULT_GRID_CONFIG } from "@/guides/grid"; +import type { GridConfig } from "@/guides/types"; + +type PreviewOverlaysState = Record<string, boolean>; + +interface PersistedPreviewState { + activeGuide?: string | null; + layoutGuide?: { + platform?: string | null; + }; + overlays?: PreviewOverlaysState; + gridConfig?: GridConfig; +} + +interface PreviewState { + activeGuide: GuideId | null; + overlays: PreviewOverlaysState; + gridConfig: GridConfig; + toggleGuide: (guideId: GuideId) => void; + setGridConfig: (config: Partial<GridConfig>) => void; + setOverlayVisibility: ({ + overlayId, + isVisible, + }: { + overlayId: string; + isVisible: boolean; + }) => void; + toggleOverlayVisibility: ({ overlayId }: { overlayId: string }) => void; +} + +const DEFAULT_PREVIEW_OVERLAYS: PreviewOverlaysState = {}; + +function getPersistedActiveGuide( + state: PersistedPreviewState | undefined, +): GuideId | null { + const persistedGuide = + state?.activeGuide ?? state?.layoutGuide?.platform ?? null; + + if (typeof persistedGuide !== "string") { + return null; + } + + return isGuideId(persistedGuide) ? persistedGuide : null; +} + +export const usePreviewStore = create<PreviewState>()( + persist( + (set) => ({ + activeGuide: null, + overlays: DEFAULT_PREVIEW_OVERLAYS, + gridConfig: DEFAULT_GRID_CONFIG, + toggleGuide: (guideId) => { + set((state) => ({ + activeGuide: state.activeGuide === guideId ? null : guideId, + })); + }, + setGridConfig: (config) => { + set((state) => ({ + gridConfig: { ...state.gridConfig, ...config }, + })); + }, + setOverlayVisibility: ({ overlayId, isVisible }) => { + set((state) => ({ + overlays: { + ...state.overlays, + [overlayId]: isVisible, + }, + })); + }, + toggleOverlayVisibility: ({ overlayId }) => { + set((state) => ({ + overlays: { + ...state.overlays, + [overlayId]: !state.overlays[overlayId], + }, + })); + }, + }), + { + name: "preview-settings", + version: 6, + migrate: (persistedState) => { + const state = persistedState as PersistedPreviewState | undefined; + + return { + activeGuide: getPersistedActiveGuide(state), + overlays: DEFAULT_PREVIEW_OVERLAYS, + gridConfig: { + rows: state?.gridConfig?.rows ?? DEFAULT_GRID_CONFIG.rows, + cols: state?.gridConfig?.cols ?? DEFAULT_GRID_CONFIG.cols, + }, + }; + }, + partialize: (state) => ({ + activeGuide: state.activeGuide, + overlays: state.overlays, + gridConfig: state.gridConfig, + }), + }, + ), +); diff --git a/apps/web/src/lib/preview/zoom.ts b/apps/web/src/preview/zoom.ts index 9e6f5905..9e6f5905 100644 --- a/apps/web/src/lib/preview/zoom.ts +++ b/apps/web/src/preview/zoom.ts diff --git a/apps/web/src/components/editor/dialogs/delete-project-dialog.tsx b/apps/web/src/project/components/delete-project-dialog.tsx index 0086384e..0086384e 100644 --- a/apps/web/src/components/editor/dialogs/delete-project-dialog.tsx +++ b/apps/web/src/project/components/delete-project-dialog.tsx diff --git a/apps/web/src/components/editor/dialogs/migration-dialog.tsx b/apps/web/src/project/components/migration-dialog.tsx index 4c9550c2..4aac26d5 100644 --- a/apps/web/src/components/editor/dialogs/migration-dialog.tsx +++ b/apps/web/src/project/components/migration-dialog.tsx @@ -7,7 +7,7 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; -import { useEditor } from "@/hooks/use-editor"; +import { useEditor } from "@/editor/use-editor"; import { Loader2 } from "lucide-react"; export function MigrationDialog() { diff --git a/apps/web/src/components/editor/dialogs/project-info-dialog.tsx b/apps/web/src/project/components/project-info-dialog.tsx index ed4eb990..f0ea364e 100644 --- a/apps/web/src/components/editor/dialogs/project-info-dialog.tsx +++ b/apps/web/src/project/components/project-info-dialog.tsx @@ -6,7 +6,7 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; -import type { TProjectMetadata } from "@/lib/project/types"; +import type { TProjectMetadata } from "@/project/types"; import { formatDate } from "@/utils/date"; import { formatTimecode, mediaTimeToSeconds } from "opencut-wasm"; import { Button } from "@/components/ui/button"; diff --git a/apps/web/src/components/editor/dialogs/rename-project-dialog.tsx b/apps/web/src/project/components/rename-project-dialog.tsx index 73024d71..73024d71 100644 --- a/apps/web/src/components/editor/dialogs/rename-project-dialog.tsx +++ b/apps/web/src/project/components/rename-project-dialog.tsx diff --git a/apps/web/src/lib/project/types.ts b/apps/web/src/project/types.ts index 0a454e07..42e844f6 100644 --- a/apps/web/src/lib/project/types.ts +++ b/apps/web/src/project/types.ts @@ -1,5 +1,5 @@ import type { FrameRate } from "opencut-wasm"; -import type { TScene } from "@/lib/timeline/types"; +import type { TScene } from "@/timeline/types"; export type TBackground = | { diff --git a/apps/web/src/components/editor/panels/properties/tabs/blending-tab.tsx b/apps/web/src/rendering/components/blending-tab.tsx index f8e5397d..3e0db46c 100644 --- a/apps/web/src/components/editor/panels/properties/tabs/blending-tab.tsx +++ b/apps/web/src/rendering/components/blending-tab.tsx @@ -1,9 +1,9 @@ -import { useEditor } from "@/hooks/use-editor"; +import { useEditor } from "@/editor/use-editor"; import { clamp } from "@/utils/math"; import { NumberField } from "@/components/ui/number-field"; import { OcCheckerboardIcon } from "@/components/icons"; import { Fragment, useRef } from "react"; -import { useMenuPreview } from "@/hooks/use-menu-preview"; +import { useMenuPreview } from "@/editor/use-menu-preview"; import { Section, SectionContent, @@ -19,16 +19,16 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import type { BlendMode } from "@/lib/rendering"; -import type { ElementType } from "@/lib/timeline"; -import type { ElementAnimations } from "@/lib/animation/types"; +import type { BlendMode } from "@/rendering"; +import type { ElementType } from "@/timeline"; +import type { ElementAnimations } from "@/animation/types"; import { HugeiconsIcon } from "@hugeicons/react"; import { RainDropIcon } from "@hugeicons/core-free-icons"; -import { KeyframeToggle } from "../components/keyframe-toggle"; -import { useKeyframedNumberProperty } from "../hooks/use-keyframed-number-property"; -import { useElementPlayhead } from "../hooks/use-element-playhead"; -import { resolveOpacityAtTime } from "@/lib/animation"; -import { DEFAULTS } from "@/lib/timeline/defaults"; +import { KeyframeToggle } from "@/components/editor/panels/properties/components/keyframe-toggle"; +import { useKeyframedNumberProperty } from "@/components/editor/panels/properties/hooks/use-keyframed-number-property"; +import { useElementPlayhead } from "@/components/editor/panels/properties/hooks/use-element-playhead"; +import { resolveOpacityAtTime } from "@/animation"; +import { DEFAULTS } from "@/timeline/defaults"; import { isPropertyAtDefault } from "./transform-tab"; type BlendingElement = { diff --git a/apps/web/src/components/editor/panels/properties/tabs/transform-tab.tsx b/apps/web/src/rendering/components/transform-tab.tsx index c3cef2d6..60d4c7df 100644 --- a/apps/web/src/components/editor/panels/properties/tabs/transform-tab.tsx +++ b/apps/web/src/rendering/components/transform-tab.tsx @@ -1,7 +1,7 @@ import { NumberField } from "@/components/ui/number-field"; -import { useEditor } from "@/hooks/use-editor"; +import { useEditor } from "@/editor/use-editor"; import { clamp, isNearlyEqual } from "@/utils/math"; -import type { VisualElement } from "@/lib/timeline"; +import type { VisualElement } from "@/timeline"; import { Section, SectionContent, @@ -21,12 +21,12 @@ import { getGroupKeyframesAtTime, hasGroupKeyframeAtTime, resolveTransformAtTime, -} from "@/lib/animation"; -import { DEFAULTS } from "@/lib/timeline/defaults"; -import { useElementPlayhead } from "../hooks/use-element-playhead"; -import { KeyframeToggle } from "../components/keyframe-toggle"; -import { useKeyframedNumberProperty } from "../hooks/use-keyframed-number-property"; -import { usePropertiesStore } from "../stores/properties-store"; +} from "@/animation"; +import { DEFAULTS } from "@/timeline/defaults"; +import { useElementPlayhead } from "@/components/editor/panels/properties/hooks/use-element-playhead"; +import { KeyframeToggle } from "@/components/editor/panels/properties/components/keyframe-toggle"; +import { useKeyframedNumberProperty } from "@/components/editor/panels/properties/hooks/use-keyframed-number-property"; +import { usePropertiesStore } from "@/components/editor/panels/properties/stores/properties-store"; export function parseNumericInput({ input }: { input: string }): number | null { const parsed = parseFloat(input); diff --git a/apps/web/src/lib/rendering.ts b/apps/web/src/rendering/index.ts index c5555371..c5555371 100644 --- a/apps/web/src/lib/rendering.ts +++ b/apps/web/src/rendering/index.ts diff --git a/apps/web/src/lib/retime/__tests__/resolve.test.ts b/apps/web/src/retime/__tests__/resolve.test.ts index 2fbd6675..bb7b9a6d 100644 --- a/apps/web/src/lib/retime/__tests__/resolve.test.ts +++ b/apps/web/src/retime/__tests__/resolve.test.ts @@ -4,8 +4,8 @@ import { getEffectiveRateAt,
getSourceTimeAtClipTime,
getTimelineDurationForSourceSpan,
-} from "@/lib/retime";
-import type { RetimeConfig } from "@/lib/timeline";
+} from "@/retime";
+import type { RetimeConfig } from "@/timeline";
const twoX: RetimeConfig = { rate: 2 };
const halfX: RetimeConfig = { rate: 0.5 };
diff --git a/apps/web/src/lib/retime/__tests__/split.test.ts b/apps/web/src/retime/__tests__/split.test.ts index a00eadac..3c7bef63 100644 --- a/apps/web/src/lib/retime/__tests__/split.test.ts +++ b/apps/web/src/retime/__tests__/split.test.ts @@ -2,8 +2,8 @@ import { describe, expect, test } from "bun:test"; import {
getSourceSpanAtClipTime,
splitRetimeAtClipTime,
-} from "@/lib/retime";
-import type { RetimeConfig } from "@/lib/timeline";
+} from "@/retime";
+import type { RetimeConfig } from "@/timeline";
describe("retime split", () => {
test("measures source span at a clip time", () => {
diff --git a/apps/web/src/lib/retime/audio-stretch.ts b/apps/web/src/retime/audio-stretch.ts index 92902dff..7df67f36 100644 --- a/apps/web/src/lib/retime/audio-stretch.ts +++ b/apps/web/src/retime/audio-stretch.ts @@ -1,6 +1,6 @@ import { PitchShifter } from "soundtouchjs";
-import { clampRetimeRate, shouldMaintainPitch } from "@/lib/retime/rate";
-import type { RetimeConfig } from "@/lib/timeline";
+import { clampRetimeRate, shouldMaintainPitch } from "@/retime/rate";
+import type { RetimeConfig } from "@/timeline";
import { getSourceTimeAtClipTime } from "./resolve";
const RATE_EPSILON = 1e-6;
diff --git a/apps/web/src/lib/retime/index.ts b/apps/web/src/retime/index.ts index 2ab1d10f..2ab1d10f 100644 --- a/apps/web/src/lib/retime/index.ts +++ b/apps/web/src/retime/index.ts diff --git a/apps/web/src/lib/retime/presets.ts b/apps/web/src/retime/presets.ts index fbd391ff..816c8675 100644 --- a/apps/web/src/lib/retime/presets.ts +++ b/apps/web/src/retime/presets.ts @@ -1,5 +1,5 @@ -import type { RetimeConfig } from "@/lib/timeline";
-import { clampRetimeRate } from "@/lib/retime/rate";
+import type { RetimeConfig } from "@/timeline";
+import { clampRetimeRate } from "@/retime/rate";
export function buildConstantRetime({
rate,
diff --git a/apps/web/src/lib/retime/rate.ts b/apps/web/src/retime/rate.ts index 75ea530b..75ea530b 100644 --- a/apps/web/src/lib/retime/rate.ts +++ b/apps/web/src/retime/rate.ts diff --git a/apps/web/src/lib/retime/resolve.ts b/apps/web/src/retime/resolve.ts index 5e7236d5..0dea9dd8 100644 --- a/apps/web/src/lib/retime/resolve.ts +++ b/apps/web/src/retime/resolve.ts @@ -1,5 +1,5 @@ -import type { RetimeConfig } from "@/lib/timeline";
-import { clampRetimeRate } from "@/lib/retime/rate";
+import type { RetimeConfig } from "@/timeline";
+import { clampRetimeRate } from "@/retime/rate";
function getSafeRate({ rate }: { rate: number }): number {
return clampRetimeRate({ rate });
diff --git a/apps/web/src/lib/retime/split.ts b/apps/web/src/retime/split.ts index 2635590e..1b7360cc 100644 --- a/apps/web/src/lib/retime/split.ts +++ b/apps/web/src/retime/split.ts @@ -1,4 +1,4 @@ -import type { RetimeConfig } from "@/lib/timeline";
+import type { RetimeConfig } from "@/timeline";
import { getSourceTimeAtClipTime } from "./resolve";
export function getSourceSpanAtClipTime({
diff --git a/apps/web/src/lib/ripple/apply.ts b/apps/web/src/ripple/apply.ts index f96e4600..4ad83529 100644 --- a/apps/web/src/lib/ripple/apply.ts +++ b/apps/web/src/ripple/apply.ts @@ -1,4 +1,4 @@ -import type { SceneTracks, TimelineTrack } from "@/lib/timeline/types"; +import type { SceneTracks, TimelineTrack } from "@/timeline/types"; import { rippleShiftElements } from "./shift"; export interface RippleAdjustment { diff --git a/apps/web/src/lib/ripple/diff.ts b/apps/web/src/ripple/diff.ts index 49238738..e450d5ae 100644 --- a/apps/web/src/lib/ripple/diff.ts +++ b/apps/web/src/ripple/diff.ts @@ -1,4 +1,4 @@ -import type { SceneTracks, TimelineElement, TimelineTrack } from "@/lib/timeline/types"; +import type { SceneTracks, TimelineElement, TimelineTrack } from "@/timeline/types"; import type { RippleAdjustment } from "./apply"; interface Interval { diff --git a/apps/web/src/lib/ripple/index.ts b/apps/web/src/ripple/index.ts index 5a14de64..5a14de64 100644 --- a/apps/web/src/lib/ripple/index.ts +++ b/apps/web/src/ripple/index.ts diff --git a/apps/web/src/lib/ripple/shift.ts b/apps/web/src/ripple/shift.ts index 03ab4a67..a88d2b2d 100644 --- a/apps/web/src/lib/ripple/shift.ts +++ b/apps/web/src/ripple/shift.ts @@ -1,4 +1,4 @@ -import type { TimelineElement } from "@/lib/timeline/types"; +import type { TimelineElement } from "@/timeline/types"; export function rippleShiftElements<TElement extends TimelineElement>({ elements, diff --git a/apps/web/src/lib/selection/attributes.ts b/apps/web/src/selection/attributes.ts index a1aee842..a1aee842 100644 --- a/apps/web/src/lib/selection/attributes.ts +++ b/apps/web/src/selection/attributes.ts diff --git a/apps/web/src/lib/selection/context.tsx b/apps/web/src/selection/context.tsx index 8e87aa40..8e87aa40 100644 --- a/apps/web/src/lib/selection/context.tsx +++ b/apps/web/src/selection/context.tsx diff --git a/apps/web/src/lib/selection/editor-selection.ts b/apps/web/src/selection/editor-selection.ts index e9563b97..21ab6466 100644 --- a/apps/web/src/lib/selection/editor-selection.ts +++ b/apps/web/src/selection/editor-selection.ts @@ -1,5 +1,5 @@ -import type { SelectedKeyframeRef } from "@/lib/animation/types"; -import type { ElementRef } from "@/lib/timeline/types"; +import type { SelectedKeyframeRef } from "@/animation/types"; +import type { ElementRef } from "@/timeline/types"; export interface SelectedMaskPointSelection { trackId: string; diff --git a/apps/web/src/lib/selection/hit-testing.ts b/apps/web/src/selection/hit-testing.ts index d8186238..d8186238 100644 --- a/apps/web/src/lib/selection/hit-testing.ts +++ b/apps/web/src/selection/hit-testing.ts diff --git a/apps/web/src/lib/selection/hooks/use-box-select.ts b/apps/web/src/selection/hooks/use-box-select.ts index ab8464ec..90a584b5 100644 --- a/apps/web/src/lib/selection/hooks/use-box-select.ts +++ b/apps/web/src/selection/hooks/use-box-select.ts @@ -1,170 +1,170 @@ -"use client";
-
-import { useCallback, useEffect, useRef, useState } from "react";
-import type {
- BoxSelectionSnapshot,
- ResolveIntersections,
-} from "@/lib/selection/types";
-
-interface SelectionBoxState<TId> extends BoxSelectionSnapshot<TId> {
- startPos: { x: number; y: number };
- currentPos: { x: number; y: number };
- isActive: boolean;
- isAdditive: boolean;
-}
-
-export function useBoxSelect<TId>({
- containerRef,
- resolveIntersections,
- selectedIds,
- anchorId,
- onSelectionChange,
- shouldStartSelection,
- getIsAdditiveSelection,
- isEnabled = true,
-}: {
- containerRef: React.RefObject<HTMLElement | null>;
- resolveIntersections: ResolveIntersections<TId>;
- selectedIds: TId[];
- anchorId: TId | null;
- onSelectionChange: (state: {
- intersectedIds: TId[];
- initialSelectedIds: TId[];
- initialAnchorId: TId | null;
- isAdditive: boolean;
- }) => void;
- shouldStartSelection?: (event: React.MouseEvent<Element>) => boolean;
- getIsAdditiveSelection?: (event: React.MouseEvent<Element>) => boolean;
- isEnabled?: boolean;
-}) {
- const [selectionBox, setSelectionBox] = useState<SelectionBoxState<TId> | null>(
- null,
- );
- const justFinishedSelectingRef = useRef(false);
- const shouldStartSelectionCheck = shouldStartSelection ?? (() => true);
- const getIsAdditiveSelectionCheck =
- getIsAdditiveSelection ??
- ((event: React.MouseEvent<Element>) => event.ctrlKey || event.metaKey);
-
- const handleMouseDown = useCallback(
- (event: React.MouseEvent<Element>) => {
- const canStartSelection = shouldStartSelectionCheck(event);
- if (!isEnabled || event.button !== 0 || !canStartSelection) {
- return;
- }
-
- setSelectionBox({
- startPos: { x: event.clientX, y: event.clientY },
- currentPos: { x: event.clientX, y: event.clientY },
- isActive: false,
- isAdditive: getIsAdditiveSelectionCheck(event),
- initialSelectedIds: selectedIds,
- initialAnchorId: anchorId,
- });
- },
- [
- anchorId,
- getIsAdditiveSelectionCheck,
- isEnabled,
- selectedIds,
- shouldStartSelectionCheck,
- ],
- );
-
- const updateSelection = useCallback(
- ({
- startPos,
- currentPos,
- isAdditive,
- initialSelectedIds,
- initialAnchorId,
- }: SelectionBoxState<TId>) => {
- const intersectedIds = resolveIntersections({
- startPos,
- currentPos,
- });
- onSelectionChange({
- intersectedIds,
- initialSelectedIds,
- initialAnchorId,
- isAdditive,
- });
- },
- [onSelectionChange, resolveIntersections],
- );
-
- useEffect(() => {
- if (!selectionBox) {
- return;
- }
-
- const handleMouseMove = ({ clientX, clientY }: MouseEvent) => {
- const deltaX = Math.abs(clientX - selectionBox.startPos.x);
- const deltaY = Math.abs(clientY - selectionBox.startPos.y);
- const nextSelectionBox = {
- ...selectionBox,
- currentPos: { x: clientX, y: clientY },
- isActive: deltaX > 5 || deltaY > 5 || selectionBox.isActive,
- };
-
- setSelectionBox(nextSelectionBox);
-
- if (!nextSelectionBox.isActive) {
- return;
- }
-
- updateSelection(nextSelectionBox);
- };
-
- const handleMouseUp = () => {
- if (selectionBox.isActive) {
- justFinishedSelectingRef.current = true;
- requestAnimationFrame(() => {
- justFinishedSelectingRef.current = false;
- });
- }
-
- setSelectionBox(null);
- };
-
- window.addEventListener("mousemove", handleMouseMove);
- window.addEventListener("mouseup", handleMouseUp);
-
- return () => {
- window.removeEventListener("mousemove", handleMouseMove);
- window.removeEventListener("mouseup", handleMouseUp);
- };
- }, [selectionBox, updateSelection]);
-
- useEffect(() => {
- if (!selectionBox) {
- return;
- }
-
- const previousBodyUserSelect = document.body.style.userSelect;
- const previousContainerUserSelect = containerRef.current?.style.userSelect ?? "";
-
- document.body.style.userSelect = "none";
- if (containerRef.current) {
- containerRef.current.style.userSelect = "none";
- }
-
- return () => {
- document.body.style.userSelect = previousBodyUserSelect;
- if (containerRef.current) {
- containerRef.current.style.userSelect = previousContainerUserSelect;
- }
- };
- }, [containerRef, selectionBox]);
-
- const shouldIgnoreClick = useCallback(() => {
- return justFinishedSelectingRef.current;
- }, []);
-
- return {
- selectionBox,
- handleMouseDown,
- isSelecting: selectionBox?.isActive ?? false,
- shouldIgnoreClick,
- };
-}
+"use client"; + +import { useCallback, useEffect, useRef, useState } from "react"; +import type { + BoxSelectionSnapshot, + ResolveIntersections, +} from "@/selection/types"; + +interface SelectionBoxState<TId> extends BoxSelectionSnapshot<TId> { + startPos: { x: number; y: number }; + currentPos: { x: number; y: number }; + isActive: boolean; + isAdditive: boolean; +} + +export function useBoxSelect<TId>({ + containerRef, + resolveIntersections, + selectedIds, + anchorId, + onSelectionChange, + shouldStartSelection, + getIsAdditiveSelection, + isEnabled = true, +}: { + containerRef: React.RefObject<HTMLElement | null>; + resolveIntersections: ResolveIntersections<TId>; + selectedIds: TId[]; + anchorId: TId | null; + onSelectionChange: (state: { + intersectedIds: TId[]; + initialSelectedIds: TId[]; + initialAnchorId: TId | null; + isAdditive: boolean; + }) => void; + shouldStartSelection?: (event: React.MouseEvent<Element>) => boolean; + getIsAdditiveSelection?: (event: React.MouseEvent<Element>) => boolean; + isEnabled?: boolean; +}) { + const [selectionBox, setSelectionBox] = + useState<SelectionBoxState<TId> | null>(null); + const justFinishedSelectingRef = useRef(false); + const shouldStartSelectionCheck = shouldStartSelection ?? (() => true); + const getIsAdditiveSelectionCheck = + getIsAdditiveSelection ?? + ((event: React.MouseEvent<Element>) => event.ctrlKey || event.metaKey); + + const handleMouseDown = useCallback( + (event: React.MouseEvent<Element>) => { + const canStartSelection = shouldStartSelectionCheck(event); + if (!isEnabled || event.button !== 0 || !canStartSelection) { + return; + } + + setSelectionBox({ + startPos: { x: event.clientX, y: event.clientY }, + currentPos: { x: event.clientX, y: event.clientY }, + isActive: false, + isAdditive: getIsAdditiveSelectionCheck(event), + initialSelectedIds: selectedIds, + initialAnchorId: anchorId, + }); + }, + [ + anchorId, + getIsAdditiveSelectionCheck, + isEnabled, + selectedIds, + shouldStartSelectionCheck, + ], + ); + + const updateSelection = useCallback( + ({ + startPos, + currentPos, + isAdditive, + initialSelectedIds, + initialAnchorId, + }: SelectionBoxState<TId>) => { + const intersectedIds = resolveIntersections({ + startPos, + currentPos, + }); + onSelectionChange({ + intersectedIds, + initialSelectedIds, + initialAnchorId, + isAdditive, + }); + }, + [onSelectionChange, resolveIntersections], + ); + + useEffect(() => { + if (!selectionBox) { + return; + } + + const handleMouseMove = ({ clientX, clientY }: MouseEvent) => { + const deltaX = Math.abs(clientX - selectionBox.startPos.x); + const deltaY = Math.abs(clientY - selectionBox.startPos.y); + const nextSelectionBox = { + ...selectionBox, + currentPos: { x: clientX, y: clientY }, + isActive: deltaX > 5 || deltaY > 5 || selectionBox.isActive, + }; + + setSelectionBox(nextSelectionBox); + + if (!nextSelectionBox.isActive) { + return; + } + + updateSelection(nextSelectionBox); + }; + + const handleMouseUp = () => { + if (selectionBox.isActive) { + justFinishedSelectingRef.current = true; + requestAnimationFrame(() => { + justFinishedSelectingRef.current = false; + }); + } + + setSelectionBox(null); + }; + + window.addEventListener("mousemove", handleMouseMove); + window.addEventListener("mouseup", handleMouseUp); + + return () => { + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("mouseup", handleMouseUp); + }; + }, [selectionBox, updateSelection]); + + useEffect(() => { + if (!selectionBox) { + return; + } + + const previousBodyUserSelect = document.body.style.userSelect; + const previousContainerUserSelect = + containerRef.current?.style.userSelect ?? ""; + + document.body.style.userSelect = "none"; + if (containerRef.current) { + containerRef.current.style.userSelect = "none"; + } + + return () => { + document.body.style.userSelect = previousBodyUserSelect; + if (containerRef.current) { + containerRef.current.style.userSelect = previousContainerUserSelect; + } + }; + }, [containerRef, selectionBox]); + + const shouldIgnoreClick = useCallback(() => { + return justFinishedSelectingRef.current; + }, []); + + return { + selectionBox, + handleMouseDown, + isSelecting: selectionBox?.isActive ?? false, + shouldIgnoreClick, + }; +} diff --git a/apps/web/src/lib/selection/hooks/use-selection-scope.ts b/apps/web/src/selection/hooks/use-selection-scope.ts index a7090c5d..ad4d2ce8 100644 --- a/apps/web/src/lib/selection/hooks/use-selection-scope.ts +++ b/apps/web/src/selection/hooks/use-selection-scope.ts @@ -1,6 +1,6 @@ import { useEffect, useRef } from "react";
-import { useSelectionContext } from "@/lib/selection/context";
-import { activateScope, type ScopeEntry } from "@/lib/selection/scope";
+import { useSelectionContext } from "@/selection/context";
+import { activateScope, type ScopeEntry } from "@/selection/scope";
export function useSelectionScope() {
const { selectedIds, clearSelection } = useSelectionContext();
diff --git a/apps/web/src/selection/index.ts b/apps/web/src/selection/index.ts new file mode 100644 index 00000000..9d62731b --- /dev/null +++ b/apps/web/src/selection/index.ts @@ -0,0 +1,4 @@ +export { SelectableItem } from "@/selection/selectable-item";
+export { SelectableSurface } from "@/selection/selectable-surface";
+export { useSelection } from "@/selection/context";
+export { useSelectionScope } from "@/selection/hooks/use-selection-scope";
diff --git a/apps/web/src/lib/selection/scope.ts b/apps/web/src/selection/scope.ts index 22c7710c..22c7710c 100644 --- a/apps/web/src/lib/selection/scope.ts +++ b/apps/web/src/selection/scope.ts diff --git a/apps/web/src/lib/selection/selectable-item.tsx b/apps/web/src/selection/selectable-item.tsx index 03264753..cc9e1dbf 100644 --- a/apps/web/src/lib/selection/selectable-item.tsx +++ b/apps/web/src/selection/selectable-item.tsx @@ -1,9 +1,9 @@ "use client";
import { forwardRef, useCallback } from "react";
-import { useSelectionContext } from "@/lib/selection/context";
-import { SELECTABLE_ITEM_ATTRIBUTE } from "@/lib/selection/attributes";
-import type { SelectableItemProps } from "@/lib/selection/types";
+import { useSelectionContext } from "@/selection/context";
+import { SELECTABLE_ITEM_ATTRIBUTE } from "@/selection/attributes";
+import type { SelectableItemProps } from "@/selection/types";
import { cn } from "@/utils/ui";
function setForwardedRef<T>(ref: React.ForwardedRef<T>, value: T | null) {
diff --git a/apps/web/src/lib/selection/selectable-surface.tsx b/apps/web/src/selection/selectable-surface.tsx index 012bc486..24166daf 100644 --- a/apps/web/src/lib/selection/selectable-surface.tsx +++ b/apps/web/src/selection/selectable-surface.tsx @@ -1,10 +1,10 @@ "use client";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
-import { SelectionBox } from "@/lib/selection/selection-box";
-import { SelectionContext } from "@/lib/selection/context";
-import { SELECTABLE_ITEM_ATTRIBUTE } from "@/lib/selection/attributes";
-import { resolveElementIntersections } from "@/lib/selection/hit-testing";
+import { SelectionBox } from "@/selection/selection-box";
+import { SelectionContext } from "@/selection/context";
+import { SELECTABLE_ITEM_ATTRIBUTE } from "@/selection/attributes";
+import { resolveElementIntersections } from "@/selection/hit-testing";
import {
applyBoxSelection,
clearSelection,
@@ -13,12 +13,12 @@ import { replaceSelection,
selectRange,
toggleSelection,
-} from "@/lib/selection/state";
+} from "@/selection/state";
import type {
SelectableSurfaceProps,
SelectionState,
-} from "@/lib/selection/types";
-import { useBoxSelect } from "@/lib/selection/hooks/use-box-select";
+} from "@/selection/types";
+import { useBoxSelect } from "@/selection/hooks/use-box-select";
import { cn } from "@/utils/ui";
export function SelectableSurface({
diff --git a/apps/web/src/lib/selection/selection-box.tsx b/apps/web/src/selection/selection-box.tsx index e30f574d..e30f574d 100644 --- a/apps/web/src/lib/selection/selection-box.tsx +++ b/apps/web/src/selection/selection-box.tsx diff --git a/apps/web/src/lib/selection/state.ts b/apps/web/src/selection/state.ts index b0ba3089..b44578a1 100644 --- a/apps/web/src/lib/selection/state.ts +++ b/apps/web/src/selection/state.ts @@ -1,7 +1,7 @@ import type {
BoxSelectionChange,
SelectionState,
-} from "@/lib/selection/types";
+} from "@/selection/types";
function dedupeIds({ ids }: { ids: string[] }) {
return [...new Set(ids)];
diff --git a/apps/web/src/lib/selection/types.ts b/apps/web/src/selection/types.ts index 31520788..31520788 100644 --- a/apps/web/src/lib/selection/types.ts +++ b/apps/web/src/selection/types.ts diff --git a/apps/web/src/services/renderer/compositor/frame-descriptor.ts b/apps/web/src/services/renderer/compositor/frame-descriptor.ts index f93eae27..bae9203f 100644 --- a/apps/web/src/services/renderer/compositor/frame-descriptor.ts +++ b/apps/web/src/services/renderer/compositor/frame-descriptor.ts @@ -1,5 +1,5 @@ -import { drawCssBackground } from "@/lib/gradients"; -import { masksRegistry } from "@/lib/masks"; +import { drawCssBackground } from "@/gradients"; +import { masksRegistry } from "@/masks"; import type { AnyBaseNode } from "../nodes/base-node"; import type { CanvasRenderer } from "../canvas-renderer"; import { createOffscreenCanvas } from "../canvas-utils"; @@ -19,7 +19,7 @@ import type { LayerMaskDescriptor, QuadTransformDescriptor, } from "./types"; -import { DEFAULT_GRAPHIC_SOURCE_SIZE } from "@/lib/graphics"; +import { DEFAULT_GRAPHIC_SOURCE_SIZE } from "@/graphics"; export type TextureUploadDescriptor = { id: string; diff --git a/apps/web/src/services/renderer/compositor/types.ts b/apps/web/src/services/renderer/compositor/types.ts index 5d9322e8..af5a58c2 100644 --- a/apps/web/src/services/renderer/compositor/types.ts +++ b/apps/web/src/services/renderer/compositor/types.ts @@ -1,5 +1,5 @@ -import type { BlendMode } from "@/lib/rendering"; -import type { EffectPass } from "@/lib/effects/types"; +import type { BlendMode } from "@/rendering"; +import type { EffectPass } from "@/effects/types"; export type FrameDescriptor = { width: number; diff --git a/apps/web/src/services/renderer/effect-preview.ts b/apps/web/src/services/renderer/effect-preview.ts index 06e64d0e..c4cacde0 100644 --- a/apps/web/src/services/renderer/effect-preview.ts +++ b/apps/web/src/services/renderer/effect-preview.ts @@ -1,7 +1,7 @@ import { createOffscreenCanvas } from "./canvas-utils"; -import { effectsRegistry, resolveEffectPasses } from "@/lib/effects"; -import { buildDefaultParamValues } from "@/lib/registry"; -import type { ParamValues } from "@/lib/params"; +import { effectsRegistry, resolveEffectPasses } from "@/effects"; +import { buildDefaultParamValues } from "@/params/registry"; +import type { ParamValues } from "@/params"; import { gpuRenderer } from "./gpu-renderer"; const PREVIEW_SIZE = 160; diff --git a/apps/web/src/services/renderer/gpu-renderer.ts b/apps/web/src/services/renderer/gpu-renderer.ts index c11c3015..c61b6b98 100644 --- a/apps/web/src/services/renderer/gpu-renderer.ts +++ b/apps/web/src/services/renderer/gpu-renderer.ts @@ -3,7 +3,7 @@ import { applyMaskFeather as applyMaskFeatherWasm, initializeGpu, } from "opencut-wasm"; -import type { EffectPass, EffectUniformValue } from "@/lib/effects/types"; +import type { EffectPass, EffectUniformValue } from "@/effects/types"; let gpuAvailable = false; let initPromise: Promise<void> | null = null; diff --git a/apps/web/src/services/renderer/nodes/blur-background-node.ts b/apps/web/src/services/renderer/nodes/blur-background-node.ts index d20e2533..d83c1cf5 100644 --- a/apps/web/src/services/renderer/nodes/blur-background-node.ts +++ b/apps/web/src/services/renderer/nodes/blur-background-node.ts @@ -1,5 +1,5 @@ -import type { EffectPass } from "@/lib/effects/types"; -import type { RetimeConfig } from "@/lib/timeline"; +import type { EffectPass } from "@/effects/types"; +import type { RetimeConfig } from "@/timeline"; import { BaseNode } from "./base-node"; export type BlurBackgroundNodeParams = { diff --git a/apps/web/src/services/renderer/nodes/effect-layer-node.ts b/apps/web/src/services/renderer/nodes/effect-layer-node.ts index d6e70fcf..d1b51fa3 100644 --- a/apps/web/src/services/renderer/nodes/effect-layer-node.ts +++ b/apps/web/src/services/renderer/nodes/effect-layer-node.ts @@ -1,5 +1,5 @@ -import type { EffectPass } from "@/lib/effects/types"; -import type { ParamValues } from "@/lib/params"; +import type { EffectPass } from "@/effects/types"; +import type { ParamValues } from "@/params"; import { BaseNode } from "./base-node"; export type EffectLayerNodeParams = { diff --git a/apps/web/src/services/renderer/nodes/graphic-node.ts b/apps/web/src/services/renderer/nodes/graphic-node.ts index 494d2179..2d022510 100644 --- a/apps/web/src/services/renderer/nodes/graphic-node.ts +++ b/apps/web/src/services/renderer/nodes/graphic-node.ts @@ -3,8 +3,8 @@ import { DEFAULT_GRAPHIC_SOURCE_SIZE, getGraphicDefinition, registerDefaultGraphics, -} from "@/lib/graphics"; -import type { ParamValues } from "@/lib/params"; +} from "@/graphics"; +import type { ParamValues } from "@/params"; import { VisualNode, type ResolvedVisualNodeState, diff --git a/apps/web/src/services/renderer/nodes/sticker-node.ts b/apps/web/src/services/renderer/nodes/sticker-node.ts index 3c155cb6..3d22799e 100644 --- a/apps/web/src/services/renderer/nodes/sticker-node.ts +++ b/apps/web/src/services/renderer/nodes/sticker-node.ts @@ -1,4 +1,4 @@ -import { resolveStickerId } from "@/lib/stickers"; +import { resolveStickerId } from "@/stickers"; import { VisualNode, type ResolvedVisualSourceNodeState, diff --git a/apps/web/src/services/renderer/nodes/text-node.ts b/apps/web/src/services/renderer/nodes/text-node.ts index 86384328..d5755315 100644 --- a/apps/web/src/services/renderer/nodes/text-node.ts +++ b/apps/web/src/services/renderer/nodes/text-node.ts @@ -1,11 +1,11 @@ import { BaseNode } from "./base-node"; -import type { TextElement } from "@/lib/timeline"; -import type { EffectPass } from "@/lib/effects/types"; -import type { Transform } from "@/lib/rendering"; +import type { TextElement } from "@/timeline"; +import type { EffectPass } from "@/effects/types"; +import type { Transform } from "@/rendering"; import { drawMeasuredTextLayout, -} from "@/lib/text/primitives"; -import type { MeasuredTextElement } from "@/lib/text/measure-element"; +} from "@/text/primitives"; +import type { MeasuredTextElement } from "@/text/measure-element"; export type TextNodeParams = TextElement & { canvasCenter: { x: number; y: number }; diff --git a/apps/web/src/services/renderer/nodes/visual-node.ts b/apps/web/src/services/renderer/nodes/visual-node.ts index 010f146e..995e598d 100644 --- a/apps/web/src/services/renderer/nodes/visual-node.ts +++ b/apps/web/src/services/renderer/nodes/visual-node.ts @@ -1,8 +1,8 @@ import { BaseNode } from "./base-node"; -import type { Effect, EffectPass } from "@/lib/effects/types"; -import type { Mask } from "@/lib/masks/types"; -import type { BlendMode, Transform } from "@/lib/rendering"; -import type { RetimeConfig, VisualElement } from "@/lib/timeline"; +import type { Effect, EffectPass } from "@/effects/types"; +import type { Mask } from "@/masks/types"; +import type { BlendMode, Transform } from "@/rendering"; +import type { RetimeConfig, VisualElement } from "@/timeline"; export interface VisualNodeParams { duration: number; diff --git a/apps/web/src/services/renderer/resolve.ts b/apps/web/src/services/renderer/resolve.ts index 3f30f0d5..578c9603 100644 --- a/apps/web/src/services/renderer/resolve.ts +++ b/apps/web/src/services/renderer/resolve.ts @@ -5,20 +5,20 @@ import { resolveGraphicParamsAtTime, resolveOpacityAtTime, resolveTransformAtTime, -} from "@/lib/animation"; -import { resolveEffectParamsAtTime } from "@/lib/animation/effect-param-channel"; +} from "@/animation"; +import { resolveEffectParamsAtTime } from "@/animation/effect-param-channel"; import { buildGaussianBlurPasses, intensityToSigma, -} from "@/lib/effects/definitions/blur"; -import { effectsRegistry, resolveEffectPasses } from "@/lib/effects"; -import type { Effect, EffectPass } from "@/lib/effects/types"; -import { getSourceTimeAtClipTime } from "@/lib/retime"; -import { DEFAULT_GRAPHIC_SOURCE_SIZE } from "@/lib/graphics"; +} from "@/effects/definitions/blur"; +import { effectsRegistry, resolveEffectPasses } from "@/effects"; +import type { Effect, EffectPass } from "@/effects/types"; +import { getSourceTimeAtClipTime } from "@/retime"; +import { DEFAULT_GRAPHIC_SOURCE_SIZE } from "@/graphics"; import { getTextMeasurementContext, measureTextElement, -} from "@/lib/text/measure-element"; +} from "@/text/measure-element"; import { videoCache } from "@/services/video-cache/service"; import type { CanvasRenderer } from "./canvas-renderer"; import type { AnyBaseNode } from "./nodes/base-node"; diff --git a/apps/web/src/services/renderer/scene-builder.ts b/apps/web/src/services/renderer/scene-builder.ts index 47e6139c..e79c62d4 100644 --- a/apps/web/src/services/renderer/scene-builder.ts +++ b/apps/web/src/services/renderer/scene-builder.ts @@ -1,5 +1,5 @@ -import type { SceneTracks, TimelineTrack } from "@/lib/timeline"; -import type { MediaAsset } from "@/lib/media/types"; +import type { SceneTracks, TimelineTrack } from "@/timeline"; +import type { MediaAsset } from "@/media/types"; import { RootNode } from "./nodes/root-node"; import { VideoNode } from "./nodes/video-node"; import { ImageNode } from "./nodes/image-node"; @@ -10,8 +10,8 @@ import { ColorNode } from "./nodes/color-node"; import { BlurBackgroundNode } from "./nodes/blur-background-node"; import { EffectLayerNode } from "./nodes/effect-layer-node"; import type { AnyBaseNode } from "./nodes/base-node"; -import type { TBackground, TCanvasSize } from "@/lib/project/types"; -import { DEFAULT_BACKGROUND_BLUR_INTENSITY } from "@/lib/background/blur"; +import type { TBackground, TCanvasSize } from "@/project/types"; +import { DEFAULT_BACKGROUND_BLUR_INTENSITY } from "@/background/blur"; const PREVIEW_MAX_IMAGE_SIZE = 2048; diff --git a/apps/web/src/services/renderer/scene-exporter.ts b/apps/web/src/services/renderer/scene-exporter.ts index b19005cb..1b3267a7 100644 --- a/apps/web/src/services/renderer/scene-exporter.ts +++ b/apps/web/src/services/renderer/scene-exporter.ts @@ -14,10 +14,10 @@ import { } from "mediabunny"; import type { FrameRate } from "opencut-wasm"; import { mediaTimeToSeconds } from "opencut-wasm"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; -import { frameRateToFloat } from "@/lib/fps/utils"; +import { TICKS_PER_SECOND } from "@/wasm"; +import { frameRateToFloat } from "@/fps/utils"; import type { RootNode } from "./nodes/root-node"; -import type { ExportFormat, ExportQuality } from "@/lib/export"; +import type { ExportFormat, ExportQuality } from "@/export"; import { CanvasRenderer } from "./canvas-renderer"; type ExportParams = { diff --git a/apps/web/src/components/editor/dialogs/storage-persistence-dialog.tsx b/apps/web/src/services/storage/components/storage-persistence-dialog.tsx index 0e854a93..4544ef1d 100644 --- a/apps/web/src/components/editor/dialogs/storage-persistence-dialog.tsx +++ b/apps/web/src/services/storage/components/storage-persistence-dialog.tsx @@ -9,7 +9,7 @@ import { DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
-import { useStoragePersistence } from "@/hooks/use-storage-persistence";
+import { useStoragePersistence } from "@/services/storage/use-storage-persistence";
export function StoragePersistenceDialog() {
const { showDialog, onConfirm, onDismiss } = useStoragePersistence();
diff --git a/apps/web/src/services/storage/migrations/__tests__/v1-to-v2.test.ts b/apps/web/src/services/storage/migrations/__tests__/v1-to-v2.test.ts index 9cea9a2e..6d0c37fb 100644 --- a/apps/web/src/services/storage/migrations/__tests__/v1-to-v2.test.ts +++ b/apps/web/src/services/storage/migrations/__tests__/v1-to-v2.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test"; -import { DEFAULT_BACKGROUND_BLUR_INTENSITY } from "@/lib/background/blur"; -import { DEFAULT_BACKGROUND_COLOR } from "@/lib/background/color"; -import { DEFAULT_CANVAS_SIZE } from "@/lib/canvas/sizes"; +import { DEFAULT_BACKGROUND_BLUR_INTENSITY } from "@/background/blur"; +import { DEFAULT_BACKGROUND_COLOR } from "@/background/color"; +import { DEFAULT_CANVAS_SIZE } from "@/canvas/sizes"; const DEFAULT_FPS = 30; import type { MediaAssetData } from "@/services/storage/types"; import { getProjectId, transformProjectV1ToV2 } from "../transformers/v1-to-v2"; diff --git a/apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts b/apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts index a19269cd..c4b64e50 100644 --- a/apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts +++ b/apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts @@ -1,6 +1,6 @@ -import { DEFAULT_BACKGROUND_BLUR_INTENSITY } from "@/lib/background/blur"; -import { DEFAULT_BACKGROUND_COLOR } from "@/lib/background/color"; -import { DEFAULT_CANVAS_SIZE } from "@/lib/canvas/sizes"; +import { DEFAULT_BACKGROUND_BLUR_INTENSITY } from "@/background/blur"; +import { DEFAULT_BACKGROUND_COLOR } from "@/background/color"; +import { DEFAULT_CANVAS_SIZE } from "@/canvas/sizes"; const DEFAULT_FPS = 30; import { IndexedDBAdapter } from "@/services/storage/indexeddb-adapter"; import type { MediaAssetData } from "@/services/storage/types"; diff --git a/apps/web/src/services/storage/migrations/transformers/v14-to-v15.ts b/apps/web/src/services/storage/migrations/transformers/v14-to-v15.ts index 36e3d9a8..5f3c01e9 100644 --- a/apps/web/src/services/storage/migrations/transformers/v14-to-v15.ts +++ b/apps/web/src/services/storage/migrations/transformers/v14-to-v15.ts @@ -1,4 +1,4 @@ -import { STICKER_INTRINSIC_SIZE_FALLBACK } from "@/lib/stickers/intrinsic-size";
+import { STICKER_INTRINSIC_SIZE_FALLBACK } from "@/stickers/intrinsic-size";
import type { MigrationResult, ProjectRecord } from "./types";
import { getProjectId, isRecord } from "./utils";
diff --git a/apps/web/src/services/storage/migrations/transformers/v17-to-v18.ts b/apps/web/src/services/storage/migrations/transformers/v17-to-v18.ts index 5dcf6feb..3ef6cde9 100644 --- a/apps/web/src/services/storage/migrations/transformers/v17-to-v18.ts +++ b/apps/web/src/services/storage/migrations/transformers/v17-to-v18.ts @@ -1,6 +1,6 @@ import type { MigrationResult, ProjectRecord } from "./types";
-import { VOLUME_DB_MIN } from "@/lib/timeline/audio-constants";
-import { clampDb } from "@/lib/timeline/audio-state";
+import { VOLUME_DB_MIN } from "@/timeline/audio-constants";
+import { clampDb } from "@/timeline/audio-state";
import { getProjectId, isRecord } from "./utils";
export function transformProjectV17ToV18({
diff --git a/apps/web/src/services/storage/migrations/transformers/v20-to-v21.ts b/apps/web/src/services/storage/migrations/transformers/v20-to-v21.ts index f5194bda..6c30e5c8 100644 --- a/apps/web/src/services/storage/migrations/transformers/v20-to-v21.ts +++ b/apps/web/src/services/storage/migrations/transformers/v20-to-v21.ts @@ -1,4 +1,4 @@ -import { INTENSITY_TO_SIGMA_DIVISOR } from "@/lib/effects/definitions/blur"; +import { INTENSITY_TO_SIGMA_DIVISOR } from "@/effects/definitions/blur"; import type { MigrationResult, ProjectRecord } from "./types"; import { getProjectId, isRecord } from "./utils"; diff --git a/apps/web/src/services/storage/migrations/transformers/v22-to-v23.ts b/apps/web/src/services/storage/migrations/transformers/v22-to-v23.ts index cde1294e..5e3adc33 100644 --- a/apps/web/src/services/storage/migrations/transformers/v22-to-v23.ts +++ b/apps/web/src/services/storage/migrations/transformers/v22-to-v23.ts @@ -1,7 +1,7 @@ import type { MigrationResult, ProjectRecord } from "./types"; import { getProjectId, isRecord } from "./utils"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; +import { TICKS_PER_SECOND } from "@/wasm"; const ARBITRARY_FPS_DENOMINATOR = 1_000_000; const STANDARD_FRAME_RATES = [ { value: 24_000 / 1_001, numerator: 24_000, denominator: 1_001 }, diff --git a/apps/web/src/services/storage/migrations/transformers/v26-to-v27.ts b/apps/web/src/services/storage/migrations/transformers/v26-to-v27.ts index 186ae1e9..820b0087 100644 --- a/apps/web/src/services/storage/migrations/transformers/v26-to-v27.ts +++ b/apps/web/src/services/storage/migrations/transformers/v26-to-v27.ts @@ -1,4 +1,4 @@ -import { parseCustomMaskPath } from "@/lib/masks/custom-path"; +import { parseCustomMaskPath } from "@/masks/custom-path"; import type { MigrationResult, ProjectRecord } from "./types"; import { getProjectId, isRecord } from "./utils"; diff --git a/apps/web/src/services/storage/service.ts b/apps/web/src/services/storage/service.ts index 1aea0297..8ef77846 100644 --- a/apps/web/src/services/storage/service.ts +++ b/apps/web/src/services/storage/service.ts @@ -1,6 +1,6 @@ -import type { TProject, TProjectMetadata } from "@/lib/project/types"; -import { getProjectDurationFromScenes } from "@/lib/scenes"; -import type { MediaAsset } from "@/lib/media/types"; +import type { TProject, TProjectMetadata } from "@/project/types"; +import { getProjectDurationFromScenes } from "@/timeline/scenes"; +import type { MediaAsset } from "@/media/types"; import { IndexedDBAdapter } from "./indexeddb-adapter"; import { OPFSAdapter } from "./opfs-adapter"; import { @@ -16,12 +16,12 @@ import type { SerializedProject, SerializedScene, } from "./types"; -import type { SavedSoundsData, SavedSound, SoundEffect } from "@/lib/sounds/types"; +import type { SavedSoundsData, SavedSound, SoundEffect } from "@/sounds/types"; import { migrations, runStorageMigrations, } from "@/services/storage/migrations"; -import type { Bookmark, SceneTracks, TScene } from "@/lib/timeline"; +import type { Bookmark, SceneTracks, TScene } from "@/timeline"; function normalizeBookmarks({ raw }: { raw: unknown }): Bookmark[] { if (!Array.isArray(raw)) return []; diff --git a/apps/web/src/services/storage/types.ts b/apps/web/src/services/storage/types.ts index 1dcf6545..58c14b49 100644 --- a/apps/web/src/services/storage/types.ts +++ b/apps/web/src/services/storage/types.ts @@ -1,10 +1,10 @@ -import type { MediaType } from "@/lib/media/types"; +import type { MediaType } from "@/media/types"; import type { TProject, TProjectMetadata, TTimelineViewState, -} from "@/lib/project/types"; -import type { TScene } from "@/lib/timeline"; +} from "@/project/types"; +import type { TScene } from "@/timeline"; export interface StorageAdapter<T> { get(key: string): Promise<T | null>; diff --git a/apps/web/src/hooks/storage/use-local-storage.ts b/apps/web/src/services/storage/use-local-storage.ts index d0188ab3..d0188ab3 100644 --- a/apps/web/src/hooks/storage/use-local-storage.ts +++ b/apps/web/src/services/storage/use-local-storage.ts diff --git a/apps/web/src/hooks/use-storage-persistence.ts b/apps/web/src/services/storage/use-storage-persistence.ts index a026b510..a026b510 100644 --- a/apps/web/src/hooks/use-storage-persistence.ts +++ b/apps/web/src/services/storage/use-storage-persistence.ts diff --git a/apps/web/src/services/transcription/service.ts b/apps/web/src/services/transcription/service.ts index 443a4230..9fa17a87 100644 --- a/apps/web/src/services/transcription/service.ts +++ b/apps/web/src/services/transcription/service.ts @@ -3,11 +3,11 @@ import type { TranscriptionResult,
TranscriptionProgress,
TranscriptionModelId,
-} from "@/lib/transcription/types";
+} from "@/transcription/types";
import {
DEFAULT_TRANSCRIPTION_MODEL,
TRANSCRIPTION_MODELS,
-} from "@/lib/transcription/models";
+} from "@/transcription/models";
import type { WorkerMessage, WorkerResponse } from "./worker";
type ProgressCallback = (progress: TranscriptionProgress) => void;
diff --git a/apps/web/src/services/transcription/worker.ts b/apps/web/src/services/transcription/worker.ts index 928b580e..0ced6484 100644 --- a/apps/web/src/services/transcription/worker.ts +++ b/apps/web/src/services/transcription/worker.ts @@ -3,11 +3,11 @@ import { type AutomaticSpeechRecognitionPipeline,
type AutomaticSpeechRecognitionOutput,
} from "@huggingface/transformers";
-import type { TranscriptionSegment } from "@/lib/transcription/types";
+import type { TranscriptionSegment } from "@/transcription/types";
import {
DEFAULT_CHUNK_LENGTH_SECONDS,
DEFAULT_STRIDE_SECONDS,
-} from "@/lib/transcription/audio";
+} from "@/transcription/audio";
export type WorkerMessage =
| { type: "init"; modelId: string }
diff --git a/apps/web/src/services/waveform-cache/service.ts b/apps/web/src/services/waveform-cache/service.ts index 3b7d34f3..f37cf98a 100644 --- a/apps/web/src/services/waveform-cache/service.ts +++ b/apps/web/src/services/waveform-cache/service.ts @@ -1,10 +1,10 @@ "use client"; -import { createAudioContext } from "@/lib/media/audio"; +import { createAudioContext } from "@/media/audio"; import { buildSourceWaveformSummary, type SourceWaveformSummary, -} from "@/lib/media/waveform-summary"; +} from "@/media/waveform-summary"; interface GetSourceWaveformSummaryArgs { sourceKey: string; diff --git a/apps/web/src/lib/site/brand.ts b/apps/web/src/site/brand.ts index 2701608c..2701608c 100644 --- a/apps/web/src/lib/site/brand.ts +++ b/apps/web/src/site/brand.ts diff --git a/apps/web/src/lib/site/external-tools.ts b/apps/web/src/site/external-tools.ts index 610ea158..610ea158 100644 --- a/apps/web/src/lib/site/external-tools.ts +++ b/apps/web/src/site/external-tools.ts diff --git a/apps/web/src/lib/site/social.ts b/apps/web/src/site/social.ts index 571e45d5..571e45d5 100644 --- a/apps/web/src/lib/site/social.ts +++ b/apps/web/src/site/social.ts diff --git a/apps/web/src/lib/site/sponsors.ts b/apps/web/src/site/sponsors.ts index 2bd201b7..2bd201b7 100644 --- a/apps/web/src/lib/site/sponsors.ts +++ b/apps/web/src/site/sponsors.ts diff --git a/apps/web/src/components/editor/panels/assets/views/sounds.tsx b/apps/web/src/sounds/components/assets-view.tsx index c7a1029c..fc7528c8 100644 --- a/apps/web/src/components/editor/panels/assets/views/sounds.tsx +++ b/apps/web/src/sounds/components/assets-view.tsx @@ -22,9 +22,9 @@ import { ScrollArea } from "@/components/ui/scroll-area"; import { Separator } from "@/components/ui/separator";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useInfiniteScroll } from "@/hooks/use-infinite-scroll";
-import { useSoundSearch } from "@/hooks/use-sound-search";
-import { useSoundsStore } from "@/stores/sounds-store";
-import type { SavedSound, SoundEffect } from "@/lib/sounds/types";
+import { useSoundSearch } from "@/sounds/use-sound-search";
+import { useSoundsStore } from "@/sounds/sounds-store";
+import type { SavedSound, SoundEffect } from "@/sounds/types";
import { cn } from "@/utils/ui";
import {
FavouriteIcon,
diff --git a/apps/web/src/stores/sounds-store.ts b/apps/web/src/sounds/sounds-store.ts index b4b5772b..b9b3f070 100644 --- a/apps/web/src/stores/sounds-store.ts +++ b/apps/web/src/sounds/sounds-store.ts @@ -1,9 +1,9 @@ import { create } from "zustand"; -import type { SoundEffect, SavedSound } from "@/lib/sounds/types"; +import type { SoundEffect, SavedSound } from "@/sounds/types"; import { storageService } from "@/services/storage/service"; import { toast } from "sonner"; import { EditorCore } from "@/core"; -import { buildLibraryAudioElement } from "@/lib/timeline/element-utils"; +import { buildLibraryAudioElement } from "@/timeline/element-utils"; interface SoundsStore { topSoundEffects: SoundEffect[]; diff --git a/apps/web/src/lib/sounds/types.ts b/apps/web/src/sounds/types.ts index f7526846..f7526846 100644 --- a/apps/web/src/lib/sounds/types.ts +++ b/apps/web/src/sounds/types.ts diff --git a/apps/web/src/hooks/use-sound-search.ts b/apps/web/src/sounds/use-sound-search.ts index 263a01e2..79bc3110 100644 --- a/apps/web/src/hooks/use-sound-search.ts +++ b/apps/web/src/sounds/use-sound-search.ts @@ -1,5 +1,5 @@ import { useEffect } from "react";
-import { useSoundsStore } from "@/stores/sounds-store";
+import { useSoundsStore } from "@/sounds/sounds-store";
export function useSoundSearch({
query,
diff --git a/apps/web/src/components/editor/panels/properties/tabs/speed-tab.tsx b/apps/web/src/speed/components/speed-tab.tsx index cb10aac3..2f9231d6 100644 --- a/apps/web/src/components/editor/panels/properties/tabs/speed-tab.tsx +++ b/apps/web/src/speed/components/speed-tab.tsx @@ -1,18 +1,18 @@ import { useRef } from "react";
-import { useEditor } from "@/hooks/use-editor";
+import { useEditor } from "@/editor/use-editor";
import { NumberField } from "@/components/ui/number-field";
import { Switch } from "@/components/ui/switch";
import { HugeiconsIcon } from "@hugeicons/react";
import { DashboardSpeed02Icon } from "@hugeicons/core-free-icons";
-import { buildConstantRetime } from "@/lib/retime";
+import { buildConstantRetime } from "@/retime";
import {
DEFAULT_RETIME_RATE,
MIN_RETIME_RATE,
MAX_RETIME_RATE,
clampRetimeRate,
canMaintainPitch,
-} from "@/lib/retime/rate";
-import type { AudioElement, VideoElement } from "@/lib/timeline";
+} from "@/retime/rate";
+import type { AudioElement, VideoElement } from "@/timeline";
import {
Section,
SectionContent,
@@ -21,7 +21,7 @@ import { SectionHeader,
SectionTitle,
} from "@/components/section";
-import { usePropertyDraft } from "../hooks/use-property-draft";
+import { usePropertyDraft } from "@/components/editor/panels/properties/hooks/use-property-draft";
import {
formatNumberForDisplay,
getFractionDigitsForStep,
diff --git a/apps/web/src/lib/stickers/__tests__/sticker-id.test.ts b/apps/web/src/stickers/__tests__/sticker-id.test.ts index 26458a7c..26458a7c 100644 --- a/apps/web/src/lib/stickers/__tests__/sticker-id.test.ts +++ b/apps/web/src/stickers/__tests__/sticker-id.test.ts diff --git a/apps/web/src/lib/stickers/categories.ts b/apps/web/src/stickers/categories.ts index e8a8b71b..e8a8b71b 100644 --- a/apps/web/src/lib/stickers/categories.ts +++ b/apps/web/src/stickers/categories.ts diff --git a/apps/web/src/components/editor/panels/assets/views/stickers.tsx b/apps/web/src/stickers/components/assets-view.tsx index 318aaccb..aab84c12 100644 --- a/apps/web/src/components/editor/panels/assets/views/stickers.tsx +++ b/apps/web/src/stickers/components/assets-view.tsx @@ -9,22 +9,22 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Spinner } from "@/components/ui/spinner"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { useEditor } from "@/hooks/use-editor"; -import { resolveStickerIntrinsicSize } from "@/lib/stickers"; +import { useEditor } from "@/editor/use-editor"; +import { resolveStickerIntrinsicSize } from "@/stickers"; import { buildGraphicElement, buildStickerElement, -} from "@/lib/timeline/element-utils"; -import { STICKER_CATEGORIES } from "@/lib/stickers/categories"; -import { getRegionLabel, resolveQueryToRegions } from "@/lib/stickers"; -import { parseShapeStickerId } from "@/lib/stickers/providers/shapes"; -import type { TimelineDragData } from "@/lib/timeline/drag"; +} from "@/timeline/element-utils"; +import { STICKER_CATEGORIES } from "@/stickers/categories"; +import { getRegionLabel, resolveQueryToRegions } from "@/stickers"; +import { parseShapeStickerId } from "@/stickers/providers/shapes"; +import type { TimelineDragData } from "@/timeline/drag"; import type { StickerBrowseSection, StickerCategory, StickerItem as StickerData, -} from "@/lib/stickers"; -import { useStickersStore } from "@/stores/stickers-store"; +} from "@/stickers"; +import { useStickersStore } from "@/stickers/stickers-store"; import { cn } from "@/utils/ui"; import { HappyIcon, diff --git a/apps/web/src/lib/stickers/index.ts b/apps/web/src/stickers/index.ts index 2d71d097..d41e277d 100644 --- a/apps/web/src/lib/stickers/index.ts +++ b/apps/web/src/stickers/index.ts @@ -1,8 +1,8 @@ import { STICKER_CATEGORIES, -} from "@/lib/stickers/categories"; -import { STICKER_INTRINSIC_SIZE_FALLBACK } from "@/lib/stickers/intrinsic-size"; -import type { StickerCategory } from "@/lib/stickers/types"; +} from "@/stickers/categories"; +import { STICKER_INTRINSIC_SIZE_FALLBACK } from "@/stickers/intrinsic-size"; +import type { StickerCategory } from "@/stickers/types"; import { stickersRegistry } from "./registry"; import { resolveStickerId } from "./resolver"; import { registerDefaultStickerProviders } from "./providers"; diff --git a/apps/web/src/lib/stickers/intrinsic-size.ts b/apps/web/src/stickers/intrinsic-size.ts index 43436237..43436237 100644 --- a/apps/web/src/lib/stickers/intrinsic-size.ts +++ b/apps/web/src/stickers/intrinsic-size.ts diff --git a/apps/web/src/lib/stickers/providers/countries-data.ts b/apps/web/src/stickers/providers/countries-data.ts index 05551699..05551699 100644 --- a/apps/web/src/lib/stickers/providers/countries-data.ts +++ b/apps/web/src/stickers/providers/countries-data.ts diff --git a/apps/web/src/lib/stickers/providers/flags.ts b/apps/web/src/stickers/providers/flags.ts index 0cca292f..0cca292f 100644 --- a/apps/web/src/lib/stickers/providers/flags.ts +++ b/apps/web/src/stickers/providers/flags.ts diff --git a/apps/web/src/lib/stickers/providers/index.ts b/apps/web/src/stickers/providers/index.ts index 830a1129..ca23963c 100644 --- a/apps/web/src/lib/stickers/providers/index.ts +++ b/apps/web/src/stickers/providers/index.ts @@ -1,5 +1,5 @@ import { stickersRegistry } from "../registry";
-import type { StickerProvider } from "@/lib/stickers/types";
+import type { StickerProvider } from "@/stickers/types";
import { flagsProvider } from "./flags";
import { logosProvider } from "./logos";
import { shapesProvider } from "./shapes";
diff --git a/apps/web/src/lib/stickers/providers/logos.ts b/apps/web/src/stickers/providers/logos.ts index 474c64da..474c64da 100644 --- a/apps/web/src/lib/stickers/providers/logos.ts +++ b/apps/web/src/stickers/providers/logos.ts diff --git a/apps/web/src/lib/stickers/providers/shapes.ts b/apps/web/src/stickers/providers/shapes.ts index a23f14a7..e3137425 100644 --- a/apps/web/src/lib/stickers/providers/shapes.ts +++ b/apps/web/src/stickers/providers/shapes.ts @@ -1,5 +1,5 @@ -import { buildGraphicPreviewUrl, buildDefaultGraphicInstance, graphicsRegistry, registerDefaultGraphics } from "@/lib/graphics"; -import type { ParamValues } from "@/lib/params"; +import { buildGraphicPreviewUrl, buildDefaultGraphicInstance, graphicsRegistry, registerDefaultGraphics } from "@/graphics"; +import type { ParamValues } from "@/params"; import { buildStickerId, parseStickerId } from "../sticker-id"; import type { StickerBrowseResult, diff --git a/apps/web/src/lib/stickers/registry.ts b/apps/web/src/stickers/registry.ts index 7148c09d..a7d79df0 100644 --- a/apps/web/src/lib/stickers/registry.ts +++ b/apps/web/src/stickers/registry.ts @@ -1,5 +1,5 @@ -import type { StickerProvider } from "@/lib/stickers/types"; -import { DefinitionRegistry } from "@/lib/registry"; +import type { StickerProvider } from "@/stickers/types"; +import { DefinitionRegistry } from "@/params/registry"; export class StickersRegistry extends DefinitionRegistry<string, StickerProvider> { constructor() { diff --git a/apps/web/src/lib/stickers/resolver.ts b/apps/web/src/stickers/resolver.ts index 0002e716..a7825bbc 100644 --- a/apps/web/src/lib/stickers/resolver.ts +++ b/apps/web/src/stickers/resolver.ts @@ -1,7 +1,7 @@ import { stickersRegistry } from "./registry"; import { parseStickerId } from "./sticker-id"; import { registerDefaultStickerProviders } from "./providers"; -import type { StickerResolveOptions } from "@/lib/stickers/types"; +import type { StickerResolveOptions } from "@/stickers/types"; export function resolveStickerId({ stickerId, diff --git a/apps/web/src/lib/stickers/sticker-id.ts b/apps/web/src/stickers/sticker-id.ts index cb56db4c..cb56db4c 100644 --- a/apps/web/src/lib/stickers/sticker-id.ts +++ b/apps/web/src/stickers/sticker-id.ts diff --git a/apps/web/src/stores/stickers-store.ts b/apps/web/src/stickers/stickers-store.ts index 191050dd..aacc5a79 100644 --- a/apps/web/src/stores/stickers-store.ts +++ b/apps/web/src/stickers/stickers-store.ts @@ -5,13 +5,13 @@ import { browseCategory, searchAll, searchStickers as searchStickersFromProviders, -} from "@/lib/stickers"; -import type { StickerBrowseResult, StickerSearchResult } from "@/lib/stickers"; -import { STICKER_CATEGORIES } from "@/lib/stickers/categories"; -import type { StickerCategory } from "@/lib/stickers/types"; -import { registerDefaultStickerProviders } from "@/lib/stickers/providers"; -import { stickersRegistry } from "@/lib/stickers/registry"; -import { parseStickerId } from "@/lib/stickers/sticker-id"; +} from "@/stickers"; +import type { StickerBrowseResult, StickerSearchResult } from "@/stickers"; +import { STICKER_CATEGORIES } from "@/stickers/categories"; +import type { StickerCategory } from "@/stickers/types"; +import { registerDefaultStickerProviders } from "@/stickers/providers"; +import { stickersRegistry } from "@/stickers/registry"; +import { parseStickerId } from "@/stickers/sticker-id"; const MAX_RECENT_STICKERS = 50; let browseRequestVersion = 0; diff --git a/apps/web/src/lib/stickers/types.ts b/apps/web/src/stickers/types.ts index bc7dd490..86aa4299 100644 --- a/apps/web/src/lib/stickers/types.ts +++ b/apps/web/src/stickers/types.ts @@ -1,4 +1,4 @@ -import type { STICKER_CATEGORIES } from "@/lib/stickers/categories"; +import type { STICKER_CATEGORIES } from "@/stickers/categories"; export type StickerCategory = keyof typeof STICKER_CATEGORIES; diff --git a/apps/web/src/stores/properties-store.ts b/apps/web/src/stores/properties-store.ts deleted file mode 100644 index f45f176d..00000000 --- a/apps/web/src/stores/properties-store.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { create } from "zustand"; - -interface ClipEffectsTarget { - elementId: string; - trackId: string; -} - -interface PropertiesState { - clipEffectsTarget: ClipEffectsTarget | null; - openClipEffects: ({ elementId, trackId }: ClipEffectsTarget) => void; - closeClipEffects: () => void; -} - -export const usePropertiesStore = create<PropertiesState>()((set) => ({ - clipEffectsTarget: null, - openClipEffects: ({ elementId, trackId }) => - set({ clipEffectsTarget: { elementId, trackId } }), - closeClipEffects: () => set({ clipEffectsTarget: null }), -})); diff --git a/apps/web/src/lib/subtitles/ass.ts b/apps/web/src/subtitles/ass.ts index 030c5b36..030c5b36 100644 --- a/apps/web/src/lib/subtitles/ass.ts +++ b/apps/web/src/subtitles/ass.ts diff --git a/apps/web/src/lib/subtitles/build-subtitle-text-element.ts b/apps/web/src/subtitles/build-subtitle-text-element.ts index 698310a6..c4ac21ac 100644 --- a/apps/web/src/lib/subtitles/build-subtitle-text-element.ts +++ b/apps/web/src/subtitles/build-subtitle-text-element.ts @@ -1,12 +1,12 @@ -import { FONT_SIZE_SCALE_REFERENCE } from "@/lib/text/typography"; +import { FONT_SIZE_SCALE_REFERENCE } from "@/text/typography"; import { getTextVisualRect, measureTextBlock, setCanvasLetterSpacing, -} from "@/lib/text/layout"; -import { DEFAULTS } from "@/lib/timeline/defaults"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; -import type { CreateTextElement } from "@/lib/timeline"; +} from "@/text/layout"; +import { DEFAULTS } from "@/timeline/defaults"; +import { TICKS_PER_SECOND } from "@/wasm"; +import type { CreateTextElement } from "@/timeline"; import type { SubtitleCue, SubtitleStyleOverrides } from "./types"; const SUBTITLE_MAX_WIDTH_RATIO = 0.8; diff --git a/apps/web/src/components/editor/panels/assets/views/captions.tsx b/apps/web/src/subtitles/components/assets-view.tsx index 040df749..7e2ddad9 100644 --- a/apps/web/src/components/editor/panels/assets/views/captions.tsx +++ b/apps/web/src/subtitles/components/assets-view.tsx @@ -8,21 +8,21 @@ import { SelectValue, } from "@/components/ui/select"; import { useReducer, useRef, useState } from "react"; -import { extractTimelineAudio } from "@/lib/media/mediabunny"; -import { useEditor } from "@/hooks/use-editor"; -import { TRANSCRIPTION_DIAGNOSTICS_SCOPE } from "@/lib/transcription/diagnostics"; -import { DEFAULT_TRANSCRIPTION_SAMPLE_RATE } from "@/lib/transcription/audio"; -import { TRANSCRIPTION_LANGUAGES } from "@/lib/transcription/supported-languages"; +import { extractTimelineAudio } from "@/media/mediabunny"; +import { useEditor } from "@/editor/use-editor"; +import { TRANSCRIPTION_DIAGNOSTICS_SCOPE } from "@/transcription/diagnostics"; +import { DEFAULT_TRANSCRIPTION_SAMPLE_RATE } from "@/transcription/audio"; +import { TRANSCRIPTION_LANGUAGES } from "@/transcription/supported-languages"; import type { CaptionChunk, TranscriptionLanguage, TranscriptionProgress, -} from "@/lib/transcription/types"; +} from "@/transcription/types"; import { transcriptionService } from "@/services/transcription/service"; -import { decodeAudioToFloat32 } from "@/lib/media/audio"; -import { buildCaptionChunks } from "@/lib/transcription/caption"; -import { insertCaptionChunksAsTextTrack } from "@/lib/subtitles/insert"; -import { parseSubtitleFile } from "@/lib/subtitles/parse"; +import { decodeAudioToFloat32 } from "@/media/audio"; +import { buildCaptionChunks } from "@/transcription/caption"; +import { insertCaptionChunksAsTextTrack } from "@/subtitles/insert"; +import { parseSubtitleFile } from "@/subtitles/parse"; import { Spinner } from "@/components/ui/spinner"; import { Section, @@ -38,7 +38,7 @@ import { TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; -import type { DiagnosticSeverity } from "@/lib/diagnostics/types"; +import type { DiagnosticSeverity } from "@/diagnostics/types"; const DIAGNOSTIC_BUTTON_VARIANT: Record< DiagnosticSeverity, diff --git a/apps/web/src/lib/subtitles/insert.ts b/apps/web/src/subtitles/insert.ts index 1bb04fc8..50d6c40b 100644 --- a/apps/web/src/lib/subtitles/insert.ts +++ b/apps/web/src/subtitles/insert.ts @@ -3,7 +3,7 @@ import { AddTrackCommand, BatchCommand, InsertElementCommand, -} from "@/lib/commands"; +} from "@/commands"; import { buildSubtitleTextElement } from "./build-subtitle-text-element"; import type { SubtitleCue } from "./types"; diff --git a/apps/web/src/lib/subtitles/parse.ts b/apps/web/src/subtitles/parse.ts index 7a2d2342..7a2d2342 100644 --- a/apps/web/src/lib/subtitles/parse.ts +++ b/apps/web/src/subtitles/parse.ts diff --git a/apps/web/src/lib/subtitles/srt.ts b/apps/web/src/subtitles/srt.ts index 197e96f9..197e96f9 100644 --- a/apps/web/src/lib/subtitles/srt.ts +++ b/apps/web/src/subtitles/srt.ts diff --git a/apps/web/src/lib/subtitles/types.ts b/apps/web/src/subtitles/types.ts index 86f6525d..e4f5db3c 100644 --- a/apps/web/src/lib/subtitles/types.ts +++ b/apps/web/src/subtitles/types.ts @@ -1,5 +1,5 @@ -import type { TextBackground, TextElement } from "@/lib/timeline"; -import type { CaptionChunk } from "@/lib/transcription/types"; +import type { TextBackground, TextElement } from "@/timeline"; +import type { CaptionChunk } from "@/transcription/types"; export interface SubtitlePlacementStyle { verticalAlign?: "top" | "middle" | "bottom"; diff --git a/apps/web/src/lib/text/background.ts b/apps/web/src/text/background.ts index be5ab6f5..be5ab6f5 100644 --- a/apps/web/src/lib/text/background.ts +++ b/apps/web/src/text/background.ts diff --git a/apps/web/src/components/editor/panels/assets/views/text.tsx b/apps/web/src/text/components/assets-view.tsx index da295da3..25a49893 100644 --- a/apps/web/src/components/editor/panels/assets/views/text.tsx +++ b/apps/web/src/text/components/assets-view.tsx @@ -1,8 +1,8 @@ import { DraggableItem } from "@/components/editor/panels/assets/draggable-item";
import { PanelView } from "@/components/editor/panels/assets/views/base-panel";
-import { useEditor } from "@/hooks/use-editor";
-import { DEFAULTS } from "@/lib/timeline/defaults";
-import { buildTextElement } from "@/lib/timeline/element-utils";
+import { useEditor } from "@/editor/use-editor";
+import { DEFAULTS } from "@/timeline/defaults";
+import { buildTextElement } from "@/timeline/element-utils";
export function TextView() {
const editor = useEditor();
diff --git a/apps/web/src/components/editor/panels/properties/tabs/text-tab.tsx b/apps/web/src/text/components/text-tab.tsx index d37379ea..bf14efba 100644 --- a/apps/web/src/components/editor/panels/properties/tabs/text-tab.tsx +++ b/apps/web/src/text/components/text-tab.tsx @@ -2,7 +2,7 @@ import { Textarea } from "@/components/ui/textarea"; import { FontPicker } from "@/components/ui/font-picker"; -import type { TextElement } from "@/lib/timeline"; +import type { TextElement } from "@/timeline"; import { NumberField } from "@/components/ui/number-field"; import { useRef } from "react"; import { @@ -17,23 +17,20 @@ import { ColorPicker } from "@/components/ui/color-picker"; import { Button } from "@/components/ui/button"; import { uppercase } from "@/utils/string"; import { clamp, formatNumberForDisplay } from "@/utils/math"; -import { useEditor } from "@/hooks/use-editor"; -import { - CORNER_RADIUS_MAX, - CORNER_RADIUS_MIN, -} from "@/lib/text/background"; +import { useEditor } from "@/editor/use-editor"; +import { CORNER_RADIUS_MAX, CORNER_RADIUS_MIN } from "@/text/background"; import { DEFAULT_TEXT_COLOR, MAX_FONT_SIZE, MIN_FONT_SIZE, -} from "@/lib/text/typography"; -import { usePropertyDraft } from "../hooks/use-property-draft"; -import { useKeyframedColorProperty } from "../hooks/use-keyframed-color-property"; -import { useKeyframedNumberProperty } from "../hooks/use-keyframed-number-property"; -import { useElementPlayhead } from "../hooks/use-element-playhead"; -import { KeyframeToggle } from "../components/keyframe-toggle"; -import { isPropertyAtDefault } from "./transform-tab"; -import { resolveColorAtTime, resolveNumberAtTime } from "@/lib/animation"; +} from "@/text/typography"; +import { usePropertyDraft } from "@/components/editor/panels/properties/hooks/use-property-draft"; +import { useKeyframedColorProperty } from "@/components/editor/panels/properties/hooks/use-keyframed-color-property"; +import { useKeyframedNumberProperty } from "@/components/editor/panels/properties/hooks/use-keyframed-number-property"; +import { useElementPlayhead } from "@/components/editor/panels/properties/hooks/use-element-playhead"; +import { KeyframeToggle } from "@/components/editor/panels/properties/components/keyframe-toggle"; +import { isPropertyAtDefault } from "@/rendering/components/transform-tab"; +import { resolveColorAtTime, resolveNumberAtTime } from "@/animation"; import { HugeiconsIcon } from "@hugeicons/react"; import { MinusSignIcon, @@ -41,7 +38,7 @@ import { TextFontIcon, } from "@hugeicons/core-free-icons"; import { OcTextHeightIcon, OcTextWidthIcon } from "@/components/icons"; -import { DEFAULTS } from "@/lib/timeline/defaults"; +import { DEFAULTS } from "@/timeline/defaults"; import { cn } from "@/utils/ui"; export function TextTab({ diff --git a/apps/web/src/lib/text/layout.ts b/apps/web/src/text/layout.ts index a3badfdc..aabf5ca9 100644 --- a/apps/web/src/lib/text/layout.ts +++ b/apps/web/src/text/layout.ts @@ -1,5 +1,5 @@ -import type { TextBackground, TextElement } from "@/lib/timeline"; -import { DEFAULTS } from "@/lib/timeline/defaults"; +import type { TextBackground, TextElement } from "@/timeline"; +import { DEFAULTS } from "@/timeline/defaults"; type TextRect = { left: number; diff --git a/apps/web/src/lib/text/measure-element.ts b/apps/web/src/text/measure-element.ts index 0b107587..3a49696f 100644 --- a/apps/web/src/lib/text/measure-element.ts +++ b/apps/web/src/text/measure-element.ts @@ -1,7 +1,7 @@ -import { CORNER_RADIUS_MIN } from "@/lib/text/background"; -import { resolveNumberAtTime } from "@/lib/animation"; -import { DEFAULTS } from "@/lib/timeline/defaults"; -import type { TextBackground, TextElement } from "@/lib/timeline"; +import { CORNER_RADIUS_MIN } from "@/text/background"; +import { resolveNumberAtTime } from "@/animation"; +import { DEFAULTS } from "@/timeline/defaults"; +import type { TextBackground, TextElement } from "@/timeline"; import { getTextVisualRect, } from "./layout"; diff --git a/apps/web/src/lib/text/primitives.ts b/apps/web/src/text/primitives.ts index 20674c3c..9a9262a4 100644 --- a/apps/web/src/lib/text/primitives.ts +++ b/apps/web/src/text/primitives.ts @@ -1,5 +1,5 @@ -import type { TextCanvasContext, TextBlockMeasurement } from "@/lib/text/layout"; -import { DEFAULTS } from "@/lib/timeline/defaults"; +import type { TextCanvasContext, TextBlockMeasurement } from "@/text/layout"; +import { DEFAULTS } from "@/timeline/defaults"; import { clamp } from "@/utils/math"; import { CORNER_RADIUS_MAX, CORNER_RADIUS_MIN } from "./background"; import { diff --git a/apps/web/src/lib/text/typography.ts b/apps/web/src/text/typography.ts index 837236ab..837236ab 100644 --- a/apps/web/src/lib/text/typography.ts +++ b/apps/web/src/text/typography.ts diff --git a/apps/web/src/lib/timeline/audio-constants.ts b/apps/web/src/timeline/audio-constants.ts index f5bb8a3e..f5bb8a3e 100644 --- a/apps/web/src/lib/timeline/audio-constants.ts +++ b/apps/web/src/timeline/audio-constants.ts diff --git a/apps/web/src/lib/timeline/audio-display.ts b/apps/web/src/timeline/audio-display.ts index 9958c472..9958c472 100644 --- a/apps/web/src/lib/timeline/audio-display.ts +++ b/apps/web/src/timeline/audio-display.ts diff --git a/apps/web/src/lib/timeline/audio-separation/index.ts b/apps/web/src/timeline/audio-separation/index.ts index 849dbda3..beb488de 100644 --- a/apps/web/src/lib/timeline/audio-separation/index.ts +++ b/apps/web/src/timeline/audio-separation/index.ts @@ -1,7 +1,7 @@ -import { cloneAnimations } from "@/lib/animation"; -import type { ElementAnimations } from "@/lib/animation/types"; -import type { MediaAsset } from "@/lib/media/types"; -import { DEFAULTS } from "@/lib/timeline/defaults"; +import { cloneAnimations } from "@/animation"; +import type { ElementAnimations } from "@/animation/types"; +import type { MediaAsset } from "@/media/types"; +import { DEFAULTS } from "@/timeline/defaults"; import type { CreateUploadAudioElement, TimelineElement, diff --git a/apps/web/src/lib/timeline/audio-state.ts b/apps/web/src/timeline/audio-state.ts index 165ece94..4ef559ea 100644 --- a/apps/web/src/lib/timeline/audio-state.ts +++ b/apps/web/src/timeline/audio-state.ts @@ -1,5 +1,5 @@ -import { hasKeyframesForPath } from "@/lib/animation/keyframe-query"; -import { resolveNumberAtTime } from "@/lib/animation/resolve"; +import { hasKeyframesForPath } from "@/animation/keyframe-query"; +import { resolveNumberAtTime } from "@/animation/resolve"; import { VOLUME_DB_MAX, VOLUME_DB_MIN } from "./audio-constants"; import type { TimelineElement } from "./types"; const DEFAULT_STEP_SECONDS = 1 / 60; @@ -32,7 +32,7 @@ export function hasAnimatedVolume({ }); } -import { TICKS_PER_SECOND } from "@/lib/wasm"; +import { TICKS_PER_SECOND } from "@/wasm"; export function resolveEffectiveAudioGain({ element, diff --git a/apps/web/src/components/editor/panels/timeline/bookmarks.tsx b/apps/web/src/timeline/bookmarks/components/bookmarks.tsx index c432b7d4..20e22c57 100644 --- a/apps/web/src/components/editor/panels/timeline/bookmarks.tsx +++ b/apps/web/src/timeline/bookmarks/components/bookmarks.tsx @@ -2,20 +2,18 @@ import { useEffect, useState } from "react"; import type { EditorCore } from "@/core"; -import { useEditor } from "@/hooks/use-editor"; -import type { BookmarkDragState } from "@/hooks/timeline/use-bookmark-drag"; -import { - DEFAULT_TIMELINE_BOOKMARK_COLOR, -} from "./theme"; -import { TIMELINE_BOOKMARK_ROW_HEIGHT_PX } from "./layout"; -import { DEFAULT_FPS } from "@/lib/fps/defaults"; +import { useEditor } from "@/editor/use-editor"; +import type { BookmarkDragState } from "../hooks/use-bookmark-drag"; +import { DEFAULT_TIMELINE_BOOKMARK_COLOR } from "@/timeline/components/theme"; +import { TIMELINE_BOOKMARK_ROW_HEIGHT_PX } from "@/timeline/components/layout"; +import { DEFAULT_FPS } from "@/fps/defaults"; import { snappedSeekTime } from "opencut-wasm"; import { ArrowTurnBackwardIcon, Delete02Icon, } from "@hugeicons/core-free-icons"; import { HugeiconsIcon } from "@hugeicons/react"; -import type { Bookmark } from "@/lib/timeline"; +import type { Bookmark } from "@/timeline"; import { Popover, PopoverAnchor, @@ -27,10 +25,7 @@ import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { uppercase } from "@/utils/string"; import { clamp, formatNumberForDisplay } from "@/utils/math"; -import { - timelineTimeToPixels, - timelineTimeToSnappedPixels, -} from "@/lib/timeline"; +import { timelineTimeToPixels, timelineTimeToSnappedPixels } from "@/timeline"; const MIN_BOOKMARK_WIDTH_PX = 2; const BOOKMARK_MARKER_WIDTH_PX = 12; @@ -360,7 +355,10 @@ function BookmarkPopoverContent({ /> {bookmark.color && bookmark.color.replace(/^#/, "").toUpperCase() !== - DEFAULT_TIMELINE_BOOKMARK_COLOR.replace(/^#/, "").toUpperCase() && ( + DEFAULT_TIMELINE_BOOKMARK_COLOR.replace( + /^#/, + "", + ).toUpperCase() && ( <Button type="button" variant="text" diff --git a/apps/web/src/hooks/timeline/use-bookmark-drag.ts b/apps/web/src/timeline/bookmarks/hooks/use-bookmark-drag.ts index 3ff1cb21..1071c6dd 100644 --- a/apps/web/src/hooks/timeline/use-bookmark-drag.ts +++ b/apps/web/src/timeline/bookmarks/hooks/use-bookmark-drag.ts @@ -5,17 +5,22 @@ import { useRef, type RefObject, } from "react"; -import { useEditor } from "@/hooks/use-editor"; +import { useEditor } from "@/editor/use-editor"; import { useShiftKey } from "@/hooks/use-shift-key"; -import { TIMELINE_DRAG_THRESHOLD_PX } from "@/components/editor/panels/timeline/interaction"; +import { TIMELINE_DRAG_THRESHOLD_PX } from "@/timeline/components/interaction"; import { roundToFrame } from "opencut-wasm"; -import { getMouseTimeFromClientX } from "@/lib/timeline/drag-utils"; +import { getMouseTimeFromClientX } from "@/timeline/drag-utils"; import { - findSnapPoints, - snapToNearestPoint, + buildTimelineSnapPoints, + getTimelineSnapThresholdInTicks, + resolveTimelineSnap, type SnapPoint, -} from "@/lib/timeline/snap-utils"; -import type { Bookmark } from "@/lib/timeline"; +} from "@/timeline/snapping"; +import { getBookmarkSnapPoints } from "../snap-source"; +import { getElementEdgeSnapPoints } from "@/timeline/element-snap-source"; +import { getPlayheadSnapPoints } from "@/timeline/playhead-snap-source"; +import { getAnimationKeyframeSnapPointsForTimeline } from "@/animation/timeline-snap-points"; +import type { Bookmark } from "@/timeline"; export interface BookmarkDragState { isDragging: boolean; @@ -97,23 +102,32 @@ export function useBookmarkDrag({ return { snappedTime: rawTime, snapPoint: null }; } - const snapPoints = findSnapPoints({ - tracks, - playheadTime, - bookmarks, - excludeBookmarkTime, + const snapPoints = buildTimelineSnapPoints({ + sources: [ + () => getElementEdgeSnapPoints({ tracks }), + () => getPlayheadSnapPoints({ playheadTime }), + () => getBookmarkSnapPoints({ bookmarks, excludeBookmarkTime }), + () => getAnimationKeyframeSnapPointsForTimeline({ tracks }), + ], }); - const result = snapToNearestPoint({ + const result = resolveTimelineSnap({ targetTime: rawTime, snapPoints, - zoomLevel, + maxSnapDistance: getTimelineSnapThresholdInTicks({ zoomLevel }), }); return { snappedTime: result.snappedTime, snapPoint: result.snapPoint, }; }, - [snappingEnabled, tracks, playheadTime, bookmarks, zoomLevel, isShiftHeldRef], + [ + snappingEnabled, + tracks, + playheadTime, + bookmarks, + zoomLevel, + isShiftHeldRef, + ], ); useEffect(() => { @@ -148,7 +162,11 @@ export function useBookmarkDrag({ zoomLevel, scrollLeft, }); - const frameSnappedTime = roundToFrame({ time: Math.max(0, Math.min(mouseTime, duration)), rate: activeProject.settings.fps }) ?? Math.max(0, Math.min(mouseTime, duration)); + const frameSnappedTime = + roundToFrame({ + time: Math.max(0, Math.min(mouseTime, duration)), + rate: activeProject.settings.fps, + }) ?? Math.max(0, Math.min(mouseTime, duration)); const { snappedTime: initialTime } = getSnapResult({ rawTime: frameSnappedTime, excludeBookmarkTime: bookmarkTime, @@ -176,9 +194,11 @@ export function useBookmarkDrag({ scrollLeft, }); const clampedTime = Math.max(0, Math.min(mouseTime, duration)); - const frameSnappedTime = roundToFrame({ time: clampedTime, rate: activeProject.settings.fps }) ?? clampedTime; - const snapResult = getSnapResult({ - rawTime: frameSnappedTime, + const frameSnappedTime = + roundToFrame({ time: clampedTime, rate: activeProject.settings.fps }) ?? + clampedTime; + const snapResult = getSnapResult({ + rawTime: frameSnappedTime, excludeBookmarkTime: dragState.bookmarkTime, }); diff --git a/apps/web/src/timeline/bookmarks/index.ts b/apps/web/src/timeline/bookmarks/index.ts new file mode 100644 index 00000000..667979b4 --- /dev/null +++ b/apps/web/src/timeline/bookmarks/index.ts @@ -0,0 +1,19 @@ +export { + findBookmarkIndex, + isBookmarkAtTime, + toggleBookmarkInArray, + removeBookmarkFromArray, + updateBookmarkInArray, + moveBookmarkInArray, + getFrameTime, + getBookmarkAtTime, + getBookmarksActiveAtTime, +} from "./utils"; +export { getBookmarkSnapPoints } from "./snap-source"; +export { + bookmarkNotesPreviewOverlay, + getBookmarkPreviewOverlaySource, +} from "./preview-overlay-source"; +export { useBookmarkDrag } from "./hooks/use-bookmark-drag"; +export type { BookmarkDragState } from "./hooks/use-bookmark-drag"; +export { TimelineBookmarksRow } from "./components/bookmarks"; diff --git a/apps/web/src/timeline/bookmarks/preview-overlay-source.tsx b/apps/web/src/timeline/bookmarks/preview-overlay-source.tsx new file mode 100644 index 00000000..8e2b4bcf --- /dev/null +++ b/apps/web/src/timeline/bookmarks/preview-overlay-source.tsx @@ -0,0 +1,85 @@ +import type { Bookmark } from "@/timeline"; +import { + EMPTY_PREVIEW_OVERLAY_SOURCE_RESULT, + type PreviewOverlayDefinition, + type PreviewOverlaySourceResult, +} from "@/preview/overlays"; +import { getBookmarksActiveAtTime } from "./utils"; + +export const bookmarkNotesPreviewOverlay: PreviewOverlayDefinition = { + id: "bookmark-notes", + label: "Show bookmark notes", + defaultVisible: true, +}; + +function BookmarkNotesOverlay({ + bookmarks, +}: { + bookmarks: Array<{ time: number; note: string; color?: string }>; +}) { + return ( + <div className="flex flex-col gap-1.5" aria-live="polite"> + {bookmarks.map((bookmark) => ( + <div + key={bookmark.time} + className="flex max-w-[min(200px,50vw)] px-2.5 py-1.5 text-left text-white text-xs shadow-md backdrop-blur-sm" + style={{ + backgroundColor: "rgb(0 0 0 / 0.5)", + borderLeft: bookmark.color + ? `3px solid ${bookmark.color}` + : "3px solid var(--primary)", + }} + > + {bookmark.note} + </div> + ))} + </div> + ); +} + +export function getBookmarkPreviewOverlaySource({ + bookmarks, + time, + isVisible, +}: { + bookmarks: Bookmark[]; + time: number; + isVisible: boolean; +}): PreviewOverlaySourceResult { + const bookmarksWithNotes = getBookmarksActiveAtTime({ + bookmarks, + time, + }).flatMap((bookmark) => { + if (bookmark.note == null || bookmark.note.trim() === "") { + return []; + } + + return [ + { + time: bookmark.time, + note: bookmark.note, + color: bookmark.color, + }, + ]; + }); + + if (!isVisible || bookmarksWithNotes.length === 0) { + return { + ...EMPTY_PREVIEW_OVERLAY_SOURCE_RESULT, + definitions: [bookmarkNotesPreviewOverlay], + }; + } + + return { + definitions: [bookmarkNotesPreviewOverlay], + instances: [ + { + id: bookmarkNotesPreviewOverlay.id, + mount: { kind: "hud", anchor: "top-left", order: 0 }, + plane: "over-interaction", + pointerEvents: "none", + render: () => <BookmarkNotesOverlay bookmarks={bookmarksWithNotes} />, + }, + ], + }; +} diff --git a/apps/web/src/timeline/bookmarks/snap-source.ts b/apps/web/src/timeline/bookmarks/snap-source.ts new file mode 100644 index 00000000..32a84baa --- /dev/null +++ b/apps/web/src/timeline/bookmarks/snap-source.ts @@ -0,0 +1,20 @@ +import type { Bookmark } from "@/timeline"; +import type { SnapPoint } from "@/timeline/snapping"; + +export function getBookmarkSnapPoints({ + bookmarks, + excludeBookmarkTime, +}: { + bookmarks: Bookmark[]; + excludeBookmarkTime?: number; +}): SnapPoint[] { + return bookmarks.flatMap((bookmark) => { + if (excludeBookmarkTime != null && bookmark.time === excludeBookmarkTime) { + return []; + } + + return [ + { time: bookmark.time, type: "bookmark" satisfies SnapPoint["type"] }, + ]; + }); +} diff --git a/apps/web/src/lib/timeline/bookmarks.ts b/apps/web/src/timeline/bookmarks/utils.ts index 1fbf17bf..d05fe3a9 100644 --- a/apps/web/src/lib/timeline/bookmarks.ts +++ b/apps/web/src/timeline/bookmarks/utils.ts @@ -1,4 +1,4 @@ -import type { Bookmark } from "@/lib/timeline"; +import type { Bookmark } from "@/timeline"; import type { FrameRate } from "opencut-wasm"; import { roundToFrame } from "opencut-wasm"; diff --git a/apps/web/src/components/editor/panels/properties/tabs/audio-tab.tsx b/apps/web/src/timeline/components/audio-tab.tsx index 5ac0fbb7..1dbb3d1a 100644 --- a/apps/web/src/components/editor/panels/properties/tabs/audio-tab.tsx +++ b/apps/web/src/timeline/components/audio-tab.tsx @@ -1,8 +1,8 @@ import { Button } from "@/components/ui/button";
import { NumberField } from "@/components/ui/number-field";
-import { VOLUME_DB_MAX, VOLUME_DB_MIN } from "@/lib/timeline/audio-constants";
-import { isSourceAudioSeparated } from "@/lib/timeline/audio-separation";
-import { DEFAULTS } from "@/lib/timeline/defaults";
+import { VOLUME_DB_MAX, VOLUME_DB_MIN } from "@/timeline/audio-constants";
+import { isSourceAudioSeparated } from "@/timeline/audio-separation";
+import { DEFAULTS } from "@/timeline/defaults";
import {
clamp,
formatNumberForDisplay,
@@ -10,12 +10,12 @@ import { isNearlyEqual,
snapToStep,
} from "@/utils/math";
-import type { AudioElement, VideoElement } from "@/lib/timeline";
-import { resolveNumberAtTime } from "@/lib/animation";
-import { useEditor } from "@/hooks/use-editor";
-import { useElementPlayhead } from "../hooks/use-element-playhead";
-import { useKeyframedNumberProperty } from "../hooks/use-keyframed-number-property";
-import { KeyframeToggle } from "../components/keyframe-toggle";
+import type { AudioElement, VideoElement } from "@/timeline";
+import { resolveNumberAtTime } from "@/animation";
+import { useEditor } from "@/editor/use-editor";
+import { useElementPlayhead } from "@/components/editor/panels/properties/hooks/use-element-playhead";
+import { useKeyframedNumberProperty } from "@/components/editor/panels/properties/hooks/use-keyframed-number-property";
+import { KeyframeToggle } from "@/components/editor/panels/properties/components/keyframe-toggle";
import { HugeiconsIcon } from "@hugeicons/react";
import { VolumeHighIcon } from "@hugeicons/core-free-icons";
import {
diff --git a/apps/web/src/components/editor/panels/timeline/audio-volume-line.tsx b/apps/web/src/timeline/components/audio-volume-line.tsx index 208f3beb..1baac2bd 100644 --- a/apps/web/src/components/editor/panels/timeline/audio-volume-line.tsx +++ b/apps/web/src/timeline/components/audio-volume-line.tsx @@ -2,15 +2,15 @@ import { useCallback, useRef, useState } from "react"; import { createPortal } from "react-dom"; -import { useEditor } from "@/hooks/use-editor"; -import { DEFAULTS } from "@/lib/timeline/defaults"; +import { useEditor } from "@/editor/use-editor"; +import { DEFAULTS } from "@/timeline/defaults"; import { getDbFromLinePos, getLinePosFromDb, -} from "@/lib/timeline/audio-display"; -import { VOLUME_DB_MAX, VOLUME_DB_MIN } from "@/lib/timeline/audio-constants"; -import { hasAnimatedVolume } from "@/lib/timeline/audio-state"; -import type { AudioElement } from "@/lib/timeline/types"; +} from "@/timeline/audio-display"; +import { VOLUME_DB_MAX, VOLUME_DB_MIN } from "@/timeline/audio-constants"; +import { hasAnimatedVolume } from "@/timeline/audio-state"; +import type { AudioElement } from "@/timeline/types"; import { clamp, formatNumberForDisplay, diff --git a/apps/web/src/components/editor/panels/timeline/audio-waveform.tsx b/apps/web/src/timeline/components/audio-waveform.tsx index a94267dd..7777bc09 100644 --- a/apps/web/src/components/editor/panels/timeline/audio-waveform.tsx +++ b/apps/web/src/timeline/components/audio-waveform.tsx @@ -7,9 +7,9 @@ import { buildWaveformSampleBuckets, sampleSourceWaveformSummary, type SourceWaveformSummary, -} from "@/lib/media/waveform-summary"; -import type { RetimeConfig } from "@/lib/timeline"; -import { getBarFractionFromOutputAmplitude } from "@/lib/timeline/audio-display"; +} from "@/media/waveform-summary"; +import type { RetimeConfig } from "@/timeline"; +import { getBarFractionFromOutputAmplitude } from "@/timeline/audio-display"; import { waveformCache } from "@/services/waveform-cache/service"; import { findScrollParent } from "@/utils/browser"; import { cn } from "@/utils/ui"; diff --git a/apps/web/src/components/editor/panels/timeline/drag-line.tsx b/apps/web/src/timeline/components/drag-line.tsx index 89a4c93f..49621f5c 100644 --- a/apps/web/src/components/editor/panels/timeline/drag-line.tsx +++ b/apps/web/src/timeline/components/drag-line.tsx @@ -1,5 +1,5 @@ import { getDropLineY } from "./drop-target";
-import type { TimelineTrack, DropTarget } from "@/lib/timeline";
+import type { TimelineTrack, DropTarget } from "@/timeline";
import { TIMELINE_LAYERS } from "./layers";
interface DragLineProps {
diff --git a/apps/web/src/components/editor/panels/timeline/drop-target.ts b/apps/web/src/timeline/components/drop-target.ts index ef6cbd90..be2495e4 100644 --- a/apps/web/src/components/editor/panels/timeline/drop-target.ts +++ b/apps/web/src/timeline/components/drop-target.ts @@ -1,9 +1,9 @@ -import type { TimelineTrack, TimelineElement } from "@/lib/timeline"; -import type { ComputeDropTargetParams, DropTarget } from "@/lib/timeline"; -import { resolveTrackPlacement } from "@/lib/timeline/placement"; +import type { TimelineTrack, TimelineElement } from "@/timeline"; +import type { ComputeDropTargetParams, DropTarget } from "@/timeline"; +import { resolveTrackPlacement } from "@/timeline/placement"; import { TIMELINE_TRACK_GAP_PX } from "./layout"; import { getTrackHeight } from "./track-layout"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; +import { TICKS_PER_SECOND } from "@/wasm"; function findElementAtPosition({ mouseX, diff --git a/apps/web/src/components/editor/panels/timeline/expanded-layout.ts b/apps/web/src/timeline/components/expanded-layout.ts index 6b9734a7..fae0e50c 100644 --- a/apps/web/src/components/editor/panels/timeline/expanded-layout.ts +++ b/apps/web/src/timeline/components/expanded-layout.ts @@ -1,9 +1,9 @@ import type { AnimationPath, ElementAnimations, -} from "@/lib/animation/types"; -import type { TimelineTrack } from "@/lib/timeline"; -import { getElementKeyframes } from "@/lib/animation"; +} from "@/animation/types"; +import type { TimelineTrack } from "@/timeline"; +import { getElementKeyframes } from "@/animation"; import { KEYFRAME_LANE_HEIGHT_PX } from "./layout"; export interface ExpandedRow { diff --git a/apps/web/src/components/editor/panels/timeline/graph-editor/bezier-graph.tsx b/apps/web/src/timeline/components/graph-editor/bezier-graph.tsx index 3f78350b..7d77b985 100644 --- a/apps/web/src/components/editor/panels/timeline/graph-editor/bezier-graph.tsx +++ b/apps/web/src/timeline/components/graph-editor/bezier-graph.tsx @@ -2,8 +2,8 @@ import { useRef, useState, type PointerEvent } from "react"; import { useShiftKey } from "@/hooks/use-shift-key"; -import { getBezierPoint } from "@/lib/animation/bezier"; -import type { NormalizedCubicBezier } from "@/lib/animation/types"; +import { getBezierPoint } from "@/animation/bezier"; +import type { NormalizedCubicBezier } from "@/animation/types"; import { cn } from "@/utils/ui"; const GRAPH_WIDTH = 140; diff --git a/apps/web/src/components/editor/panels/timeline/graph-editor/custom-presets-store.ts b/apps/web/src/timeline/components/graph-editor/custom-presets-store.ts index 684ac7c9..4be2adfd 100644 --- a/apps/web/src/components/editor/panels/timeline/graph-editor/custom-presets-store.ts +++ b/apps/web/src/timeline/components/graph-editor/custom-presets-store.ts @@ -2,7 +2,7 @@ import { useSyncExternalStore } from "react"; import { generateUUID } from "@/utils/id"; -import type { NormalizedCubicBezier } from "@/lib/animation/types"; +import type { NormalizedCubicBezier } from "@/animation/types"; import type { EasingPreset } from "./easing-presets"; const STORAGE_KEY = "graph-editor-presets"; diff --git a/apps/web/src/components/editor/panels/timeline/graph-editor/easing-presets.ts b/apps/web/src/timeline/components/graph-editor/easing-presets.ts index 6ccc9071..78a605d4 100644 --- a/apps/web/src/components/editor/panels/timeline/graph-editor/easing-presets.ts +++ b/apps/web/src/timeline/components/graph-editor/easing-presets.ts @@ -1,4 +1,4 @@ -import type { NormalizedCubicBezier } from "@/lib/animation/types"; +import type { NormalizedCubicBezier } from "@/animation/types"; export const PRESET_MATCH_TOLERANCE = 0.02; diff --git a/apps/web/src/components/editor/panels/timeline/graph-editor/popover.tsx b/apps/web/src/timeline/components/graph-editor/popover.tsx index 733195ed..1b8194c0 100644 --- a/apps/web/src/components/editor/panels/timeline/graph-editor/popover.tsx +++ b/apps/web/src/timeline/components/graph-editor/popover.tsx @@ -11,8 +11,8 @@ import { Delete02Icon, PlusSignIcon, } from "@hugeicons/core-free-icons"; -import { getBezierPoint } from "@/lib/animation/bezier"; -import type { NormalizedCubicBezier } from "@/lib/animation/types"; +import { getBezierPoint } from "@/animation/bezier"; +import type { NormalizedCubicBezier } from "@/animation/types"; import type { GraphEditorComponentOption } from "./session"; import { BUILTIN_PRESETS, diff --git a/apps/web/src/components/editor/panels/timeline/graph-editor/session.ts b/apps/web/src/timeline/components/graph-editor/session.ts index cc0fb070..d7c6b001 100644 --- a/apps/web/src/components/editor/panels/timeline/graph-editor/session.ts +++ b/apps/web/src/timeline/components/graph-editor/session.ts @@ -5,7 +5,7 @@ import { getNormalizedCubicBezierForScalarSegment, getScalarKeyframeContext, updateScalarKeyframeCurve, -} from "@/lib/animation"; +} from "@/animation"; import type { AnimationPath, ElementAnimations, @@ -13,8 +13,8 @@ import type { ScalarCurveKeyframePatch, ScalarGraphKeyframeContext, SelectedKeyframeRef, -} from "@/lib/animation/types"; -import type { SceneTracks, TimelineElement } from "@/lib/timeline"; +} from "@/animation/types"; +import type { SceneTracks, TimelineElement } from "@/timeline"; const GRAPH_LINEAR_CURVE: NormalizedCubicBezier = [0, 0, 1, 1]; const FLAT_VALUE_EPSILON = 1e-6; diff --git a/apps/web/src/components/editor/panels/timeline/graph-editor/use-controller.ts b/apps/web/src/timeline/components/graph-editor/use-controller.ts index 51d5a0ff..ea091485 100644 --- a/apps/web/src/components/editor/panels/timeline/graph-editor/use-controller.ts +++ b/apps/web/src/timeline/components/graph-editor/use-controller.ts @@ -1,10 +1,10 @@ "use client"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { useEditor } from "@/hooks/use-editor"; -import { registerCanceller } from "@/lib/cancel-interaction"; -import type { NormalizedCubicBezier } from "@/lib/animation/types"; -import { useKeyframeSelection } from "@/hooks/timeline/element/use-keyframe-selection"; +import { useEditor } from "@/editor/use-editor"; +import { registerCanceller } from "@/editor/cancel-interaction"; +import type { NormalizedCubicBezier } from "@/animation/types"; +import { useKeyframeSelection } from "@/timeline/hooks/element/use-keyframe-selection"; import { applyGraphEditorCurvePreview, buildGraphEditorCurvePatches, diff --git a/apps/web/src/components/editor/panels/timeline/index.tsx b/apps/web/src/timeline/components/index.tsx index 20e9faf1..e6a9df49 100644 --- a/apps/web/src/components/editor/panels/timeline/index.tsx +++ b/apps/web/src/timeline/components/index.tsx @@ -20,7 +20,7 @@ import { ContextMenuItem, ContextMenuTrigger, } from "@/components/ui/context-menu"; -import { useTimelineZoom } from "@/hooks/timeline/use-timeline-zoom"; +import { useTimelineZoom } from "@/timeline/hooks/use-timeline-zoom"; import { useCallback, useEffect, @@ -29,14 +29,14 @@ import { useState, type ReactNode, } from "react"; -import type { ElementDragState, DropTarget } from "@/lib/timeline"; +import type { ElementDragState, DropTarget } from "@/timeline"; import { TimelineTrackContent } from "./timeline-track"; import { TimelinePlayhead } from "./timeline-playhead"; -import { SelectionBox } from "@/lib/selection/selection-box"; -import { useBoxSelect } from "@/lib/selection/hooks/use-box-select"; +import { SelectionBox } from "@/selection/selection-box"; +import { useBoxSelect } from "@/selection/hooks/use-box-select"; import { SnapIndicator } from "./snap-indicator"; -import type { SnapPoint } from "@/lib/timeline/snap-utils"; -import type { TimelineTrack } from "@/lib/timeline"; +import type { SnapPoint } from "@/timeline/snapping"; +import type { TimelineTrack } from "@/timeline"; import { TIMELINE_SCROLLBAR_SIZE_PX, TIMELINE_CONTENT_TOP_PADDING_PX, @@ -44,14 +44,14 @@ import { TIMELINE_TRACK_LABELS_COLUMN_WIDTH_PX, KEYFRAME_LANE_HEIGHT_PX, } from "./layout"; -import { useElementInteraction } from "@/hooks/timeline/element/use-element-interaction"; +import { useElementInteraction } from "@/timeline/hooks/element/use-element-interaction"; import { canTrackHaveAudio, canTrackBeHidden, getTimelineZoomMin, getTimelinePaddingPx, -} from "@/lib/timeline"; -import { timelineTimeToPixels } from "@/lib/timeline/pixel-utils"; +} from "@/timeline"; +import { timelineTimeToPixels } from "@/timeline/pixel-utils"; import { getTrackHeight, getCumulativeHeightBefore, @@ -66,20 +66,22 @@ import { } from "./expanded-layout"; import { TIMELINE_HORIZONTAL_WHEEL_STEP_PX } from "./interaction"; import { TimelineToolbar } from "./timeline-toolbar"; -import { useElementSelection } from "@/hooks/timeline/element/use-element-selection"; -import { useTimelineSeek } from "@/hooks/timeline/use-timeline-seek"; -import { useTimelineDragDrop } from "@/hooks/timeline/use-timeline-drag-drop"; +import { useElementSelection } from "@/timeline/hooks/element/use-element-selection"; +import { useTimelineSeek } from "@/timeline/hooks/use-timeline-seek"; +import { useTimelineDragDrop } from "@/timeline/hooks/use-timeline-drag-drop"; import { TimelineRuler } from "./timeline-ruler"; -import { TimelineBookmarksRow } from "./bookmarks"; -import { useBookmarkDrag } from "@/hooks/timeline/use-bookmark-drag"; -import { useEdgeAutoScroll } from "@/hooks/timeline/use-edge-auto-scroll"; -import { useInitialScrollBottom } from "@/hooks/timeline/use-initial-scroll-bottom"; -import { useTimelineResize } from "@/hooks/timeline/use-timeline-resize"; -import { useTimelineStore } from "@/stores/timeline-store"; -import { useEditor } from "@/hooks/use-editor"; -import { useTimelinePlayhead } from "@/hooks/timeline/use-timeline-playhead"; +import { + TimelineBookmarksRow, + useBookmarkDrag, +} from "@/timeline/bookmarks/index"; +import { useEdgeAutoScroll } from "@/timeline/hooks/use-edge-auto-scroll"; +import { useInitialScrollBottom } from "@/timeline/hooks/use-initial-scroll-bottom"; +import { useTimelineResize } from "@/timeline/hooks/use-timeline-resize"; +import { useTimelineStore } from "@/timeline/timeline-store"; +import { useEditor } from "@/editor/use-editor"; +import { useTimelinePlayhead } from "@/timeline/hooks/use-timeline-playhead"; import { DragLine } from "./drag-line"; -import { invokeAction } from "@/lib/actions"; +import { invokeAction } from "@/actions"; import { resolveTimelineElementIntersections } from "./selection-hit-testing"; import { cn } from "@/utils/ui"; diff --git a/apps/web/src/components/editor/panels/timeline/interaction.ts b/apps/web/src/timeline/components/interaction.ts index fe0210a4..fe0210a4 100644 --- a/apps/web/src/components/editor/panels/timeline/interaction.ts +++ b/apps/web/src/timeline/components/interaction.ts diff --git a/apps/web/src/components/editor/panels/timeline/layers.ts b/apps/web/src/timeline/components/layers.ts index d59db412..d59db412 100644 --- a/apps/web/src/components/editor/panels/timeline/layers.ts +++ b/apps/web/src/timeline/components/layers.ts diff --git a/apps/web/src/components/editor/panels/timeline/layout.ts b/apps/web/src/timeline/components/layout.ts index c30b7877..57a7f5df 100644 --- a/apps/web/src/components/editor/panels/timeline/layout.ts +++ b/apps/web/src/timeline/components/layout.ts @@ -1,4 +1,4 @@ -import type { TrackType } from "@/lib/timeline"; +import type { TrackType } from "@/timeline"; export const TIMELINE_TRACK_HEIGHTS_PX: Record<TrackType, number> = { video: 65, diff --git a/apps/web/src/components/editor/panels/timeline/selection-hit-testing.ts b/apps/web/src/timeline/components/selection-hit-testing.ts index 551393e9..da5617c6 100644 --- a/apps/web/src/components/editor/panels/timeline/selection-hit-testing.ts +++ b/apps/web/src/timeline/components/selection-hit-testing.ts @@ -1,5 +1,5 @@ -import type { TimelineTrack } from "@/lib/timeline"; -import { timelineTimeToPixels } from "@/lib/timeline/pixel-utils"; +import type { TimelineTrack } from "@/timeline"; +import { timelineTimeToPixels } from "@/timeline/pixel-utils"; import { TIMELINE_CONTENT_TOP_PADDING_PX, } from "./layout"; diff --git a/apps/web/src/components/editor/panels/timeline/snap-indicator.tsx b/apps/web/src/timeline/components/snap-indicator.tsx index a86e2a5e..6c1dbf1a 100644 --- a/apps/web/src/components/editor/panels/timeline/snap-indicator.tsx +++ b/apps/web/src/timeline/components/snap-indicator.tsx @@ -1,50 +1,50 @@ -"use client";
-
-import { useSnapIndicatorPosition } from "@/hooks/timeline/use-snap-indicator-position";
-import type { SnapPoint } from "@/lib/timeline/snap-utils";
-import {
- getCenteredLineLeft,
- TIMELINE_INDICATOR_LINE_WIDTH_PX,
-} from "@/lib/timeline";
-import { TIMELINE_LAYERS } from "./layers";
-interface SnapIndicatorProps {
- snapPoint: SnapPoint | null;
- zoomLevel: number;
- isVisible: boolean;
- timelineRef: React.RefObject<HTMLDivElement | null>;
- tracksScrollRef: React.RefObject<HTMLDivElement | null>;
-}
-
-export function SnapIndicator({
- snapPoint,
- zoomLevel,
- isVisible,
- timelineRef,
- tracksScrollRef,
-}: SnapIndicatorProps) {
- const { leftPosition, topPosition, height } = useSnapIndicatorPosition({
- snapPoint,
- zoomLevel,
- timelineRef,
- tracksScrollRef,
- });
-
- if (!isVisible || !snapPoint) {
- return null;
- }
-
- return (
- <div
- className="pointer-events-none absolute"
- style={{
- left: `${getCenteredLineLeft({ centerPixel: leftPosition })}px`,
- top: topPosition,
- height: `${height}px`,
- width: `${TIMELINE_INDICATOR_LINE_WIDTH_PX}px`,
- zIndex: TIMELINE_LAYERS.snapIndicator,
- }}
- >
- <div className={"bg-primary/40 h-full w-0.5 opacity-80"} />
- </div>
- );
-}
+"use client"; + +import { useSnapIndicatorPosition } from "@/timeline/hooks/use-snap-indicator-position"; +import type { SnapPoint } from "@/timeline/snapping"; +import { + getCenteredLineLeft, + TIMELINE_INDICATOR_LINE_WIDTH_PX, +} from "@/timeline"; +import { TIMELINE_LAYERS } from "./layers"; +interface SnapIndicatorProps { + snapPoint: SnapPoint | null; + zoomLevel: number; + isVisible: boolean; + timelineRef: React.RefObject<HTMLDivElement | null>; + tracksScrollRef: React.RefObject<HTMLDivElement | null>; +} + +export function SnapIndicator({ + snapPoint, + zoomLevel, + isVisible, + timelineRef, + tracksScrollRef, +}: SnapIndicatorProps) { + const { leftPosition, topPosition, height } = useSnapIndicatorPosition({ + snapPoint, + zoomLevel, + timelineRef, + tracksScrollRef, + }); + + if (!isVisible || !snapPoint) { + return null; + } + + return ( + <div + className="pointer-events-none absolute" + style={{ + left: `${getCenteredLineLeft({ centerPixel: leftPosition })}px`, + top: topPosition, + height: `${height}px`, + width: `${TIMELINE_INDICATOR_LINE_WIDTH_PX}px`, + zIndex: TIMELINE_LAYERS.snapIndicator, + }} + > + <div className={"bg-primary/40 h-full w-0.5 opacity-80"} /> + </div> + ); +} diff --git a/apps/web/src/components/editor/panels/timeline/theme.ts b/apps/web/src/timeline/components/theme.ts index 0afe3eb4..9f93dc16 100644 --- a/apps/web/src/components/editor/panels/timeline/theme.ts +++ b/apps/web/src/timeline/components/theme.ts @@ -1,4 +1,4 @@ -import type { TrackType } from "@/lib/timeline"; +import type { TrackType } from "@/timeline"; export const TIMELINE_AUDIO_WAVEFORM_COLOR = "rgba(255, 255, 255, 0.7)"; diff --git a/apps/web/src/components/editor/panels/timeline/timeline-element.tsx b/apps/web/src/timeline/components/timeline-element.tsx index 75a2dfa8..74c34607 100644 --- a/apps/web/src/components/editor/panels/timeline/timeline-element.tsx +++ b/apps/web/src/timeline/components/timeline-element.tsx @@ -1,19 +1,19 @@ "use client"; import { createContext, useContext } from "react"; -import { useEditor } from "@/hooks/use-editor"; -import { useAssetsPanelStore } from "@/stores/assets-panel-store"; +import { useEditor } from "@/editor/use-editor"; +import { useAssetsPanelStore } from "@/components/editor/panels/assets/assets-panel-store"; import { AudioWaveform, WAVEFORM_GAIN_SAMPLE_COUNT } from "./audio-waveform"; import { AudioVolumeLine } from "./audio-volume-line"; -import { useElementPreview } from "@/hooks/use-element-preview"; +import { useElementPreview } from "@/timeline/hooks/use-element-preview"; import { useKeyframeDrag, type KeyframeDragState, -} from "@/hooks/timeline/element/use-keyframe-drag"; -import { useKeyframeSelection } from "@/hooks/timeline/element/use-keyframe-selection"; -import { useKeyframeBoxSelect } from "@/hooks/timeline/element/use-keyframe-box-select"; -import { SelectionBox } from "@/lib/selection/selection-box"; -import { getElementKeyframes } from "@/lib/animation"; +} from "@/timeline/hooks/element/use-keyframe-drag"; +import { useKeyframeSelection } from "@/timeline/hooks/element/use-keyframe-selection"; +import { useKeyframeBoxSelect } from "@/timeline/hooks/element/use-keyframe-box-select"; +import { SelectionBox } from "@/selection/selection-box"; +import { getElementKeyframes } from "@/animation"; import { canElementHaveAudio, canElementBeHidden, @@ -21,7 +21,7 @@ import { hasMediaId, timelineTimeToPixels, timelineTimeToSnappedPixels, -} from "@/lib/timeline"; +} from "@/timeline"; import { getTrackHeight } from "./track-layout"; import { getTimelineElementClassName, TIMELINE_TRACK_THEME } from "./theme"; import { @@ -38,27 +38,27 @@ import type { VideoElement, ImageElement, AudioElement, -} from "@/lib/timeline"; -import type { MediaAsset } from "@/lib/media/types"; -import { mediaSupportsAudio } from "@/lib/media/media-utils"; +} from "@/timeline"; +import type { MediaAsset } from "@/media/types"; +import { mediaSupportsAudio } from "@/media/media-utils"; import { canToggleSourceAudio, getSourceAudioActionLabel, isSourceAudioSeparated, -} from "@/lib/timeline/audio-separation"; -import { buildWaveformGainSamples } from "@/lib/timeline/audio-state"; -import { getTimelinePixelsPerSecond } from "@/lib/timeline"; -import { buildWaveformSourceKey } from "@/lib/media/waveform-summary"; -import { TICKS_PER_SECOND } from "@/lib/wasm/ticks"; +} from "@/timeline/audio-separation"; +import { buildWaveformGainSamples } from "@/timeline/audio-state"; +import { getTimelinePixelsPerSecond } from "@/timeline"; +import { buildWaveformSourceKey } from "@/media/waveform-summary"; +import { TICKS_PER_SECOND } from "@/wasm/ticks"; import { getActionDefinition, type TAction, type TActionWithOptionalArgs, invokeAction, -} from "@/lib/actions"; -import { useElementSelection } from "@/hooks/timeline/element/use-element-selection"; -import { resolveStickerId } from "@/lib/stickers"; -import { buildGraphicPreviewUrl } from "@/lib/graphics"; +} from "@/actions"; +import { useElementSelection } from "@/timeline/hooks/element/use-element-selection"; +import { resolveStickerId } from "@/stickers"; +import { buildGraphicPreviewUrl } from "@/graphics"; import Image from "next/image"; import { ScissorIcon, @@ -77,14 +77,11 @@ import { import { HugeiconsIcon } from "@hugeicons/react"; import { uppercase } from "@/utils/string"; import { useMemo, type ComponentProps, type ReactNode } from "react"; -import type { - SelectedKeyframeRef, - ElementKeyframe, -} from "@/lib/animation/types"; +import type { SelectedKeyframeRef, ElementKeyframe } from "@/animation/types"; import { cn } from "@/utils/ui"; import { usePropertiesStore } from "@/components/editor/panels/properties/stores/properties-store"; -import { getTrackTypeForElementType } from "@/lib/timeline/placement/compatibility"; -import { useTimelineStore } from "@/stores/timeline-store"; +import { getTrackTypeForElementType } from "@/timeline/placement/compatibility"; +import { useTimelineStore } from "@/timeline/timeline-store"; import { KEYFRAME_LANE_HEIGHT_PX } from "./layout"; import { getExpandedRows, diff --git a/apps/web/src/components/editor/panels/timeline/timeline-playhead.tsx b/apps/web/src/timeline/components/timeline-playhead.tsx index d9218371..4dd3329f 100644 --- a/apps/web/src/components/editor/panels/timeline/timeline-playhead.tsx +++ b/apps/web/src/timeline/components/timeline-playhead.tsx @@ -5,13 +5,11 @@ import { getCenteredLineLeft, TIMELINE_INDICATOR_LINE_WIDTH_PX, timelineTimeToSnappedPixels, -} from "@/lib/timeline"; -import { useTimelinePlayhead } from "@/hooks/timeline/use-timeline-playhead"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; -import { useEditor } from "@/hooks/use-editor"; -import { - TIMELINE_SCROLLBAR_SIZE_PX, -} from "./layout"; +} from "@/timeline"; +import { useTimelinePlayhead } from "@/timeline/hooks/use-timeline-playhead"; +import { TICKS_PER_SECOND } from "@/wasm"; +import { useEditor } from "@/editor/use-editor"; +import { TIMELINE_SCROLLBAR_SIZE_PX } from "./layout"; import { TIMELINE_LAYERS } from "./layers"; interface TimelinePlayheadProps { @@ -74,10 +72,15 @@ export function TimelinePlayhead({ event.preventDefault(); const fps = editor.project.getActive().settings.fps; - const ticksPerFrame = Math.round(TICKS_PER_SECOND * fps.denominator / fps.numerator); + const ticksPerFrame = Math.round( + (TICKS_PER_SECOND * fps.denominator) / fps.numerator, + ); const direction = event.key === "ArrowRight" ? 1 : -1; const now = editor.playback.getCurrentTime(); - const nextTime = Math.max(0, Math.min(duration, now + direction * ticksPerFrame)); + const nextTime = Math.max( + 0, + Math.min(duration, now + direction * ticksPerFrame), + ); editor.playback.seek({ time: nextTime }); }; diff --git a/apps/web/src/components/editor/panels/timeline/timeline-ruler.tsx b/apps/web/src/timeline/components/timeline-ruler.tsx index 7b69e820..e2a379ee 100644 --- a/apps/web/src/components/editor/panels/timeline/timeline-ruler.tsx +++ b/apps/web/src/timeline/components/timeline-ruler.tsx @@ -1,14 +1,12 @@ import { type JSX, useLayoutEffect, useRef } from "react"; -import { - BASE_TIMELINE_PIXELS_PER_SECOND, -} from "@/lib/timeline/scale"; +import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/timeline/scale"; import { mediaTimeToSeconds } from "opencut-wasm"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; +import { TICKS_PER_SECOND } from "@/wasm"; import { TIMELINE_RULER_HEIGHT_PX } from "./layout"; -import { DEFAULT_FPS } from "@/lib/fps/defaults"; -import { useEditor } from "@/hooks/use-editor"; -import { getRulerConfig, shouldShowLabel } from "@/lib/timeline/ruler-utils"; -import { useScrollPosition } from "@/hooks/timeline/use-scroll-position"; +import { DEFAULT_FPS } from "@/fps/defaults"; +import { useEditor } from "@/editor/use-editor"; +import { getRulerConfig, shouldShowLabel } from "@/timeline/ruler-utils"; +import { useScrollPosition } from "@/timeline/hooks/use-scroll-position"; import { TimelineTick } from "./timeline-tick"; interface TimelineRulerProps { @@ -36,14 +34,18 @@ export function TimelineRuler({ const durationSeconds = mediaTimeToSeconds({ time: durationTicks }); const pixelsPerSecond = BASE_TIMELINE_PIXELS_PER_SECOND * zoomLevel; const visibleDurationSeconds = dynamicTimelineWidth / pixelsPerSecond; - const effectiveDurationSeconds = Math.max(durationSeconds, visibleDurationSeconds); + const effectiveDurationSeconds = Math.max( + durationSeconds, + visibleDurationSeconds, + ); const fps = useEditor((e) => e.project.getActiveOrNull()?.settings.fps) ?? DEFAULT_FPS; const { labelIntervalSeconds, tickIntervalSeconds } = getRulerConfig({ zoomLevel, fps, }); - const tickCount = Math.ceil(effectiveDurationSeconds / tickIntervalSeconds) + 1; + const tickCount = + Math.ceil(effectiveDurationSeconds / tickIntervalSeconds) + 1; const { scrollLeft, viewportWidth } = useScrollPosition({ scrollRef: tracksScrollRef, @@ -90,7 +92,10 @@ export function TimelineRuler({ if (timeSeconds > effectiveDurationSeconds) break; const timeTicks = Math.round(timeSeconds * TICKS_PER_SECOND); - const showLabel = shouldShowLabel({ time: timeSeconds, labelIntervalSeconds }); + const showLabel = shouldShowLabel({ + time: timeSeconds, + labelIntervalSeconds, + }); timelineTicks.push( <TimelineTick key={tickIndex} diff --git a/apps/web/src/components/editor/panels/timeline/timeline-tick.tsx b/apps/web/src/timeline/components/timeline-tick.tsx index fca55752..1b1e46d3 100644 --- a/apps/web/src/components/editor/panels/timeline/timeline-tick.tsx +++ b/apps/web/src/timeline/components/timeline-tick.tsx @@ -1,8 +1,8 @@ "use client"; import type { FrameRate } from "opencut-wasm"; -import { timelineTimeToSnappedPixels } from "@/lib/timeline"; -import { formatRulerLabel } from "@/lib/timeline/ruler-utils"; +import { timelineTimeToSnappedPixels } from "@/timeline"; +import { formatRulerLabel } from "@/timeline/ruler-utils"; interface TimelineTickProps { time: number; diff --git a/apps/web/src/components/editor/panels/timeline/timeline-toolbar.tsx b/apps/web/src/timeline/components/timeline-toolbar.tsx index 80ea10d0..22acd447 100644 --- a/apps/web/src/components/editor/panels/timeline/timeline-toolbar.tsx +++ b/apps/web/src/timeline/components/timeline-toolbar.tsx @@ -1,5 +1,5 @@ -import { useEditor } from "@/hooks/use-editor"; -import { useElementSelection } from "@/hooks/timeline/element/use-element-selection"; +import { useEditor } from "@/editor/use-editor"; +import { useElementSelection } from "@/timeline/hooks/element/use-element-selection"; import { TooltipProvider, Tooltip, @@ -15,18 +15,18 @@ import { } from "@/components/ui/split-button"; import { Slider } from "@/components/ui/slider"; import { TIMELINE_ZOOM_BUTTON_FACTOR } from "./interaction"; -import { TIMELINE_ZOOM_MAX } from "@/lib/timeline/scale"; -import { sliderToZoom, zoomToSlider } from "@/lib/timeline/zoom-utils"; +import { TIMELINE_ZOOM_MAX } from "@/timeline/scale"; +import { sliderToZoom, zoomToSlider } from "@/timeline/zoom-utils"; import { ScenesView } from "@/components/editor/scenes-view"; -import { type TActionWithOptionalArgs, invokeAction } from "@/lib/actions"; +import { type TActionWithOptionalArgs, invokeAction } from "@/actions"; import { canToggleSourceAudio, getSourceAudioActionLabel, isSourceAudioSeparated, -} from "@/lib/timeline/audio-separation"; -import { hasMediaId } from "@/lib/timeline"; +} from "@/timeline/audio-separation"; +import { hasMediaId } from "@/timeline"; import { cn } from "@/utils/ui"; -import { useTimelineStore } from "@/stores/timeline-store"; +import { useTimelineStore } from "@/timeline/timeline-store"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Bookmark02Icon, diff --git a/apps/web/src/components/editor/panels/timeline/timeline-track.tsx b/apps/web/src/timeline/components/timeline-track.tsx index 32b1b93d..6c40637e 100644 --- a/apps/web/src/components/editor/panels/timeline/timeline-track.tsx +++ b/apps/web/src/timeline/components/timeline-track.tsx @@ -1,14 +1,14 @@ "use client"; -import { useElementSelection } from "@/hooks/timeline/element/use-element-selection"; +import { useElementSelection } from "@/timeline/hooks/element/use-element-selection"; import { TimelineElement } from "./timeline-element"; -import type { TimelineTrack } from "@/lib/timeline"; -import type { TimelineElement as TimelineElementType } from "@/lib/timeline"; +import type { TimelineTrack } from "@/timeline"; +import type { TimelineElement as TimelineElementType } from "@/timeline"; import { TIMELINE_LAYERS } from "./layers"; -import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/lib/timeline/scale"; -import { useEdgeAutoScroll } from "@/hooks/timeline/use-edge-auto-scroll"; -import type { ElementDragState } from "@/lib/timeline"; -import { useEditor } from "@/hooks/use-editor"; +import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/timeline/scale"; +import { useEdgeAutoScroll } from "@/timeline/hooks/use-edge-auto-scroll"; +import type { ElementDragState } from "@/timeline"; +import { useEditor } from "@/editor/use-editor"; interface TimelineTrackContentProps { track: TimelineTrack; diff --git a/apps/web/src/components/editor/panels/timeline/track-layout.ts b/apps/web/src/timeline/components/track-layout.ts index 8f833a57..59f4a5f9 100644 --- a/apps/web/src/components/editor/panels/timeline/track-layout.ts +++ b/apps/web/src/timeline/components/track-layout.ts @@ -1,4 +1,4 @@ -import type { TrackType } from "@/lib/timeline"; +import type { TrackType } from "@/timeline"; import { KEYFRAME_LANE_HEIGHT_PX, TIMELINE_TRACK_GAP_PX, diff --git a/apps/web/src/lib/timeline/creation.ts b/apps/web/src/timeline/creation.ts index 19f46115..2ac7f246 100644 --- a/apps/web/src/lib/timeline/creation.ts +++ b/apps/web/src/timeline/creation.ts @@ -1,3 +1,3 @@ -import { TICKS_PER_SECOND } from "@/lib/wasm/ticks"; +import { TICKS_PER_SECOND } from "@/wasm/ticks"; export const DEFAULT_NEW_ELEMENT_DURATION = 5 * TICKS_PER_SECOND; diff --git a/apps/web/src/lib/timeline/defaults.ts b/apps/web/src/timeline/defaults.ts index cf452800..06639e01 100644 --- a/apps/web/src/lib/timeline/defaults.ts +++ b/apps/web/src/timeline/defaults.ts @@ -1,6 +1,6 @@ -import { DEFAULT_NEW_ELEMENT_DURATION } from "@/lib/timeline/creation"; -import type { TTimelineViewState } from "@/lib/project/types"; -import type { BlendMode, Transform } from "@/lib/rendering"; +import { DEFAULT_NEW_ELEMENT_DURATION } from "@/timeline/creation"; +import type { TTimelineViewState } from "@/project/types"; +import type { BlendMode, Transform } from "@/rendering"; import type { TextElement } from "./types"; const defaultTransform: Transform = { diff --git a/apps/web/src/lib/drag-data.ts b/apps/web/src/timeline/drag-data.ts index 5cbcfbe0..1fc9ce9a 100644 --- a/apps/web/src/lib/drag-data.ts +++ b/apps/web/src/timeline/drag-data.ts @@ -1,4 +1,4 @@ -import type { TimelineDragData } from "@/lib/timeline/drag";
+import type { TimelineDragData } from "@/timeline/drag";
const MIME_TYPE = "application/x-timeline-drag";
let lastDragData: TimelineDragData | null = null;
diff --git a/apps/web/src/lib/timeline/drag-utils.ts b/apps/web/src/timeline/drag-utils.ts index 35d00613..a731d7a0 100644 --- a/apps/web/src/lib/timeline/drag-utils.ts +++ b/apps/web/src/timeline/drag-utils.ts @@ -1,5 +1,5 @@ -import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/lib/timeline/scale"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; +import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/timeline/scale"; +import { TICKS_PER_SECOND } from "@/wasm"; export function getMouseTimeFromClientX({ clientX, diff --git a/apps/web/src/lib/timeline/drag.ts b/apps/web/src/timeline/drag.ts index 6e74e17c..7bf99a6d 100644 --- a/apps/web/src/lib/timeline/drag.ts +++ b/apps/web/src/timeline/drag.ts @@ -1,5 +1,5 @@ import type { MaskableElement, VisualElement } from "./types";
-import type { ParamValues } from "@/lib/params";
+import type { ParamValues } from "@/params";
interface BaseDragData {
id: string;
diff --git a/apps/web/src/timeline/element-snap-source.ts b/apps/web/src/timeline/element-snap-source.ts new file mode 100644 index 00000000..ec1e5f65 --- /dev/null +++ b/apps/web/src/timeline/element-snap-source.ts @@ -0,0 +1,38 @@ +import type { SceneTracks } from "@/timeline"; +import type { SnapPoint } from "@/timeline/snapping"; + +export function getElementEdgeSnapPoints({ + tracks, + excludeElementIds, +}: { + tracks: SceneTracks; + excludeElementIds?: Set<string>; +}): SnapPoint[] { + const snapPoints: SnapPoint[] = []; + const orderedTracks = [...tracks.overlay, tracks.main, ...tracks.audio]; + + for (const track of orderedTracks) { + for (const element of track.elements) { + if (excludeElementIds?.has(element.id)) { + continue; + } + + snapPoints.push( + { + time: element.startTime, + type: "element-start", + elementId: element.id, + trackId: track.id, + }, + { + time: element.startTime + element.duration, + type: "element-end", + elementId: element.id, + trackId: track.id, + }, + ); + } + } + + return snapPoints; +} diff --git a/apps/web/src/lib/timeline/element-utils.ts b/apps/web/src/timeline/element-utils.ts index e7ec4065..676bc823 100644 --- a/apps/web/src/lib/timeline/element-utils.ts +++ b/apps/web/src/timeline/element-utils.ts @@ -1,4 +1,4 @@ -import { DEFAULT_NEW_ELEMENT_DURATION } from "@/lib/timeline/creation"; +import { DEFAULT_NEW_ELEMENT_DURATION } from "@/timeline/creation"; import { MASKABLE_ELEMENT_TYPES, RETIMABLE_ELEMENT_TYPES, @@ -22,12 +22,12 @@ import { type RetimableElement, type VisualElement, type UploadAudioElement, -} from "@/lib/timeline"; -import { DEFAULTS } from "@/lib/timeline/defaults"; -import type { MediaType } from "@/lib/media/types"; -import { buildDefaultEffectInstance } from "@/lib/effects"; -import { buildDefaultGraphicInstance } from "@/lib/graphics"; -import type { ParamValues } from "@/lib/params"; +} from "@/timeline"; +import { DEFAULTS } from "@/timeline/defaults"; +import type { MediaType } from "@/media/types"; +import { buildDefaultEffectInstance } from "@/effects"; +import { buildDefaultGraphicInstance } from "@/graphics"; +import type { ParamValues } from "@/params"; import { capitalizeFirstLetter } from "@/utils/string"; export function canElementHaveAudio( diff --git a/apps/web/src/lib/timeline/group-move/build-group.ts b/apps/web/src/timeline/group-move/build-group.ts index 16d720eb..00f39d55 100644 --- a/apps/web/src/lib/timeline/group-move/build-group.ts +++ b/apps/web/src/timeline/group-move/build-group.ts @@ -1,5 +1,5 @@ -import type { ElementRef, SceneTracks } from "@/lib/timeline"; -import { findTrackInSceneTracks } from "@/lib/timeline/track-element-update"; +import type { ElementRef, SceneTracks } from "@/timeline"; +import { findTrackInSceneTracks } from "@/timeline/track-element-update"; import type { GroupMember, MoveGroup } from "./types"; import { getTrackPlacementById } from "./track-placement"; diff --git a/apps/web/src/lib/timeline/group-move/index.ts b/apps/web/src/timeline/group-move/index.ts index 82bd3895..82bd3895 100644 --- a/apps/web/src/lib/timeline/group-move/index.ts +++ b/apps/web/src/timeline/group-move/index.ts diff --git a/apps/web/src/lib/timeline/group-move/resolve-move.ts b/apps/web/src/timeline/group-move/resolve-move.ts index fbe4cbda..60940315 100644 --- a/apps/web/src/lib/timeline/group-move/resolve-move.ts +++ b/apps/web/src/timeline/group-move/resolve-move.ts @@ -1,6 +1,6 @@ -import type { SceneTracks } from "@/lib/timeline"; -import { getTrackTypeForElementType } from "@/lib/timeline/placement/compatibility"; -import { canPlaceTimeSpansOnTrack } from "@/lib/timeline/placement/overlap"; +import type { SceneTracks } from "@/timeline"; +import { getTrackTypeForElementType } from "@/timeline/placement/compatibility"; +import { canPlaceTimeSpansOnTrack } from "@/timeline/placement/overlap"; import type { GroupMoveResult, MoveGroup, diff --git a/apps/web/src/lib/timeline/group-move/snap.ts b/apps/web/src/timeline/group-move/snap.ts index 3502ae30..32c0a2f1 100644 --- a/apps/web/src/lib/timeline/group-move/snap.ts +++ b/apps/web/src/timeline/group-move/snap.ts @@ -1,9 +1,13 @@ -import type { SceneTracks } from "@/lib/timeline"; +import type { SceneTracks } from "@/timeline"; import { - findSnapPoints, - snapToNearestPoint, + buildTimelineSnapPoints, + getTimelineSnapThresholdInTicks, + resolveTimelineSnap, type SnapPoint, -} from "@/lib/timeline/snap-utils"; +} from "@/timeline/snapping"; +import { getElementEdgeSnapPoints } from "@/timeline/element-snap-source"; +import { getPlayheadSnapPoints } from "@/timeline/playhead-snap-source"; +import { getAnimationKeyframeSnapPointsForTimeline } from "@/animation/timeline-snap-points"; import type { MoveGroup } from "./types"; export function snapGroupEdges({ @@ -22,11 +26,21 @@ export function snapGroupEdges({ snappedAnchorStartTime: number; snapPoint: SnapPoint | null; } { - const snapPoints = findSnapPoints({ - tracks, - playheadTime, - excludeElementIds: new Set(group.members.map((member) => member.elementId)), + const excludeElementIds = new Set( + group.members.map((member) => member.elementId), + ); + const snapPoints = buildTimelineSnapPoints({ + sources: [ + () => getElementEdgeSnapPoints({ tracks, excludeElementIds }), + () => getPlayheadSnapPoints({ playheadTime }), + () => + getAnimationKeyframeSnapPointsForTimeline({ + tracks, + excludeElementIds, + }), + ], }); + const maxSnapDistance = getTimelineSnapThresholdInTicks({ zoomLevel }); let closestSnapDistance = Infinity; let snappedAnchorStartTime = anchorStartTime; @@ -34,10 +48,10 @@ export function snapGroupEdges({ for (const member of group.members) { const memberStartTime = anchorStartTime + member.timeOffset; - const memberStartSnap = snapToNearestPoint({ + const memberStartSnap = resolveTimelineSnap({ targetTime: memberStartTime, snapPoints, - zoomLevel, + maxSnapDistance, }); if ( memberStartSnap.snapPoint && @@ -48,10 +62,10 @@ export function snapGroupEdges({ snapPoint = memberStartSnap.snapPoint; } - const memberEndSnap = snapToNearestPoint({ + const memberEndSnap = resolveTimelineSnap({ targetTime: memberStartTime + member.duration, snapPoints, - zoomLevel, + maxSnapDistance, }); if ( memberEndSnap.snapPoint && diff --git a/apps/web/src/lib/timeline/group-move/track-placement.ts b/apps/web/src/timeline/group-move/track-placement.ts index d301e5ef..c4c5efb5 100644 --- a/apps/web/src/lib/timeline/group-move/track-placement.ts +++ b/apps/web/src/timeline/group-move/track-placement.ts @@ -1,4 +1,4 @@ -import type { SceneTracks, TimelineTrack } from "@/lib/timeline"; +import type { SceneTracks, TimelineTrack } from "@/timeline"; import type { GroupTrackSection } from "./types"; export interface TrackPlacement { diff --git a/apps/web/src/lib/timeline/group-move/types.ts b/apps/web/src/timeline/group-move/types.ts index 5ac1e207..daf9c6e9 100644 --- a/apps/web/src/lib/timeline/group-move/types.ts +++ b/apps/web/src/timeline/group-move/types.ts @@ -1,4 +1,4 @@ -import type { ElementRef, ElementType, TrackType } from "@/lib/timeline"; +import type { ElementRef, ElementType, TrackType } from "@/timeline"; export type GroupTrackSection = "overlay" | "main" | "audio"; diff --git a/apps/web/src/lib/timeline/group-resize/compute-resize.ts b/apps/web/src/timeline/group-resize/compute-resize.ts index 5730b117..dd542967 100644 --- a/apps/web/src/lib/timeline/group-resize/compute-resize.ts +++ b/apps/web/src/timeline/group-resize/compute-resize.ts @@ -2,8 +2,8 @@ import { roundToFrame } from "opencut-wasm"; import { getSourceSpanAtClipTime, getTimelineDurationForSourceSpan, -} from "@/lib/retime"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; +} from "@/retime"; +import { TICKS_PER_SECOND } from "@/wasm"; import type { ComputeGroupResizeArgs, GroupResizeMember, diff --git a/apps/web/src/lib/timeline/group-resize/index.ts b/apps/web/src/timeline/group-resize/index.ts index bc0a7b35..bc0a7b35 100644 --- a/apps/web/src/lib/timeline/group-resize/index.ts +++ b/apps/web/src/timeline/group-resize/index.ts diff --git a/apps/web/src/lib/timeline/group-resize/types.ts b/apps/web/src/timeline/group-resize/types.ts index d5f00c1b..b07e6fe6 100644 --- a/apps/web/src/lib/timeline/group-resize/types.ts +++ b/apps/web/src/timeline/group-resize/types.ts @@ -1,5 +1,5 @@ import type { FrameRate } from "opencut-wasm"; -import type { ElementRef, RetimeConfig } from "@/lib/timeline/types"; +import type { ElementRef, RetimeConfig } from "@/timeline/types"; export type ResizeSide = "left" | "right"; diff --git a/apps/web/src/hooks/timeline/element/use-element-interaction.ts b/apps/web/src/timeline/hooks/element/use-element-interaction.ts index 5458efe7..621259b9 100644 --- a/apps/web/src/hooks/timeline/element/use-element-interaction.ts +++ b/apps/web/src/timeline/hooks/element/use-element-interaction.ts @@ -6,25 +6,25 @@ import { type MouseEvent as ReactMouseEvent, type RefObject, } from "react"; -import { useEditor } from "@/hooks/use-editor"; +import { useEditor } from "@/editor/use-editor"; import { useShiftKey } from "@/hooks/use-shift-key"; -import { useElementSelection } from "@/hooks/timeline/element/use-element-selection"; +import { useElementSelection } from "@/timeline/hooks/element/use-element-selection"; import { buildMoveGroup, resolveGroupMove, snapGroupEdges, type GroupMoveResult, type MoveGroup, -} from "@/lib/timeline/group-move"; -import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/lib/timeline/scale"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; -import { TIMELINE_DRAG_THRESHOLD_PX } from "@/components/editor/panels/timeline/interaction"; +} from "@/timeline/group-move"; +import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/timeline/scale"; +import { TICKS_PER_SECOND } from "@/wasm"; +import { TIMELINE_DRAG_THRESHOLD_PX } from "@/timeline/components/interaction"; import { roundToFrame } from "opencut-wasm"; -import { computeDropTarget } from "@/components/editor/panels/timeline/drop-target"; -import { getMouseTimeFromClientX } from "@/lib/timeline/drag-utils"; +import { computeDropTarget } from "@/timeline/components/drop-target"; +import { getMouseTimeFromClientX } from "@/timeline/drag-utils"; import { generateUUID } from "@/utils/id"; -import type { SnapPoint } from "@/lib/timeline/snap-utils"; -import { registerCanceller } from "@/lib/cancel-interaction"; +import type { SnapPoint } from "@/timeline/snapping"; +import { registerCanceller } from "@/editor/cancel-interaction"; import type { DropTarget, ElementRef, @@ -32,7 +32,7 @@ import type { SceneTracks, TimelineElement, TimelineTrack, -} from "@/lib/timeline"; +} from "@/timeline"; interface UseElementInteractionProps { zoomLevel: number; diff --git a/apps/web/src/hooks/timeline/element/use-element-selection.ts b/apps/web/src/timeline/hooks/element/use-element-selection.ts index 42187b7f..19037e10 100644 --- a/apps/web/src/hooks/timeline/element/use-element-selection.ts +++ b/apps/web/src/timeline/hooks/element/use-element-selection.ts @@ -1,6 +1,6 @@ import { useCallback } from "react";
-import { useEditor } from "@/hooks/use-editor";
-import type { ElementRef } from "@/lib/timeline/types";
+import { useEditor } from "@/editor/use-editor";
+import type { ElementRef } from "@/timeline/types";
export function useElementSelection() {
const editor = useEditor();
diff --git a/apps/web/src/hooks/timeline/element/use-keyframe-box-select.ts b/apps/web/src/timeline/hooks/element/use-keyframe-box-select.ts index 0fc46372..e471e2d4 100644 --- a/apps/web/src/hooks/timeline/element/use-keyframe-box-select.ts +++ b/apps/web/src/timeline/hooks/element/use-keyframe-box-select.ts @@ -1,19 +1,16 @@ import { useCallback, useRef, useMemo } from "react"; -import { useBoxSelect } from "@/lib/selection/hooks/use-box-select"; +import { useBoxSelect } from "@/selection/hooks/use-box-select"; import { useKeyframeSelection, getSelectedKeyframeId, } from "./use-keyframe-selection"; -import type { - SelectedKeyframeRef, - ElementKeyframe, -} from "@/lib/animation/types"; -import type { ExpandedRow } from "@/components/editor/panels/timeline/expanded-layout"; -import { timelineTimeToSnappedPixels } from "@/lib/timeline"; +import type { SelectedKeyframeRef, ElementKeyframe } from "@/animation/types"; +import type { ExpandedRow } from "@/timeline/components/expanded-layout"; +import { timelineTimeToSnappedPixels } from "@/timeline"; import { KEYFRAME_LANE_HEIGHT_PX, KEYFRAME_DIAMOND_SIZE_PX, -} from "@/components/editor/panels/timeline/layout"; +} from "@/timeline/components/layout"; export function useKeyframeBoxSelect({ trackId, @@ -96,7 +93,8 @@ export function useKeyframeBoxSelect({ }, [keyframeEntries]); const selectedIds = useMemo( - () => selectedKeyframes.map((keyframe) => getSelectedKeyframeId({ keyframe })), + () => + selectedKeyframes.map((keyframe) => getSelectedKeyframeId({ keyframe })), [selectedKeyframes], ); @@ -168,10 +166,7 @@ export function useKeyframeBoxSelect({ if (isAdditive) { setKeyframeSelection({ - keyframes: [ - ...initialKeyframesRef.current, - ...intersectedRefs, - ], + keyframes: [...initialKeyframesRef.current, ...intersectedRefs], }); } else { setKeyframeSelection({ keyframes: intersectedRefs }); diff --git a/apps/web/src/hooks/timeline/element/use-keyframe-drag.ts b/apps/web/src/timeline/hooks/element/use-keyframe-drag.ts index 24bff26a..92b6f0d8 100644 --- a/apps/web/src/hooks/timeline/element/use-keyframe-drag.ts +++ b/apps/web/src/timeline/hooks/element/use-keyframe-drag.ts @@ -5,20 +5,20 @@ import { useRef, type MouseEvent as ReactMouseEvent, } from "react"; -import { useEditor } from "@/hooks/use-editor"; -import { getKeyframeById } from "@/lib/animation"; +import { useEditor } from "@/editor/use-editor"; +import { getKeyframeById } from "@/animation"; import { useKeyframeSelection } from "./use-keyframe-selection"; import { roundToFrame, snappedSeekTime } from "opencut-wasm"; -import { timelineTimeToSnappedPixels } from "@/lib/timeline"; -import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/lib/timeline/scale"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; -import { TIMELINE_DRAG_THRESHOLD_PX } from "@/components/editor/panels/timeline/interaction"; -import { RetimeKeyframeCommand } from "@/lib/commands/timeline/element/keyframes/retime-keyframe"; -import { BatchCommand } from "@/lib/commands"; -import type { SelectedKeyframeRef } from "@/lib/animation/types"; -import type { TimelineElement } from "@/lib/timeline"; -import type { Command } from "@/lib/commands/base-command"; -import { registerCanceller } from "@/lib/cancel-interaction"; +import { timelineTimeToSnappedPixels } from "@/timeline"; +import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/timeline/scale"; +import { TICKS_PER_SECOND } from "@/wasm"; +import { TIMELINE_DRAG_THRESHOLD_PX } from "@/timeline/components/interaction"; +import { RetimeKeyframeCommand } from "@/commands/timeline/element/keyframes/retime-keyframe"; +import { BatchCommand } from "@/commands"; +import type { SelectedKeyframeRef } from "@/animation/types"; +import type { TimelineElement } from "@/timeline"; +import type { Command } from "@/commands/base-command"; +import { registerCanceller } from "@/editor/cancel-interaction"; export interface KeyframeDragState { isDragging: boolean; draggingKeyframeIds: Set<string>; @@ -145,9 +145,12 @@ export function useKeyframeDrag({ if (!dragState.isDragging) return; - const startX = mouseDownXRef.current ?? clientX; - const rawDelta = Math.round(((clientX - startX) / pixelsPerSecond) * TICKS_PER_SECOND); - const snappedDelta = roundToFrame({ time: rawDelta, rate: fps }) ?? rawDelta; + const startX = mouseDownXRef.current ?? clientX; + const rawDelta = Math.round( + ((clientX - startX) / pixelsPerSecond) * TICKS_PER_SECOND, + ); + const snappedDelta = + roundToFrame({ time: rawDelta, rate: fps }) ?? rawDelta; setDragState((previous) => ({ ...previous, deltaTime: snappedDelta })); }; @@ -211,27 +214,27 @@ export function useKeyframeDrag({ event.preventDefault(); event.stopPropagation(); - mouseDownXRef.current = event.clientX; + mouseDownXRef.current = event.clientX; - const anySelected = keyframes.some((keyframe) => - isKeyframeSelected({ keyframe }), - ); + const anySelected = keyframes.some((keyframe) => + isKeyframeSelected({ keyframe }), + ); - const isModifierKey = event.shiftKey || event.metaKey || event.ctrlKey; - if (!anySelected && !isModifierKey) { - setKeyframeSelection({ keyframes }); - } + const isModifierKey = event.shiftKey || event.metaKey || event.ctrlKey; + if (!anySelected && !isModifierKey) { + setKeyframeSelection({ keyframes }); + } - const keyframeRefsToTrack = anySelected ? selectedKeyframes : keyframes; + const keyframeRefsToTrack = anySelected ? selectedKeyframes : keyframes; - pendingDragRef.current = { - keyframeRefs: keyframeRefsToTrack, - startMouseX: event.clientX, - }; - setIsPendingDrag(true); - }, - [isKeyframeSelected, selectedKeyframes, setKeyframeSelection], -); + pendingDragRef.current = { + keyframeRefs: keyframeRefsToTrack, + startMouseX: event.clientX, + }; + setIsPendingDrag(true); + }, + [isKeyframeSelected, selectedKeyframes, setKeyframeSelection], + ); const handleKeyframeClick = useCallback( ({ @@ -250,13 +253,18 @@ export function useKeyframeDrag({ const wasDrag = mouseDownXRef.current !== null && Math.abs(event.clientX - mouseDownXRef.current) > - TIMELINE_DRAG_THRESHOLD_PX; + TIMELINE_DRAG_THRESHOLD_PX; mouseDownXRef.current = null; if (wasDrag) return; const duration = editor.timeline.getTotalDuration(); - const seekTime = snappedSeekTime({ time: displayedStartTime + indicatorTime, duration, rate: fps }) ?? displayedStartTime + indicatorTime; + const seekTime = + snappedSeekTime({ + time: displayedStartTime + indicatorTime, + duration, + rate: fps, + }) ?? displayedStartTime + indicatorTime; editor.playback.seek({ time: seekTime }); if (event.shiftKey) { @@ -273,7 +281,13 @@ export function useKeyframeDrag({ isMultiKey: event.metaKey || event.ctrlKey, }); }, - [toggleKeyframeSelection, selectKeyframeRange, editor, displayedStartTime, fps], + [ + toggleKeyframeSelection, + selectKeyframeRange, + editor, + displayedStartTime, + fps, + ], ); const getVisualOffsetPx = useCallback( diff --git a/apps/web/src/hooks/timeline/element/use-keyframe-selection.ts b/apps/web/src/timeline/hooks/element/use-keyframe-selection.ts index 2b5a0566..c5d3ffc9 100644 --- a/apps/web/src/hooks/timeline/element/use-keyframe-selection.ts +++ b/apps/web/src/timeline/hooks/element/use-keyframe-selection.ts @@ -1,6 +1,6 @@ import { useCallback, useSyncExternalStore } from "react"; -import { useEditor } from "@/hooks/use-editor"; -import type { SelectedKeyframeRef } from "@/lib/animation/types"; +import { useEditor } from "@/editor/use-editor"; +import type { SelectedKeyframeRef } from "@/animation/types"; export function getSelectedKeyframeId({ keyframe, diff --git a/apps/web/src/hooks/timeline/use-edge-auto-scroll.ts b/apps/web/src/timeline/hooks/use-edge-auto-scroll.ts index 509031bb..509031bb 100644 --- a/apps/web/src/hooks/timeline/use-edge-auto-scroll.ts +++ b/apps/web/src/timeline/hooks/use-edge-auto-scroll.ts diff --git a/apps/web/src/hooks/use-element-preview.ts b/apps/web/src/timeline/hooks/use-element-preview.ts index 4f28be83..67213094 100644 --- a/apps/web/src/hooks/use-element-preview.ts +++ b/apps/web/src/timeline/hooks/use-element-preview.ts @@ -1,5 +1,5 @@ -import { useEditor } from "@/hooks/use-editor";
-import { findTrackInSceneTracks, type TimelineElement } from "@/lib/timeline";
+import { useEditor } from "@/editor/use-editor";
+import { findTrackInSceneTracks, type TimelineElement } from "@/timeline";
/**
* Subscribes to render tracks and returns the live (preview-aware) version of
diff --git a/apps/web/src/hooks/timeline/use-initial-scroll-bottom.ts b/apps/web/src/timeline/hooks/use-initial-scroll-bottom.ts index 0b1cf92a..0b1cf92a 100644 --- a/apps/web/src/hooks/timeline/use-initial-scroll-bottom.ts +++ b/apps/web/src/timeline/hooks/use-initial-scroll-bottom.ts diff --git a/apps/web/src/hooks/timeline/use-scroll-position.ts b/apps/web/src/timeline/hooks/use-scroll-position.ts index ad2c6374..ad2c6374 100644 --- a/apps/web/src/hooks/timeline/use-scroll-position.ts +++ b/apps/web/src/timeline/hooks/use-scroll-position.ts diff --git a/apps/web/src/hooks/timeline/use-scroll-sync.ts b/apps/web/src/timeline/hooks/use-scroll-sync.ts index e147c106..e147c106 100644 --- a/apps/web/src/hooks/timeline/use-scroll-sync.ts +++ b/apps/web/src/timeline/hooks/use-scroll-sync.ts diff --git a/apps/web/src/hooks/timeline/use-snap-indicator-position.ts b/apps/web/src/timeline/hooks/use-snap-indicator-position.ts index b941dcb5..2081b939 100644 --- a/apps/web/src/hooks/timeline/use-snap-indicator-position.ts +++ b/apps/web/src/timeline/hooks/use-snap-indicator-position.ts @@ -1,6 +1,6 @@ import { useEffect, useState } from "react";
-import { timelineTimeToSnappedPixels } from "@/lib/timeline";
-import { TIMELINE_TRACK_LABELS_COLUMN_WIDTH_PX } from "@/components/editor/panels/timeline/layout";
+import { timelineTimeToSnappedPixels } from "@/timeline";
+import { TIMELINE_TRACK_LABELS_COLUMN_WIDTH_PX } from "@/timeline/components/layout";
interface UseSnapIndicatorPositionParams {
snapPoint: { time: number } | null;
zoomLevel: number;
diff --git a/apps/web/src/hooks/timeline/use-timeline-drag-drop.ts b/apps/web/src/timeline/hooks/use-timeline-drag-drop.ts index 25c24b8b..775b7337 100644 --- a/apps/web/src/hooks/timeline/use-timeline-drag-drop.ts +++ b/apps/web/src/timeline/hooks/use-timeline-drag-drop.ts @@ -1,11 +1,11 @@ import { useState, useCallback, type RefObject } from "react"; -import { useEditor } from "@/hooks/use-editor"; -import { processMediaAssets } from "@/lib/media/processing"; +import { useEditor } from "@/editor/use-editor"; +import { processMediaAssets } from "@/media/processing"; import { toast } from "sonner"; -import { showMediaUploadToast } from "@/lib/media/upload-toast"; -import { DEFAULT_NEW_ELEMENT_DURATION } from "@/lib/timeline/creation"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; -import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/lib/timeline/scale"; +import { showMediaUploadToast } from "@/media/upload-toast"; +import { DEFAULT_NEW_ELEMENT_DURATION } from "@/timeline/creation"; +import { TICKS_PER_SECOND } from "@/wasm"; +import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/timeline/scale"; import { roundToFrame } from "opencut-wasm"; import { buildTextElement, @@ -13,18 +13,18 @@ import { buildStickerElement, buildElementFromMedia, buildEffectElement, -} from "@/lib/timeline/element-utils"; -import { AddTrackCommand, InsertElementCommand } from "@/lib/commands/timeline"; -import { BatchCommand } from "@/lib/commands"; -import { computeDropTarget } from "@/components/editor/panels/timeline/drop-target"; -import { getDragData, hasDragData } from "@/lib/drag-data"; -import type { TrackType, DropTarget, ElementType } from "@/lib/timeline"; +} from "@/timeline/element-utils"; +import { AddTrackCommand, InsertElementCommand } from "@/commands/timeline"; +import { BatchCommand } from "@/commands"; +import { computeDropTarget } from "@/timeline/components/drop-target"; +import { getDragData, hasDragData } from "@/timeline/drag-data"; +import type { TrackType, DropTarget, ElementType } from "@/timeline"; import type { MediaDragData, GraphicDragData, StickerDragData, EffectDragData, -} from "@/lib/timeline/drag"; +} from "@/timeline/drag"; interface UseTimelineDragDropProps { containerRef: RefObject<HTMLDivElement | null>; diff --git a/apps/web/src/hooks/timeline/use-timeline-playhead.ts b/apps/web/src/timeline/hooks/use-timeline-playhead.ts index 1886222f..7d9cf447 100644 --- a/apps/web/src/hooks/timeline/use-timeline-playhead.ts +++ b/apps/web/src/timeline/hooks/use-timeline-playhead.ts @@ -1,16 +1,23 @@ import { snappedSeekTime } from "opencut-wasm"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; +import { TICKS_PER_SECOND } from "@/wasm"; import { useEffect, useCallback, useRef } from "react"; -import { useEdgeAutoScroll } from "@/hooks/timeline/use-edge-auto-scroll"; -import { useEditor } from "../use-editor"; +import { useEdgeAutoScroll } from "@/timeline/hooks/use-edge-auto-scroll"; +import { useEditor } from "@/editor/use-editor"; import { useShiftKey } from "@/hooks/use-shift-key"; -import { findSnapPoints, snapToNearestPoint } from "@/lib/timeline/snap-utils"; +import { + buildTimelineSnapPoints, + getTimelineSnapThresholdInTicks, + resolveTimelineSnap, +} from "@/timeline/snapping"; +import { getBookmarkSnapPoints } from "@/timeline/bookmarks/index"; +import { getElementEdgeSnapPoints } from "@/timeline/element-snap-source"; +import { getAnimationKeyframeSnapPointsForTimeline } from "@/animation/timeline-snap-points"; import { getCenteredLineLeft, timelineTimeToPixels, timelineTimeToSnappedPixels, -} from "@/lib/timeline"; -import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/lib/timeline/scale"; +} from "@/timeline"; +import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/timeline/scale"; interface UseTimelinePlayheadProps { zoomLevel: number; @@ -68,40 +75,45 @@ export function useTimelinePlayhead({ const rulerRect = ruler.getBoundingClientRect(); const relativeMouseX = event.clientX - rulerRect.left; - const timelineContentWidth = timelineTimeToPixels({ time: duration, zoomLevel }); + const timelineContentWidth = timelineTimeToPixels({ + time: duration, + zoomLevel, + }); - const clampedMouseX = Math.max( - 0, - Math.min(timelineContentWidth, relativeMouseX), - ); + const clampedMouseX = Math.max( + 0, + Math.min(timelineContentWidth, relativeMouseX), + ); - const rawTimeSeconds = Math.max( - 0, - Math.min( - duration / TICKS_PER_SECOND, - clampedMouseX / (BASE_TIMELINE_PIXELS_PER_SECOND * zoomLevel), - ), - ); - const rawTime = Math.round(rawTimeSeconds * TICKS_PER_SECOND); + const rawTimeSeconds = Math.max( + 0, + Math.min( + duration / TICKS_PER_SECOND, + clampedMouseX / (BASE_TIMELINE_PIXELS_PER_SECOND * zoomLevel), + ), + ); + const rawTime = Math.round(rawTimeSeconds * TICKS_PER_SECOND); - const rate = activeProject.settings.fps; - const frameTime = snappedSeekTime({ time: rawTime, duration, rate }) ?? rawTime; + const rate = activeProject.settings.fps; + const frameTime = + snappedSeekTime({ time: rawTime, duration, rate }) ?? rawTime; const shouldSnap = snappingEnabled && !isShiftHeldRef.current; const time = (() => { if (!shouldSnap) return frameTime; const tracks = editor.scenes.getActiveScene().tracks; const bookmarks = editor.scenes.getActiveScene()?.bookmarks ?? []; - const snapPoints = findSnapPoints({ - tracks, - playheadTime: frameTime, - bookmarks, - enablePlayheadSnapping: false, + const snapPoints = buildTimelineSnapPoints({ + sources: [ + () => getElementEdgeSnapPoints({ tracks }), + () => getBookmarkSnapPoints({ bookmarks }), + () => getAnimationKeyframeSnapPointsForTimeline({ tracks }), + ], }); - const snapResult = snapToNearestPoint({ + const snapResult = resolveTimelineSnap({ targetTime: frameTime, snapPoints, - zoomLevel, + maxSnapDistance: getTimelineSnapThresholdInTicks({ zoomLevel }), }); return snapResult.snapPoint ? snapResult.snappedTime : frameTime; })(); @@ -119,7 +131,6 @@ export function useTimelinePlayhead({ activeProject.settings.fps, isShiftHeldRef, editor.scenes, - editor.timeline, ], ); @@ -249,10 +260,10 @@ export function useTimelinePlayhead({ const tracksViewport = tracksScrollRef.current; if (!rulerViewport || !tracksViewport) return; - const playheadPixels = timelineTimeToPixels({ - time, - zoomLevel: zoomLevelRef.current, - }); + const playheadPixels = timelineTimeToPixels({ + time, + zoomLevel: zoomLevelRef.current, + }); const viewportWidth = rulerViewport.clientWidth; const scrollMinimum = 0; const scrollMaximum = rulerViewport.scrollWidth - viewportWidth; diff --git a/apps/web/src/hooks/timeline/use-timeline-resize.ts b/apps/web/src/timeline/hooks/use-timeline-resize.ts index f246261a..fb8da291 100644 --- a/apps/web/src/hooks/timeline/use-timeline-resize.ts +++ b/apps/web/src/timeline/hooks/use-timeline-resize.ts @@ -1,29 +1,29 @@ import { useCallback, useEffect, useRef, useState } from "react"; import type { EditorCore } from "@/core"; -import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/lib/timeline/scale"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; -import { useEditor } from "@/hooks/use-editor"; -import { useElementSelection } from "@/hooks/timeline/element/use-element-selection"; +import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/timeline/scale"; +import { TICKS_PER_SECOND } from "@/wasm"; +import { useEditor } from "@/editor/use-editor"; +import { useElementSelection } from "@/timeline/hooks/element/use-element-selection"; import { useShiftKey } from "@/hooks/use-shift-key"; -import { useTimelineStore } from "@/stores/timeline-store"; +import { useTimelineStore } from "@/timeline/timeline-store"; import { computeGroupResize, type GroupResizeMember, type GroupResizeResult, type ResizeSide, -} from "@/lib/timeline/group-resize"; +} from "@/timeline/group-resize"; import { - findSnapPoints, - snapToNearestPoint, + buildTimelineSnapPoints, + getTimelineSnapThresholdInTicks, + resolveTimelineSnap, type SnapPoint, -} from "@/lib/timeline/snap-utils"; -import type { - SceneTracks, - TimelineElement, - TimelineTrack, -} from "@/lib/timeline"; -import { isRetimableElement } from "@/lib/timeline"; -import { registerCanceller } from "@/lib/cancel-interaction"; +} from "@/timeline/snapping"; +import { getElementEdgeSnapPoints } from "@/timeline/element-snap-source"; +import { getPlayheadSnapPoints } from "@/timeline/playhead-snap-source"; +import type { SceneTracks, TimelineElement, TimelineTrack } from "@/timeline"; +import { isRetimableElement } from "@/timeline"; +import { registerCanceller } from "@/editor/cancel-interaction"; +import { getAnimationKeyframeSnapPointsForTimeline } from "@/animation/timeline-snap-points"; interface ResizeInteractionState { side: ResizeSide; @@ -269,13 +269,23 @@ function getSnappedResizeDelta({ return { deltaTime: rawDeltaTime }; } - const snapPoints = findSnapPoints({ - tracks: editor.scenes.getActiveScene().tracks, - playheadTime: editor.playback.getCurrentTime(), - excludeElementIds: new Set( - resizeState.members.map((member) => member.elementId), - ), + const tracks = editor.scenes.getActiveScene().tracks; + const playheadTime = editor.playback.getCurrentTime(); + const excludeElementIds = new Set( + resizeState.members.map((member) => member.elementId), + ); + const snapPoints = buildTimelineSnapPoints({ + sources: [ + () => getElementEdgeSnapPoints({ tracks, excludeElementIds }), + () => getPlayheadSnapPoints({ playheadTime }), + () => + getAnimationKeyframeSnapPointsForTimeline({ + tracks, + excludeElementIds, + }), + ], }); + const maxSnapDistance = getTimelineSnapThresholdInTicks({ zoomLevel }); let closestSnapPoint: SnapPoint | null = null; let closestSnapDistance = Infinity; let deltaTime = rawDeltaTime; @@ -285,10 +295,10 @@ function getSnappedResizeDelta({ resizeState.side === "left" ? member.startTime : member.startTime + member.duration; - const snapResult = snapToNearestPoint({ + const snapResult = resolveTimelineSnap({ targetTime: baseEdgeTime + rawDeltaTime, snapPoints, - zoomLevel, + maxSnapDistance, }); if (snapResult.snapPoint && snapResult.snapDistance < closestSnapDistance) { closestSnapDistance = snapResult.snapDistance; diff --git a/apps/web/src/hooks/timeline/use-timeline-seek.ts b/apps/web/src/timeline/hooks/use-timeline-seek.ts index 756ec7ee..889177c9 100644 --- a/apps/web/src/hooks/timeline/use-timeline-seek.ts +++ b/apps/web/src/timeline/hooks/use-timeline-seek.ts @@ -1,9 +1,9 @@ import { useCallback, useRef } from "react"; import type { MutableRefObject, RefObject } from "react"; -import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/lib/timeline/scale"; +import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/timeline/scale"; import { snappedSeekTime } from "opencut-wasm"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; -import { useEditor } from "../use-editor"; +import { TICKS_PER_SECOND } from "@/wasm"; +import { useEditor } from "@/editor/use-editor"; interface UseTimelineSeekProps { playheadRef: RefObject<HTMLDivElement | null>; @@ -128,18 +128,19 @@ export function useTimelineSeek({ const mouseX = event.clientX - rect.left; const scrollLeft = scrollContainer.scrollLeft; - const rawTimeSeconds = Math.max( - 0, - Math.min( - duration / TICKS_PER_SECOND, - (mouseX + scrollLeft) / - (BASE_TIMELINE_PIXELS_PER_SECOND * zoomLevel), - ), - ); - const rawTime = Math.round(rawTimeSeconds * TICKS_PER_SECOND); + const rawTimeSeconds = Math.max( + 0, + Math.min( + duration / TICKS_PER_SECOND, + (mouseX + scrollLeft) / (BASE_TIMELINE_PIXELS_PER_SECOND * zoomLevel), + ), + ); + const rawTime = Math.round(rawTimeSeconds * TICKS_PER_SECOND); - const rate = activeProject?.settings.fps; - const time = rate ? (snappedSeekTime({ time: rawTime, duration, rate }) ?? rawTime) : rawTime; + const rate = activeProject?.settings.fps; + const time = rate + ? (snappedSeekTime({ time: rawTime, duration, rate }) ?? rawTime) + : rawTime; seek(time); editor.project.setTimelineViewState({ viewState: { @@ -157,7 +158,7 @@ export function useTimelineSeek({ seek, editor, activeProject?.settings.fps.numerator, - activeProject?.settings.fps.denominator, + activeProject?.settings.fps.denominator, ], ); diff --git a/apps/web/src/hooks/timeline/use-timeline-zoom.ts b/apps/web/src/timeline/hooks/use-timeline-zoom.ts index 1f79a5b2..7929d495 100644 --- a/apps/web/src/hooks/timeline/use-timeline-zoom.ts +++ b/apps/web/src/timeline/hooks/use-timeline-zoom.ts @@ -7,16 +7,11 @@ import { useRef, useState, } from "react"; -import { - TIMELINE_ZOOM_ANCHOR_PLAYHEAD_THRESHOLD, -} from "@/components/editor/panels/timeline/interaction"; -import { - TIMELINE_ZOOM_MAX, - TIMELINE_ZOOM_MIN, -} from "@/lib/timeline/scale"; -import { timelineTimeToPixels } from "@/lib/timeline/pixel-utils"; -import { useEditor } from "@/hooks/use-editor"; -import { zoomToSlider } from "@/lib/timeline/zoom-utils"; +import { TIMELINE_ZOOM_ANCHOR_PLAYHEAD_THRESHOLD } from "@/timeline/components/interaction"; +import { TIMELINE_ZOOM_MAX, TIMELINE_ZOOM_MIN } from "@/timeline/scale"; +import { timelineTimeToPixels } from "@/timeline/pixel-utils"; +import { useEditor } from "@/editor/use-editor"; +import { zoomToSlider } from "@/timeline/zoom-utils"; interface UseTimelineZoomProps { containerRef: RefObject<HTMLDivElement | null>; @@ -54,10 +49,7 @@ export function useTimelineZoom({ const [zoomLevel, setZoomLevelRaw] = useState(() => { if (initialZoom !== undefined) { hasInitializedRef.current = true; - return Math.max( - minZoom, - Math.min(TIMELINE_ZOOM_MAX, initialZoom), - ); + return Math.max(minZoom, Math.min(TIMELINE_ZOOM_MAX, initialZoom)); } return minZoom; }); @@ -88,22 +80,22 @@ export function useTimelineZoom({ return; } - // pinch-zoom (ctrl/meta + wheel) - if (isZoomGesture) { - const normalizedDelta = - event.deltaMode === 1 ? event.deltaY * 16 : event.deltaY; - const cappedDelta = - Math.sign(normalizedDelta) * Math.min(Math.abs(normalizedDelta), 30); - const zoomFactor = Math.exp(-cappedDelta / 300); - setZoomLevel((prev) => { - const nextZoom = Math.max( - minZoom, - Math.min(TIMELINE_ZOOM_MAX, prev * zoomFactor), - ); - return nextZoom; - }); - return; - } + // pinch-zoom (ctrl/meta + wheel) + if (isZoomGesture) { + const normalizedDelta = + event.deltaMode === 1 ? event.deltaY * 16 : event.deltaY; + const cappedDelta = + Math.sign(normalizedDelta) * Math.min(Math.abs(normalizedDelta), 30); + const zoomFactor = Math.exp(-cappedDelta / 300); + setZoomLevel((prev) => { + const nextZoom = Math.max( + minZoom, + Math.min(TIMELINE_ZOOM_MAX, prev * zoomFactor), + ); + return nextZoom; + }); + return; + } }, [minZoom, setZoomLevel], ); @@ -111,9 +103,7 @@ export function useTimelineZoom({ useEffect(() => { if (initialZoom !== undefined && !hasInitializedRef.current) { hasInitializedRef.current = true; - setZoomLevel( - Math.max(minZoom, Math.min(TIMELINE_ZOOM_MAX, initialZoom)), - ); + setZoomLevel(Math.max(minZoom, Math.min(TIMELINE_ZOOM_MAX, initialZoom))); return; } setZoomLevel((prev) => { @@ -159,12 +149,10 @@ export function useTimelineZoom({ minZoom, }); const isCrossingThresholdUp = - previousSliderPercent < - TIMELINE_ZOOM_ANCHOR_PLAYHEAD_THRESHOLD && + previousSliderPercent < TIMELINE_ZOOM_ANCHOR_PLAYHEAD_THRESHOLD && sliderPercent >= TIMELINE_ZOOM_ANCHOR_PLAYHEAD_THRESHOLD; const isCrossingThresholdDown = - previousSliderPercent >= - TIMELINE_ZOOM_ANCHOR_PLAYHEAD_THRESHOLD && + previousSliderPercent >= TIMELINE_ZOOM_ANCHOR_PLAYHEAD_THRESHOLD && sliderPercent < TIMELINE_ZOOM_ANCHOR_PLAYHEAD_THRESHOLD; const syncScroll = (scrollLeft: number) => { @@ -186,8 +174,14 @@ export function useTimelineZoom({ } if (sliderPercent >= TIMELINE_ZOOM_ANCHOR_PLAYHEAD_THRESHOLD) { - const playheadPixelsBefore = timelineTimeToPixels({ time: playheadTime, zoomLevel: previousZoom }); - const playheadPixelsAfter = timelineTimeToPixels({ time: playheadTime, zoomLevel }); + const playheadPixelsBefore = timelineTimeToPixels({ + time: playheadTime, + zoomLevel: previousZoom, + }); + const playheadPixelsAfter = timelineTimeToPixels({ + time: playheadTime, + zoomLevel, + }); const viewportOffset = playheadPixelsBefore - currentScrollLeft; const newScrollLeft = playheadPixelsAfter - viewportOffset; diff --git a/apps/web/src/lib/timeline/index.ts b/apps/web/src/timeline/index.ts index 5ee079b1..5ee079b1 100644 --- a/apps/web/src/lib/timeline/index.ts +++ b/apps/web/src/timeline/index.ts diff --git a/apps/web/src/lib/timeline/pixel-utils.ts b/apps/web/src/timeline/pixel-utils.ts index 10f258aa..71e8466a 100644 --- a/apps/web/src/lib/timeline/pixel-utils.ts +++ b/apps/web/src/timeline/pixel-utils.ts @@ -1,5 +1,5 @@ -import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/lib/timeline/scale"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; +import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/timeline/scale"; +import { TICKS_PER_SECOND } from "@/wasm"; export const TIMELINE_INDICATOR_LINE_WIDTH_PX = 2; diff --git a/apps/web/src/lib/timeline/placement/__tests__/resolve.test.ts b/apps/web/src/timeline/placement/__tests__/resolve.test.ts index 7cadb9b7..ebbfe578 100644 --- a/apps/web/src/lib/timeline/placement/__tests__/resolve.test.ts +++ b/apps/web/src/timeline/placement/__tests__/resolve.test.ts @@ -11,9 +11,9 @@ import type { TimelineTrack, VideoElement, VideoTrack, -} from "@/lib/timeline"; -import type { Transform } from "@/lib/rendering"; -import { resolveTrackPlacement } from "@/lib/timeline/placement"; +} from "@/timeline"; +import type { Transform } from "@/rendering"; +import { resolveTrackPlacement } from "@/timeline/placement"; function buildTransform(): Transform { return { diff --git a/apps/web/src/lib/timeline/placement/apply.ts b/apps/web/src/timeline/placement/apply.ts index 639b1b42..214b0611 100644 --- a/apps/web/src/lib/timeline/placement/apply.ts +++ b/apps/web/src/timeline/placement/apply.ts @@ -8,11 +8,11 @@ import type { TimelineElement, TimelineTrack, VideoTrack, -} from "@/lib/timeline"; +} from "@/timeline"; import { generateUUID } from "@/utils/id"; import { buildEmptyTrack } from "./track-factory"; import type { PlacementResult } from "./types"; -import { updateTrackInSceneTracks } from "@/lib/timeline/track-element-update"; +import { updateTrackInSceneTracks } from "@/timeline/track-element-update"; export function applyPlacement({ tracks, diff --git a/apps/web/src/lib/timeline/placement/compatibility.ts b/apps/web/src/timeline/placement/compatibility.ts index de1baff3..b1af547c 100644 --- a/apps/web/src/lib/timeline/placement/compatibility.ts +++ b/apps/web/src/timeline/placement/compatibility.ts @@ -1,4 +1,4 @@ -import type { ElementType, TrackType } from "@/lib/timeline"; +import type { ElementType, TrackType } from "@/timeline"; const ELEMENT_TRACK_MAP: Record<ElementType, TrackType> = { audio: "audio", diff --git a/apps/web/src/lib/timeline/placement/index.ts b/apps/web/src/timeline/placement/index.ts index 530cb69f..530cb69f 100644 --- a/apps/web/src/lib/timeline/placement/index.ts +++ b/apps/web/src/timeline/placement/index.ts diff --git a/apps/web/src/lib/timeline/placement/insert-index.ts b/apps/web/src/timeline/placement/insert-index.ts index 04fa5a4a..5e2786dc 100644 --- a/apps/web/src/lib/timeline/placement/insert-index.ts +++ b/apps/web/src/timeline/placement/insert-index.ts @@ -1,4 +1,4 @@ -import type { SceneTracks, TrackType } from "@/lib/timeline"; +import type { SceneTracks, TrackType } from "@/timeline"; export function getDefaultInsertIndexForTrack({ tracks, diff --git a/apps/web/src/lib/timeline/placement/main-track.ts b/apps/web/src/timeline/placement/main-track.ts index 7e206517..ab2ab7bd 100644 --- a/apps/web/src/lib/timeline/placement/main-track.ts +++ b/apps/web/src/timeline/placement/main-track.ts @@ -1,4 +1,4 @@ -import type { SceneTracks, TimelineElement, VideoTrack } from "@/lib/timeline"; +import type { SceneTracks, TimelineElement, VideoTrack } from "@/timeline"; export const MAIN_TRACK_NAME = "Main Track"; diff --git a/apps/web/src/lib/timeline/placement/overlap.ts b/apps/web/src/timeline/placement/overlap.ts index 5e37c052..daf3e627 100644 --- a/apps/web/src/lib/timeline/placement/overlap.ts +++ b/apps/web/src/timeline/placement/overlap.ts @@ -1,4 +1,4 @@ -import type { TimelineElement } from "@/lib/timeline"; +import type { TimelineElement } from "@/timeline"; import type { PlacementTimeSpan } from "./types"; interface TrackWithElements { diff --git a/apps/web/src/lib/timeline/placement/resolve.ts b/apps/web/src/timeline/placement/resolve.ts index 67943912..26275392 100644 --- a/apps/web/src/lib/timeline/placement/resolve.ts +++ b/apps/web/src/timeline/placement/resolve.ts @@ -1,4 +1,4 @@ -import type { SceneTracks, TrackType, TimelineTrack } from "@/lib/timeline"; +import type { SceneTracks, TrackType, TimelineTrack } from "@/timeline"; import { getDefaultInsertIndexForTrack, getHighestInsertIndexForTrack, diff --git a/apps/web/src/lib/timeline/placement/track-factory.ts b/apps/web/src/timeline/placement/track-factory.ts index b73c5155..9c69ea8c 100644 --- a/apps/web/src/lib/timeline/placement/track-factory.ts +++ b/apps/web/src/timeline/placement/track-factory.ts @@ -1,4 +1,4 @@ -import { DEFAULT_TRACK_NAMES } from "@/lib/timeline/tracks"; +import { DEFAULT_TRACK_NAMES } from "@/timeline/tracks"; import type { AudioTrack, EffectTrack, @@ -7,7 +7,7 @@ import type { TrackType, TimelineTrack, VideoTrack, -} from "@/lib/timeline"; +} from "@/timeline"; export function buildEmptyTrack({ id, diff --git a/apps/web/src/lib/timeline/placement/types.ts b/apps/web/src/timeline/placement/types.ts index 559e85cc..6847ab36 100644 --- a/apps/web/src/lib/timeline/placement/types.ts +++ b/apps/web/src/timeline/placement/types.ts @@ -1,4 +1,4 @@ -import type { ElementType, TrackType } from "@/lib/timeline"; +import type { ElementType, TrackType } from "@/timeline"; export interface PlacementTimeSpan { startTime: number; diff --git a/apps/web/src/timeline/playhead-snap-source.ts b/apps/web/src/timeline/playhead-snap-source.ts new file mode 100644 index 00000000..88e21eba --- /dev/null +++ b/apps/web/src/timeline/playhead-snap-source.ts @@ -0,0 +1,9 @@ +import type { SnapPoint } from "@/timeline/snapping"; + +export function getPlayheadSnapPoints({ + playheadTime, +}: { + playheadTime: number; +}): SnapPoint[] { + return [{ time: playheadTime, type: "playhead" }]; +} diff --git a/apps/web/src/lib/timeline/ruler-utils.ts b/apps/web/src/timeline/ruler-utils.ts index ea2982aa..d355ef31 100644 --- a/apps/web/src/lib/timeline/ruler-utils.ts +++ b/apps/web/src/timeline/ruler-utils.ts @@ -1,6 +1,6 @@ import type { FrameRate } from "opencut-wasm"; -import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/lib/timeline/scale"; -import { frameRateToFloat } from "@/lib/fps/utils"; +import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/timeline/scale"; +import { frameRateToFloat } from "@/fps/utils"; /** * frame intervals for labels - starts at 2 so there's always at least diff --git a/apps/web/src/lib/timeline/scale.ts b/apps/web/src/timeline/scale.ts index 7770f59f..7770f59f 100644 --- a/apps/web/src/lib/timeline/scale.ts +++ b/apps/web/src/timeline/scale.ts diff --git a/apps/web/src/lib/scenes.ts b/apps/web/src/timeline/scenes.ts index 91285275..d48c943c 100644 --- a/apps/web/src/lib/scenes.ts +++ b/apps/web/src/timeline/scenes.ts @@ -1,7 +1,7 @@ -import type { TScene } from "@/lib/timeline";
+import type { TScene } from "@/timeline";
import { generateUUID } from "@/utils/id";
-import { calculateTotalDuration } from "@/lib/timeline";
-import { MAIN_TRACK_NAME } from "@/lib/timeline/placement/main-track";
+import { calculateTotalDuration } from "@/timeline";
+import { MAIN_TRACK_NAME } from "@/timeline/placement/main-track";
export function getMainScene({ scenes }: { scenes: TScene[] }): TScene | null {
return scenes.find((scene) => scene.isMain) || null;
diff --git a/apps/web/src/timeline/snapping/build.ts b/apps/web/src/timeline/snapping/build.ts new file mode 100644 index 00000000..87d3db2e --- /dev/null +++ b/apps/web/src/timeline/snapping/build.ts @@ -0,0 +1,17 @@ +import type { SnapPoint, TimelineSnapPointSource } from "./types"; + +export function buildTimelineSnapPoints({ + sources, +}: { + sources: TimelineSnapPointSource[]; +}): SnapPoint[] { + const snapPoints: SnapPoint[] = []; + + for (const source of sources) { + for (const snapPoint of source()) { + snapPoints.push(snapPoint); + } + } + + return snapPoints; +} diff --git a/apps/web/src/timeline/snapping/index.ts b/apps/web/src/timeline/snapping/index.ts new file mode 100644 index 00000000..73f49272 --- /dev/null +++ b/apps/web/src/timeline/snapping/index.ts @@ -0,0 +1,9 @@ +export { buildTimelineSnapPoints } from "./build"; +export { resolveTimelineSnap } from "./resolve"; +export { getTimelineSnapThresholdInTicks } from "./threshold"; +export type { + SnapPoint, + SnapPointType, + SnapResult, + TimelineSnapPointSource, +} from "./types"; diff --git a/apps/web/src/timeline/snapping/resolve.ts b/apps/web/src/timeline/snapping/resolve.ts new file mode 100644 index 00000000..2f00d081 --- /dev/null +++ b/apps/web/src/timeline/snapping/resolve.ts @@ -0,0 +1,28 @@ +import type { SnapPoint, SnapResult } from "./types"; + +export function resolveTimelineSnap({ + targetTime, + snapPoints, + maxSnapDistance, +}: { + targetTime: number; + snapPoints: SnapPoint[]; + maxSnapDistance: number; +}): SnapResult { + let closestSnapPoint: SnapPoint | null = null; + let closestDistance = Infinity; + + for (const snapPoint of snapPoints) { + const distance = Math.abs(targetTime - snapPoint.time); + if (distance <= maxSnapDistance && distance < closestDistance) { + closestDistance = distance; + closestSnapPoint = snapPoint; + } + } + + return { + snappedTime: closestSnapPoint ? closestSnapPoint.time : targetTime, + snapPoint: closestSnapPoint, + snapDistance: closestDistance, + }; +} diff --git a/apps/web/src/timeline/snapping/threshold.ts b/apps/web/src/timeline/snapping/threshold.ts new file mode 100644 index 00000000..f03111dc --- /dev/null +++ b/apps/web/src/timeline/snapping/threshold.ts @@ -0,0 +1,15 @@ +import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/timeline/scale"; +import { TICKS_PER_SECOND } from "@/wasm"; + +const DEFAULT_TIMELINE_SNAP_THRESHOLD_PX = 10; + +export function getTimelineSnapThresholdInTicks({ + zoomLevel, + snapThresholdPx = DEFAULT_TIMELINE_SNAP_THRESHOLD_PX, +}: { + zoomLevel: number; + snapThresholdPx?: number; +}): number { + const pixelsPerSecond = BASE_TIMELINE_PIXELS_PER_SECOND * zoomLevel; + return (snapThresholdPx / pixelsPerSecond) * TICKS_PER_SECOND; +} diff --git a/apps/web/src/timeline/snapping/types.ts b/apps/web/src/timeline/snapping/types.ts new file mode 100644 index 00000000..2a6d0903 --- /dev/null +++ b/apps/web/src/timeline/snapping/types.ts @@ -0,0 +1,21 @@ +export type SnapPointType = + | "element-start" + | "element-end" + | "playhead" + | "bookmark" + | "keyframe"; + +export interface SnapPoint { + time: number; + type: SnapPointType; + elementId?: string; + trackId?: string; +} + +export interface SnapResult { + snappedTime: number; + snapPoint: SnapPoint | null; + snapDistance: number; +} + +export type TimelineSnapPointSource = () => Iterable<SnapPoint>; diff --git a/apps/web/src/stores/timeline-store.ts b/apps/web/src/timeline/timeline-store.ts index b7ddb885..b7ddb885 100644 --- a/apps/web/src/stores/timeline-store.ts +++ b/apps/web/src/timeline/timeline-store.ts diff --git a/apps/web/src/lib/timeline/track-capabilities.ts b/apps/web/src/timeline/track-capabilities.ts index 08ee841c..e6ff3945 100644 --- a/apps/web/src/lib/timeline/track-capabilities.ts +++ b/apps/web/src/timeline/track-capabilities.ts @@ -5,7 +5,7 @@ import type { GraphicTrack, TextTrack, EffectTrack, -} from "@/lib/timeline"; +} from "@/timeline"; export function canTrackHaveAudio( track: TimelineTrack, diff --git a/apps/web/src/lib/timeline/track-element-update.ts b/apps/web/src/timeline/track-element-update.ts index f1c2028e..7d86a4e8 100644 --- a/apps/web/src/lib/timeline/track-element-update.ts +++ b/apps/web/src/timeline/track-element-update.ts @@ -1,4 +1,4 @@ -import type { SceneTracks, TimelineElement, TimelineTrack } from "@/lib/timeline";
+import type { SceneTracks, TimelineElement, TimelineTrack } from "@/timeline";
export function findTrackInSceneTracks({
tracks,
diff --git a/apps/web/src/lib/timeline/tracks.ts b/apps/web/src/timeline/tracks.ts index 181b9fcf..9ce5511d 100644 --- a/apps/web/src/lib/timeline/tracks.ts +++ b/apps/web/src/timeline/tracks.ts @@ -1,4 +1,4 @@ -import type { TrackType } from "@/lib/timeline"; +import type { TrackType } from "@/timeline"; export const DEFAULT_TRACK_NAMES: Record<TrackType, string> = { video: "Video track", diff --git a/apps/web/src/lib/timeline/types.ts b/apps/web/src/timeline/types.ts index ca9ac999..cd543cdf 100644 --- a/apps/web/src/lib/timeline/types.ts +++ b/apps/web/src/timeline/types.ts @@ -1,8 +1,8 @@ -import type { ElementAnimations } from "@/lib/animation/types";
-import type { Effect } from "@/lib/effects/types";
-import type { Mask } from "@/lib/masks/types";
-import type { ParamValues } from "@/lib/params";
-import type { BlendMode, Transform } from "@/lib/rendering";
+import type { ElementAnimations } from "@/animation/types";
+import type { Effect } from "@/effects/types";
+import type { Mask } from "@/masks/types";
+import type { ParamValues } from "@/params";
+import type { BlendMode, Transform } from "@/rendering";
export type ElementRef = {
trackId: string;
diff --git a/apps/web/src/lib/timeline/update-pipeline.ts b/apps/web/src/timeline/update-pipeline.ts index 2be73441..beacf897 100644 --- a/apps/web/src/lib/timeline/update-pipeline.ts +++ b/apps/web/src/timeline/update-pipeline.ts @@ -1,11 +1,11 @@ -import { clampAnimationsToDuration } from "@/lib/animation"; +import { clampAnimationsToDuration } from "@/animation"; import { clampRetimeRate, getSourceSpanAtClipTime, getTimelineDurationForSourceSpan, -} from "@/lib/retime"; -import type { RetimeConfig, SceneTracks, TimelineElement } from "@/lib/timeline"; -import { isRetimableElement } from "@/lib/timeline"; +} from "@/retime"; +import type { RetimeConfig, SceneTracks, TimelineElement } from "@/timeline"; +import { isRetimableElement } from "@/timeline"; type ElementUpdateField = keyof TimelineElement | string; diff --git a/apps/web/src/lib/timeline/zoom-utils.ts b/apps/web/src/timeline/zoom-utils.ts index 680f8d0d..39090d92 100644 --- a/apps/web/src/lib/timeline/zoom-utils.ts +++ b/apps/web/src/timeline/zoom-utils.ts @@ -1,8 +1,8 @@ import { BASE_TIMELINE_PIXELS_PER_SECOND, TIMELINE_ZOOM_MAX, -} from "@/lib/timeline/scale"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; +} from "@/timeline/scale"; +import { TICKS_PER_SECOND } from "@/wasm"; const PADDING_MAX_RATIO = 0.75; const PADDING_MIN_RATIO = 0.15; diff --git a/apps/web/src/lib/transcription/audio.ts b/apps/web/src/transcription/audio.ts index e2dc96d9..e2dc96d9 100644 --- a/apps/web/src/lib/transcription/audio.ts +++ b/apps/web/src/transcription/audio.ts diff --git a/apps/web/src/lib/transcription/caption-defaults.ts b/apps/web/src/transcription/caption-defaults.ts index fc277158..fc277158 100644 --- a/apps/web/src/lib/transcription/caption-defaults.ts +++ b/apps/web/src/transcription/caption-defaults.ts diff --git a/apps/web/src/lib/transcription/caption.ts b/apps/web/src/transcription/caption.ts index 5e23bad8..8e6ae864 100644 --- a/apps/web/src/lib/transcription/caption.ts +++ b/apps/web/src/transcription/caption.ts @@ -1,8 +1,8 @@ -import type { TranscriptionSegment, CaptionChunk } from "@/lib/transcription/types";
+import type { TranscriptionSegment, CaptionChunk } from "@/transcription/types";
import {
DEFAULT_WORDS_PER_CAPTION,
MIN_CAPTION_DURATION_SECONDS,
-} from "@/lib/transcription/caption-defaults";
+} from "@/transcription/caption-defaults";
export function buildCaptionChunks({
segments,
diff --git a/apps/web/src/lib/transcription/diagnostics.ts b/apps/web/src/transcription/diagnostics.ts index a1921699..6765d07f 100644 --- a/apps/web/src/lib/transcription/diagnostics.ts +++ b/apps/web/src/transcription/diagnostics.ts @@ -1,5 +1,5 @@ import type { DiagnosticsManager } from "@/core/managers/diagnostics-manager"; -import { timelineHasAudio } from "@/lib/media/audio"; +import { timelineHasAudio } from "@/media/audio"; export const TRANSCRIPTION_DIAGNOSTICS_SCOPE = "transcription"; diff --git a/apps/web/src/lib/transcription/languages.ts b/apps/web/src/transcription/languages.ts index e5678145..e5678145 100644 --- a/apps/web/src/lib/transcription/languages.ts +++ b/apps/web/src/transcription/languages.ts diff --git a/apps/web/src/lib/transcription/models.ts b/apps/web/src/transcription/models.ts index ee9047e7..ee9047e7 100644 --- a/apps/web/src/lib/transcription/models.ts +++ b/apps/web/src/transcription/models.ts diff --git a/apps/web/src/lib/transcription/supported-languages.ts b/apps/web/src/transcription/supported-languages.ts index 0adc1332..0adc1332 100644 --- a/apps/web/src/lib/transcription/supported-languages.ts +++ b/apps/web/src/transcription/supported-languages.ts diff --git a/apps/web/src/lib/transcription/types.ts b/apps/web/src/transcription/types.ts index 7ab0da7f..7ab0da7f 100644 --- a/apps/web/src/lib/transcription/types.ts +++ b/apps/web/src/transcription/types.ts diff --git a/apps/web/src/lib/wasm/index.ts b/apps/web/src/wasm/index.ts index 60031a0e..60031a0e 100644 --- a/apps/web/src/lib/wasm/index.ts +++ b/apps/web/src/wasm/index.ts diff --git a/apps/web/src/lib/wasm/ticks.ts b/apps/web/src/wasm/ticks.ts index ababe6b3..ababe6b3 100644 --- a/apps/web/src/lib/wasm/ticks.ts +++ b/apps/web/src/wasm/ticks.ts |
