diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-02-08 22:21:17 +0100 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-02-08 22:21:17 +0100 |
| commit | e50a817b97b87baa0cd8fdde0fdb5d607143c1c3 (patch) | |
| tree | 8f33d19153be9a6d9fe81b1a7e7a54179ab88feb /apps/web/src/components/editor/panels/properties | |
| parent | 484ce47867b1c92c83e4cfab9fa8371bb6e68e12 (diff) | |
| download | opencut-e50a817b97b87baa0cd8fdde0fdb5d607143c1c3.tar.gz | |
add transform + few fixes
feat: transform
feat: batch command
fix: slider undo
fix: timeline zoom perf
refactor: make updateElement support multiple
Diffstat (limited to 'apps/web/src/components/editor/panels/properties')
| -rw-r--r-- | apps/web/src/components/editor/panels/properties/text-properties.tsx | 268 |
1 files changed, 190 insertions, 78 deletions
diff --git a/apps/web/src/components/editor/panels/properties/text-properties.tsx b/apps/web/src/components/editor/panels/properties/text-properties.tsx index 43c1eb26..35230c75 100644 --- a/apps/web/src/components/editor/panels/properties/text-properties.tsx +++ b/apps/web/src/components/editor/panels/properties/text-properties.tsx @@ -50,6 +50,8 @@ export function TextProperties({ ); const lastSelectedColor = useRef(DEFAULT_COLOR); + const initialFontSizeRef = useRef<number | null>(null); + const initialOpacityRef = useRef<number | null>(null); const handleFontSizeChange = ({ value }: { value: string }) => { setFontSizeInput(value); @@ -59,10 +61,8 @@ export function TextProperties({ const fontSize = Number.isNaN(parsed) ? element.fontSize : clamp({ value: parsed, min: MIN_FONT_SIZE, max: MAX_FONT_SIZE }); - editor.timeline.updateElement({ - trackId, - elementId: element.id, - updates: { fontSize }, + editor.timeline.updateElements({ + updates: [{ trackId, elementId: element.id, updates: { fontSize } }], }); } }; @@ -73,10 +73,8 @@ export function TextProperties({ ? element.fontSize : clamp({ value: parsed, min: MIN_FONT_SIZE, max: MAX_FONT_SIZE }); setFontSizeInput(fontSize.toString()); - editor.timeline.updateElement({ - trackId, - elementId: element.id, - updates: { fontSize }, + editor.timeline.updateElements({ + updates: [{ trackId, elementId: element.id, updates: { fontSize } }], }); }; @@ -88,10 +86,14 @@ export function TextProperties({ const opacityPercent = Number.isNaN(parsed) ? Math.round(element.opacity * 100) : clamp({ value: parsed, min: 0, max: 100 }); - editor.timeline.updateElement({ - trackId, - elementId: element.id, - updates: { opacity: opacityPercent / 100 }, + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId: element.id, + updates: { opacity: opacityPercent / 100 }, + }, + ], }); } }; @@ -102,10 +104,14 @@ export function TextProperties({ ? Math.round(element.opacity * 100) : clamp({ value: parsed, min: 0, max: 100 }); setOpacityInput(opacityPercent.toString()); - editor.timeline.updateElement({ - trackId, - elementId: element.id, - updates: { opacity: opacityPercent / 100 }, + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId: element.id, + updates: { opacity: opacityPercent / 100 }, + }, + ], }); }; @@ -113,10 +119,14 @@ export function TextProperties({ if (color !== "transparent") { lastSelectedColor.current = color; } - editor.timeline.updateElement({ - trackId, - elementId: element.id, - updates: { backgroundColor: color }, + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId: element.id, + updates: { backgroundColor: color }, + }, + ], }); }; @@ -126,10 +136,14 @@ export function TextProperties({ isTransparent: boolean; }) => { const newColor = isTransparent ? "transparent" : lastSelectedColor.current; - editor.timeline.updateElement({ - trackId, - elementId: element.id, - updates: { backgroundColor: newColor }, + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId: element.id, + updates: { backgroundColor: newColor }, + }, + ], }); }; @@ -154,10 +168,14 @@ export function TextProperties({ defaultValue={element.content} className="bg-panel-accent min-h-20" onChange={(e) => - editor.timeline.updateElement({ - trackId, - elementId: element.id, - updates: { content: e.target.value }, + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId: element.id, + updates: { content: e.target.value }, + }, + ], }) } /> @@ -167,10 +185,14 @@ export function TextProperties({ <FontPicker defaultValue={element.fontFamily} onValueChange={(value: FontFamily) => - editor.timeline.updateElement({ - trackId, - elementId: element.id, - updates: { fontFamily: value }, + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId: element.id, + updates: { fontFamily: value }, + }, + ], }) } /> @@ -186,13 +208,19 @@ export function TextProperties({ } size="sm" onClick={() => - editor.timeline.updateElement({ - trackId, - elementId: element.id, - updates: { - fontWeight: - element.fontWeight === "bold" ? "normal" : "bold", - }, + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId: element.id, + updates: { + fontWeight: + element.fontWeight === "bold" + ? "normal" + : "bold", + }, + }, + ], }) } className="h-8 px-3 font-bold" @@ -205,15 +233,19 @@ export function TextProperties({ } size="sm" onClick={() => - editor.timeline.updateElement({ - trackId, - elementId: element.id, - updates: { - fontStyle: - element.fontStyle === "italic" - ? "normal" - : "italic", - }, + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId: element.id, + updates: { + fontStyle: + element.fontStyle === "italic" + ? "normal" + : "italic", + }, + }, + ], }) } className="h-8 px-3 italic" @@ -228,15 +260,19 @@ export function TextProperties({ } size="sm" onClick={() => - editor.timeline.updateElement({ - trackId, - elementId: element.id, - updates: { - textDecoration: - element.textDecoration === "underline" - ? "none" - : "underline", - }, + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId: element.id, + updates: { + textDecoration: + element.textDecoration === "underline" + ? "none" + : "underline", + }, + }, + ], }) } className="h-8 px-3 underline" @@ -251,15 +287,19 @@ export function TextProperties({ } size="sm" onClick={() => - editor.timeline.updateElement({ - trackId, - elementId: element.id, - updates: { - textDecoration: - element.textDecoration === "line-through" - ? "none" - : "line-through", - }, + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId: element.id, + updates: { + textDecoration: + element.textDecoration === "line-through" + ? "none" + : "line-through", + }, + }, + ], }) } className="h-8 px-3 line-through" @@ -279,13 +319,48 @@ export function TextProperties({ max={MAX_FONT_SIZE} step={1} onValueChange={([value]) => { - editor.timeline.updateElement({ - trackId, - elementId: element.id, - updates: { fontSize: value }, + if (initialFontSizeRef.current === null) { + initialFontSizeRef.current = element.fontSize; + } + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId: element.id, + updates: { fontSize: value }, + }, + ], + pushHistory: false, }); setFontSizeInput(value.toString()); }} + onValueCommit={([value]) => { + if (initialFontSizeRef.current !== null) { + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId: element.id, + updates: { + fontSize: initialFontSizeRef.current, + }, + }, + ], + pushHistory: false, + }); + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId: element.id, + updates: { fontSize: value }, + }, + ], + pushHistory: true, + }); + initialFontSizeRef.current = null; + } + }} className="w-full" /> <Input @@ -310,10 +385,14 @@ export function TextProperties({ string: (element.color || "FFFFFF").replace("#", ""), })} onChange={(color) => { - editor.timeline.updateElement({ - trackId, - elementId: element.id, - updates: { color: `#${color}` }, + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId: element.id, + updates: { color: `#${color}` }, + }, + ], }); }} containerRef={containerRef} @@ -330,13 +409,46 @@ export function TextProperties({ max={100} step={1} onValueChange={([value]) => { - editor.timeline.updateElement({ - trackId, - elementId: element.id, - updates: { opacity: value / 100 }, + if (initialOpacityRef.current === null) { + initialOpacityRef.current = element.opacity; + } + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId: element.id, + updates: { opacity: value / 100 }, + }, + ], + pushHistory: false, }); setOpacityInput(value.toString()); }} + onValueCommit={([value]) => { + if (initialOpacityRef.current !== null) { + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId: element.id, + updates: { opacity: initialOpacityRef.current }, + }, + ], + pushHistory: false, + }); + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId: element.id, + updates: { opacity: value / 100 }, + }, + ], + pushHistory: true, + }); + initialOpacityRef.current = null; + } + }} className="w-full" /> <Input |
