From c555f1eaebff90749bdc543592cfa80a060b29e2 Mon Sep 17 00:00:00 2001 From: cashmere Date: Sun, 9 Aug 2026 02:37:37 +0200 Subject: feat: lens effects tab, standalone motion effects, clip/asset rename, preview popout - New Lens category in the assets panel: Blur, Motion Blur, Zoom In/Out, Pan x4, Shake, Pulse, Spin, Custom Zoom. EffectDefinition gains a category field; tabs with an empty category are hidden, and the properties-panel 'Open effects' button opens the first non-empty one. - Motion effects work standalone: dropping them on the timeline creates an effect-track element that animates (scale/translate/rotate) every track below it, computed purely in the TS frame descriptor - no wasm changes, identical in preview and export. supportsStandalone removed. - Motion Blur: directional gaussian smear (amount + angle) reusing the existing WGSL shader with arbitrary u_direction. - Custom Zoom: start/end scale params (keyframable clip-level). - MotionOffset gains optional rotate, accumulated clip-level and applied to standalone layer quads via rotationDegrees. - Rename clips (timeline context menu) and media assets (media panel context menu) via a shared RenameDialog; asset rename persists through storageService with rollback on error. - Preview pops out into a Document Picture-in-Picture window and docks back; button hidden where the API is unsupported. - Export keeps running when the export popover is closed; only the explicit Cancel button aborts. - Captions and Adjustment tabs removed from the assets panel. --- .../editor/panels/assets/assets-panel-store.tsx | 27 +++++----- .../src/components/editor/panels/assets/index.tsx | 10 +--- .../src/components/editor/panels/assets/tabbar.tsx | 23 ++++++--- .../editor/panels/assets/views/assets.tsx | 59 +++++++++++++++++----- 4 files changed, 77 insertions(+), 42 deletions(-) (limited to 'apps/web/src/components/editor/panels') diff --git a/apps/web/src/components/editor/panels/assets/assets-panel-store.tsx b/apps/web/src/components/editor/panels/assets/assets-panel-store.tsx index b412de7b..9e9f77c6 100644 --- a/apps/web/src/components/editor/panels/assets/assets-panel-store.tsx +++ b/apps/web/src/components/editor/panels/assets/assets-panel-store.tsx @@ -3,17 +3,16 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; import { ArrowRightDoubleIcon, - ClosedCaptionIcon, + CameraLensIcon, Folder03Icon, Happy01Icon, HeadphonesIcon, MagicWand05Icon, TextIcon, Settings01Icon, - SlidersHorizontalIcon, - ColorsIcon, } from "@hugeicons/core-free-icons"; import { HugeiconsIcon, type IconSvgElement } from "@hugeicons/react"; +import { effectsRegistry } from "@/effects"; export const TAB_KEYS = [ "media", @@ -21,9 +20,8 @@ export const TAB_KEYS = [ "text", "stickers", "effects", + "lens", "transitions", - "captions", - "adjustment", "settings", ] as const; @@ -56,18 +54,14 @@ export const tabs = { icon: createHugeiconsIcon({ icon: MagicWand05Icon }), label: "Effects", }, + lens: { + icon: createHugeiconsIcon({ icon: CameraLensIcon }), + label: "Lens", + }, transitions: { icon: createHugeiconsIcon({ icon: ArrowRightDoubleIcon }), label: "Transitions", }, - captions: { - icon: createHugeiconsIcon({ icon: ClosedCaptionIcon }), - label: "Captions", - }, - adjustment: { - icon: createHugeiconsIcon({ icon: SlidersHorizontalIcon }), - label: "Adjustment", - }, settings: { icon: createHugeiconsIcon({ icon: Settings01Icon }), label: "Settings", @@ -81,6 +75,13 @@ export type MediaViewMode = "grid" | "list"; export type MediaSortKey = "name" | "type" | "duration" | "size"; export type MediaSortOrder = "asc" | "desc"; +export function getDefaultEffectsTab(): Tab { + const hasLensEffects = effectsRegistry + .getAll() + .some((definition) => definition.category === "lens"); + return hasLensEffects ? "lens" : "effects"; +} + interface AssetsPanelStore { activeTab: Tab; setActiveTab: (tab: Tab) => void; diff --git a/apps/web/src/components/editor/panels/assets/index.tsx b/apps/web/src/components/editor/panels/assets/index.tsx index 09d64eed..c0204b12 100644 --- a/apps/web/src/components/editor/panels/assets/index.tsx +++ b/apps/web/src/components/editor/panels/assets/index.tsx @@ -3,7 +3,6 @@ import { Separator } from "@/components/ui/separator"; import { type Tab, useAssetsPanelStore } from "@/components/editor/panels/assets/assets-panel-store"; import { TabBar } from "./tabbar"; -import { Captions } from "@/subtitles/components/assets-view"; import { MediaView } from "./views/assets"; import { SettingsView } from "./views/settings"; import { SoundsView } from "@/sounds/components/assets-view"; @@ -20,18 +19,13 @@ export function AssetsPanel() { sounds: FEATURES.sounds ? : null, text: , stickers: FEATURES.stickers ? : null, - effects: FEATURES.effects ? : null, + effects: FEATURES.effects ? : null, + lens: FEATURES.effects ? : null, transitions: (
Transitions view coming soon...
), - captions: , - adjustment: ( -
- Adjustment view coming soon... -
- ), settings: , }; diff --git a/apps/web/src/components/editor/panels/assets/tabbar.tsx b/apps/web/src/components/editor/panels/assets/tabbar.tsx index b468b5bb..9b029e3d 100644 --- a/apps/web/src/components/editor/panels/assets/tabbar.tsx +++ b/apps/web/src/components/editor/panels/assets/tabbar.tsx @@ -14,13 +14,22 @@ import { useAssetsPanelStore, } from "@/components/editor/panels/assets/assets-panel-store"; import { FEATURES } from "@/features"; +import { effectsRegistry } from "@/effects"; -const VISIBLE_TAB_KEYS = TAB_KEYS.filter((tabKey) => { - if (tabKey === "sounds") return FEATURES.sounds; - if (tabKey === "stickers") return FEATURES.stickers; - if (tabKey === "effects") return FEATURES.effects; - return true; -}); +function getVisibleTabKeys() { + const effectCategories = new Set( + effectsRegistry.getAll().map((definition) => definition.category), + ); + return TAB_KEYS.filter((tabKey) => { + if (tabKey === "sounds") return FEATURES.sounds; + if (tabKey === "stickers") return FEATURES.stickers; + if (tabKey === "effects") + return FEATURES.effects && effectCategories.has("effect"); + if (tabKey === "lens") + return FEATURES.effects && effectCategories.has("lens"); + return true; + }); +} export function TabBar() { const { activeTab, setActiveTab } = useAssetsPanelStore(); @@ -59,7 +68,7 @@ export function TabBar() { ref={scrollRef} className="scrollbar-hidden relative flex size-full p-1 flex-col items-center justify-start gap-0.5 overflow-y-auto" > - {VISIBLE_TAB_KEYS.map((tabKey) => { + {getVisibleTabKeys().map((tabKey) => { const tab = tabs[tabKey]; return ( 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 4dbed41e..049cb5f4 100644 --- a/apps/web/src/components/editor/panels/assets/views/assets.tsx +++ b/apps/web/src/components/editor/panels/assets/views/assets.tsx @@ -48,8 +48,10 @@ import { import { MASKABLE_ELEMENT_TYPES } from "@/timeline"; import type { MediaAsset } from "@/media/types"; import { cn } from "@/utils/ui"; +import { RenameDialog } from "@/components/editor/rename-dialog"; import { CloudUploadIcon, + Edit02Icon, GridViewIcon, LeftToRightListDashIcon, SortingOneNineIcon, @@ -316,25 +318,54 @@ function MediaItemWithContextMenu({ }) => void; }) { const { isSelected, selectedIds } = useSelection(); + const editor = useEditor(); + const activeProject = useEditor((e) => e.project.getActive()); + const [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false); const idsToDelete = isSelected(item.id) ? selectedIds : [item.id]; const deleteLabel = idsToDelete.length > 1 ? `Delete ${idsToDelete.length} items` : "Delete"; return ( - - {children} - - Export clips - ) => - onRemove({ event, ids: idsToDelete }) - } - > - {deleteLabel} - - - + <> + + {children} + + } + onClick={(event: React.MouseEvent) => { + event.stopPropagation(); + setIsRenameDialogOpen(true); + }} + > + Rename + + Export clips + ) => + onRemove({ event, ids: idsToDelete }) + } + > + {deleteLabel} + + + + { + if (!activeProject) return; + editor.media.renameMediaAsset({ + projectId: activeProject.metadata.id, + id: item.id, + name: newName, + }); + setIsRenameDialogOpen(false); + }} + /> + ); } -- cgit v1.3.1