bazarr/frontend/src/Settings/Providers/components.tsx

275 lines
6.7 KiB
TypeScript

import { capitalize, isArray, isBoolean } from "lodash";
import React, {
FunctionComponent,
useCallback,
useEffect,
useMemo,
useState,
} from "react";
import { Button, Col, Container, Row } from "react-bootstrap";
import { components } from "react-select";
import { SelectComponents } from "react-select/dist/declarations/src/components";
import {
BaseModal,
Selector,
useModalInformation,
useOnModalShow,
useShowModal,
} from "../../components";
import { BuildKey, isReactText } from "../../utilities";
import {
Check,
ColCard,
Message,
StagedChangesContext,
Text,
useLatest,
useMultiUpdate,
} from "../components";
import { ProviderInfo, ProviderList } from "./list";
const ModalKey = "provider-modal";
const ProviderKey = "settings-general-enabled_providers";
export const ProviderView: FunctionComponent = () => {
const providers = useLatest<string[]>(ProviderKey, isArray);
const showModal = useShowModal();
const select = useCallback(
(v?: ProviderInfo) => {
showModal(ModalKey, v ?? null);
},
[showModal]
);
const cards = useMemo(() => {
if (providers) {
return providers
.flatMap((v) => {
const item = ProviderList.find((inn) => inn.key === v);
if (item) {
return item;
} else {
return [];
}
})
.map((v, idx) => (
<ColCard
key={BuildKey(idx, v.name)}
header={v.name ?? capitalize(v.key)}
subheader={v.description}
onClick={() => select(v)}
></ColCard>
));
} else {
return [];
}
}, [providers, select]);
return (
<Container fluid>
<Row>
{cards}
<ColCard key="add-card" plus onClick={select}></ColCard>
</Row>
</Container>
);
};
export const ProviderModal: FunctionComponent = () => {
const { payload, closeModal } = useModalInformation<ProviderInfo>(ModalKey);
const [staged, setChange] = useState<LooseObject>({});
useEffect(() => {
setInfo(payload);
}, [payload]);
const [info, setInfo] = useState<Nullable<ProviderInfo>>(payload);
useOnModalShow<ProviderInfo>((p) => setInfo(p), ModalKey);
const providers = useLatest<string[]>(ProviderKey, isArray);
const updateGlobal = useMultiUpdate();
const deletePayload = useCallback(() => {
if (payload && providers) {
const idx = providers.findIndex((v) => v === payload.key);
if (idx !== -1) {
const newProviders = [...providers];
newProviders.splice(idx, 1);
updateGlobal({ [ProviderKey]: newProviders });
closeModal();
}
}
}, [payload, providers, updateGlobal, closeModal]);
const addProvider = useCallback(() => {
if (info && providers) {
const changes = { ...staged };
// Add this provider if not exist
if (providers.find((v) => v === info.key) === undefined) {
const newProviders = [...providers, info.key];
changes[ProviderKey] = newProviders;
}
updateGlobal(changes);
closeModal();
}
}, [info, providers, staged, closeModal, updateGlobal]);
const canSave = info !== null;
const footer = useMemo(
() => (
<React.Fragment>
<Button hidden={!payload} variant="danger" onClick={deletePayload}>
Delete
</Button>
<Button disabled={!canSave} onClick={addProvider}>
Save
</Button>
</React.Fragment>
),
[canSave, payload, deletePayload, addProvider]
);
const onSelect = useCallback((item: Nullable<ProviderInfo>) => {
if (item) {
setInfo(item);
} else {
setInfo({
key: "",
description: "Unknown Provider",
});
}
}, []);
const options = useMemo<SelectorOption<ProviderInfo>[]>(
() =>
ProviderList.filter(
(v) => providers?.find((p) => p === v.key) === undefined
).map((v) => ({
label: v.name ?? capitalize(v.key),
value: v,
})),
[providers]
);
const modification = useMemo(() => {
if (info === null) {
return null;
}
const defaultKey = info.defaultKey;
const override = info.keyNameOverride ?? {};
if (defaultKey === undefined) {
return null;
}
const itemKey = info.key;
const elements: JSX.Element[] = [];
const checks: JSX.Element[] = [];
for (const key in defaultKey) {
const value = defaultKey[key];
let visibleKey = key;
if (visibleKey in override) {
visibleKey = override[visibleKey];
} else {
visibleKey = capitalize(key);
}
if (isReactText(value)) {
elements.push(
<Col key={key} xs={12} className="mt-2">
<Text
password={key === "password"}
placeholder={visibleKey}
settingKey={`settings-${itemKey}-${key}`}
></Text>
</Col>
);
} else if (isBoolean(value)) {
checks.push(
<Check
key={key}
inline
label={visibleKey}
settingKey={`settings-${itemKey}-${key}`}
></Check>
);
}
}
return (
<Row>
{elements}
<Col hidden={checks.length === 0} className="mt-2">
{checks}
</Col>
</Row>
);
}, [info]);
const selectorComponents = useMemo<
Partial<SelectComponents<ProviderInfo, false, any>>
>(
() => ({
Option: ({ data, ...other }) => {
const { label, value } =
data as unknown as SelectorOption<ProviderInfo>;
return (
<components.Option data={data} {...other}>
{label}
<p className="small m-0 text-muted">{value.description}</p>
</components.Option>
);
},
}),
[]
);
const getLabel = useCallback(
(v: ProviderInfo) => v.name ?? capitalize(v.key),
[]
);
return (
<BaseModal title="Provider" footer={footer} modalKey={ModalKey}>
<StagedChangesContext.Provider value={[staged, setChange]}>
<Container>
<Row>
<Col>
<Selector
components={selectorComponents}
disabled={payload !== null}
options={options}
value={info}
label={getLabel}
onChange={onSelect}
></Selector>
</Col>
</Row>
<Row>
<Col className="mb-2">
<Message>{info?.description}</Message>
</Col>
</Row>
{modification}
<Row hidden={info?.message === undefined}>
<Col>
<Message>{info?.message}</Message>
</Col>
</Row>
</Container>
</StagedChangesContext.Provider>
</BaseModal>
);
};