diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index c74eeb1d0..accb088a3 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -260,6 +260,10 @@ "createSubtitleDropdown-label": "Pick a language", "backButton": "Back", "backButton-tooltip": "Return to subtitle selection", + "deleteButton-title": "Delete", + "deleteButton-tooltip": "Delete subtitle", + "deleteButton-warning-header": "Caution!", + "deleteButton-warning": "You will remove the current subtitle. This cannot be undone. Are you sure?", "downloadButton-title": "Download", "downloadButton-tooltip": "Download subtitle as vtt file", "uploadButton-title": "Upload", diff --git a/src/main/Save.tsx b/src/main/Save.tsx index 7e9323674..75ecc09e9 100644 --- a/src/main/Save.tsx +++ b/src/main/Save.tsx @@ -145,18 +145,13 @@ export const SaveButton: React.FC<{ } }; - const prepareSubtitles = () => { - const subtitlesForPosting = []; - - for (const identifier in subtitles) { - subtitlesForPosting.push({ - id: identifier, - subtitle: serializeSubtitle(subtitles[identifier].cues), - tags: subtitles[identifier].tags, - }); - } - return subtitlesForPosting; - }; + const prepareSubtitles = () => + Object.entries(subtitles).map(([id, { deleted, cues, tags }]) => ({ + id, + subtitle: deleted ? "" : serializeSubtitle(cues), + tags: deleted ? [] : tags, + deleted, + })); const save = () => { dispatch(postVideoInformation({ diff --git a/src/main/SubtitleEditor.tsx b/src/main/SubtitleEditor.tsx index aa3be72d8..ac3be666c 100644 --- a/src/main/SubtitleEditor.tsx +++ b/src/main/SubtitleEditor.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from "react"; import { css } from "@emotion/react"; import { basicButtonStyle } from "../cssStyles"; -import { LuChevronLeft, LuDownload, LuUpload } from "react-icons/lu"; +import { LuChevronLeft, LuDownload, LuTrash2, LuUpload } from "react-icons/lu"; import { selectSubtitlesFromOpencastById, } from "../redux/videoSlice"; @@ -12,6 +12,7 @@ import { selectSelectedSubtitleById, selectSelectedSubtitleId, setSubtitle, + removeSubtitle, } from "../redux/subtitleSlice"; import SubtitleVideoArea from "./SubtitleVideoArea"; import SubtitleTimeline from "./SubtitleTimeline"; @@ -47,7 +48,7 @@ const SubtitleEditor: React.FC = () => { try { dispatch(setSubtitle({ identifier: selectedId, - subtitles: { cues: parseSubtitle(captionTrack.subtitle), tags: captionTrack.tags }, + subtitles: { cues: parseSubtitle(captionTrack.subtitle), tags: captionTrack.tags, deleted: false }, })); } catch (error) { if (error instanceof Error) { @@ -60,7 +61,7 @@ const SubtitleEditor: React.FC = () => { // Or create a new subtitle instead } else if (subtitle?.cues === undefined && captionTrack === undefined && selectedId) { // Create an empty subtitle - dispatch(setSubtitle({ identifier: selectedId, subtitles: { cues: [], tags: [] } })); + dispatch(setSubtitle({ identifier: selectedId, subtitles: { cues: [], tags: [], deleted: false } })); } }, [dispatch, captionTrack, subtitle, selectedId]); @@ -135,6 +136,7 @@ const SubtitleEditor: React.FC = () => { {t("subtitles.editTitle", { title: getTitle() })}
+ css({ background: `${theme.element_bg}`, }); +const DeleteButton: React.FC = () => { + + const { t } = useTranslation(); + const theme = useTheme(); + const dispatch = useAppDispatch(); + + const selectedId = useAppSelector(selectSelectedSubtitleId); + + // Modal Ref + const modalRef = React.useRef(null); + + return ( + <> + +
modalRef.current?.open()} + > + + {t("subtitles.deleteButton-title")} +
+
+ {/* Hidden input field for upload */} + { + modalRef.current?.done(); + dispatch(removeSubtitle({ identifier: selectedId })); + dispatch(setIsDisplayEditView(false)); + }} + ref={modalRef} + text={{ + cancel: t("modal.cancel"), + close: t("modal.close"), + areYouSure: t("modal.areYouSure"), + }} + > + {t("subtitles.deleteButton-warning")} + + + ); +}; const DownloadButton: React.FC = () => { const subtitle = useAppSelector(selectSelectedSubtitleById); diff --git a/src/main/SubtitleSelect.tsx b/src/main/SubtitleSelect.tsx index c415a8670..173919455 100644 --- a/src/main/SubtitleSelect.tsx +++ b/src/main/SubtitleSelect.tsx @@ -32,7 +32,7 @@ const SubtitleSelect: React.FC = () => { const subtitlesFromOpencast = useAppSelector(selectSubtitlesFromOpencast); // track objects received from Opencast const subtitles = useAppSelector(selectSubtitles); // parsed subtitles stored in redux - const [displaySubtitles, setDisplaySubtitles] = useState<{ id: string, tags: string[]; }[]>([]); + const [displaySubtitles, setDisplaySubtitles] = useState<{ id: string, tags: string[], deleted: boolean; }[]>([]); const [canBeAddedSubtitles, setCanBeAddedSubtitles] = useState<{ id: string, tags: string[]; }[]>([]); // Update the collections for the select and add buttons @@ -43,12 +43,12 @@ const SubtitleSelect: React.FC = () => { let existingSubtitles = subtitlesFromOpencast .filter(track => !subtitles[track.id]) .map(track => { - return { id: track.id, tags: track.tags }; + return { id: track.id, tags: track.tags, deleted: false }; }); existingSubtitles = Object.entries(subtitles) .map(track => { - return { id: track[0], tags: track[1].tags }; + return { id: track[0], tags: track[1].tags, deleted: track[1].deleted }; }) .concat(existingSubtitles); @@ -57,7 +57,7 @@ const SubtitleSelect: React.FC = () => { const subtitlesFromOpencastLangs = subtitlesFromOpencast .reduce((result: { id: string, lang: string; }[], track) => { const lang = track.tags.find(e => e.startsWith("lang:")); - if (lang) { + if (lang && !subtitles[track.id]?.deleted) { result.push({ id: track.id, lang: lang.split(":")[1].trim() }); } return result; @@ -66,7 +66,7 @@ const SubtitleSelect: React.FC = () => { const subtitlesLangs = Object.entries(subtitles) .reduce((result: { id: string, lang: string; }[], track) => { const lang = track[1].tags.find(e => e.startsWith("lang:")); - if (lang) { + if (lang && !subtitles[track[0]]?.deleted) { result.push({ id: track[0], lang: lang.split(":")[1].trim() }); } return result; @@ -112,6 +112,9 @@ const SubtitleSelect: React.FC = () => { } for (const subtitle of displaySubtitles) { + if (subtitle.deleted) { + continue; + } let lang = subtitle.tags.find(e => e.startsWith("lang:")); lang = lang ? lang.split(":")[1].trim() : undefined; const icon = lang ? ((settings.subtitles || {}).icons || {})[lang] : undefined; @@ -232,7 +235,7 @@ const SubtitleAddButton: React.FC<{ const id = values.selectedSubtitle; const relatedSubtitle = subtitlesForDropdown.find(tag => tag.id === id); const tags = relatedSubtitle ? relatedSubtitle.tags : []; - dispatch(setSubtitle({ identifier: id, subtitles: { cues: [], tags: tags } })); + dispatch(setSubtitle({ identifier: id, subtitles: { cues: [], tags: tags, delete: false } })); // Reset setIsPlusDisplay(true); diff --git a/src/redux/subtitleSlice.ts b/src/redux/subtitleSlice.ts index 3fff70fc6..770edbe7a 100644 --- a/src/redux/subtitleSlice.ts +++ b/src/redux/subtitleSlice.ts @@ -1,7 +1,6 @@ import { Segment, SubtitleCue, SubtitlesInEditor } from "./../types"; import { createAsyncThunk, createSlice, nanoid, PayloadAction } from "@reduxjs/toolkit"; import { roundToDecimalPlace } from "../util/utilityFunctions"; -import type { RootState } from "../redux/store"; import { video } from "./videoSlice"; export interface subtitle { @@ -78,6 +77,10 @@ export const subtitleSlice = createSlice({ setSubtitle: (state, action: PayloadAction<{ identifier: string, subtitles: SubtitlesInEditor; }>) => { state.subtitles[action.payload.identifier] = action.payload.subtitles; }, + removeSubtitle: (state, action: PayloadAction<{ identifier: string; }>) => { + state.subtitles[action.payload.identifier].deleted = true; + state.hasChanges = true; + }, setCueAtIndex: (state, action: PayloadAction<{ identifier: string, cueIndex: number, newCue: SubtitleCue; }>) => { if (action.payload.cueIndex < 0 || action.payload.cueIndex >= state.subtitles[action.payload.identifier].cues.length) { @@ -223,7 +226,7 @@ const sortSubtitle = (state: subtitle, identifier: string) => { // Export Actions export const { setIsDisplayEditView, setIsPlaying, setIsPlayPreview, setPreviewTriggered, setCurrentlyAt, - setCurrentlyAtInSeconds, setClickTriggered, setSubtitle, setCueAtIndex, addCueAtIndex, removeCue, + setCurrentlyAtInSeconds, setClickTriggered, setSubtitle, removeSubtitle, setCueAtIndex, addCueAtIndex, removeCue, setSelectedSubtitleId, setFocusSegmentTriggered, setFocusSegmentId, setFocusSegmentTriggered2, setFocusToSegmentAboveId, setFocusToSegmentBelowId, setAspectRatio, setHasChanges } = subtitleSlice.actions; diff --git a/src/types.ts b/src/types.ts index 1ee6ff3cd..600ee8f52 100644 --- a/src/types.ts +++ b/src/types.ts @@ -43,6 +43,7 @@ export interface SubtitlesFromOpencast { export interface SubtitlesInEditor { cues: SubtitleCue[], tags: string[], + deleted: boolean, } export interface SubtitleCue {