Skip to content

Commit

Permalink
Merge pull request opencast#1511 from geichelberger/delete-subtitles
Browse files Browse the repository at this point in the history
Add subtitle delete button
  • Loading branch information
Arnei authored Dec 11, 2024
2 parents ef8aec1 + 6ace5c0 commit 6c2e214
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 23 deletions.
4 changes: 4 additions & 0 deletions src/i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
19 changes: 7 additions & 12 deletions src/main/Save.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
51 changes: 48 additions & 3 deletions src/main/SubtitleEditor.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -12,6 +12,7 @@ import {
selectSelectedSubtitleById,
selectSelectedSubtitleId,
setSubtitle,
removeSubtitle,
} from "../redux/subtitleSlice";
import SubtitleVideoArea from "./SubtitleVideoArea";
import SubtitleTimeline from "./SubtitleTimeline";
Expand Down Expand Up @@ -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) {
Expand All @@ -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]);

Expand Down Expand Up @@ -135,6 +136,7 @@ const SubtitleEditor: React.FC = () => {
{t("subtitles.editTitle", { title: getTitle() })}
</div>
<div css={topRightButtons}>
<DeleteButton />
<UploadButton setErrorMessage={setUploadErrorMessage} />
<DownloadButton />
<Modal
Expand Down Expand Up @@ -172,6 +174,49 @@ const subtitleButtonStyle = (theme: Theme) => 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<ConfirmationModalHandle>(null);

return (
<>
<ThemedTooltip title={t("subtitles.deleteButton-tooltip")}>
<div css={[basicButtonStyle(theme), subtitleButtonStyle(theme)]}
role="button"
onClick={() => modalRef.current?.open()}
>
<LuTrash2 css={{ fontSize: "16px" }}/>
<span>{t("subtitles.deleteButton-title")}</span>
</div>
</ThemedTooltip>
{/* Hidden input field for upload */}
<ConfirmationModal
title={t("subtitles.deleteButton-warning-header")}
buttonContent={t("modal.confirm")}
onSubmit={() => {
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")}
</ConfirmationModal>
</>
);
};
const DownloadButton: React.FC = () => {

const subtitle = useAppSelector(selectSelectedSubtitleById);
Expand Down
15 changes: 9 additions & 6 deletions src/main/SubtitleSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);

Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
7 changes: 5 additions & 2 deletions src/redux/subtitleSlice.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;

Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface SubtitlesFromOpencast {
export interface SubtitlesInEditor {
cues: SubtitleCue[],
tags: string[],
deleted: boolean,
}

export interface SubtitleCue {
Expand Down

0 comments on commit 6c2e214

Please sign in to comment.