Files
OpenCut/apps/web/src/stores/assets-panel-store.tsx
T
Maze Winther e7dcb586c0 feat: Clip effects, asset sorting, and timeline improvements
Major features and improvements:

* **Clip Effects**:
  * Added UI in Properties Panel to manage effects on video/image clips (add, remove, toggle, reorder).
  * Implemented dynamic parameter fields for effects.
  * Added support for keyframing effect parameters.

* **Assets Panel**:
  * Added sorting options: Name, Type, Duration, and File Size.
  * Persisted view preferences (grid/list mode, sort order) to local storage.
  * Refactored media item rendering and drag interactions.

* **Timeline & Interaction**:
  * **Keyframe Dragging**: Added ability to drag keyframes directly on the timeline element.
  * **Resizing**: Improved resize logic to respect neighboring clips (prevents overlaps).
  * **Visuals**: Implemented tiled background rendering for video/image clips on the timeline.
  * **Shortcuts**: Added "Deselect All" action bound to the `Escape` key.
  * **Fixes**: Corrected drag-and-drop coordinate calculations when the timeline track area is scrolled.

* **Text Elements**:
  * Refactored text background storage to use an explicit `enabled` flag.
  * Added `V8toV9` storage migration to update existing projects.

* **Architecture**:
  * Moved export state management to `ProjectManager` for better lifecycle handling.
  * Refactored `PropertiesPanel` sections to be more composable (custom headers, borders).
2026-03-02 13:13:07 +01:00

130 lines
3.1 KiB
TypeScript

import type { ElementType } from "react";
import { create } from "zustand";
import { persist } from "zustand/middleware";
import {
ArrowRightDoubleIcon,
ClosedCaptionIcon,
Folder03Icon,
Happy01Icon,
HeadphonesIcon,
MagicWand05Icon,
TextIcon,
Settings01Icon,
SlidersHorizontalIcon,
ColorsIcon,
} from "@hugeicons/core-free-icons";
import { HugeiconsIcon, type IconSvgElement } from "@hugeicons/react";
export const TAB_KEYS = [
"media",
"sounds",
"text",
"stickers",
"effects",
"transitions",
"captions",
"filters",
"adjustment",
"settings",
] as const;
export type Tab = (typeof TAB_KEYS)[number];
const createHugeiconsIcon =
({ icon }: { icon: IconSvgElement }) =>
({ className }: { className?: string }) => (
<HugeiconsIcon icon={icon} className={className} />
);
export const tabs = {
media: {
icon: createHugeiconsIcon({ icon: Folder03Icon }),
label: "Media",
},
sounds: {
icon: createHugeiconsIcon({ icon: HeadphonesIcon }),
label: "Sounds",
},
text: {
icon: createHugeiconsIcon({ icon: TextIcon }),
label: "Text",
},
stickers: {
icon: createHugeiconsIcon({ icon: Happy01Icon }),
label: "Stickers",
},
effects: {
icon: createHugeiconsIcon({ icon: MagicWand05Icon }),
label: "Effects",
},
transitions: {
icon: createHugeiconsIcon({ icon: ArrowRightDoubleIcon }),
label: "Transitions",
},
captions: {
icon: createHugeiconsIcon({ icon: ClosedCaptionIcon }),
label: "Captions",
},
filters: {
icon: createHugeiconsIcon({ icon: ColorsIcon }),
label: "Filters",
},
adjustment: {
icon: createHugeiconsIcon({ icon: SlidersHorizontalIcon }),
label: "Adjustment",
},
settings: {
icon: createHugeiconsIcon({ icon: Settings01Icon }),
label: "Settings",
},
} satisfies Record<
Tab,
{ icon: ElementType<{ className?: string }>; label: string }
>;
export type MediaViewMode = "grid" | "list";
export type MediaSortKey = "name" | "type" | "duration" | "size";
export type MediaSortOrder = "asc" | "desc";
interface AssetsPanelStore {
activeTab: Tab;
setActiveTab: (tab: Tab) => void;
highlightMediaId: string | null;
requestRevealMedia: (mediaId: string) => void;
clearHighlight: () => void;
/* Media */
mediaViewMode: MediaViewMode;
setMediaViewMode: (mode: MediaViewMode) => void;
mediaSortBy: MediaSortKey;
mediaSortOrder: MediaSortOrder;
setMediaSort: (key: MediaSortKey, order: MediaSortOrder) => void;
}
export const useAssetsPanelStore = create<AssetsPanelStore>()(
persist(
(set) => ({
activeTab: "media",
setActiveTab: (tab) => set({ activeTab: tab }),
highlightMediaId: null,
requestRevealMedia: (mediaId) =>
set({ activeTab: "media", highlightMediaId: mediaId }),
clearHighlight: () => set({ highlightMediaId: null }),
mediaViewMode: "grid",
setMediaViewMode: (mode) => set({ mediaViewMode: mode }),
mediaSortBy: "name",
mediaSortOrder: "asc",
setMediaSort: (key, order) =>
set({ mediaSortBy: key, mediaSortOrder: order }),
}),
{
name: "assets-panel",
partialize: (state) => ({
mediaViewMode: state.mediaViewMode,
mediaSortBy: state.mediaSortBy,
mediaSortOrder: state.mediaSortOrder,
}),
},
),
);