mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-04 13:07:11 +00:00
fix(studio): delete key removes preview-selected elements
The consolidated keyboard handler only checked selectedElementId (timeline clips). When a user selected a child element in the preview via the inspector, selectedElementId was null because the element didn't correspond to a top-level timeline clip, so Delete/Backspace did nothing. Add handleDomEditElementDelete that removes the element referenced by the current domEditSelection via the remove-element mutation API. The Delete key handler now falls through from timeline selection to DOM edit selection.
This commit is contained in:
@@ -1858,6 +1858,75 @@ export function StudioApp() {
|
||||
[activeCompPath, editHistory.recordEdit, showToast, timelineElements, writeProjectFile],
|
||||
);
|
||||
|
||||
const handleDomEditElementDelete = useCallback(
|
||||
async (selection: DomEditSelection) => {
|
||||
const pid = projectIdRef.current;
|
||||
if (!pid) return;
|
||||
|
||||
const targetPath = selection.sourceFile || activeCompPath || "index.html";
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/projects/${pid}/files/${encodeURIComponent(targetPath)}`,
|
||||
);
|
||||
if (!response.ok) throw new Error(`Failed to read ${targetPath}`);
|
||||
|
||||
const data = (await response.json()) as { content?: string };
|
||||
const originalContent = data.content;
|
||||
if (typeof originalContent !== "string")
|
||||
throw new Error(`Missing file contents for ${targetPath}`);
|
||||
|
||||
const patchTarget: { id?: string; selector?: string; selectorIndex?: number } = selection.id
|
||||
? {
|
||||
id: selection.id,
|
||||
selector: selection.selector,
|
||||
selectorIndex: selection.selectorIndex,
|
||||
}
|
||||
: selection.selector
|
||||
? { selector: selection.selector, selectorIndex: selection.selectorIndex }
|
||||
: ({} as never);
|
||||
if (!patchTarget.id && !patchTarget.selector) {
|
||||
throw new Error("Selected element has no patchable target");
|
||||
}
|
||||
|
||||
const removeResponse = await fetch(
|
||||
`/api/projects/${pid}/file-mutations/remove-element/${encodeURIComponent(targetPath)}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ target: patchTarget }),
|
||||
},
|
||||
);
|
||||
if (!removeResponse.ok) throw new Error(`Failed to delete element from ${targetPath}`);
|
||||
|
||||
const removeData = (await removeResponse.json()) as { changed?: boolean; content?: string };
|
||||
const patchedContent =
|
||||
typeof removeData.content === "string" ? removeData.content : originalContent;
|
||||
|
||||
domEditSaveTimestampRef.current = Date.now();
|
||||
await saveProjectFilesWithHistory({
|
||||
projectId: pid,
|
||||
label: "Delete element",
|
||||
kind: "timeline",
|
||||
files: { [targetPath]: patchedContent },
|
||||
readFile: async () => originalContent,
|
||||
writeFile: writeProjectFile,
|
||||
recordEdit: editHistory.recordEdit,
|
||||
});
|
||||
|
||||
domEditSelectionRef.current = null;
|
||||
domEditGroupSelectionsRef.current = [];
|
||||
setDomEditSelection(null);
|
||||
setDomEditGroupSelections([]);
|
||||
usePlayerStore.getState().setSelectedElementId(null);
|
||||
setRefreshKey((k) => k + 1);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Failed to delete element";
|
||||
showToast(message);
|
||||
}
|
||||
},
|
||||
[activeCompPath, editHistory.recordEdit, showToast, writeProjectFile],
|
||||
);
|
||||
|
||||
// ── Consolidated keyboard shortcuts ────────────────────────────────
|
||||
// All app-level window keydown handlers live here.
|
||||
// Component-scoped shortcuts (playback J/K/L/Space, caption nudge)
|
||||
@@ -1866,6 +1935,8 @@ export function StudioApp() {
|
||||
handleToggleRef.current = handleTimelineToggleHotkey;
|
||||
const handleDeleteRef = useRef(handleTimelineElementDelete);
|
||||
handleDeleteRef.current = handleTimelineElementDelete;
|
||||
const handleDomEditDeleteRef = useRef(handleDomEditElementDelete);
|
||||
handleDomEditDeleteRef.current = handleDomEditElementDelete;
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
useEffect(() => {
|
||||
@@ -1904,7 +1975,7 @@ export function StudioApp() {
|
||||
}
|
||||
}
|
||||
|
||||
// Delete / Backspace — remove selected timeline element
|
||||
// Delete / Backspace — remove selected element (timeline clip or preview selection)
|
||||
if (
|
||||
(event.key === "Delete" || event.key === "Backspace") &&
|
||||
!event.metaKey &&
|
||||
@@ -1913,11 +1984,19 @@ export function StudioApp() {
|
||||
!isEditableTarget(event.target)
|
||||
) {
|
||||
const { selectedElementId, elements } = usePlayerStore.getState();
|
||||
if (!selectedElementId) return;
|
||||
const element = elements.find((el) => (el.key ?? el.id) === selectedElementId);
|
||||
if (!element) return;
|
||||
event.preventDefault();
|
||||
void handleDeleteRef.current(element);
|
||||
if (selectedElementId) {
|
||||
const element = elements.find((el) => (el.key ?? el.id) === selectedElementId);
|
||||
if (element) {
|
||||
event.preventDefault();
|
||||
void handleDeleteRef.current(element);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const domSelection = domEditSelectionRef.current;
|
||||
if (domSelection) {
|
||||
event.preventDefault();
|
||||
void handleDomEditDeleteRef.current(domSelection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user