Add support of uploading multiple movie subtitles at the same time

This commit is contained in:
LASER-Yi 2021-08-22 21:59:48 +08:00
parent f05daa8223
commit a5ecd84605
2 changed files with 169 additions and 99 deletions

View File

@ -1,105 +1,199 @@
import React, { FunctionComponent, useEffect, useMemo, useState } from "react";
import { faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, {
FunctionComponent,
useCallback,
useMemo,
useState,
} from "react";
import { Button, Container, Form } from "react-bootstrap";
import { FileForm, LanguageSelector } from "..";
import { Column, Row } from "react-table";
import { dispatchTask } from "../../@modules/task";
import { createTask } from "../../@modules/task/utilites";
import {
useEnabledLanguages,
useLanguageBy,
useProfileBy,
} from "../../@redux/hooks";
import { useProfileBy, useProfileItemsToLanguages } from "../../@redux/hooks";
import { MoviesApi } from "../../apis";
import { BuildKey } from "../../utilites";
import { FileForm } from "../inputs";
import { LanguageSelector } from "../LanguageSelector";
import { SimpleTable } from "../tables";
import BaseModal, { BaseModalProps } from "./BaseModal";
import { useModalInformation } from "./hooks";
interface PendingSubtitle {
file: File;
language: Language.Info;
forced: boolean;
}
export const TaskGroupName = "Uploading Subtitles...";
const MovieUploadModal: FunctionComponent<BaseModalProps> = (props) => {
const modal = props;
const availableLanguages = useEnabledLanguages();
const { payload, closeModal } = useModalInformation<Item.Movie>(
modal.modalKey
);
const [language, setLanguage] = useState<Nullable<Language.Info>>(null);
const profile = useProfileBy(payload?.profileId);
const defaultLanguage = useLanguageBy(profile?.items[0]?.language);
const availableLanguages = useProfileItemsToLanguages(profile);
useEffect(() => setLanguage(defaultLanguage ?? null), [defaultLanguage]);
const [pending, setPending] = useState<PendingSubtitle[]>([]);
const [file, setFile] = useState<Nullable<File>>(null);
const [forced, setForced] = useState(false);
const filelist = useMemo(() => pending.map((v) => v.file), [pending]);
const canUpload = useMemo(() => {
return file !== null && language?.code2;
}, [language, file]);
const setFiles = useCallback(
(files: File[]) => {
const list: PendingSubtitle[] = files.map((v) => ({
file: v,
forced: availableLanguages[0].forced ?? false,
language: availableLanguages[0],
}));
setPending(list);
},
[availableLanguages]
);
const upload = useCallback(() => {
if (payload === null || pending.length === 0) {
return;
}
const { radarrId } = payload;
const tasks = pending.map((v) => {
const { file, language, forced } = v;
return createTask(
file.name,
radarrId,
MoviesApi.uploadSubtitles.bind(MoviesApi),
radarrId,
{
file: file,
forced,
hi: false,
language: language.code2,
}
);
});
dispatchTask(TaskGroupName, tasks, "Uploading...");
setFiles([]);
closeModal();
}, [payload, closeModal, pending, setFiles]);
const modify = useCallback(
(row: Row<PendingSubtitle>, info?: PendingSubtitle) => {
setPending((pd) => {
const newPending = [...pd];
if (info) {
newPending[row.index] = info;
} else {
newPending.splice(row.index, 1);
}
return newPending;
});
},
[]
);
const columns = useMemo<Column<PendingSubtitle>[]>(
() => [
{
id: "name",
Header: "File",
accessor: (d) => d.file.name,
},
{
Header: "Forced",
accessor: "forced",
Cell: ({ row, value, update }) => {
const { original, index } = row;
return (
<Form.Check
custom
id={BuildKey(index, original.file.name, "forced")}
checked={value}
onChange={(v) => {
const newInfo = { ...row.original };
newInfo.forced = v.target.checked;
update && update(row, newInfo);
}}
></Form.Check>
);
},
},
{
Header: "Language",
accessor: "language",
className: "w-25",
Cell: ({ row, update, value }) => {
return (
<LanguageSelector
options={availableLanguages}
value={value}
onChange={(lang) => {
if (lang && update) {
const newInfo = { ...row.original };
newInfo.language = lang;
update(row, newInfo);
}
}}
></LanguageSelector>
);
},
},
{
accessor: "file",
Cell: ({ row, update }) => {
return (
<Button
size="sm"
variant="light"
onClick={() => {
update && update(row);
}}
>
<FontAwesomeIcon icon={faTrash}></FontAwesomeIcon>
</Button>
);
},
},
],
[availableLanguages]
);
const canUpload = pending.length > 0;
const footer = (
<Button
disabled={!canUpload}
onClick={() => {
if (file && payload && language) {
const id = payload.radarrId;
const task = createTask(
file.name,
id,
MoviesApi.uploadSubtitles.bind(MoviesApi),
id,
{
file: file,
forced,
hi: false,
language: language.code2,
}
);
dispatchTask(TaskGroupName, [task], "Uploading subtitles...");
closeModal(props.modalKey);
}
}}
>
<Button disabled={!canUpload} onClick={upload}>
Upload
</Button>
);
return (
<BaseModal title={`Upload - ${payload?.title}`} footer={footer} {...modal}>
<Container fluid>
<Container fluid className="flex-column">
<Form>
<Form.Group>
<Form.Label>Language</Form.Label>
<LanguageSelector
options={availableLanguages}
value={language}
onChange={(lang) => {
if (lang) {
setLanguage(lang);
}
}}
></LanguageSelector>
</Form.Group>
<Form.Group>
<Form.Label>Subtitle File</Form.Label>
<FileForm
emptyText="Select..."
onChange={(list) => {
setFile(list[0]);
}}
disabled={canUpload || availableLanguages.length === 0}
multiple
value={filelist}
onChange={setFiles}
></FileForm>
</Form.Group>
<Form.Group>
<Form.Check
custom
id="forced-checkbox"
defaultChecked={forced}
onChange={(e) => setForced(e.target.checked)}
label="Forced"
></Form.Check>
</Form.Group>
</Form>
<div hidden={!canUpload}>
<SimpleTable
columns={columns}
data={pending}
responsive={false}
update={modify}
></SimpleTable>
</div>
</Container>
</BaseModal>
);

View File

@ -14,13 +14,7 @@ import React, {
} from "react";
import { Button, Container, Form } from "react-bootstrap";
import { Column, TableUpdater } from "react-table";
import {
AsyncButton,
FileForm,
LanguageSelector,
MessageIcon,
SimpleTable,
} from "..";
import { FileForm, LanguageSelector, MessageIcon, SimpleTable } from "..";
import { dispatchTask } from "../../@modules/task";
import { createTask } from "../../@modules/task/utilites";
import { useProfileBy, useProfileItemsToLanguages } from "../../@redux/hooks";
@ -53,8 +47,6 @@ const SeriesUploadModal: FunctionComponent<SerieProps & BaseModalProps> = ({
modal.modalKey
);
const [uploading, setUpload] = useState(false);
const [pending, setPending] = useState<PendingSubtitle[]>([]);
const profile = useProfileBy(payload?.profileId);
@ -119,7 +111,7 @@ const SeriesUploadModal: FunctionComponent<SerieProps & BaseModalProps> = ({
[checkEpisodes]
);
const uploadSubtitles = useCallback(async () => {
const upload = useCallback(() => {
if (payload === null || language === null) {
return;
}
@ -150,7 +142,9 @@ const SeriesUploadModal: FunctionComponent<SerieProps & BaseModalProps> = ({
});
dispatchTask(TaskGroupName, tasks, "Uploading subtitles...");
}, [payload, pending, language]);
setFiles([]);
closeModal();
}, [payload, pending, language, closeModal, setFiles]);
const canUpload = useMemo(
() =>
@ -234,7 +228,6 @@ const SeriesUploadModal: FunctionComponent<SerieProps & BaseModalProps> = ({
return (
<Selector
disabled={uploading}
options={options}
value={value ?? null}
onChange={change}
@ -249,7 +242,6 @@ const SeriesUploadModal: FunctionComponent<SerieProps & BaseModalProps> = ({
<Button
size="sm"
variant="light"
disabled={uploading}
onClick={() => {
update && update(row);
}}
@ -260,7 +252,7 @@ const SeriesUploadModal: FunctionComponent<SerieProps & BaseModalProps> = ({
},
},
],
[language?.code2, episodes, uploading]
[language?.code2, episodes]
);
const updateItem = useCallback<TableUpdater<PendingSubtitle>>(
@ -282,7 +274,6 @@ const SeriesUploadModal: FunctionComponent<SerieProps & BaseModalProps> = ({
<div className="d-flex flex-row flex-grow-1 justify-content-between">
<div className="w-25">
<LanguageSelector
disabled={uploading}
options={avaliableLanguages}
value={language}
onChange={(l) => {
@ -294,7 +285,6 @@ const SeriesUploadModal: FunctionComponent<SerieProps & BaseModalProps> = ({
</div>
<div>
<Button
hidden={uploading}
disabled={pending.length === 0}
variant="outline-secondary"
className="mr-2"
@ -302,29 +292,15 @@ const SeriesUploadModal: FunctionComponent<SerieProps & BaseModalProps> = ({
>
Clean
</Button>
<AsyncButton
disabled={!canUpload}
onChange={setUpload}
promise={uploadSubtitles}
onSuccess={() => {
closeModal();
setFiles([]);
}}
>
<Button disabled={!canUpload} onClick={upload}>
Upload
</AsyncButton>
</Button>
</div>
</div>
);
return (
<BaseModal
size="lg"
title="Upload Subtitles"
closeable={!uploading}
footer={footer}
{...modal}
>
<BaseModal size="lg" title="Upload Subtitles" footer={footer} {...modal}>
<Container fluid className="flex-column">
<Form>
<Form.Group>