fix: remove thick black border from checkbox for cleaner UI

This commit is contained in:
antonio-jb 2025-07-27 17:03:33 +02:00
parent 14475dc9a6
commit df3b092010
9 changed files with 230 additions and 112 deletions

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

12
.idea/OpenCut.iml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/OpenCut.iml" filepath="$PROJECT_DIR$/.idea/OpenCut.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -72,6 +72,7 @@
"devDependencies": {
"@tailwindcss/typography": "^0.5.16",
"@types/bun": "latest",
"@types/node": "^24.1.0",
"@types/pg": "^8.15.4",
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18",

View File

@ -399,7 +399,7 @@ function ProjectCard({
>
{isSelectionMode && (
<div className="absolute top-3 left-3 z-10">
<div className="w-5 h-5 rounded bg-background/80 backdrop-blur-sm border flex items-center justify-center">
<div className="w-4.9 h-4.9 rounded-full bg-background/80 backdrop-blur-sm border flex items-center justify-center">
<Checkbox
checked={isSelected}
onCheckedChange={(checked) =>

View File

@ -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("seek-forward", (args) => {
const seconds = args?.seconds ?? 1;
seek(Math.min(duration, currentTime + seconds));
}, undefined);
useActionHandler(
"stop-playback",
() => {
if (isPlaying) {
toggle();
}
seek(0);
},
undefined
);
useActionHandler("seek-backward", (args) => {
const seconds = args?.seconds ?? 1;
seek(Math.max(0, currentTime - seconds));
}, undefined);
useActionHandler(
"seek-forward",
(args) => {
const seconds = args?.seconds ?? 1;
seek(Math.min(duration, currentTime + seconds));
},
undefined
);
useActionHandler("frame-step-forward", () => {
const projectFps = activeProject?.fps || 30;
seek(Math.min(duration, currentTime + 1 / projectFps));
}, undefined);
useActionHandler(
"seek-backward",
(args) => {
const seconds = args?.seconds ?? 1;
seek(Math.max(0, currentTime - seconds));
},
undefined
);
useActionHandler("frame-step-backward", () => {
const projectFps = activeProject?.fps || 30;
seek(Math.max(0, currentTime - 1 / projectFps));
}, undefined);
useActionHandler(
"frame-step-forward",
() => {
const projectFps = activeProject?.fps || 30;
seek(Math.min(duration, currentTime + 1 / projectFps));
},
undefined
);
useActionHandler("jump-forward", (args) => {
const seconds = args?.seconds ?? 5;
seek(Math.min(duration, currentTime + seconds));
}, undefined);
useActionHandler(
"frame-step-backward",
() => {
const projectFps = activeProject?.fps || 30;
seek(Math.max(0, currentTime - 1 / projectFps));
},
undefined
);
useActionHandler("jump-backward", (args) => {
const seconds = args?.seconds ?? 5;
seek(Math.max(0, currentTime - seconds));
}, undefined);
useActionHandler(
"jump-forward",
(args) => {
const seconds = args?.seconds ?? 5;
seek(Math.min(duration, currentTime + seconds));
},
undefined
);
useActionHandler("goto-start", () => {
seek(0);
}, undefined);
useActionHandler(
"jump-backward",
(args) => {
const seconds = args?.seconds ?? 5;
seek(Math.max(0, currentTime - seconds));
},
undefined
);
useActionHandler("goto-end", () => {
seek(duration);
}, undefined);
useActionHandler(
"goto-start",
() => {
seek(0);
},
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;
}
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 (currentTime > effectiveStart && currentTime < effectiveEnd) {
splitElement(trackId, elementId, currentTime);
} else {
toast.error("Playhead must be within selected element");
useActionHandler(
"split-element",
() => {
if (selectedElements.length !== 1) {
toast.error("Select exactly one element to split");
return;
}
}
}, undefined);
useActionHandler("delete-selected", () => {
if (selectedElements.length === 0) {
return;
}
selectedElements.forEach(
({ trackId, elementId }: { trackId: string; elementId: string }) => {
removeElementFromTrack(trackId, 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 (currentTime > effectiveStart && currentTime < effectiveEnd) {
splitElement(trackId, elementId, currentTime);
} else {
toast.error("Playhead must be within selected element");
}
}
);
clearSelectedElements();
}, undefined);
},
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(
"delete-selected",
() => {
if (selectedElements.length === 0) {
return;
}
selectedElements.forEach(
({ trackId, elementId }: { trackId: string; elementId: string }) => {
removeElementFromTrack(trackId, elementId);
}
);
clearSelectedElements();
},
undefined
);
useActionHandler("duplicate-selected", () => {
if (selectedElements.length !== 1) {
toast.error("Select exactly one element to duplicate");
return;
}
useActionHandler(
"select-all",
() => {
const allElements = tracks.flatMap((track: any) =>
track.elements.map((element: any) => ({
trackId: track.id,
elementId: element.id,
}))
);
setSelectedElements(allElements);
},
undefined
);
const { trackId, elementId } = selectedElements[0];
const track = tracks.find((t: any) => t.id === trackId);
const element = track?.elements.find((el: any) => el.id === elementId);
useActionHandler(
"duplicate-selected",
() => {
if (selectedElements.length !== 1) {
toast.error("Select exactly one element to duplicate");
return;
}
if (element) {
const newStartTime =
element.startTime +
(element.duration - element.trimStart - element.trimEnd) +
0.1;
const { id, ...elementWithoutId } = element;
const { trackId, elementId } = selectedElements[0];
const track = tracks.find((t: any) => t.id === trackId);
const element = track?.elements.find((el: any) => el.id === elementId);
addElementToTrack(trackId, {
...elementWithoutId,
startTime: newStartTime,
});
}
}, undefined);
if (element) {
const newStartTime =
element.startTime +
(element.duration - element.trimStart - element.trimEnd) +
0.1;
const { id, ...elementWithoutId } = element;
useActionHandler("toggle-snapping", () => {
toggleSnapping();
}, undefined);
addElementToTrack(trackId, {
...elementWithoutId,
startTime: newStartTime,
});
}
},
undefined
);
useActionHandler(
"toggle-snapping",
() => {
toggleSnapping();
},
undefined
);
// History actions
useActionHandler("undo", () => {
undo();
}, undefined);
useActionHandler(
"undo",
() => {
undo();
},
undefined
);
useActionHandler("redo", () => {
redo();
}, undefined);
useActionHandler(
"redo",
() => {
redo();
},
undefined
);
}

View File

@ -9,6 +9,7 @@
},
"devDependencies": {
"@biomejs/biome": "2.1.2",
"@types/node": "^24.1.0",
"husky": "^9.1.7",
"turbo": "^2.5.4",
"typescript": "5.8.3",
@ -75,6 +76,7 @@
"devDependencies": {
"@tailwindcss/typography": "^0.5.16",
"@types/bun": "latest",
"@types/node": "^24.1.0",
"@types/pg": "^8.15.4",
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18",
@ -536,7 +538,7 @@
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
"@types/node": ["@types/node@22.16.5", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ=="],
"@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="],
"@types/pg": ["@types/pg@8.15.4", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg=="],
@ -1236,7 +1238,7 @@
"uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="],
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
"unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
@ -1300,10 +1302,16 @@
"@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
"@opencut/auth/@types/node": ["@types/node@22.16.5", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ=="],
"@types/pg/@types/node": ["@types/node@22.16.5", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ=="],
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"better-auth/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"bun-types/@types/node": ["@types/node@22.16.5", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ=="],
"chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
@ -1388,6 +1396,12 @@
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
"@opencut/auth/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"@types/pg/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"bun-types/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"motion/framer-motion/motion-dom": ["motion-dom@12.23.6", "", { "dependencies": { "motion-utils": "^12.23.6" } }, "sha512-G2w6Nw7ZOVSzcQmsdLc0doMe64O/Sbuc2bVAbgMz6oP/6/pRStKRiVRV4bQfHp5AHYAKEGhEdVHTM+R3FDgi5w=="],
"motion/framer-motion/motion-utils": ["motion-utils@12.23.6", "", {}, "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ=="],

View File

@ -3,6 +3,7 @@
"packageManager": "bun@1.2.18",
"devDependencies": {
"@biomejs/biome": "2.1.2",
"@types/node": "^24.1.0",
"husky": "^9.1.7",
"turbo": "^2.5.4",
"typescript": "5.8.3",