summaryrefslogtreecommitdiff
path: root/apps/web/src/lib/timeline
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-03-02 13:13:07 +0100
committerMaze Winther <mazewinther@gmail.com>2026-03-02 13:13:07 +0100
commite7dcb586c0408255a0bf788e355fdb6d50d9b923 (patch)
tree74ff0fc0f1ba501a075342db9b4e454450926154 /apps/web/src/lib/timeline
parent93bea01c9ed3fcbebc64c9cef382a51d24c925ed (diff)
downloadopencut-e7dcb586c0408255a0bf788e355fdb6d50d9b923.tar.gz
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).
Diffstat (limited to 'apps/web/src/lib/timeline')
-rw-r--r--apps/web/src/lib/timeline/element-utils.ts37
1 files changed, 24 insertions, 13 deletions
diff --git a/apps/web/src/lib/timeline/element-utils.ts b/apps/web/src/lib/timeline/element-utils.ts
index 8c02c097..11564376 100644
--- a/apps/web/src/lib/timeline/element-utils.ts
+++ b/apps/web/src/lib/timeline/element-utils.ts
@@ -13,6 +13,7 @@ import type {
CreateStickerElement,
CreateUploadAudioElement,
CreateLibraryAudioElement,
+ TextBackground,
TextElement,
TimelineElement,
TimelineTrack,
@@ -46,7 +47,7 @@ export function isVisualElement(
export function canElementBeHidden(
element: TimelineElement,
): element is VisualElement {
- return element.type !== "audio";
+ return isVisualElement(element);
}
export function hasMediaId(
@@ -127,13 +128,30 @@ export function wouldElementOverlap({
endTime: number;
excludeElementId?: string;
}): boolean {
- return elements.some((el) => {
- if (excludeElementId && el.id === excludeElementId) return false;
- const elEnd = el.startTime + el.duration;
- return startTime < elEnd && endTime > el.startTime;
+ return elements.some((element) => {
+ if (excludeElementId && element.id === excludeElementId) return false;
+ const elementEnd = element.startTime + element.duration;
+ return startTime < elementEnd && endTime > element.startTime;
});
}
+function buildTextBackground(
+ raw: Partial<TextBackground> | undefined,
+): TextBackground {
+ const color = raw?.color ?? DEFAULT_TEXT_ELEMENT.background.color;
+ const enabled =
+ typeof raw?.enabled === "boolean" ? raw.enabled : color !== "transparent";
+ return {
+ enabled,
+ color,
+ cornerRadius: raw?.cornerRadius,
+ paddingX: raw?.paddingX,
+ paddingY: raw?.paddingY,
+ offsetX: raw?.offsetX,
+ offsetY: raw?.offsetY,
+ };
+}
+
export function buildTextElement({
raw,
startTime,
@@ -157,14 +175,7 @@ export function buildTextElement({
: DEFAULT_TEXT_ELEMENT.fontSize,
fontFamily: t.fontFamily ?? DEFAULT_TEXT_ELEMENT.fontFamily,
color: t.color ?? DEFAULT_TEXT_ELEMENT.color,
- background: {
- color: t.background?.color ?? DEFAULT_TEXT_ELEMENT.background.color,
- cornerRadius: t.background?.cornerRadius,
- paddingX: t.background?.paddingX,
- paddingY: t.background?.paddingY,
- offsetX: t.background?.offsetX,
- offsetY: t.background?.offsetY,
- },
+ background: buildTextBackground(t.background),
textAlign: t.textAlign ?? DEFAULT_TEXT_ELEMENT.textAlign,
fontWeight: t.fontWeight ?? DEFAULT_TEXT_ELEMENT.fontWeight,
fontStyle: t.fontStyle ?? DEFAULT_TEXT_ELEMENT.fontStyle,