mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-26 07:45:34 +00:00
fix(editor): address PR review feedback
This commit is contained in:
@@ -41,6 +41,9 @@ function normalizeRecordingTimeOffsetMs(value: unknown): number {
|
||||
return typeof value === "number" && Number.isFinite(value) ? Math.round(value) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a filesystem-safe project base name without the project extension.
|
||||
*/
|
||||
function normalizeProjectSaveName(projectName?: string | null) {
|
||||
if (typeof projectName !== "string") {
|
||||
return null;
|
||||
@@ -55,8 +58,11 @@ function normalizeProjectSaveName(projectName?: string | null) {
|
||||
new RegExp(`\\.${PROJECT_FILE_EXTENSION}$`, "i"),
|
||||
"",
|
||||
);
|
||||
const sanitizedName = withoutExtension
|
||||
.replace(/[<>:"/\\|?*\u0000-\u001F]/g, "")
|
||||
const withoutInvalidFilesystemChars = withoutExtension.replace(/[<>:"/\\|?*]/g, "");
|
||||
const withoutControlChars = Array.from(withoutInvalidFilesystemChars)
|
||||
.filter((character) => character.charCodeAt(0) > 31)
|
||||
.join("");
|
||||
const sanitizedName = withoutControlChars
|
||||
.replace(/\s+/g, " ")
|
||||
.replace(/[. ]+$/g, "")
|
||||
.trim();
|
||||
@@ -64,6 +70,64 @@ function normalizeProjectSaveName(projectName?: string | null) {
|
||||
return sanitizedName || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the persisted source video path from a saved project payload.
|
||||
*/
|
||||
function getProjectVideoPath(projectData: unknown) {
|
||||
if (!projectData || typeof projectData !== "object") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const candidate = projectData as { videoPath?: unknown };
|
||||
return typeof candidate.videoPath === "string" ? candidate.videoPath : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents a named save from silently overwriting a different project file.
|
||||
*/
|
||||
async function ensureNamedProjectSaveDoesNotOverwriteDifferentProject(
|
||||
targetProjectPath: string,
|
||||
projectData: unknown,
|
||||
) {
|
||||
try {
|
||||
await fs.stat(targetProjectPath);
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException)?.code === "ENOENT") {
|
||||
return { success: true };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
const incomingVideoPath = getProjectVideoPath(projectData);
|
||||
if (!incomingVideoPath) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Unable to verify project identity for the chosen name",
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const existingProjectRaw = await fs.readFile(targetProjectPath, "utf-8");
|
||||
const existingProjectData = JSON.parse(existingProjectRaw) as unknown;
|
||||
const existingVideoPath = getProjectVideoPath(existingProjectData);
|
||||
|
||||
if (existingVideoPath === incomingVideoPath) {
|
||||
return { success: true };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to verify existing named project before overwrite:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "A different project already uses this name",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: "A different project already uses this name",
|
||||
};
|
||||
}
|
||||
|
||||
export function registerProjectHandlers() {
|
||||
ipcMain.handle('reveal-in-folder', async (_, filePath: string) => {
|
||||
try {
|
||||
@@ -222,6 +286,14 @@ export function registerProjectHandlers() {
|
||||
`${normalizedProjectName}.${PROJECT_FILE_EXTENSION}`,
|
||||
)
|
||||
|
||||
const overwriteCheck = await ensureNamedProjectSaveDoesNotOverwriteDifferentProject(
|
||||
targetProjectPath,
|
||||
projectData,
|
||||
)
|
||||
if (!overwriteCheck.success) {
|
||||
return overwriteCheck
|
||||
}
|
||||
|
||||
await fs.writeFile(targetProjectPath, JSON.stringify(projectData, null, 2), 'utf-8')
|
||||
setCurrentProjectPath(targetProjectPath)
|
||||
await saveProjectThumbnail(targetProjectPath, thumbnailDataUrl)
|
||||
|
||||
@@ -2202,6 +2202,9 @@ export default function VideoEditor() {
|
||||
}
|
||||
}, [saveProject]);
|
||||
|
||||
/**
|
||||
* Saves the current project directly into the projects library under a chosen name.
|
||||
*/
|
||||
const saveProjectWithName = useCallback(
|
||||
async (projectName: string) => {
|
||||
const trimmedProjectName = projectName.trim();
|
||||
@@ -2258,11 +2261,17 @@ export default function VideoEditor() {
|
||||
],
|
||||
);
|
||||
|
||||
/**
|
||||
* Resets the inline project-name editor back to the current saved display name.
|
||||
*/
|
||||
const closeProjectNameEditor = useCallback(() => {
|
||||
setProjectNameDraft(projectDisplayName);
|
||||
setIsEditingProjectName(false);
|
||||
}, [projectDisplayName]);
|
||||
|
||||
/**
|
||||
* Commits the inline project-name editor and persists the project under that name.
|
||||
*/
|
||||
const handleProjectNameSubmit = useCallback(
|
||||
async (event?: React.FormEvent<HTMLFormElement>) => {
|
||||
event?.preventDefault();
|
||||
@@ -2273,8 +2282,12 @@ export default function VideoEditor() {
|
||||
}
|
||||
|
||||
setIsSavingProjectName(true);
|
||||
const saved = await saveProjectWithName(trimmedProjectName);
|
||||
setIsSavingProjectName(false);
|
||||
let saved = false;
|
||||
try {
|
||||
saved = await saveProjectWithName(trimmedProjectName);
|
||||
} finally {
|
||||
setIsSavingProjectName(false);
|
||||
}
|
||||
|
||||
if (saved) {
|
||||
setIsEditingProjectName(false);
|
||||
@@ -2922,19 +2935,19 @@ export default function VideoEditor() {
|
||||
if (deletedClip) {
|
||||
const { startMs, endMs } = deletedClip;
|
||||
setZoomRegions((prev) =>
|
||||
prev.filter((region) => region.startMs < startMs || region.endMs > endMs),
|
||||
prev.filter((region) => region.endMs <= startMs || region.startMs >= endMs),
|
||||
);
|
||||
setAnnotationRegions((prev) =>
|
||||
prev.filter((region) => region.startMs < startMs || region.endMs > endMs),
|
||||
prev.filter((region) => region.endMs <= startMs || region.startMs >= endMs),
|
||||
);
|
||||
setTrimRegions((prev) =>
|
||||
prev.filter((region) => region.startMs < startMs || region.endMs > endMs),
|
||||
prev.filter((region) => region.endMs <= startMs || region.startMs >= endMs),
|
||||
);
|
||||
setSpeedRegions((prev) =>
|
||||
prev.filter((region) => region.startMs < startMs || region.endMs > endMs),
|
||||
prev.filter((region) => region.endMs <= startMs || region.startMs >= endMs),
|
||||
);
|
||||
setAudioRegions((prev) =>
|
||||
prev.filter((region) => region.startMs < startMs || region.endMs > endMs),
|
||||
prev.filter((region) => region.endMs <= startMs || region.startMs >= endMs),
|
||||
);
|
||||
}
|
||||
if (selectedClipId === id) {
|
||||
|
||||
Reference in New Issue
Block a user