mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
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).
130 lines
3.1 KiB
TypeScript
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,
|
|
}),
|
|
},
|
|
),
|
|
);
|