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 = (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 ( - - {title} - {children} - - - ); -}; - -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 = (props) => { - const { ...modal } = props; +const MovieHistoryView: FunctionComponent = () => { + const movie = usePayload(); - const movie = usePayload(modal.modalKey); + const Modal = useModal({ size: "lg" }); const history = useMovieHistory(movie?.radarrId); @@ -84,7 +83,7 @@ export const MovieHistoryModal: FunctionComponent = (props) => { ); return ( - + = (props) => { data={data ?? []} > - + ); }; -export const EpisodeHistoryModal: FunctionComponent = ( - props -) => { - const episode = usePayload(props.modalKey); +export const MovieHistoryModal = withModal(MovieHistoryView, "movie-history"); + +const EpisodeHistoryView: FunctionComponent = () => { + const episode = usePayload(); + + const Modal = useModal({ size: "lg" }); const history = useEpisodeHistory(episode?.sonarrEpisodeId); @@ -175,7 +176,7 @@ export const EpisodeHistoryModal: FunctionComponent = ( ); return ( - + = ( data={data ?? []} > - + ); }; + +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; } -const Editor: FunctionComponent = (props) => { - const { mutation, ...modal } = props; - +const Editor: FunctionComponent = ({ mutation }) => { const { data: profiles } = useLanguageProfiles(); - const payload = usePayload(modal.modalKey); - const { hide } = useModalControl(); - + const payload = usePayload(); const { mutateAsync, isLoading } = mutation; + const { hide } = useModalControl(); + const hasTask = useIsAnyActionRunning(); const profileOptions = useMemo[]>( @@ -33,9 +35,12 @@ const Editor: FunctionComponent = (props) => { const [id, setId] = useState>(payload?.profileId ?? null); - useEffect(() => { - setId(payload?.profileId ?? null); - }, [payload]); + const Modal = useModal({ + closeable: !isLoading, + onMounted: () => { + setId(payload?.profileId ?? null); + }, + }); const footer = ( = (props) => { return null; } }} - onSuccess={() => { - hide(); - }} + onSuccess={() => hide()} > Save ); return ( - +
@@ -95,8 +93,8 @@ const Editor: FunctionComponent = (props) => {
-
+ ); }; -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 { ) => UseQueryResult; } -export function ManualSearchModal( - props: Props & BaseModalProps -) { - const { download, query: useSearch, ...modal } = props; +function ManualSearchView(props: Props) { + const { download, query: useSearch } = props; - const item = usePayload(modal.modalKey); + const item = usePayload(); const itemId = useMemo(() => GetItemId(item ?? {}), [item]); const [id, setId] = useState(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( } }; - const footer = ( - - ); - const title = useMemo(() => { let title = "Unknown"; @@ -246,19 +225,39 @@ export function ManualSearchModal( 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 = ( + + ); + return ( - + {content()} - + ); } +export const MovieSearchModal = withModal>( + ManualSearchView, + "movie-manual-search" +); +export const EpisodeSearchModal = withModal>( + 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 = (props) => { - const modal = props; - - const payload = usePayload(modal.modalKey); +const MovieUploadModal: FunctionComponent = () => { + const payload = usePayload(); const profile = useLanguageProfileBy(payload?.profileId); @@ -87,7 +84,7 @@ const MovieUploadModal: FunctionComponent = (props) => { ); return ( - = (props) => { upload={upload} update={update} validate={validate} - {...modal} - > + > ); }; -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 = ({ - episodes, - ...modal -}) => { - const payload = usePayload(modal.modalKey); +const SeriesUploadModal: FunctionComponent = ({ episodes }) => { + const payload = usePayload(); const profile = useLanguageProfileBy(payload?.profileId); @@ -165,16 +161,15 @@ const SeriesUploadModal: FunctionComponent = ({ ); return ( - + > ); }; -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 = ( - props -) => { - const { process, ...modal } = props; +const ColorTool: FunctionComponent = ({ process }) => { const [selection, setSelection] = useState>(null); + const Modal = useModal(); + const submit = useCallback(() => { if (selection) { const action = submodProcessColor(selection); @@ -90,31 +93,29 @@ const AddColorModal: FunctionComponent = ( } }, [selection, process]); - const footer = useMemo( - () => ( - - ), - [selection, submit] + const footer = ( + ); + return ( - + - + ); }; -const FrameRateModal: FunctionComponent = ( - props -) => { - const { process, ...modal } = props; +const ColorToolModal = withModal(ColorTool, "color-tool"); +const FrameRateTool: FunctionComponent = ({ process }) => { const [from, setFrom] = useState>(null); const [to, setTo] = useState>(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 = ( ); return ( - + = ( }} > - + ); }; -const AdjustTimesModal: FunctionComponent = ( - props -) => { - const { process, ...modal } = props; +const FrameRateModal = withModal(FrameRateTool, "frame-rate-tool"); +const TimeAdjustmentTool: FunctionComponent = ({ 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 => { return (e) => { @@ -200,17 +201,14 @@ const AdjustTimesModal: FunctionComponent = ( } }, [process, canSave, offset, isPlus]); - const footer = useMemo( - () => ( - - ), - [submit, canSave] + const footer = ( + ); return ( - + - ), - [submit, selectedLanguage] + const footer = ( + ); - return ( - + Enabled languages not listed here are unsupported by Google Translate. @@ -284,18 +279,21 @@ const TranslateModal: FunctionComponent = ({ options={available} onChange={setLanguage} > - + ); }; +const TranslationModal = withModal(TranslationTool, "translate-tool"); + const CanSelectSubtitle = (item: TableColumnType) => { return item.path.endsWith(".srt"); }; -const STM: FunctionComponent = ({ ...props }) => { - const payload = usePayload(props.modalKey); +const STM: FunctionComponent = () => { + const payload = usePayload(); const [selections, setSelections] = useState([]); + const Modal = useModal({ size: "xl" }); const { hide } = useModalControl(); const { mutateAsync } = useSubtitleAction(); @@ -303,8 +301,7 @@ const STM: FunctionComponent = ({ ...props }) => { const process = useCallback( (action: string, override?: Partial) => { 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 = ({ ...props }) => { dispatchTask(tasks, "modify-subtitles"); }, - [hide, props.modalKey, selections, mutateAsync] + [hide, selections, mutateAsync] ); const { show } = useModalControl(); @@ -383,92 +380,74 @@ const STM: FunctionComponent = ({ ...props }) => { const plugins = [useRowSelect, useCustomSelection]; - const footer = useMemo( - () => ( - k && process(k)}> - process("sync")} - > - Sync - - - - - Remove HI Tags - - - Remove Style Tags - - - OCR Fixes - - - Common Fixes - - - - Fix Uppercase - - - - - Reverse RTL - - - show("add-color")}> - Add Color - - show("change-frame-rate")}> - Change Frame Rate - - show("adjust-times")}> - Adjust Times - - show("translate-sub")}> - Translate - - - - ), - [selections.length, process, show] + const footer = ( + k && process(k)}> + process("sync")} + > + Sync + + + + + Remove HI Tags + + + Remove Style Tags + + + OCR Fixes + + + Common Fixes + + + Fix Uppercase + + + Reverse RTL + + show(ColorToolModal)}> + Add Color + + show(FrameRateModal)}> + Change Frame Rate + + show(TimeAdjustmentModal)}> + Adjust Times + + show(TranslationModal)}> + Translate + + + ); return ( - <> - - - - - - - - + + + + + + + ); }; -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 = (index: number, info?: PendingSubtitle) => void; @@ -59,10 +58,7 @@ interface Props { hideAllLanguages?: boolean; } -type ComponentProps = Props & - Omit; - -function SubtitleUploadModal(props: ComponentProps) { +function SubtitleUploader(props: Props) { const { initial, columns, @@ -73,10 +69,16 @@ function SubtitleUploadModal(props: ComponentProps) { hideAllLanguages, } = props; - const { hide } = useModalControl(); - const [pending, setPending] = useState[]>([]); + 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(props: ComponentProps) { [columns, availableLanguages] ); - const showTable = pending.length > 0; - const canUpload = useMemo( () => pending.length > 0 && @@ -332,12 +332,7 @@ function SubtitleUploadModal(props: ComponentProps) { ); return ( - +
@@ -360,8 +355,8 @@ function SubtitleUploadModal(props: ComponentProps) { - + ); } -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]: Dispatch>; +}; + +export const ModalDataContext = createContext(null); +export const ModalSetterContext = createContext(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 = ({ 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 ( + + {children} + + ); +}; + +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

= FunctionComponent

& { + modalKey: string; +}; + +export default function withModal( + Content: FunctionComponent, + key: string +) { + const Comp: ModalComponent = (props: ModalProps & T) => { + const [closeable, setCloseable] = useState(true); + const [size, setSize] = useState(undefined); + const data: ModalData = useMemo( + () => ({ + key, + size, + closeable, + }), + [closeable, size] + ); + + const setter: ModalSetter = useMemo( + () => ({ + closeable: setCloseable, + size: setSize, + }), + [] + ); + + return ( + + + + + + + + ); + }; + 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 = ({ + children, + footer, + title, +}) => { + const { closeable } = useModalData(); + return ( + <> + {title} + {children} + + + ); +}; 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> & { + 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(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( +

(comp: ModalComponent

, 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 | 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(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 = () => { show("tools", episodes)} + onClick={() => show(SubtitleToolModal, episodes)} > Tools @@ -120,14 +120,14 @@ const SeriesEpisodesView: FunctionComponent = () => { !available } icon={faCloudUploadAlt} - onClick={() => show("upload", series)} + onClick={() => show(SeriesUploadModal, series)} > Upload show("edit", series)} + onClick={() => show(ItemEditorModal, series)} > Edit Series @@ -158,11 +158,8 @@ const SeriesEpisodesView: FunctionComponent = () => { > )} - - + + ); }; 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 = ({ icon={faUser} disabled={series?.profileId === null || disabled} onClick={() => { - show("manual-search", row.original); + show(EpisodeSearchModal, row.original); }} > { - show("history", row.original); + show(EpisodeHistoryModal, row.original); }} > { - show("tools", [row.original]); + show(SubtitleToolModal, [row.original]); }} > @@ -214,13 +209,12 @@ const Table: FunctionComponent = ({ }} emptyText="No Episode Found For This Series" > - - - + + + > ); }; 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 = () => { show("manual-search", movie)} + onClick={() => show(MovieSearchModal, movie)} > Manual show("history", movie)} + onClick={() => show(MovieHistoryModal, movie)} > History show("tools", [movie])} + onClick={() => show(SubtitleToolModal, [movie])} > Tools @@ -145,14 +144,14 @@ const MovieDetailView: FunctionComponent = () => { show("upload", movie)} + onClick={() => show(MovieUploadModal, movie)} > Upload show("edit", movie)} + onClick={() => show(ItemEditorModal, movie)} > Edit Movie @@ -174,15 +173,14 @@ const MovieDetailView: FunctionComponent = () => {
- - - - - + + + + + > ); }; 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 ( show("edit", row.original)} + onClick={() => show(ItemEditorModal, row.original)} > ); }, @@ -105,7 +106,7 @@ const MovieView: FunctionComponent = () => { Movies - Bazarr - + ); }; 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 ( show("edit", original)} + onClick={() => show(ItemEditorModal, original)} > ); }, @@ -107,7 +108,7 @@ const SeriesView: FunctionComponent = () => { Series - Bazarr - + ); }; 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 -) => { - const { update, ...modal } = props; - - const profile = usePayload(modal.modalKey); +const LanguagesProfileModal: FunctionComponent = ({ update }) => { + const profile = usePayload(); const { hide } = useModalControl(); @@ -66,13 +64,12 @@ const LanguagesProfileModal: FunctionComponent = ( 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[] = useMemo(() => { const options = [...cutoffOptions]; @@ -134,18 +131,6 @@ const LanguagesProfileModal: FunctionComponent = ( const canSave = current.name.length > 0 && current.items.length > 0; - const footer = ( - - ); - const columns = useMemo[]>( () => [ { @@ -253,8 +238,20 @@ const LanguagesProfileModal: FunctionComponent = ( [languages] ); + const footer = ( + + ); + return ( - + = ( > Download subtitle file without format conversion - + ); }; -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( (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"} - + ); }; 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 = ({ - selections, - ...modal -}) => { +const NotificationTool: FunctionComponent = ({ selections }) => { const options = useMemo[]>( () => selections @@ -43,16 +33,11 @@ const NotificationModal: FunctionComponent = ({ "name" ); - const payload = usePayload(modal.modalKey); - const { hide } = useModalControl(); + const payload = usePayload(); const [current, setCurrent] = useState>(payload); - useEffect(() => { - setCurrent(payload); - }, [payload]); - const updateUrl = useCallback((url: string) => { setCurrent((current) => { if (current) { @@ -69,55 +54,60 @@ const NotificationModal: FunctionComponent = ({ const canSave = current !== null && current?.url !== null && current?.url.length !== 0; - const footer = useMemo( - () => ( - <> - { - if (current && current.url) { - return api.system.testNotification(current.url); - } else { - return null; - } - }} - > - Test - - - - - ), - [canSave, payload, current, hide, update] - ); - const getLabel = useCallback((v: Settings.NotificationInfo) => v.name, []); + const Modal = useModal({ + onMounted: () => { + setCurrent(payload); + }, + }); + + const { hide } = useModalControl(); + + const footer = ( + <> + { + if (current && current.url) { + return api.system.testNotification(current.url); + } else { + return null; + } + }} + > + Test + + + + + ); + return ( - + @@ -145,10 +135,12 @@ const NotificationModal: FunctionComponent = ({ - + ); }; +const NotificationModal = withModal(NotificationTool, "notification-tool"); + export const NotificationView: FunctionComponent = () => { const notifications = useLatestArray( notificationsKey, @@ -165,7 +157,7 @@ export const NotificationView: FunctionComponent = () => { show("notifications", v)} + onClick={() => show(NotificationModal, v)} > )); }, [notifications, show]); @@ -174,12 +166,9 @@ export const NotificationView: FunctionComponent = () => { {elements}{" "} - show("notifications")}> + show(NotificationModal)}> - + ); }; 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} + ); }; -export const ProviderModal: FunctionComponent = () => { - const payload = usePayload(ModalKey); +const ProviderTool: FunctionComponent = () => { + const payload = usePayload(); + const Modal = useModal(); const { hide } = useModalControl(); const [staged, setChange] = useState({}); @@ -121,20 +122,6 @@ export const ProviderModal: FunctionComponent = () => { const canSave = info !== null; - const footer = useMemo( - () => ( - <> - - - - ), - [canSave, payload, deletePayload, addProvider] - ); - const onSelect = useCallback((item: Nullable) => { if (item) { setInfo(item); @@ -237,8 +224,19 @@ export const ProviderModal: FunctionComponent = () => { [] ); + const footer = ( + <> + + + + ); + return ( - + @@ -266,6 +264,8 @@ export const ProviderModal: FunctionComponent = () => { - + ); }; + +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 = () => { - ); }; 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 = ({ ...modal }) => { +const SystemBackupDeleteModal: FunctionComponent = () => { const { mutateAsync } = useDeleteBackups(); - const result = usePayload(modal.modalKey); + const result = usePayload(); + const Modal = useModal(); const { hide } = useModalControl(); const footer = ( @@ -19,9 +23,7 @@ const SystemBackupDeleteModal: FunctionComponent = ({ ...modal }) => { @@ -34,7 +36,7 @@ const SystemBackupDeleteModal: FunctionComponent = ({ ...modal }) => { return null; } }} - onSuccess={() => hide(modal.modalKey)} + onSuccess={() => hide()} > Delete @@ -43,10 +45,10 @@ const SystemBackupDeleteModal: FunctionComponent = ({ ...modal }) => { ); return ( - - Are you sure you want to delete the backup '{result}'? - + + Are you sure you want to delete the backup '{result}'? + ); }; -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(); -const SystemBackupRestoreModal: FunctionComponent = ({ ...modal }) => { - const result = usePayload(modal.modalKey); + const Modal = useModal(); + const { hide } = useModalControl(); const { mutateAsync } = useRestoreBackups(); - const { hide } = useModalControl(); - const footer = (

@@ -34,7 +36,7 @@ const SystemBackupRestoreModal: FunctionComponent = ({ ...modal }) => { return null; } }} - onSuccess={() => hide(modal.modalKey)} + onSuccess={() => hide()} > Restore @@ -43,11 +45,13 @@ const SystemBackupRestoreModal: FunctionComponent = ({ ...modal }) => { ); return ( - - Are you sure you want to restore the backup '{result}'? Bazarr will - automatically restart and reload the UI during the restore process. - + + + Are you sure you want to restore the backup '{result}'? Bazarr will + automatically restart and reload the UI during the restore process. + + ); }; -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 = ({ backups }) => { show("restore", row.row.original.filename)} + onClick={() => + show(SystemBackupRestoreModal, row.row.original.filename) + } > show("delete", row.row.original.filename)} + onClick={() => + show(SystemBackupDeleteModal, row.row.original.filename) + } > ); @@ -59,14 +63,8 @@ const Table: FunctionComponent = ({ backups }) => { return ( - - + + ); }; 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 = ({ ...modal }) => { - const stack = usePayload(modal.modalKey); +const SystemLogModal: FunctionComponent = () => { + const stack = usePayload(); + + const Modal = useModal(); + const result = useMemo( () => stack?.split("\\n").map((v, idx) => ( @@ -13,13 +15,14 @@ const SystemLogModal: FunctionComponent = ({ ...modal }) => { )), [stack] ); + return ( - +
         {result}
       
-
+ ); }; -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 = ({ logs }) => { return ( show("system-log", value)} + onClick={() => show(SystemLogModal, value)} > ); } else { @@ -75,7 +75,7 @@ const Table: FunctionComponent = ({ logs }) => { return ( <> - + ); };