bazarr/frontend/src/Settings/Languages/modal.tsx

293 lines
7.4 KiB
TypeScript

import { faTrash } from "@fortawesome/free-solid-svg-icons";
import React, {
FunctionComponent,
useCallback,
useEffect,
useMemo,
useState,
} from "react";
import { Button, Form } from "react-bootstrap";
import { Column, TableUpdater } from "react-table";
import { useEnabledLanguagesContext } from ".";
import {
ActionButton,
BaseModal,
BaseModalProps,
Chips,
LanguageSelector,
Selector,
SimpleTable,
useModalInformation,
} from "../../components";
import { BuildKey } from "../../utilities";
import { Input, Message } from "../components";
import { cutoffOptions } from "./options";
interface Props {
update: (profile: Language.Profile) => void;
}
function createDefaultProfile(): Language.Profile {
return {
profileId: -1,
name: "",
items: [],
cutoff: null,
mustContain: [],
mustNotContain: [],
};
}
const LanguagesProfileModal: FunctionComponent<Props & BaseModalProps> = (
props
) => {
const { update, ...modal } = props;
const { payload: profile, closeModal } =
useModalInformation<Language.Profile>(modal.modalKey);
const languages = useEnabledLanguagesContext();
const [current, setProfile] = useState(createDefaultProfile);
useEffect(() => {
if (profile) {
setProfile(profile);
} else {
setProfile(createDefaultProfile);
}
}, [profile]);
const cutoff: SelectorOption<number>[] = useMemo(() => {
const options = [...cutoffOptions];
const newOptions = current.items.map<SelectorOption<number>>((v) => ({
label: `ID ${v.id} (${v.language})`,
value: v.id,
}));
options.push(...newOptions);
return options;
}, [current.items]);
const updateProfile = useCallback(
<K extends keyof Language.Profile>(key: K, value: Language.Profile[K]) => {
const newProfile = { ...current };
newProfile[key] = value;
setProfile(newProfile);
},
[current]
);
const updateRow = useCallback<TableUpdater<Language.ProfileItem>>(
(row, item: Language.ProfileItem) => {
const list = [...current.items];
if (item) {
list[row.index] = item;
} else {
list.splice(row.index, 1);
}
updateProfile("items", list);
},
[current.items, updateProfile]
);
const addItem = useCallback(() => {
const id =
1 +
current.items.reduce<number>((val, item) => Math.max(item.id, val), 0);
if (languages.length > 0) {
const language = languages[0].code2;
const item: Language.ProfileItem = {
id,
language,
audio_exclude: "False",
hi: "False",
forced: "False",
};
const list = [...current.items];
list.push(item);
updateProfile("items", list);
}
}, [current.items, updateProfile, languages]);
const canSave = current.name.length > 0 && current.items.length > 0;
const footer = (
<Button
disabled={!canSave}
onClick={() => {
closeModal();
update(current);
}}
>
Save
</Button>
);
const columns = useMemo<Column<Language.ProfileItem>[]>(
() => [
{
Header: "ID",
accessor: "id",
},
{
Header: "Language",
accessor: "language",
Cell: ({ value, row, update }) => {
const code = value;
const item = row.original;
const lang = useMemo(
() => languages.find((l) => l.code2 === code) ?? null,
[code]
);
return (
<div style={{ width: "8rem" }}>
<LanguageSelector
options={languages}
value={lang}
onChange={(l) => {
if (l) {
item.language = l.code2;
update && update(row, item);
}
}}
></LanguageSelector>
</div>
);
},
},
{
Header: "Forced",
accessor: "forced",
Cell: ({ row, value, update }) => {
const item = row.original;
return (
<Form.Check
custom
id={BuildKey(item.id, item.language, "forced")}
checked={value === "True"}
onChange={(v) => {
item.forced = v.target.checked ? "True" : "False";
update && update(row, item);
}}
></Form.Check>
);
},
},
{
Header: "HI",
accessor: "hi",
Cell: ({ row, value, update }) => {
const item = row.original;
return (
<Form.Check
custom
id={BuildKey(item.id, item.language, "hi")}
checked={value === "True"}
onChange={(v) => {
item.hi = v.target.checked ? "True" : "False";
update && update(row, item);
}}
></Form.Check>
);
},
},
{
Header: "Exclude Audio",
accessor: "audio_exclude",
Cell: ({ row, value, update }) => {
const item = row.original;
return (
<Form.Check
custom
id={BuildKey(item.id, item.language, "audio")}
checked={value === "True"}
onChange={(v) => {
item.audio_exclude = v.target.checked ? "True" : "False";
update && update(row, item);
}}
></Form.Check>
);
},
},
{
id: "action",
accessor: "id",
Cell: ({ row, update }) => {
return (
<ActionButton
icon={faTrash}
onClick={() => update && update(row)}
></ActionButton>
);
},
},
],
[languages]
);
return (
<BaseModal size="lg" title="Languages Profile" footer={footer} {...modal}>
<Input>
<Form.Control
type="text"
placeholder="Name"
value={current.name}
onChange={(v) => {
updateProfile("name", v.target.value);
}}
></Form.Control>
</Input>
<Input>
<SimpleTable
responsive={false}
columns={columns}
data={current.items}
update={updateRow}
></SimpleTable>
<Button block variant="light" onClick={addItem}>
Add
</Button>
</Input>
<Input name="Cutoff">
<Selector
clearable
options={cutoff}
value={current.cutoff}
onChange={(num) => updateProfile("cutoff", num)}
></Selector>
<Message>Ignore others if existing</Message>
</Input>
<Input name="Release info must contain">
<Chips
value={current.mustContain}
onChange={(mc) => updateProfile("mustContain", mc)}
></Chips>
<Message>
Subtitles release info must include one of those words or they will be
excluded from search results (regex supported).
</Message>
</Input>
<Input name="Release info must not contain">
<Chips
value={current.mustNotContain}
onChange={(mnc: string[]) => {
updateProfile("mustNotContain", mnc);
}}
></Chips>
<Message>
Subtitles release info including one of those words (case insensitive)
will be excluded from search results (regex supported).
</Message>
</Input>
</BaseModal>
);
};
export default LanguagesProfileModal;