mirror of
https://github.com/morpheus65535/bazarr
synced 2025-02-21 21:47:15 +00:00
Add support of uploading multiple movie subtitles at the same time
This commit is contained in:
parent
f05daa8223
commit
a5ecd84605
2 changed files with 169 additions and 99 deletions
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue