summaryrefslogtreecommitdiff
path: root/apps/web
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2025-07-30 11:38:21 +0200
committerMaze Winther <mazewinther@gmail.com>2025-07-30 11:38:21 +0200
commit004fa0c12baabb95c2d525bdd708f41e80ff7f8e (patch)
tree3ede215eee9128b8120ad8589f4f7d66b3dfedbd /apps/web
parentdeda0e366fc934f9734d4c523e46fa8b51111237 (diff)
downloadopencut-004fa0c12baabb95c2d525bdd708f41e80ff7f8e.tar.gz
Merge pull request [#483](https://github.com/mazeincoding/AppCut/issues/483)
Diffstat (limited to 'apps/web')
-rw-r--r--apps/web/src/components/editor/preview-panel.tsx20
-rw-r--r--apps/web/src/components/editor/timeline/index.tsx36
-rw-r--r--apps/web/src/components/landing/handlebars.tsx4
-rw-r--r--apps/web/src/hooks/use-editor-actions.ts280
4 files changed, 220 insertions, 120 deletions
diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx
index 639858d0..5d038d0f 100644
--- a/apps/web/src/components/editor/preview-panel.tsx
+++ b/apps/web/src/components/editor/preview-panel.tsx
@@ -211,7 +211,7 @@ export function PreviewPanel() {
setDragState({
isDragging: true,
elementId: element.id,
- trackId: trackId,
+ trackId,
startX: e.clientX,
startY: e.clientY,
initialElementX: element.x,
@@ -355,8 +355,22 @@ export function PreviewPanel() {
handleTextMouseDown(e, element, elementData.track.id)
}
style={{
- left: `${50 + ((dragState.isDragging && dragState.elementId === element.id ? dragState.currentX : element.x) / canvasSize.width) * 100}%`,
- top: `${50 + ((dragState.isDragging && dragState.elementId === element.id ? dragState.currentY : element.y) / canvasSize.height) * 100}%`,
+ left: `${
+ 50 +
+ ((dragState.isDragging && dragState.elementId === element.id
+ ? dragState.currentX
+ : element.x) /
+ canvasSize.width) *
+ 100
+ }%`,
+ top: `${
+ 50 +
+ ((dragState.isDragging && dragState.elementId === element.id
+ ? dragState.currentY
+ : element.y) /
+ canvasSize.height) *
+ 100
+ }%`,
transform: `translate(-50%, -50%) rotate(${element.rotation}deg) scale(${scaleRatio})`,
opacity: element.opacity,
zIndex: 100 + index, // Text elements on top
diff --git a/apps/web/src/components/editor/timeline/index.tsx b/apps/web/src/components/editor/timeline/index.tsx
index bdb4433c..078f1258 100644
--- a/apps/web/src/components/editor/timeline/index.tsx
+++ b/apps/web/src/components/editor/timeline/index.tsx
@@ -552,7 +552,7 @@ export function Timeline() {
{/* Timeline Ruler */}
<div
- className="flex-1 relative overflow-hidden h-4"
+ className="flex-1 relative overflow-hidden h-10"
onWheel={(e) => {
// Check if this is horizontal scrolling - if so, don't handle it here
if (e.shiftKey || Math.abs(e.deltaX) > Math.abs(e.deltaY)) {
@@ -567,7 +567,7 @@ export function Timeline() {
<ScrollArea className="w-full" ref={rulerScrollRef}>
<div
ref={rulerRef}
- className="relative h-4 select-none cursor-default"
+ className="relative h-10 select-none cursor-default"
style={{
width: `${dynamicTimelineWidth}px`,
}}
@@ -601,13 +601,17 @@ export function Timeline() {
return (
<div
key={i}
- className={`absolute top-0 bottom-0 ${
+ className={`absolute top-0 h-4 ${
isMainMarker
? "border-l border-muted-foreground/40"
: "border-l border-muted-foreground/20"
}`}
style={{
- left: `${time * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel}px`,
+ left: `${
+ time *
+ TIMELINE_CONSTANTS.PIXELS_PER_SECOND *
+ zoomLevel
+ }px`,
}}
>
<span
@@ -624,10 +628,16 @@ export function Timeline() {
const secs = seconds % 60;
if (hours > 0) {
- return `${hours}:${minutes.toString().padStart(2, "0")}:${Math.floor(secs).toString().padStart(2, "0")}`;
+ return `${hours}:${minutes
+ .toString()
+ .padStart(2, "0")}:${Math.floor(secs)
+ .toString()
+ .padStart(2, "0")}`;
}
if (minutes > 0) {
- return `${minutes}:${Math.floor(secs).toString().padStart(2, "0")}`;
+ return `${minutes}:${Math.floor(secs)
+ .toString()
+ .padStart(2, "0")}`;
}
if (interval >= 1) {
return `${Math.floor(secs)}s`;
@@ -710,7 +720,10 @@ export function Timeline() {
<div
className="relative flex-1"
style={{
- height: `${Math.max(200, Math.min(800, getTotalTracksHeight(tracks)))}px`,
+ height: `${Math.max(
+ 200,
+ Math.min(800, getTotalTracksHeight(tracks))
+ )}px`,
width: `${dynamicTimelineWidth}px`,
}}
>
@@ -724,7 +737,10 @@ export function Timeline() {
<div
className="absolute left-0 right-0 border-b border-muted/30 py-[0.05rem]"
style={{
- top: `${getCumulativeHeightBefore(tracks, index)}px`,
+ top: `${getCumulativeHeightBefore(
+ tracks,
+ index
+ )}px`,
height: `${getTrackHeight(track.type)}px`,
}}
onClick={(e) => {
@@ -1092,7 +1108,9 @@ function TimelineToolbar({
<TooltipTrigger asChild>
<Button variant="text" size="icon" onClick={toggleRippleEditing}>
<Link
- className={`h-4 w-4 ${rippleEditingEnabled ? "text-primary" : ""}`}
+ className={`h-4 w-4 ${
+ rippleEditingEnabled ? "text-primary" : ""
+ }`}
/>
</Button>
</TooltipTrigger>
diff --git a/apps/web/src/components/landing/handlebars.tsx b/apps/web/src/components/landing/handlebars.tsx
index 26cf9254..e87d50e9 100644
--- a/apps/web/src/components/landing/handlebars.tsx
+++ b/apps/web/src/components/landing/handlebars.tsx
@@ -62,7 +62,7 @@ export function Handlebars({ children }: HandlebarsProps) {
whileDrag={{ scale: 1.1, cursor: "grabbing" }}
transition={{ type: "spring", stiffness: 400, damping: 30 }}
>
- <div className="w-2 h-8 rounded-full bg-yellow-500"></div>
+ <div className="w-2 h-8 rounded-full bg-yellow-500" />
</motion.div>
<motion.div
@@ -81,7 +81,7 @@ export function Handlebars({ children }: HandlebarsProps) {
whileDrag={{ scale: 1.1, cursor: "grabbing" }}
transition={{ type: "spring", stiffness: 400, damping: 30 }}
>
- <div className="w-2 h-8 rounded-full bg-yellow-500"></div>
+ <div className="w-2 h-8 rounded-full bg-yellow-500" />
</motion.div>
</div>
diff --git a/apps/web/src/hooks/use-editor-actions.ts b/apps/web/src/hooks/use-editor-actions.ts
index b2fad55f..bfe9a7b9 100644
--- a/apps/web/src/hooks/use-editor-actions.ts
+++ b/apps/web/src/hooks/use-editor-actions.ts
@@ -26,136 +26,204 @@ export function useEditorActions() {
const { activeProject } = useProjectStore();
// Playback actions
- useActionHandler("toggle-play", () => {
- toggle();
- }, undefined);
-
- useActionHandler("stop-playback", () => {
- if (isPlaying) {
+ useActionHandler(
+ "toggle-play",
+ () => {
toggle();
- }
- seek(0);
- }, undefined);
+ },
+ undefined
+ );
+
+ useActionHandler(
+ "stop-playback",
+ () => {
+ if (isPlaying) {
+ toggle();
+ }
+ seek(0);
+ },
+ undefined
+ );
- useActionHandler("seek-forward", (args) => {
- const seconds = args?.seconds ?? 1;
- seek(Math.min(duration, currentTime + seconds));
- }, undefined);
+ useActionHandler(
+ "seek-forward",
+ (args) => {
+ const seconds = args?.seconds ?? 1;
+ seek(Math.min(duration, currentTime + seconds));
+ },
+ undefined
+ );
- useActionHandler("seek-backward", (args) => {
- const seconds = args?.seconds ?? 1;
- seek(Math.max(0, currentTime - seconds));
- }, undefined);
+ useActionHandler(
+ "seek-backward",
+ (args) => {
+ const seconds = args?.seconds ?? 1;
+ seek(Math.max(0, currentTime - seconds));
+ },
+ undefined
+ );
- useActionHandler("frame-step-forward", () => {
- const projectFps = activeProject?.fps || 30;
- seek(Math.min(duration, currentTime + 1 / projectFps));
- }, undefined);
+ useActionHandler(
+ "frame-step-forward",
+ () => {
+ const projectFps = activeProject?.fps || 30;
+ seek(Math.min(duration, currentTime + 1 / projectFps));
+ },
+ undefined
+ );
- useActionHandler("frame-step-backward", () => {
- const projectFps = activeProject?.fps || 30;
- seek(Math.max(0, currentTime - 1 / projectFps));
- }, undefined);
+ useActionHandler(
+ "frame-step-backward",
+ () => {
+ const projectFps = activeProject?.fps || 30;
+ seek(Math.max(0, currentTime - 1 / projectFps));
+ },
+ undefined
+ );
- useActionHandler("jump-forward", (args) => {
- const seconds = args?.seconds ?? 5;
- seek(Math.min(duration, currentTime + seconds));
- }, undefined);
+ useActionHandler(
+ "jump-forward",
+ (args) => {
+ const seconds = args?.seconds ?? 5;
+ seek(Math.min(duration, currentTime + seconds));
+ },
+ undefined
+ );
- useActionHandler("jump-backward", (args) => {
- const seconds = args?.seconds ?? 5;
- seek(Math.max(0, currentTime - seconds));
- }, undefined);
+ useActionHandler(
+ "jump-backward",
+ (args) => {
+ const seconds = args?.seconds ?? 5;
+ seek(Math.max(0, currentTime - seconds));
+ },
+ undefined
+ );
- useActionHandler("goto-start", () => {
- seek(0);
- }, undefined);
+ useActionHandler(
+ "goto-start",
+ () => {
+ seek(0);
+ },
+ undefined
+ );
- useActionHandler("goto-end", () => {
- seek(duration);
- }, undefined);
+ useActionHandler(
+ "goto-end",
+ () => {
+ seek(duration);
+ },
+ undefined
+ );
// Timeline editing actions
- useActionHandler("split-element", () => {
- if (selectedElements.length !== 1) {
- toast.error("Select exactly one element to split");
- return;
- }
+ useActionHandler(
+ "split-element",
+ () => {
+ if (selectedElements.length !== 1) {
+ toast.error("Select exactly one element to split");
+ return;
+ }
- const { trackId, elementId } = selectedElements[0];
- const track = tracks.find((t: any) => t.id === trackId);
- const element = track?.elements.find((el: any) => el.id === elementId);
+ const { trackId, elementId } = selectedElements[0];
+ const track = tracks.find((t: any) => t.id === trackId);
+ const element = track?.elements.find((el: any) => el.id === elementId);
- if (element) {
- const effectiveStart = element.startTime;
- const effectiveEnd =
- element.startTime +
- (element.duration - element.trimStart - element.trimEnd);
+ if (element) {
+ const effectiveStart = element.startTime;
+ const effectiveEnd =
+ element.startTime +
+ (element.duration - element.trimStart - element.trimEnd);
- if (currentTime > effectiveStart && currentTime < effectiveEnd) {
- splitElement(trackId, elementId, currentTime);
- } else {
- toast.error("Playhead must be within selected element");
+ if (currentTime > effectiveStart && currentTime < effectiveEnd) {
+ splitElement(trackId, elementId, currentTime);
+ } else {
+ toast.error("Playhead must be within selected element");
+ }
}
- }
- }, undefined);
+ },
+ undefined
+ );
- useActionHandler("delete-selected", () => {
- if (selectedElements.length === 0) {
- return;
- }
- selectedElements.forEach(
- ({ trackId, elementId }: { trackId: string; elementId: string }) => {
- removeElementFromTrack(trackId, elementId);
+ useActionHandler(
+ "delete-selected",
+ () => {
+ if (selectedElements.length === 0) {
+ return;
}
- );
- clearSelectedElements();
- }, undefined);
+ selectedElements.forEach(
+ ({ trackId, elementId }: { trackId: string; elementId: string }) => {
+ removeElementFromTrack(trackId, elementId);
+ }
+ );
+ clearSelectedElements();
+ },
+ undefined
+ );
- useActionHandler("select-all", () => {
- const allElements = tracks.flatMap((track: any) =>
- track.elements.map((element: any) => ({
- trackId: track.id,
- elementId: element.id,
- }))
- );
- setSelectedElements(allElements);
- }, undefined);
+ useActionHandler(
+ "select-all",
+ () => {
+ const allElements = tracks.flatMap((track: any) =>
+ track.elements.map((element: any) => ({
+ trackId: track.id,
+ elementId: element.id,
+ }))
+ );
+ setSelectedElements(allElements);
+ },
+ undefined
+ );
- useActionHandler("duplicate-selected", () => {
- if (selectedElements.length !== 1) {
- toast.error("Select exactly one element to duplicate");
- return;
- }
+ useActionHandler(
+ "duplicate-selected",
+ () => {
+ if (selectedElements.length !== 1) {
+ toast.error("Select exactly one element to duplicate");
+ return;
+ }
- const { trackId, elementId } = selectedElements[0];
- const track = tracks.find((t: any) => t.id === trackId);
- const element = track?.elements.find((el: any) => el.id === elementId);
+ const { trackId, elementId } = selectedElements[0];
+ const track = tracks.find((t: any) => t.id === trackId);
+ const element = track?.elements.find((el: any) => el.id === elementId);
- if (element) {
- const newStartTime =
- element.startTime +
- (element.duration - element.trimStart - element.trimEnd) +
- 0.1;
- const { id, ...elementWithoutId } = element;
+ if (element) {
+ const newStartTime =
+ element.startTime +
+ (element.duration - element.trimStart - element.trimEnd) +
+ 0.1;
+ const { id, ...elementWithoutId } = element;
- addElementToTrack(trackId, {
- ...elementWithoutId,
- startTime: newStartTime,
- });
- }
- }, undefined);
+ addElementToTrack(trackId, {
+ ...elementWithoutId,
+ startTime: newStartTime,
+ });
+ }
+ },
+ undefined
+ );
- useActionHandler("toggle-snapping", () => {
- toggleSnapping();
- }, undefined);
+ useActionHandler(
+ "toggle-snapping",
+ () => {
+ toggleSnapping();
+ },
+ undefined
+ );
// History actions
- useActionHandler("undo", () => {
- undo();
- }, undefined);
+ useActionHandler(
+ "undo",
+ () => {
+ undo();
+ },
+ undefined
+ );
- useActionHandler("redo", () => {
- redo();
- }, undefined);
+ useActionHandler(
+ "redo",
+ () => {
+ redo();
+ },
+ undefined
+ );
}