aboutsummaryrefslogtreecommitdiffhomepage
path: root/frontend/src
diff options
context:
space:
mode:
authorLASER-Yi <[email protected]>2022-03-27 14:42:28 +0800
committerLASER-Yi <[email protected]>2022-03-27 14:42:28 +0800
commit658237dd5076a3d4823552ad17c101d3ba6177fc (patch)
tree1449c8378e36f5884cb022dd92132532fa15d3cf /frontend/src
parent87c5d0d9defdc3f01865eeb844dfe191934411fb (diff)
downloadbazarr-658237dd5076a3d4823552ad17c101d3ba6177fc.tar.gz
bazarr-658237dd5076a3d4823552ad17c101d3ba6177fc.zip
Refactor modal system
Diffstat (limited to 'frontend/src')
-rw-r--r--frontend/src/components/index.tsx1
-rw-r--r--frontend/src/components/modals/BaseModal.tsx51
-rw-r--r--frontend/src/components/modals/HistoryModal.tsx32
-rw-r--r--frontend/src/components/modals/ItemEditorModal.tsx44
-rw-r--r--frontend/src/components/modals/ManualSearchModal.tsx69
-rw-r--r--frontend/src/components/modals/MovieUploadModal.tsx18
-rw-r--r--frontend/src/components/modals/SeriesUploadModal.tsx19
-rw-r--r--frontend/src/components/modals/SubtitleToolModal.tsx251
-rw-r--r--frontend/src/components/modals/SubtitleUploadModal.tsx31
-rw-r--r--frontend/src/components/modals/index.ts1
-rw-r--r--frontend/src/modules/modals/ModalContext.ts14
-rw-r--r--frontend/src/modules/modals/ModalWrapper.tsx44
-rw-r--r--frontend/src/modules/modals/WithModal.tsx52
-rw-r--r--frontend/src/modules/modals/components.tsx23
-rw-r--r--frontend/src/modules/modals/hooks.ts90
-rw-r--r--frontend/src/modules/modals/index.ts3
-rw-r--r--frontend/src/modules/redux/hooks/modal.ts36
-rw-r--r--frontend/src/pages/Episodes/index.tsx23
-rw-r--r--frontend/src/pages/Episodes/table.tsx28
-rw-r--r--frontend/src/pages/Movies/Details/index.tsx34
-rw-r--r--frontend/src/pages/Movies/index.tsx9
-rw-r--r--frontend/src/pages/Series/index.tsx9
-rw-r--r--frontend/src/pages/Settings/Languages/modal.tsx61
-rw-r--r--frontend/src/pages/Settings/Languages/table.tsx8
-rw-r--r--frontend/src/pages/Settings/Notifications/components.tsx145
-rw-r--r--frontend/src/pages/Settings/Providers/components.tsx52
-rw-r--r--frontend/src/pages/Settings/Providers/index.tsx3
-rw-r--r--frontend/src/pages/System/Backups/BackupDeleteModal.tsx30
-rw-r--r--frontend/src/pages/System/Backups/BackupRestoreModal.tsx36
-rw-r--r--frontend/src/pages/System/Backups/table.tsx20
-rw-r--r--frontend/src/pages/System/Logs/modal.tsx17
-rw-r--r--frontend/src/pages/System/Logs/table.tsx6
32 files changed, 674 insertions, 586 deletions
diff --git a/frontend/src/components/index.tsx b/frontend/src/components/index.tsx
index acb6b1fa1..054498626 100644
--- a/frontend/src/components/index.tsx
+++ b/frontend/src/components/index.tsx
@@ -131,6 +131,5 @@ export * from "./buttons";
export * from "./header";
export * from "./inputs";
export * from "./LanguageSelector";
-export * from "./modals";
export * from "./SearchBar";
export * from "./tables";
diff --git a/frontend/src/components/modals/BaseModal.tsx b/frontend/src/components/modals/BaseModal.tsx
deleted file mode 100644
index f9488ef6b..000000000
--- a/frontend/src/components/modals/BaseModal.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { useIsShowed, useModalControl } from "@/modules/redux/hooks/modal";
-import clsx from "clsx";
-import { FunctionComponent, useCallback, useState } from "react";
-import { Modal } from "react-bootstrap";
-
-export interface BaseModalProps {
- modalKey: string;
- size?: "sm" | "lg" | "xl";
- closeable?: boolean;
- title?: string;
- footer?: JSX.Element;
-}
-
-export const BaseModal: FunctionComponent<BaseModalProps> = (props) => {
- const { size, modalKey, title, children, footer, closeable = true } = props;
- const [needExit, setExit] = useState(false);
-
- const { hide: hideModal } = useModalControl();
- const showIndex = useIsShowed(modalKey);
- const isShowed = showIndex !== -1;
-
- const hide = useCallback(() => {
- setExit(true);
- }, []);
-
- const exit = useCallback(() => {
- if (isShowed) {
- hideModal(modalKey);
- }
- setExit(false);
- }, [isShowed, hideModal, modalKey]);
-
- return (
- <Modal
- centered
- size={size}
- show={isShowed && !needExit}
- onHide={hide}
- onExited={exit}
- backdrop={closeable ? undefined : "static"}
- className={clsx(`index-${showIndex}`)}
- backdropClassName={clsx(`index-${showIndex}`)}
- >
- <Modal.Header closeButton={closeable}>{title}</Modal.Header>
- <Modal.Body>{children}</Modal.Body>
- <Modal.Footer hidden={footer === undefined}>{footer}</Modal.Footer>
- </Modal>
- );
-};
-
-export default BaseModal;
diff --git a/frontend/src/components/modals/HistoryModal.tsx b/frontend/src/components/modals/HistoryModal.tsx
index dd4adf2bd..58e47cd90 100644
--- a/frontend/src/components/modals/HistoryModal.tsx
+++ b/frontend/src/components/modals/HistoryModal.tsx
@@ -4,18 +4,17 @@ import {
useMovieAddBlacklist,
useMovieHistory,
} from "@/apis/hooks";
-import { usePayload } from "@/modules/redux/hooks/modal";
+import { useModal, usePayload, withModal } from "@/modules/modals";
import { FunctionComponent, useMemo } from "react";
import { Column } from "react-table";
import { HistoryIcon, PageTable, QueryOverlay, TextPopover } from "..";
import Language from "../bazarr/Language";
import { BlacklistButton } from "../inputs/blacklist";
-import BaseModal, { BaseModalProps } from "./BaseModal";
-export const MovieHistoryModal: FunctionComponent<BaseModalProps> = (props) => {
- const { ...modal } = props;
+const MovieHistoryView: FunctionComponent = () => {
+ const movie = usePayload<Item.Movie>();
- const movie = usePayload<Item.Movie>(modal.modalKey);
+ const Modal = useModal({ size: "lg" });
const history = useMovieHistory(movie?.radarrId);
@@ -84,7 +83,7 @@ export const MovieHistoryModal: FunctionComponent<BaseModalProps> = (props) => {
);
return (
- <BaseModal title={`History - ${movie?.title ?? ""}`} {...modal}>
+ <Modal title={`History - ${movie?.title ?? ""}`}>
<QueryOverlay result={history}>
<PageTable
emptyText="No History Found"
@@ -92,14 +91,16 @@ export const MovieHistoryModal: FunctionComponent<BaseModalProps> = (props) => {
data={data ?? []}
></PageTable>
</QueryOverlay>
- </BaseModal>
+ </Modal>
);
};
-export const EpisodeHistoryModal: FunctionComponent<BaseModalProps> = (
- props
-) => {
- const episode = usePayload<Item.Episode>(props.modalKey);
+export const MovieHistoryModal = withModal(MovieHistoryView, "movie-history");
+
+const EpisodeHistoryView: FunctionComponent = () => {
+ const episode = usePayload<Item.Episode>();
+
+ const Modal = useModal({ size: "lg" });
const history = useEpisodeHistory(episode?.sonarrEpisodeId);
@@ -175,7 +176,7 @@ export const EpisodeHistoryModal: FunctionComponent<BaseModalProps> = (
);
return (
- <BaseModal title={`History - ${episode?.title ?? ""}`} {...props}>
+ <Modal title={`History - ${episode?.title ?? ""}`}>
<QueryOverlay result={history}>
<PageTable
emptyText="No History Found"
@@ -183,6 +184,11 @@ export const EpisodeHistoryModal: FunctionComponent<BaseModalProps> = (
data={data ?? []}
></PageTable>
</QueryOverlay>
- </BaseModal>
+ </Modal>
);
};
+
+export const EpisodeHistoryModal = withModal(
+ EpisodeHistoryView,
+ "episode-history"
+);
diff --git a/frontend/src/components/modals/ItemEditorModal.tsx b/frontend/src/components/modals/ItemEditorModal.tsx
index ad714598d..ffe44feb5 100644
--- a/frontend/src/components/modals/ItemEditorModal.tsx
+++ b/frontend/src/components/modals/ItemEditorModal.tsx
@@ -1,26 +1,28 @@
import { useIsAnyActionRunning, useLanguageProfiles } from "@/apis/hooks";
-import { useModalControl, usePayload } from "@/modules/redux/hooks/modal";
+import {
+ useModal,
+ useModalControl,
+ usePayload,
+ withModal,
+} from "@/modules/modals";
import { GetItemId } from "@/utilities";
-import { FunctionComponent, useEffect, useMemo, useState } from "react";
+import { FunctionComponent, useMemo, useState } from "react";
import { Container, Form } from "react-bootstrap";
import { UseMutationResult } from "react-query";
import { AsyncButton, Selector, SelectorOption } from "..";
-import BaseModal, { BaseModalProps } from "./BaseModal";
interface Props {
mutation: UseMutationResult<void, unknown, FormType.ModifyItem, unknown>;
}
-const Editor: FunctionComponent<Props & BaseModalProps> = (props) => {
- const { mutation, ...modal } = props;
-
+const Editor: FunctionComponent<Props> = ({ mutation }) => {
const { data: profiles } = useLanguageProfiles();
- const payload = usePayload<Item.Base>(modal.modalKey);
- const { hide } = useModalControl();
-
+ const payload = usePayload<Item.Base>();
const { mutateAsync, isLoading } = mutation;
+ const { hide } = useModalControl();
+
const hasTask = useIsAnyActionRunning();
const profileOptions = useMemo<SelectorOption<number>[]>(
@@ -33,9 +35,12 @@ const Editor: FunctionComponent<Props & BaseModalProps> = (props) => {
const [id, setId] = useState<Nullable<number>>(payload?.profileId ?? null);
- useEffect(() => {
- setId(payload?.profileId ?? null);
- }, [payload]);
+ const Modal = useModal({
+ closeable: !isLoading,
+ onMounted: () => {
+ setId(payload?.profileId ?? null);
+ },
+ });
const footer = (
<AsyncButton
@@ -56,21 +61,14 @@ const Editor: FunctionComponent<Props & BaseModalProps> = (props) => {
return null;
}
}}
- onSuccess={() => {
- hide();
- }}
+ onSuccess={() => hide()}
>
Save
</AsyncButton>
);
return (
- <BaseModal
- closeable={!isLoading}
- footer={footer}
- title={payload?.title}
- {...modal}
- >
+ <Modal title={payload?.title ?? "Item Editor"} footer={footer}>
<Container fluid>
<Form>
<Form.Group>
@@ -95,8 +93,8 @@ const Editor: FunctionComponent<Props & BaseModalProps> = (props) => {
</Form.Group>
</Form>
</Container>
- </BaseModal>
+ </Modal>
);
};
-export default Editor;
+export default withModal(Editor, "edit");
diff --git a/frontend/src/components/modals/ManualSearchModal.tsx b/frontend/src/components/modals/ManualSearchModal.tsx
index 5f3e1a6f0..17e5a786b 100644
--- a/frontend/src/components/modals/ManualSearchModal.tsx
+++ b/frontend/src/components/modals/ManualSearchModal.tsx
@@ -1,4 +1,4 @@
-import { usePayload } from "@/modules/redux/hooks/modal";
+import { useModal, usePayload, withModal } from "@/modules/modals";
import { createAndDispatchTask } from "@/modules/task/utilities";
import { GetItemId, isMovie } from "@/utilities";
import {
@@ -10,13 +10,7 @@ import {
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import clsx from "clsx";
-import {
- FunctionComponent,
- useCallback,
- useEffect,
- useMemo,
- useState,
-} from "react";
+import { FunctionComponent, useCallback, useMemo, useState } from "react";
import {
Badge,
Button,
@@ -29,7 +23,7 @@ import {
} from "react-bootstrap";
import { UseQueryResult } from "react-query";
import { Column } from "react-table";
-import { BaseModal, BaseModalProps, LoadingIndicator, PageTable } from "..";
+import { LoadingIndicator, PageTable } from "..";
import Language from "../bazarr/Language";
type SupportType = Item.Movie | Item.Episode;
@@ -41,24 +35,15 @@ interface Props<T extends SupportType> {
) => UseQueryResult<SearchResultType[] | undefined, unknown>;
}
-export function ManualSearchModal<T extends SupportType>(
- props: Props<T> & BaseModalProps
-) {
- const { download, query: useSearch, ...modal } = props;
+function ManualSearchView<T extends SupportType>(props: Props<T>) {
+ const { download, query: useSearch } = props;
- const item = usePayload<T>(modal.modalKey);
+ const item = usePayload<T>();
const itemId = useMemo(() => GetItemId(item ?? {}), [item]);
const [id, setId] = useState<number | undefined>(undefined);
- // Cleanup the ID when user switches episode / movie
- useEffect(() => {
- if (itemId !== undefined && itemId !== id) {
- setId(undefined);
- }
- }, [id, itemId]);
-
const results = useSearch(id);
const isStale = results.data === undefined;
@@ -225,12 +210,6 @@ export function ManualSearchModal<T extends SupportType>(
}
};
- const footer = (
- <Button variant="light" hidden={isStale} onClick={search}>
- Search Again
- </Button>
- );
-
const title = useMemo(() => {
let title = "Unknown";
@@ -246,19 +225,39 @@ export function ManualSearchModal<T extends SupportType>(
return `Search - ${title}`;
}, [item]);
+ const Modal = useModal({
+ size: "xl",
+ closeable: results.isFetching === false,
+ onMounted: () => {
+ // Cleanup the ID when user switches episode / movie
+ if (itemId !== id) {
+ setId(undefined);
+ }
+ },
+ });
+
+ const footer = (
+ <Button variant="light" hidden={isStale} onClick={search}>
+ Search Again
+ </Button>
+ );
+
return (
- <BaseModal
- closeable={results.isFetching === false}
- size="xl"
- title={title}
- footer={footer}
- {...modal}
- >
+ <Modal title={title} footer={footer}>
{content()}
- </BaseModal>
+ </Modal>
);
}
+export const MovieSearchModal = withModal<Props<Item.Movie>>(
+ ManualSearchView,
+ "movie-manual-search"
+);
+export const EpisodeSearchModal = withModal<Props<Item.Episode>>(
+ ManualSearchView,
+ "episode-manual-search"
+);
+
const StateIcon: FunctionComponent<{ matches: string[]; dont: string[] }> = ({
matches,
dont,
diff --git a/frontend/src/components/modals/MovieUploadModal.tsx b/frontend/src/components/modals/MovieUploadModal.tsx
index 3b3730668..464c055dc 100644
--- a/frontend/src/components/modals/MovieUploadModal.tsx
+++ b/frontend/src/components/modals/MovieUploadModal.tsx
@@ -1,21 +1,18 @@
import { useMovieSubtitleModification } from "@/apis/hooks";
-import { usePayload } from "@/modules/redux/hooks/modal";
+import { usePayload, withModal } from "@/modules/modals";
import { createTask, dispatchTask } from "@/modules/task/utilities";
import {
useLanguageProfileBy,
useProfileItemsToLanguages,
} from "@/utilities/languages";
import { FunctionComponent, useCallback } from "react";
-import { BaseModalProps } from "./BaseModal";
-import SubtitleUploadModal, {
+import SubtitleUploader, {
PendingSubtitle,
Validator,
} from "./SubtitleUploadModal";
-const MovieUploadModal: FunctionComponent<BaseModalProps> = (props) => {
- const modal = props;
-
- const payload = usePayload<Item.Movie>(modal.modalKey);
+const MovieUploadModal: FunctionComponent = () => {
+ const payload = usePayload<Item.Movie>();
const profile = useLanguageProfileBy(payload?.profileId);
@@ -87,7 +84,7 @@ const MovieUploadModal: FunctionComponent<BaseModalProps> = (props) => {
);
return (
- <SubtitleUploadModal
+ <SubtitleUploader
hideAllLanguages
initial={{ forced: false }}
availableLanguages={availableLanguages}
@@ -95,9 +92,8 @@ const MovieUploadModal: FunctionComponent<BaseModalProps> = (props) => {
upload={upload}
update={update}
validate={validate}
- {...modal}
- ></SubtitleUploadModal>
+ ></SubtitleUploader>
);
};
-export default MovieUploadModal;
+export default withModal(MovieUploadModal, "movie-upload");
diff --git a/frontend/src/components/modals/SeriesUploadModal.tsx b/frontend/src/components/modals/SeriesUploadModal.tsx
index 23c3101f3..89a033e51 100644
--- a/frontend/src/components/modals/SeriesUploadModal.tsx
+++ b/frontend/src/components/modals/SeriesUploadModal.tsx
@@ -1,6 +1,6 @@
import { useEpisodeSubtitleModification } from "@/apis/hooks";
import api from "@/apis/raw";
-import { usePayload } from "@/modules/redux/hooks/modal";
+import { usePayload, withModal } from "@/modules/modals";
import { createTask, dispatchTask } from "@/modules/task/utilities";
import {
useLanguageProfileBy,
@@ -9,8 +9,7 @@ import {
import { FunctionComponent, useCallback, useMemo } from "react";
import { Column } from "react-table";
import { Selector, SelectorOption } from "../inputs";
-import { BaseModalProps } from "./BaseModal";
-import SubtitleUploadModal, {
+import SubtitleUploader, {
PendingSubtitle,
useRowMutation,
Validator,
@@ -24,11 +23,8 @@ interface SeriesProps {
episodes: readonly Item.Episode[];
}
-const SeriesUploadModal: FunctionComponent<SeriesProps & BaseModalProps> = ({
- episodes,
- ...modal
-}) => {
- const payload = usePayload<Item.Series>(modal.modalKey);
+const SeriesUploadModal: FunctionComponent<SeriesProps> = ({ episodes }) => {
+ const payload = usePayload<Item.Series>();
const profile = useLanguageProfileBy(payload?.profileId);
@@ -165,16 +161,15 @@ const SeriesUploadModal: FunctionComponent<SeriesProps & BaseModalProps> = ({
);
return (
- <SubtitleUploadModal
+ <SubtitleUploader
columns={columns}
initial={{ instance: null }}
availableLanguages={availableLanguages}
upload={upload}
update={update}
validate={validate}
- {...modal}
- ></SubtitleUploadModal>
+ ></SubtitleUploader>
);
};
-export default SeriesUploadModal;
+export default withModal(SeriesUploadModal, "series-upload");
diff --git a/frontend/src/components/modals/SubtitleToolModal.tsx b/frontend/src/components/modals/SubtitleToolModal.tsx
index b15444879..823aca5a7 100644
--- a/frontend/src/components/modals/SubtitleToolModal.tsx
+++ b/frontend/src/components/modals/SubtitleToolModal.tsx
@@ -1,5 +1,10 @@
import { useSubtitleAction } from "@/apis/hooks";
-import { useModalControl, usePayload } from "@/modules/redux/hooks/modal";
+import {
+ useModal,
+ useModalControl,
+ usePayload,
+ withModal,
+} from "@/modules/modals";
import { createTask, dispatchTask } from "@/modules/task/utilities";
import { isMovie, submodProcessColor } from "@/utilities";
import { LOG } from "@/utilities/console";
@@ -45,7 +50,6 @@ import {
} from "..";
import Language from "../bazarr/Language";
import { useCustomSelection } from "../tables/plugins";
-import BaseModal, { BaseModalProps } from "./BaseModal";
import { availableTranslation, colorOptions } from "./toolOptions";
type SupportType = Item.Episode | Item.Movie;
@@ -77,12 +81,11 @@ interface ToolModalProps {
) => void;
}
-const AddColorModal: FunctionComponent<BaseModalProps & ToolModalProps> = (
- props
-) => {
- const { process, ...modal } = props;
+const ColorTool: FunctionComponent<ToolModalProps> = ({ process }) => {
const [selection, setSelection] = useState<Nullable<string>>(null);
+ const Modal = useModal();
+
const submit = useCallback(() => {
if (selection) {
const action = submodProcessColor(selection);
@@ -90,31 +93,29 @@ const AddColorModal: FunctionComponent<BaseModalProps & ToolModalProps> = (
}
}, [selection, process]);
- const footer = useMemo(
- () => (
- <Button disabled={selection === null} onClick={submit}>
- Save
- </Button>
- ),
- [selection, submit]
+ const footer = (
+ <Button disabled={selection === null} onClick={submit}>
+ Save
+ </Button>
);
+
return (
- <BaseModal title="Choose Color" footer={footer} {...modal}>
+ <Modal title="Choose Color" footer={footer}>
<Selector options={colorOptions} onChange={setSelection}></Selector>
- </BaseModal>
+ </Modal>
);
};
-const FrameRateModal: FunctionComponent<BaseModalProps & ToolModalProps> = (
- props
-) => {
- const { process, ...modal } = props;
+const ColorToolModal = withModal(ColorTool, "color-tool");
+const FrameRateTool: FunctionComponent<ToolModalProps> = ({ process }) => {
const [from, setFrom] = useState<Nullable<number>>(null);
const [to, setTo] = useState<Nullable<number>>(null);
const canSave = from !== null && to !== null && from !== to;
+ const Modal = useModal();
+
const submit = useCallback(() => {
if (canSave) {
const action = submodProcessFrameRate(from, to);
@@ -129,7 +130,7 @@ const FrameRateModal: FunctionComponent<BaseModalProps & ToolModalProps> = (
);
return (
- <BaseModal title="Change Frame Rate" footer={footer} {...modal}>
+ <Modal title="Change Frame Rate" footer={footer}>
<InputGroup className="px-2">
<Form.Control
placeholder="From"
@@ -156,20 +157,20 @@ const FrameRateModal: FunctionComponent<BaseModalProps & ToolModalProps> = (
}}
></Form.Control>
</InputGroup>
- </BaseModal>
+ </Modal>
);
};
-const AdjustTimesModal: FunctionComponent<BaseModalProps & ToolModalProps> = (
- props
-) => {
- const { process, ...modal } = props;
+const FrameRateModal = withModal(FrameRateTool, "frame-rate-tool");
+const TimeAdjustmentTool: FunctionComponent<ToolModalProps> = ({ process }) => {
const [isPlus, setPlus] = useState(true);
const [offset, setOffset] = useState<[number, number, number, number]>([
0, 0, 0, 0,
]);
+ const Modal = useModal();
+
const updateOffset = useCallback(
(idx: number): ChangeEventHandler<HTMLInputElement> => {
return (e) => {
@@ -200,17 +201,14 @@ const AdjustTimesModal: FunctionComponent<BaseModalProps & ToolModalProps> = (
}
}, [process, canSave, offset, isPlus]);
- const footer = useMemo(
- () => (
- <Button disabled={!canSave} onClick={submit}>
- Save
- </Button>
- ),
- [submit, canSave]
+ const footer = (
+ <Button disabled={!canSave} onClick={submit}>
+ Save
+ </Button>
);
return (
- <BaseModal title="Adjust Times" footer={footer} {...modal}>
+ <Modal title="Adjust Times" footer={footer}>
<InputGroup>
<InputGroup.Prepend>
<Button
@@ -242,14 +240,13 @@ const AdjustTimesModal: FunctionComponent<BaseModalProps & ToolModalProps> = (
onChange={updateOffset(3)}
></Form.Control>
</InputGroup>
- </BaseModal>
+ </Modal>
);
};
-const TranslateModal: FunctionComponent<BaseModalProps & ToolModalProps> = ({
- process,
- ...modal
-}) => {
+const TimeAdjustmentModal = withModal(TimeAdjustmentTool, "time-adjust-tool");
+
+const TranslationTool: FunctionComponent<ToolModalProps> = ({ process }) => {
const { data: languages } = useEnabledLanguages();
const available = useMemo(
@@ -257,6 +254,8 @@ const TranslateModal: FunctionComponent<BaseModalProps & ToolModalProps> = ({
[languages]
);
+ const Modal = useModal();
+
const [selectedLanguage, setLanguage] =
useState<Nullable<Language.Info>>(null);
@@ -266,17 +265,13 @@ const TranslateModal: FunctionComponent<BaseModalProps & ToolModalProps> = ({
}
}, [selectedLanguage, process]);
- const footer = useMemo(
- () => (
- <Button disabled={!selectedLanguage} onClick={submit}>
- Translate
- </Button>
- ),
- [submit, selectedLanguage]
+ const footer = (
+ <Button disabled={!selectedLanguage} onClick={submit}>
+ Translate
+ </Button>
);
-
return (
- <BaseModal title="Translate to" footer={footer} {...modal}>
+ <Modal title="Translation" footer={footer}>
<Form.Label>
Enabled languages not listed here are unsupported by Google Translate.
</Form.Label>
@@ -284,18 +279,21 @@ const TranslateModal: FunctionComponent<BaseModalProps & ToolModalProps> = ({
options={available}
onChange={setLanguage}
></LanguageSelector>
- </BaseModal>
+ </Modal>
);
};
+const TranslationModal = withModal(TranslationTool, "translate-tool");
+
const CanSelectSubtitle = (item: TableColumnType) => {
return item.path.endsWith(".srt");
};
-const STM: FunctionComponent<BaseModalProps> = ({ ...props }) => {
- const payload = usePayload<SupportType[]>(props.modalKey);
+const STM: FunctionComponent = () => {
+ const payload = usePayload<SupportType[]>();
const [selections, setSelections] = useState<TableColumnType[]>([]);
+ const Modal = useModal({ size: "xl" });
const { hide } = useModalControl();
const { mutateAsync } = useSubtitleAction();
@@ -303,8 +301,7 @@ const STM: FunctionComponent<BaseModalProps> = ({ ...props }) => {
const process = useCallback(
(action: string, override?: Partial<FormType.ModifySubtitle>) => {
LOG("info", "executing action", action);
- hide(props.modalKey);
-
+ hide();
const tasks = selections.map((s) => {
const form: FormType.ModifySubtitle = {
id: s.id,
@@ -318,7 +315,7 @@ const STM: FunctionComponent<BaseModalProps> = ({ ...props }) => {
dispatchTask(tasks, "modify-subtitles");
},
- [hide, props.modalKey, selections, mutateAsync]
+ [hide, selections, mutateAsync]
);
const { show } = useModalControl();
@@ -383,92 +380,74 @@ const STM: FunctionComponent<BaseModalProps> = ({ ...props }) => {
const plugins = [useRowSelect, useCustomSelection];
- const footer = useMemo(
- () => (
- <Dropdown as={ButtonGroup} onSelect={(k) => k && process(k)}>
- <ActionButton
- size="sm"
- disabled={selections.length === 0}
- icon={faPlay}
- onClick={() => process("sync")}
- >
- Sync
- </ActionButton>
- <Dropdown.Toggle
- disabled={selections.length === 0}
- split
- variant="light"
- size="sm"
- className="px-2"
- ></Dropdown.Toggle>
- <Dropdown.Menu>
- <Dropdown.Item eventKey="remove_HI">
- <ActionButtonItem icon={faDeaf}>Remove HI Tags</ActionButtonItem>
- </Dropdown.Item>
- <Dropdown.Item eventKey="remove_tags">
- <ActionButtonItem icon={faCode}>Remove Style Tags</ActionButtonItem>
- </Dropdown.Item>
- <Dropdown.Item eventKey="OCR_fixes">
- <ActionButtonItem icon={faImage}>OCR Fixes</ActionButtonItem>
- </Dropdown.Item>
- <Dropdown.Item eventKey="common">
- <ActionButtonItem icon={faMagic}>Common Fixes</ActionButtonItem>
- </Dropdown.Item>
- <Dropdown.Item eventKey="fix_uppercase">
- <ActionButtonItem icon={faTextHeight}>
- Fix Uppercase
- </ActionButtonItem>
- </Dropdown.Item>
- <Dropdown.Item eventKey="reverse_rtl">
- <ActionButtonItem icon={faExchangeAlt}>
- Reverse RTL
- </ActionButtonItem>
- </Dropdown.Item>
- <Dropdown.Item onSelect={() => show("add-color")}>
- <ActionButtonItem icon={faPaintBrush}>Add Color</ActionButtonItem>
- </Dropdown.Item>
- <Dropdown.Item onSelect={() => show("change-frame-rate")}>
- <ActionButtonItem icon={faFilm}>Change Frame Rate</ActionButtonItem>
- </Dropdown.Item>
- <Dropdown.Item onSelect={() => show("adjust-times")}>
- <ActionButtonItem icon={faClock}>Adjust Times</ActionButtonItem>
- </Dropdown.Item>
- <Dropdown.Item onSelect={() => show("translate-sub")}>
- <ActionButtonItem icon={faLanguage}>Translate</ActionButtonItem>
- </Dropdown.Item>
- </Dropdown.Menu>
- </Dropdown>
- ),
- [selections.length, process, show]
+ const footer = (
+ <Dropdown as={ButtonGroup} onSelect={(k) => k && process(k)}>
+ <ActionButton
+ size="sm"
+ disabled={selections.length === 0}
+ icon={faPlay}
+ onClick={() => process("sync")}
+ >
+ Sync
+ </ActionButton>
+ <Dropdown.Toggle
+ disabled={selections.length === 0}
+ split
+ variant="light"
+ size="sm"
+ className="px-2"
+ ></Dropdown.Toggle>
+ <Dropdown.Menu>
+ <Dropdown.Item eventKey="remove_HI">
+ <ActionButtonItem icon={faDeaf}>Remove HI Tags</ActionButtonItem>
+ </Dropdown.Item>
+ <Dropdown.Item eventKey="remove_tags">
+ <ActionButtonItem icon={faCode}>Remove Style Tags</ActionButtonItem>
+ </Dropdown.Item>
+ <Dropdown.Item eventKey="OCR_fixes">
+ <ActionButtonItem icon={faImage}>OCR Fixes</ActionButtonItem>
+ </Dropdown.Item>
+ <Dropdown.Item eventKey="common">
+ <ActionButtonItem icon={faMagic}>Common Fixes</ActionButtonItem>
+ </Dropdown.Item>
+ <Dropdown.Item eventKey="fix_uppercase">
+ <ActionButtonItem icon={faTextHeight}>Fix Uppercase</ActionButtonItem>
+ </Dropdown.Item>
+ <Dropdown.Item eventKey="reverse_rtl">
+ <ActionButtonItem icon={faExchangeAlt}>Reverse RTL</ActionButtonItem>
+ </Dropdown.Item>
+ <Dropdown.Item onSelect={() => show(ColorToolModal)}>
+ <ActionButtonItem icon={faPaintBrush}>Add Color</ActionButtonItem>
+ </Dropdown.Item>
+ <Dropdown.Item onSelect={() => show(FrameRateModal)}>
+ <ActionButtonItem icon={faFilm}>Change Frame Rate</ActionButtonItem>
+ </Dropdown.Item>
+ <Dropdown.Item onSelect={() => show(TimeAdjustmentModal)}>
+ <ActionButtonItem icon={faClock}>Adjust Times</ActionButtonItem>
+ </Dropdown.Item>
+ <Dropdown.Item onSelect={() => show(TranslationModal)}>
+ <ActionButtonItem icon={faLanguage}>Translate</ActionButtonItem>
+ </Dropdown.Item>
+ </Dropdown.Menu>
+ </Dropdown>
);
return (
- <>
- <BaseModal title={"Subtitle Tools"} footer={footer} {...props}>
- <SimpleTable
- emptyText="No External Subtitles Found"
- plugins={plugins}
- columns={columns}
- onSelect={setSelections}
- canSelect={CanSelectSubtitle}
- data={data}
- ></SimpleTable>
- </BaseModal>
- <AddColorModal process={process} modalKey="add-color"></AddColorModal>
- <FrameRateModal
- process={process}
- modalKey="change-frame-rate"
- ></FrameRateModal>
- <AdjustTimesModal
- process={process}
- modalKey="adjust-times"
- ></AdjustTimesModal>
- <TranslateModal
- process={process}
- modalKey="translate-sub"
- ></TranslateModal>
- </>
+ <Modal title="Subtitle Tools" footer={footer}>
+ <SimpleTable
+ emptyText="No External Subtitles Found"
+ plugins={plugins}
+ columns={columns}
+ onSelect={setSelections}
+ canSelect={CanSelectSubtitle}
+ data={data}
+ ></SimpleTable>
+ <ColorToolModal process={process}></ColorToolModal>
+ <FrameRateModal process={process}></FrameRateModal>
+ <TimeAdjustmentModal process={process}></TimeAdjustmentModal>
+ <TranslationModal process={process}></TranslationModal>
+ </Modal>
);
};
-export default STM;
+export default withModal(STM, "subtitle-tools");
diff --git a/frontend/src/components/modals/SubtitleUploadModal.tsx b/frontend/src/components/modals/SubtitleUploadModal.tsx
index 76693abbc..c14f0b23e 100644
--- a/frontend/src/components/modals/SubtitleUploadModal.tsx
+++ b/frontend/src/components/modals/SubtitleUploadModal.tsx
@@ -1,4 +1,4 @@
-import { useModalControl } from "@/modules/redux/hooks/modal";
+import { useModal, useModalControl } from "@/modules/modals";
import { BuildKey } from "@/utilities";
import { LOG } from "@/utilities/console";
import {
@@ -23,7 +23,6 @@ import { Column } from "react-table";
import { LanguageSelector, MessageIcon } from "..";
import { FileForm } from "../inputs";
import { SimpleTable } from "../tables";
-import BaseModal, { BaseModalProps } from "./BaseModal";
type ModifyFn<T> = (index: number, info?: PendingSubtitle<T>) => void;
@@ -59,10 +58,7 @@ interface Props<T = unknown> {
hideAllLanguages?: boolean;
}
-type ComponentProps<T> = Props<T> &
- Omit<BaseModalProps, "footer" | "title" | "size">;
-
-function SubtitleUploadModal<T>(props: ComponentProps<T>) {
+function SubtitleUploader<T>(props: Props<T>) {
const {
initial,
columns,
@@ -73,10 +69,16 @@ function SubtitleUploadModal<T>(props: ComponentProps<T>) {
hideAllLanguages,
} = props;
- const { hide } = useModalControl();
-
const [pending, setPending] = useState<PendingSubtitle<T>[]>([]);
+ const showTable = pending.length > 0;
+
+ const Modal = useModal({
+ size: showTable ? "xl" : "lg",
+ });
+
+ const { hide } = useModalControl();
+
const fileList = useMemo(() => pending.map((v) => v.file), [pending]);
const initialRef = useRef(initial);
@@ -281,8 +283,6 @@ function SubtitleUploadModal<T>(props: ComponentProps<T>) {
[columns, availableLanguages]
);
- const showTable = pending.length > 0;
-
const canUpload = useMemo(
() =>
pending.length > 0 &&
@@ -332,12 +332,7 @@ function SubtitleUploadModal<T>(props: ComponentProps<T>) {
);
return (
- <BaseModal
- size={showTable ? "xl" : "lg"}
- title="Upload Subtitles"
- footer={footer}
- {...props}
- >
+ <Modal title="Update Subtitles" footer={footer}>
<Container fluid className="flex-column">
<Form>
<Form.Group>
@@ -360,8 +355,8 @@ function SubtitleUploadModal<T>(props: ComponentProps<T>) {
</RowContext.Provider>
</div>
</Container>
- </BaseModal>
+ </Modal>
);
}
-export default SubtitleUploadModal;
+export default SubtitleUploader;
diff --git a/frontend/src/components/modals/index.ts b/frontend/src/components/modals/index.ts
index 5f02dc94e..f52d9228d 100644
--- a/frontend/src/components/modals/index.ts
+++ b/frontend/src/components/modals/index.ts
@@ -1,4 +1,3 @@
-export * from "./BaseModal";
export * from "./HistoryModal";
export { default as ItemEditorModal } from "./ItemEditorModal";
export { default as MovieUploadModal } from "./MovieUploadModal";
diff --git a/frontend/src/modules/modals/ModalContext.ts b/frontend/src/modules/modals/ModalContext.ts
new file mode 100644
index 000000000..81e7fba86
--- /dev/null
+++ b/frontend/src/modules/modals/ModalContext.ts
@@ -0,0 +1,14 @@
+import { createContext, Dispatch, SetStateAction } from "react";
+
+export interface ModalData {
+ key: string;
+ closeable: boolean;
+ size: "sm" | "lg" | "xl" | undefined;
+}
+
+export type ModalSetter = {
+ [P in keyof Omit<ModalData, "key">]: Dispatch<SetStateAction<ModalData[P]>>;
+};
+
+export const ModalDataContext = createContext<ModalData | null>(null);
+export const ModalSetterContext = createContext<ModalSetter | null>(null);
diff --git a/frontend/src/modules/modals/ModalWrapper.tsx b/frontend/src/modules/modals/ModalWrapper.tsx
new file mode 100644
index 000000000..aeb176604
--- /dev/null
+++ b/frontend/src/modules/modals/ModalWrapper.tsx
@@ -0,0 +1,44 @@
+import clsx from "clsx";
+import { FunctionComponent, useCallback, useState } from "react";
+import { Modal } from "react-bootstrap";
+import { useCurrentLayer, useModalControl, useModalData } from "./hooks";
+
+interface Props {}
+
+export const ModalWrapper: FunctionComponent<Props> = ({ children }) => {
+ const { size, closeable, key } = useModalData();
+ const [needExit, setExit] = useState(false);
+
+ const { hide: hideModal } = useModalControl();
+
+ const layer = useCurrentLayer();
+ const isShowed = layer !== -1;
+
+ const hide = useCallback(() => {
+ setExit(true);
+ }, []);
+
+ const exit = useCallback(() => {
+ if (isShowed) {
+ hideModal(key);
+ }
+ setExit(false);
+ }, [isShowed, hideModal, key]);
+
+ return (
+ <Modal
+ centered
+ size={size}
+ show={isShowed && !needExit}
+ onHide={hide}
+ onExited={exit}
+ backdrop={closeable ? undefined : "static"}
+ className={clsx(`index-${layer}`)}
+ backdropClassName={clsx(`index-${layer}`)}
+ >
+ {children}
+ </Modal>
+ );
+};
+
+export default ModalWrapper;
diff --git a/frontend/src/modules/modals/WithModal.tsx b/frontend/src/modules/modals/WithModal.tsx
new file mode 100644
index 000000000..0d09e14e2
--- /dev/null
+++ b/frontend/src/modules/modals/WithModal.tsx
@@ -0,0 +1,52 @@
+import { FunctionComponent, useMemo, useState } from "react";
+import {
+ ModalData,
+ ModalDataContext,
+ ModalSetter,
+ ModalSetterContext,
+} from "./ModalContext";
+import ModalWrapper from "./ModalWrapper";
+
+export interface ModalProps {}
+
+export type ModalComponent<P> = FunctionComponent<P> & {
+ modalKey: string;
+};
+
+export default function withModal<T>(
+ Content: FunctionComponent<T>,
+ key: string
+) {
+ const Comp: ModalComponent<T> = (props: ModalProps & T) => {
+ const [closeable, setCloseable] = useState(true);
+ const [size, setSize] = useState<ModalData["size"]>(undefined);
+ const data: ModalData = useMemo(
+ () => ({
+ key,
+ size,
+ closeable,
+ }),
+ [closeable, size]
+ );
+
+ const setter: ModalSetter = useMemo(
+ () => ({
+ closeable: setCloseable,
+ size: setSize,
+ }),
+ []
+ );
+
+ return (
+ <ModalDataContext.Provider value={data}>
+ <ModalSetterContext.Provider value={setter}>
+ <ModalWrapper>
+ <Content {...props}></Content>
+ </ModalWrapper>
+ </ModalSetterContext.Provider>
+ </ModalDataContext.Provider>
+ );
+ };
+ Comp.modalKey = key;
+ return Comp;
+}
diff --git a/frontend/src/modules/modals/components.tsx b/frontend/src/modules/modals/components.tsx
new file mode 100644
index 000000000..171f19d3c
--- /dev/null
+++ b/frontend/src/modules/modals/components.tsx
@@ -0,0 +1,23 @@
+import { FunctionComponent, ReactNode } from "react";
+import { Modal } from "react-bootstrap";
+import { useModalData } from "./hooks";
+
+interface StandardModalProps {
+ title: string;
+ footer?: ReactNode;
+}
+
+export const StandardModalView: FunctionComponent<StandardModalProps> = ({
+ children,
+ footer,
+ title,
+}) => {
+ const { closeable } = useModalData();
+ return (
+ <>
+ <Modal.Header closeButton={closeable}>{title}</Modal.Header>
+ <Modal.Body>{children}</Modal.Body>
+ <Modal.Footer hidden={footer === undefined}>{footer}</Modal.Footer>
+ </>
+ );
+};
diff --git a/frontend/src/modules/modals/hooks.ts b/frontend/src/modules/modals/hooks.ts
new file mode 100644
index 000000000..03ffe0d9c
--- /dev/null
+++ b/frontend/src/modules/modals/hooks.ts
@@ -0,0 +1,90 @@
+import {
+ hideModalAction,
+ showModalAction,
+} from "@/modules/redux/actions/modal";
+import { useReduxAction, useReduxStore } from "@/modules/redux/hooks/base";
+import { useCallback, useContext, useEffect, useMemo, useRef } from "react";
+import { StandardModalView } from "./components";
+import {
+ ModalData,
+ ModalDataContext,
+ ModalSetterContext,
+} from "./ModalContext";
+import { ModalComponent } from "./WithModal";
+
+type ModalProps = Partial<Omit<ModalData, "key">> & {
+ onMounted?: () => void;
+};
+
+export function useModal(props?: ModalProps): typeof StandardModalView {
+ const setter = useContext(ModalSetterContext);
+
+ useEffect(() => {
+ if (setter && props) {
+ setter.closeable(props.closeable ?? true);
+ setter.size(props.size);
+ }
+ }, [props, setter]);
+
+ const ref = useRef<ModalProps["onMounted"]>(props?.onMounted);
+ ref.current = props?.onMounted;
+
+ const layer = useCurrentLayer();
+
+ useEffect(() => {
+ if (layer !== -1 && ref.current) {
+ ref.current();
+ }
+ }, [layer]);
+
+ return StandardModalView;
+}
+
+export function useModalControl() {
+ const showAction = useReduxAction(showModalAction);
+
+ const show = useCallback(
+ <P>(comp: ModalComponent<P>, payload?: unknown) => {
+ showAction({ key: comp.modalKey, payload });
+ },
+ [showAction]
+ );
+
+ const hideAction = useReduxAction(hideModalAction);
+
+ const hide = useCallback(
+ (key?: string) => {
+ hideAction(key);
+ },
+ [hideAction]
+ );
+
+ return { show, hide };
+}
+
+export function useModalData(): ModalData {
+ const data = useContext(ModalDataContext);
+
+ if (data === null) {
+ throw new Error("useModalData should be used inside Modal");
+ }
+
+ return data;
+}
+
+export function usePayload<T>(): T | null {
+ const { key } = useModalData();
+ const stack = useReduxStore((s) => s.modal.stack);
+
+ return useMemo(
+ () => (stack.find((m) => m.key === key)?.payload as T) ?? null,
+ [stack, key]
+ );
+}
+
+export function useCurrentLayer() {
+ const { key } = useModalData();
+ const stack = useReduxStore((s) => s.modal.stack);
+
+ return useMemo(() => stack.findIndex((m) => m.key === key), [stack, key]);
+}
diff --git a/frontend/src/modules/modals/index.ts b/frontend/src/modules/modals/index.ts
new file mode 100644
index 000000000..baaee48b7
--- /dev/null
+++ b/frontend/src/modules/modals/index.ts
@@ -0,0 +1,3 @@
+export * from "./components";
+export * from "./hooks";
+export { default as withModal } from "./WithModal";
diff --git a/frontend/src/modules/redux/hooks/modal.ts b/frontend/src/modules/redux/hooks/modal.ts
deleted file mode 100644
index ea8db3659..000000000
--- a/frontend/src/modules/redux/hooks/modal.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import {
- hideModalAction,
- showModalAction,
-} from "@/modules/redux/actions/modal";
-import { useReduxAction, useReduxStore } from "@/modules/redux/hooks/base";
-import { useCallback, useMemo } from "react";
-
-export function useModalControl() {
- const showModal = useReduxAction(showModalAction);
-
- const show = useCallback(
- (key: string, payload?: unknown) => {
- showModal({ key, payload });
- },
- [showModal]
- );
-
- const hide = useReduxAction(hideModalAction);
-
- return { show, hide };
-}
-
-export function useIsShowed(key: string) {
- const stack = useReduxStore((s) => s.modal.stack);
-
- return useMemo(() => stack.findIndex((m) => m.key === key), [stack, key]);
-}
-
-export function usePayload<T>(key: string): T | null {
- const stack = useReduxStore((s) => s.modal.stack);
-
- return useMemo(
- () => (stack.find((m) => m.key === key)?.payload as T) ?? null,
- [stack, key]
- );
-}
diff --git a/frontend/src/pages/Episodes/index.tsx b/frontend/src/pages/Episodes/index.tsx
index a4efa7c4f..9915a6293 100644
--- a/frontend/src/pages/Episodes/index.tsx
+++ b/frontend/src/pages/Episodes/index.tsx
@@ -5,14 +5,14 @@ import {
useSeriesById,
useSeriesModification,
} from "@/apis/hooks";
+import { ContentHeader, LoadingIndicator } from "@/components";
+import ItemOverview from "@/components/ItemOverview";
import {
- ContentHeader,
ItemEditorModal,
- LoadingIndicator,
SeriesUploadModal,
-} from "@/components";
-import ItemOverview from "@/components/ItemOverview";
-import { useModalControl } from "@/modules/redux/hooks/modal";
+ SubtitleToolModal,
+} from "@/components/modals";
+import { useModalControl } from "@/modules/modals";
import { createAndDispatchTask } from "@/modules/task/utilities";
import { useLanguageProfileBy } from "@/utilities/languages";
import {
@@ -109,7 +109,7 @@ const SeriesEpisodesView: FunctionComponent = () => {
<ContentHeader.Button
disabled={series.episodeFileCount === 0 || !available || hasTask}
icon={faBriefcase}
- onClick={() => show("tools", episodes)}
+ onClick={() => show(SubtitleToolModal, episodes)}
>
Tools
</ContentHeader.Button>
@@ -120,14 +120,14 @@ const SeriesEpisodesView: FunctionComponent = () => {
!available
}
icon={faCloudUploadAlt}
- onClick={() => show("upload", series)}
+ onClick={() => show(SeriesUploadModal, series)}
>
Upload
</ContentHeader.Button>
<ContentHeader.Button
icon={faWrench}
disabled={hasTask}
- onClick={() => show("edit", series)}
+ onClick={() => show(ItemEditorModal, series)}
>
Edit Series
</ContentHeader.Button>
@@ -158,11 +158,8 @@ const SeriesEpisodesView: FunctionComponent = () => {
></Table>
)}
</Row>
- <ItemEditorModal modalKey="edit" mutation={mutation}></ItemEditorModal>
- <SeriesUploadModal
- modalKey="upload"
- episodes={episodes ?? []}
- ></SeriesUploadModal>
+ <ItemEditorModal mutation={mutation}></ItemEditorModal>
+ <SeriesUploadModal episodes={episodes ?? []}></SeriesUploadModal>
</Container>
);
};
diff --git a/frontend/src/pages/Episodes/table.tsx b/frontend/src/pages/Episodes/table.tsx
index d519285af..4a9326201 100644
--- a/frontend/src/pages/Episodes/table.tsx
+++ b/frontend/src/pages/Episodes/table.tsx
@@ -1,14 +1,9 @@
import { useDownloadEpisodeSubtitles, useEpisodesProvider } from "@/apis/hooks";
-import {
- ActionButton,
- EpisodeHistoryModal,
- GroupTable,
- SubtitleToolModal,
- TextPopover,
-} from "@/components";
-import { ManualSearchModal } from "@/components/modals/ManualSearchModal";
+import { ActionButton, GroupTable, TextPopover } from "@/components";
+import { EpisodeHistoryModal, SubtitleToolModal } from "@/components/modals";
+import { EpisodeSearchModal } from "@/components/modals/ManualSearchModal";
+import { useModalControl } from "@/modules/modals";
import { useShowOnlyDesired } from "@/modules/redux/hooks";
-import { useModalControl } from "@/modules/redux/hooks/modal";
import { BuildKey, filterSubtitleBy } from "@/utilities";
import { useProfileItemsToLanguages } from "@/utilities/languages";
import { faBookmark as farBookmark } from "@fortawesome/free-regular-svg-icons";
@@ -166,21 +161,21 @@ const Table: FunctionComponent<Props> = ({
icon={faUser}
disabled={series?.profileId === null || disabled}
onClick={() => {
- show("manual-search", row.original);
+ show(EpisodeSearchModal, row.original);
}}
></ActionButton>
<ActionButton
icon={faHistory}
disabled={disabled}
onClick={() => {
- show("history", row.original);
+ show(EpisodeHistoryModal, row.original);
}}
></ActionButton>
<ActionButton
icon={faBriefcase}
disabled={disabled}
onClick={() => {
- show("tools", [row.original]);
+ show(SubtitleToolModal, [row.original]);
}}
></ActionButton>
</ButtonGroup>
@@ -214,13 +209,12 @@ const Table: FunctionComponent<Props> = ({
}}
emptyText="No Episode Found For This Series"
></GroupTable>
- <SubtitleToolModal modalKey="tools" size="lg"></SubtitleToolModal>
- <EpisodeHistoryModal modalKey="history" size="lg"></EpisodeHistoryModal>
- <ManualSearchModal
- modalKey="manual-search"
+ <SubtitleToolModal></SubtitleToolModal>
+ <EpisodeHistoryModal></EpisodeHistoryModal>
+ <EpisodeSearchModal
download={download}
query={useEpisodesProvider}
- ></ManualSearchModal>
+ ></EpisodeSearchModal>
</>
);
};
diff --git a/frontend/src/pages/Movies/Details/index.tsx b/frontend/src/pages/Movies/Details/index.tsx
index d6731f4de..41efde6c5 100644
--- a/frontend/src/pages/Movies/Details/index.tsx
+++ b/frontend/src/pages/Movies/Details/index.tsx
@@ -8,17 +8,16 @@ import {
useMovieById,
useMovieModification,
} from "@/apis/hooks/movies";
+import { ContentHeader, LoadingIndicator } from "@/components";
+import ItemOverview from "@/components/ItemOverview";
import {
- ContentHeader,
ItemEditorModal,
- LoadingIndicator,
MovieHistoryModal,
MovieUploadModal,
SubtitleToolModal,
-} from "@/components";
-import ItemOverview from "@/components/ItemOverview";
-import { ManualSearchModal } from "@/components/modals/ManualSearchModal";
-import { useModalControl } from "@/modules/redux/hooks/modal";
+} from "@/components/modals";
+import { MovieSearchModal } from "@/components/modals/ManualSearchModal";
+import { useModalControl } from "@/modules/modals";
import { createAndDispatchTask } from "@/modules/task/utilities";
import { useLanguageProfileBy } from "@/utilities/languages";
import {
@@ -122,20 +121,20 @@ const MovieDetailView: FunctionComponent = () => {
<ContentHeader.Button
icon={faUser}
disabled={movie.profileId === null || hasTask}
- onClick={() => show("manual-search", movie)}
+ onClick={() => show(MovieSearchModal, movie)}
>
Manual
</ContentHeader.Button>
<ContentHeader.Button
icon={faHistory}
- onClick={() => show("history", movie)}
+ onClick={() => show(MovieHistoryModal, movie)}
>
History
</ContentHeader.Button>
<ContentHeader.Button
icon={faToolbox}
disabled={hasTask}
- onClick={() => show("tools", [movie])}
+ onClick={() => show(SubtitleToolModal, [movie])}
>
Tools
</ContentHeader.Button>
@@ -145,14 +144,14 @@ const MovieDetailView: FunctionComponent = () => {
<ContentHeader.Button
disabled={!allowEdit || movie.profileId === null || hasTask}
icon={faCloudUploadAlt}
- onClick={() => show("upload", movie)}
+ onClick={() => show(MovieUploadModal, movie)}
>
Upload
</ContentHeader.Button>
<ContentHeader.Button
icon={faWrench}
disabled={hasTask}
- onClick={() => show("edit", movie)}
+ onClick={() => show(ItemEditorModal, movie)}
>
Edit Movie
</ContentHeader.Button>
@@ -174,15 +173,14 @@ const MovieDetailView: FunctionComponent = () => {
<Row>
<Table movie={movie} profile={profile} disabled={hasTask}></Table>
</Row>
- <ItemEditorModal modalKey="edit" mutation={mutation}></ItemEditorModal>
- <SubtitleToolModal modalKey="tools" size="lg"></SubtitleToolModal>
- <MovieHistoryModal modalKey="history" size="lg"></MovieHistoryModal>
- <MovieUploadModal modalKey="upload" size="lg"></MovieUploadModal>
- <ManualSearchModal
- modalKey="manual-search"
+ <ItemEditorModal mutation={mutation}></ItemEditorModal>
+ <SubtitleToolModal></SubtitleToolModal>
+ <MovieHistoryModal></MovieHistoryModal>
+ <MovieUploadModal></MovieUploadModal>
+ <MovieSearchModal
download={download}
query={useMoviesProvider}
- ></ManualSearchModal>
+ ></MovieSearchModal>
</Container>
);
};
diff --git a/frontend/src/pages/Movies/index.tsx b/frontend/src/pages/Movies/index.tsx
index 2939ee2a0..5daac19f6 100644
--- a/frontend/src/pages/Movies/index.tsx
+++ b/frontend/src/pages/Movies/index.tsx
@@ -1,9 +1,10 @@
import { useMovieModification, useMoviesPagination } from "@/apis/hooks";
-import { ActionBadge, ItemEditorModal, TextPopover } from "@/components";
+import { ActionBadge, TextPopover } from "@/components";
import Language from "@/components/bazarr/Language";
import LanguageProfile from "@/components/bazarr/LanguageProfile";
+import { ItemEditorModal } from "@/components/modals";
import ItemView from "@/components/views/ItemView";
-import { useModalControl } from "@/modules/redux/hooks/modal";
+import { useModalControl } from "@/modules/modals";
import { BuildKey } from "@/utilities";
import { faBookmark as farBookmark } from "@fortawesome/free-regular-svg-icons";
import { faBookmark, faWrench } from "@fortawesome/free-solid-svg-icons";
@@ -90,7 +91,7 @@ const MovieView: FunctionComponent = () => {
return (
<ActionBadge
icon={faWrench}
- onClick={() => show("edit", row.original)}
+ onClick={() => show(ItemEditorModal, row.original)}
></ActionBadge>
);
},
@@ -105,7 +106,7 @@ const MovieView: FunctionComponent = () => {
<title>Movies - Bazarr</title>
</Helmet>
<ItemView query={query} columns={columns}></ItemView>
- <ItemEditorModal modalKey="edit" mutation={mutation}></ItemEditorModal>
+ <ItemEditorModal mutation={mutation}></ItemEditorModal>
</Container>
);
};
diff --git a/frontend/src/pages/Series/index.tsx b/frontend/src/pages/Series/index.tsx
index 5f96f1d75..e03807a7f 100644
--- a/frontend/src/pages/Series/index.tsx
+++ b/frontend/src/pages/Series/index.tsx
@@ -1,8 +1,9 @@
import { useSeriesModification, useSeriesPagination } from "@/apis/hooks";
-import { ActionBadge, ItemEditorModal } from "@/components";
+import { ActionBadge } from "@/components";
import LanguageProfile from "@/components/bazarr/LanguageProfile";
+import { ItemEditorModal } from "@/components/modals";
import ItemView from "@/components/views/ItemView";
-import { useModalControl } from "@/modules/redux/hooks/modal";
+import { useModalControl } from "@/modules/modals";
import { BuildKey } from "@/utilities";
import { faWrench } from "@fortawesome/free-solid-svg-icons";
import { FunctionComponent, useMemo } from "react";
@@ -92,7 +93,7 @@ const SeriesView: FunctionComponent = () => {
return (
<ActionBadge
icon={faWrench}
- onClick={() => show("edit", original)}
+ onClick={() => show(ItemEditorModal, original)}
></ActionBadge>
);
},
@@ -107,7 +108,7 @@ const SeriesView: FunctionComponent = () => {
<title>Series - Bazarr</title>
</Helmet>
<ItemView query={query} columns={columns}></ItemView>
- <ItemEditorModal modalKey="edit" mutation={mutation}></ItemEditorModal>
+ <ItemEditorModal mutation={mutation}></ItemEditorModal>
</Container>
);
};
diff --git a/frontend/src/pages/Settings/Languages/modal.tsx b/frontend/src/pages/Settings/Languages/modal.tsx
index 1c8413c83..066418a76 100644
--- a/frontend/src/pages/Settings/Languages/modal.tsx
+++ b/frontend/src/pages/Settings/Languages/modal.tsx
@@ -1,14 +1,17 @@
import {
ActionButton,
- BaseModal,
- BaseModalProps,
Chips,
LanguageSelector,
Selector,
SelectorOption,
SimpleTable,
} from "@/components";
-import { useModalControl, usePayload } from "@/modules/redux/hooks/modal";
+import {
+ useModal,
+ useModalControl,
+ usePayload,
+ withModal,
+} from "@/modules/modals";
import { BuildKey } from "@/utilities";
import { LOG } from "@/utilities/console";
import { faTrash } from "@fortawesome/free-solid-svg-icons";
@@ -17,7 +20,6 @@ import {
FunctionComponent,
useCallback,
useContext,
- useEffect,
useMemo,
useState,
} from "react";
@@ -53,12 +55,8 @@ function createDefaultProfile(): Language.Profile {
};
}
-const LanguagesProfileModal: FunctionComponent<Props & BaseModalProps> = (
- props
-) => {
- const { update, ...modal } = props;
-
- const profile = usePayload<Language.Profile>(modal.modalKey);
+const LanguagesProfileModal: FunctionComponent<Props> = ({ update }) => {
+ const profile = usePayload<Language.Profile>();
const { hide } = useModalControl();
@@ -66,13 +64,12 @@ const LanguagesProfileModal: FunctionComponent<Props & BaseModalProps> = (
const [current, setProfile] = useState(createDefaultProfile);
- useEffect(() => {
- if (profile) {
- setProfile(profile);
- } else {
- setProfile(createDefaultProfile);
- }
- }, [profile]);
+ const Modal = useModal({
+ size: "lg",
+ onMounted: () => {
+ setProfile(profile ?? createDefaultProfile);
+ },
+ });
const cutoff: SelectorOption<number>[] = useMemo(() => {
const options = [...cutoffOptions];
@@ -134,18 +131,6 @@ const LanguagesProfileModal: FunctionComponent<Props & BaseModalProps> = (
const canSave = current.name.length > 0 && current.items.length > 0;
- const footer = (
- <Button
- disabled={!canSave}
- onClick={() => {
- hide();
- update(current);
- }}
- >
- Save
- </Button>
- );
-
const columns = useMemo<Column<Language.ProfileItem>[]>(
() => [
{
@@ -253,8 +238,20 @@ const LanguagesProfileModal: FunctionComponent<Props & BaseModalProps> = (
[languages]
);
+ const footer = (
+ <Button
+ disabled={!canSave}
+ onClick={() => {
+ hide();
+ update(current);
+ }}
+ >
+ Save
+ </Button>
+ );
+
return (
- <BaseModal size="lg" title="Languages Profile" footer={footer} {...modal}>
+ <Modal title="Languages Profile" footer={footer}>
<Input>
<Form.Control
type="text"
@@ -319,8 +316,8 @@ const LanguagesProfileModal: FunctionComponent<Props & BaseModalProps> = (
></Selector>
<Message>Download subtitle file without format conversion</Message>
</Input>
- </BaseModal>
+ </Modal>
);
};
-export default LanguagesProfileModal;
+export default withModal(LanguagesProfileModal, "languages-profile-editor");
diff --git a/frontend/src/pages/Settings/Languages/table.tsx b/frontend/src/pages/Settings/Languages/table.tsx
index ed87274da..bc2cd2c4e 100644
--- a/frontend/src/pages/Settings/Languages/table.tsx
+++ b/frontend/src/pages/Settings/Languages/table.tsx
@@ -1,5 +1,5 @@
import { ActionButton, SimpleTable } from "@/components";
-import { useModalControl } from "@/modules/redux/hooks/modal";
+import { useModalControl } from "@/modules/modals";
import { LOG } from "@/utilities/console";
import { faTrash, faWrench } from "@fortawesome/free-solid-svg-icons";
import { cloneDeep } from "lodash";
@@ -69,7 +69,7 @@ const Table: FunctionComponent = () => {
const mutateRow = useCallback<ModifyFn>(
(index, item) => {
if (item) {
- show("profile", cloneDeep(item));
+ show(Modal, cloneDeep(item));
} else {
const list = [...profiles];
list.splice(index, 1);
@@ -185,12 +185,12 @@ const Table: FunctionComponent = () => {
mustNotContain: [],
originalFormat: false,
};
- show("profile", profile);
+ show(Modal, profile);
}}
>
{canAdd ? "Add New Profile" : "No Enabled Languages"}
</Button>
- <Modal update={updateProfile} modalKey="profile"></Modal>
+ <Modal update={updateProfile}></Modal>
</>
);
};
diff --git a/frontend/src/pages/Settings/Notifications/components.tsx b/frontend/src/pages/Settings/Notifications/components.tsx
index f53ab7132..fff722c19 100644
--- a/frontend/src/pages/Settings/Notifications/components.tsx
+++ b/frontend/src/pages/Settings/Notifications/components.tsx
@@ -1,32 +1,22 @@
import api from "@/apis/raw";
+import { AsyncButton, Selector, SelectorOption } from "@/components";
import {
- AsyncButton,
- BaseModal,
- BaseModalProps,
- Selector,
- SelectorOption,
-} from "@/components";
-import { useModalControl, usePayload } from "@/modules/redux/hooks/modal";
+ useModal,
+ useModalControl,
+ usePayload,
+ withModal,
+} from "@/modules/modals";
import { BuildKey } from "@/utilities";
-import {
- FunctionComponent,
- useCallback,
- useEffect,
- useMemo,
- useState,
-} from "react";
+import { FunctionComponent, useCallback, useMemo, useState } from "react";
import { Button, Col, Container, Form, Row } from "react-bootstrap";
import { ColCard, useLatestArray, useUpdateArray } from "../components";
import { notificationsKey } from "../keys";
-interface ModalProps {
+interface Props {
selections: readonly Settings.NotificationInfo[];
}
-const NotificationModal: FunctionComponent<ModalProps & BaseModalProps> = ({
- selections,
- ...modal
-}) => {
+const NotificationTool: FunctionComponent<Props> = ({ selections }) => {
const options = useMemo<SelectorOption<Settings.NotificationInfo>[]>(
() =>
selections
@@ -43,16 +33,11 @@ const NotificationModal: FunctionComponent<ModalProps & BaseModalProps> = ({
"name"
);
- const payload = usePayload<Settings.NotificationInfo>(modal.modalKey);
- const { hide } = useModalControl();
+ const payload = usePayload<Settings.NotificationInfo>();
const [current, setCurrent] =
useState<Nullable<Settings.NotificationInfo>>(payload);
- useEffect(() => {
- setCurrent(payload);
- }, [payload]);
-
const updateUrl = useCallback((url: string) => {
setCurrent((current) => {
if (current) {
@@ -69,55 +54,60 @@ const NotificationModal: FunctionComponent<ModalProps & BaseModalProps> = ({
const canSave =
current !== null && current?.url !== null && current?.url.length !== 0;
- const footer = useMemo(
- () => (
- <>
- <AsyncButton
- className="mr-auto"
- disabled={!canSave}
- variant="outline-secondary"
- promise={() => {
- if (current && current.url) {
- return api.system.testNotification(current.url);
- } else {
- return null;
- }
- }}
- >
- Test
- </AsyncButton>
- <Button
- hidden={payload === null}
- variant="danger"
- onClick={() => {
- if (current) {
- update({ ...current, enabled: false });
- }
- hide();
- }}
- >
- Remove
- </Button>
- <Button
- disabled={!canSave}
- onClick={() => {
- if (current) {
- update({ ...current, enabled: true });
- }
- hide();
- }}
- >
- Save
- </Button>
- </>
- ),
- [canSave, payload, current, hide, update]
- );
-
const getLabel = useCallback((v: Settings.NotificationInfo) => v.name, []);
+ const Modal = useModal({
+ onMounted: () => {
+ setCurrent(payload);
+ },
+ });
+
+ const { hide } = useModalControl();
+
+ const footer = (
+ <>
+ <AsyncButton
+ className="mr-auto"
+ disabled={!canSave}
+ variant="outline-secondary"
+ promise={() => {
+ if (current && current.url) {
+ return api.system.testNotification(current.url);
+ } else {
+ return null;
+ }
+ }}
+ >
+ Test
+ </AsyncButton>
+ <Button
+ hidden={payload === null}
+ variant="danger"
+ onClick={() => {
+ if (current) {
+ update({ ...current, enabled: false });
+ }
+ hide();
+ }}
+ >
+ Remove
+ </Button>
+ <Button
+ disabled={!canSave}
+ onClick={() => {
+ if (current) {
+ update({ ...current, enabled: true });
+ }
+ hide();
+ }}
+ >
+ Save
+ </Button>
+ </>
+ );
+
return (
- <BaseModal title="Notification" footer={footer} {...modal}>
+ <Modal title="Notification" footer={footer}>
<Container fluid>
<Row>
<Col xs={12}>
@@ -145,10 +135,12 @@ const NotificationModal: FunctionComponent<ModalProps & BaseModalProps> = ({
</Col>
</Row>
</Container>
- </BaseModal>
+ </Modal>
);
};
+const NotificationModal = withModal(NotificationTool, "notification-tool");
+
export const NotificationView: FunctionComponent = () => {
const notifications = useLatestArray<Settings.NotificationInfo>(
notificationsKey,
@@ -165,7 +157,7 @@ export const NotificationView: FunctionComponent = () => {
<ColCard
key={BuildKey(idx, v.name)}
header={v.name}
- onClick={() => show("notifications", v)}
+ onClick={() => show(NotificationModal, v)}
></ColCard>
));
}, [notifications, show]);
@@ -174,12 +166,9 @@ export const NotificationView: FunctionComponent = () => {
<Container fluid>
<Row>
{elements}{" "}
- <ColCard plus onClick={() => show("notifications")}></ColCard>
+ <ColCard plus onClick={() => show(NotificationModal)}></ColCard>
</Row>
- <NotificationModal
- selections={notifications ?? []}
- modalKey="notifications"
- ></NotificationModal>
+ <NotificationModal selections={notifications ?? []}></NotificationModal>
</Container>
);
};
diff --git a/frontend/src/pages/Settings/Providers/components.tsx b/frontend/src/pages/Settings/Providers/components.tsx
index 4af98d95c..f01012ba3 100644
--- a/frontend/src/pages/Settings/Providers/components.tsx
+++ b/frontend/src/pages/Settings/Providers/components.tsx
@@ -1,10 +1,10 @@
+import { Selector, SelectorComponents, SelectorOption } from "@/components";
import {
- BaseModal,
- Selector,
- SelectorComponents,
- SelectorOption,
-} from "@/components";
-import { useModalControl, usePayload } from "@/modules/redux/hooks/modal";
+ useModal,
+ useModalControl,
+ usePayload,
+ withModal,
+} from "@/modules/modals";
import { BuildKey, isReactText } from "@/utilities";
import { capitalize, isArray, isBoolean } from "lodash";
import {
@@ -27,7 +27,6 @@ import {
} from "../components";
import { ProviderInfo, ProviderList } from "./list";
-const ModalKey = "provider-modal";
const ProviderKey = "settings-general-enabled_providers";
export const ProviderView: FunctionComponent = () => {
@@ -37,7 +36,7 @@ export const ProviderView: FunctionComponent = () => {
const select = useCallback(
(v?: ProviderInfo) => {
- show(ModalKey, v ?? null);
+ show(ProviderModal, v ?? null);
},
[show]
);
@@ -72,12 +71,14 @@ export const ProviderView: FunctionComponent = () => {
{cards}
<ColCard key="add-card" plus onClick={select}></ColCard>
</Row>
+ <ProviderModal></ProviderModal>
</Container>
);
};
-export const ProviderModal: FunctionComponent = () => {
- const payload = usePayload<ProviderInfo>(ModalKey);
+const ProviderTool: FunctionComponent = () => {
+ const payload = usePayload<ProviderInfo>();
+ const Modal = useModal();
const { hide } = useModalControl();
const [staged, setChange] = useState<LooseObject>({});
@@ -121,20 +122,6 @@ export const ProviderModal: FunctionComponent = () => {
const canSave = info !== null;
- const footer = useMemo(
- () => (
- <>
- <Button hidden={!payload} variant="danger" onClick={deletePayload}>
- Delete
- </Button>
- <Button disabled={!canSave} onClick={addProvider}>
- Save
- </Button>
- </>
- ),
- [canSave, payload, deletePayload, addProvider]
- );
-
const onSelect = useCallback((item: Nullable<ProviderInfo>) => {
if (item) {
setInfo(item);
@@ -237,8 +224,19 @@ export const ProviderModal: FunctionComponent = () => {
[]
);
+ const footer = (
+ <>
+ <Button hidden={!payload} variant="danger" onClick={deletePayload}>
+ Delete
+ </Button>
+ <Button disabled={!canSave} onClick={addProvider}>
+ Save
+ </Button>
+ </>
+ );
+
return (
- <BaseModal title="Provider" footer={footer} modalKey={ModalKey}>
+ <Modal title="Provider" footer={footer}>
<StagedChangesContext.Provider value={[staged, setChange]}>
<Container>
<Row>
@@ -266,6 +264,8 @@ export const ProviderModal: FunctionComponent = () => {
</Row>
</Container>
</StagedChangesContext.Provider>
- </BaseModal>
+ </Modal>
);
};
+
+const ProviderModal = withModal(ProviderTool, "provider-tool");
diff --git a/frontend/src/pages/Settings/Providers/index.tsx b/frontend/src/pages/Settings/Providers/index.tsx
index 7ea651f6f..991973ab0 100644
--- a/frontend/src/pages/Settings/Providers/index.tsx
+++ b/frontend/src/pages/Settings/Providers/index.tsx
@@ -1,6 +1,6 @@
import { FunctionComponent } from "react";
import { Group, Input, Layout } from "../components";
-import { ProviderModal, ProviderView } from "./components";
+import { ProviderView } from "./components";
const SettingsProvidersView: FunctionComponent = () => {
return (
@@ -10,7 +10,6 @@ const SettingsProvidersView: FunctionComponent = () => {
<ProviderView></ProviderView>
</Input>
</Group>
- <ProviderModal></ProviderModal>
</Layout>
);
};
diff --git a/frontend/src/pages/System/Backups/BackupDeleteModal.tsx b/frontend/src/pages/System/Backups/BackupDeleteModal.tsx
index 1f6ea6ece..19070dde7 100644
--- a/frontend/src/pages/System/Backups/BackupDeleteModal.tsx
+++ b/frontend/src/pages/System/Backups/BackupDeleteModal.tsx
@@ -1,16 +1,20 @@
-import { AsyncButton, BaseModal, BaseModalProps } from "@/components";
-import { useModalControl, usePayload } from "@/modules/redux/hooks/modal";
+import { AsyncButton } from "@/components";
+import {
+ useModal,
+ useModalControl,
+ usePayload,
+ withModal,
+} from "@/modules/modals";
import React, { FunctionComponent } from "react";
import { Button } from "react-bootstrap";
import { useDeleteBackups } from "../../../apis/hooks";
-interface Props extends BaseModalProps {}
-
-const SystemBackupDeleteModal: FunctionComponent<Props> = ({ ...modal }) => {
+const SystemBackupDeleteModal: FunctionComponent = () => {
const { mutateAsync } = useDeleteBackups();
- const result = usePayload<string>(modal.modalKey);
+ const result = usePayload<string>();
+ const Modal = useModal();
const { hide } = useModalControl();
const footer = (
@@ -19,9 +23,7 @@ const SystemBackupDeleteModal: FunctionComponent<Props> = ({ ...modal }) => {
<Button
variant="outline-secondary"
className="mr-2"
- onClick={() => {
- hide(modal.modalKey);
- }}
+ onClick={() => hide()}
>
Cancel
</Button>
@@ -34,7 +36,7 @@ const SystemBackupDeleteModal: FunctionComponent<Props> = ({ ...modal }) => {
return null;
}
}}
- onSuccess={() => hide(modal.modalKey)}
+ onSuccess={() => hide()}
>
Delete
</AsyncButton>
@@ -43,10 +45,10 @@ const SystemBackupDeleteModal: FunctionComponent<Props> = ({ ...modal }) => {
);
return (
- <BaseModal title="Delete Backup" footer={footer} {...modal}>
- Are you sure you want to delete the backup '{result}'?
- </BaseModal>
+ <Modal title="Delete Backup" footer={footer}>
+ <span>Are you sure you want to delete the backup '{result}'?</span>
+ </Modal>
);
};
-export default SystemBackupDeleteModal;
+export default withModal(SystemBackupDeleteModal, "delete");
diff --git a/frontend/src/pages/System/Backups/BackupRestoreModal.tsx b/frontend/src/pages/System/Backups/BackupRestoreModal.tsx
index 69d6ae12d..80015c120 100644
--- a/frontend/src/pages/System/Backups/BackupRestoreModal.tsx
+++ b/frontend/src/pages/System/Backups/BackupRestoreModal.tsx
@@ -1,27 +1,29 @@
import { useRestoreBackups } from "@/apis/hooks/system";
-import { AsyncButton, BaseModal, BaseModalProps } from "@/components";
-import { useModalControl, usePayload } from "@/modules/redux/hooks/modal";
+import { AsyncButton } from "@/components";
+import {
+ useModal,
+ useModalControl,
+ usePayload,
+ withModal,
+} from "@/modules/modals";
import React, { FunctionComponent } from "react";
import { Button } from "react-bootstrap";
-interface Props extends BaseModalProps {}
+const SystemBackupRestoreModal: FunctionComponent = () => {
+ const result = usePayload<string>();
-const SystemBackupRestoreModal: FunctionComponent<Props> = ({ ...modal }) => {
- const result = usePayload<string>(modal.modalKey);
+ const Modal = useModal();
+ const { hide } = useModalControl();
const { mutateAsync } = useRestoreBackups();
- const { hide } = useModalControl();
-
const footer = (
<div className="d-flex flex-row-reverse flex-grow-1 justify-content-between">
<div>
<Button
variant="outline-secondary"
className="mr-2"
- onClick={() => {
- hide(modal.modalKey);
- }}
+ onClick={() => hide()}
>
Cancel
</Button>
@@ -34,7 +36,7 @@ const SystemBackupRestoreModal: FunctionComponent<Props> = ({ ...modal }) => {
return null;
}
}}
- onSuccess={() => hide(modal.modalKey)}
+ onSuccess={() => hide()}
>
Restore
</AsyncButton>
@@ -43,11 +45,13 @@ const SystemBackupRestoreModal: FunctionComponent<Props> = ({ ...modal }) => {
);
return (
- <BaseModal title="Restore Backup" footer={footer} {...modal}>
- Are you sure you want to restore the backup '{result}'? Bazarr will
- automatically restart and reload the UI during the restore process.
- </BaseModal>
+ <Modal title="Restore Backup" footer={footer}>
+ <span>
+ Are you sure you want to restore the backup '{result}'? Bazarr will
+ automatically restart and reload the UI during the restore process.
+ </span>
+ </Modal>
);
};
-export default SystemBackupRestoreModal;
+export default withModal(SystemBackupRestoreModal, "restore");
diff --git a/frontend/src/pages/System/Backups/table.tsx b/frontend/src/pages/System/Backups/table.tsx
index 4beeb5e7f..d986dbaa1 100644
--- a/frontend/src/pages/System/Backups/table.tsx
+++ b/frontend/src/pages/System/Backups/table.tsx
@@ -1,5 +1,5 @@
import { ActionButton, PageTable } from "@/components";
-import { useModalControl } from "@/modules/redux/hooks/modal";
+import { useModalControl } from "@/modules/modals";
import { faClock, faHistory, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { FunctionComponent, useMemo } from "react";
@@ -42,11 +42,15 @@ const Table: FunctionComponent<Props> = ({ backups }) => {
<ButtonGroup>
<ActionButton
icon={faHistory}
- onClick={() => show("restore", row.row.original.filename)}
+ onClick={() =>
+ show(SystemBackupRestoreModal, row.row.original.filename)
+ }
></ActionButton>
<ActionButton
icon={faTrash}
- onClick={() => show("delete", row.row.original.filename)}
+ onClick={() =>
+ show(SystemBackupDeleteModal, row.row.original.filename)
+ }
></ActionButton>
</ButtonGroup>
);
@@ -59,14 +63,8 @@ const Table: FunctionComponent<Props> = ({ backups }) => {
return (
<React.Fragment>
<PageTable columns={columns} data={backups}></PageTable>
- <SystemBackupRestoreModal
- modalKey="restore"
- size="lg"
- ></SystemBackupRestoreModal>
- <SystemBackupDeleteModal
- modalKey="delete"
- size="lg"
- ></SystemBackupDeleteModal>
+ <SystemBackupRestoreModal></SystemBackupRestoreModal>
+ <SystemBackupDeleteModal></SystemBackupDeleteModal>
</React.Fragment>
);
};
diff --git a/frontend/src/pages/System/Logs/modal.tsx b/frontend/src/pages/System/Logs/modal.tsx
index 946ddf51d..5632eb412 100644
--- a/frontend/src/pages/System/Logs/modal.tsx
+++ b/frontend/src/pages/System/Logs/modal.tsx
@@ -1,9 +1,11 @@
-import { BaseModal, BaseModalProps } from "@/components";
-import { usePayload } from "@/modules/redux/hooks/modal";
+import { useModal, usePayload, withModal } from "@/modules/modals";
import { FunctionComponent, useMemo } from "react";
-const SystemLogModal: FunctionComponent<BaseModalProps> = ({ ...modal }) => {
- const stack = usePayload<string>(modal.modalKey);
+const SystemLogModal: FunctionComponent = () => {
+ const stack = usePayload<string>();
+
+ const Modal = useModal();
+
const result = useMemo(
() =>
stack?.split("\\n").map((v, idx) => (
@@ -13,13 +15,14 @@ const SystemLogModal: FunctionComponent<BaseModalProps> = ({ ...modal }) => {
)),
[stack]
);
+
return (
- <BaseModal title="Stack traceback" {...modal}>
+ <Modal title="Stack traceback">
<pre>
<code>{result}</code>
</pre>
- </BaseModal>
+ </Modal>
);
};
-export default SystemLogModal;
+export default withModal(SystemLogModal, "system-log");
diff --git a/frontend/src/pages/System/Logs/table.tsx b/frontend/src/pages/System/Logs/table.tsx
index 04a965ed1..d08c7fcbf 100644
--- a/frontend/src/pages/System/Logs/table.tsx
+++ b/frontend/src/pages/System/Logs/table.tsx
@@ -1,5 +1,5 @@
import { ActionButton, PageTable } from "@/components";
-import { useModalControl } from "@/modules/redux/hooks/modal";
+import { useModalControl } from "@/modules/modals";
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import {
faBug,
@@ -60,7 +60,7 @@ const Table: FunctionComponent<Props> = ({ logs }) => {
return (
<ActionButton
icon={faLayerGroup}
- onClick={() => show("system-log", value)}
+ onClick={() => show(SystemLogModal, value)}
></ActionButton>
);
} else {
@@ -75,7 +75,7 @@ const Table: FunctionComponent<Props> = ({ logs }) => {
return (
<>
<PageTable columns={columns} data={logs}></PageTable>
- <SystemLogModal size="xl" modalKey="system-log"></SystemLogModal>
+ <SystemLogModal></SystemLogModal>
</>
);
};