mirror of
https://github.com/morpheus65535/bazarr
synced 2025-02-22 05:51:10 +00:00
Rewrite modal system using stack, fix some visual bugs
This commit is contained in:
parent
37da3742a0
commit
82a687c8c8
9 changed files with 144 additions and 112 deletions
|
@ -50,9 +50,7 @@ const NotificationModal: FunctionComponent<ModalProps & BaseModalProps> = ({
|
||||||
payload ?? null
|
payload ?? null
|
||||||
);
|
);
|
||||||
|
|
||||||
const onShow = useCallback(() => setCurrent(payload ?? null), [payload]);
|
useOnModalShow(() => setCurrent(payload ?? null), modal.modalKey);
|
||||||
|
|
||||||
useOnModalShow(modal.modalKey, onShow);
|
|
||||||
|
|
||||||
const updateUrl = useCallback(
|
const updateUrl = useCallback(
|
||||||
(s: string) => {
|
(s: string) => {
|
||||||
|
|
|
@ -84,9 +84,7 @@ export const ProviderModal: FunctionComponent = () => {
|
||||||
|
|
||||||
const [info, setInfo] = useState<Nullable<ProviderInfo>>(payload ?? null);
|
const [info, setInfo] = useState<Nullable<ProviderInfo>>(payload ?? null);
|
||||||
|
|
||||||
const onShow = useCallback(() => setInfo(payload ?? null), [payload]);
|
useOnModalShow(() => setInfo(payload ?? null), ModalKey);
|
||||||
|
|
||||||
useOnModalShow(ModalKey, onShow);
|
|
||||||
|
|
||||||
const providers = useLatest<string[]>(ProviderKey, isArray);
|
const providers = useLatest<string[]>(ProviderKey, isArray);
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { FunctionComponent } from "react";
|
import React, { FunctionComponent, useCallback, useState } from "react";
|
||||||
import { Modal } from "react-bootstrap";
|
import { Modal } from "react-bootstrap";
|
||||||
import { useIsModalShow } from ".";
|
import { useIsModalShow } from ".";
|
||||||
import { useCloseModal } from "./provider";
|
import { useCloseModal } from "./hooks";
|
||||||
|
|
||||||
export interface BaseModalProps {
|
export interface BaseModalProps {
|
||||||
modalKey: string;
|
modalKey: string;
|
||||||
|
@ -12,22 +12,33 @@ export interface BaseModalProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BaseModal: FunctionComponent<BaseModalProps> = (props) => {
|
export const BaseModal: FunctionComponent<BaseModalProps> = (props) => {
|
||||||
const { size, closeable, modalKey, title, children, footer } = props;
|
const { size, modalKey, title, children, footer } = props;
|
||||||
|
const [needExit, setExit] = useState(false);
|
||||||
|
|
||||||
const show = useIsModalShow(modalKey);
|
const show = useIsModalShow(modalKey);
|
||||||
const closeModal = useCloseModal();
|
const close = useCloseModal();
|
||||||
|
|
||||||
const canClose = closeable !== false;
|
const closeable = props.closeable !== false;
|
||||||
|
|
||||||
|
const hide = useCallback(() => {
|
||||||
|
setExit(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const exit = useCallback(() => {
|
||||||
|
close();
|
||||||
|
setExit(false);
|
||||||
|
}, [close]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
centered
|
centered
|
||||||
size={size}
|
size={size}
|
||||||
show={show}
|
show={show && !needExit}
|
||||||
onHide={closeModal}
|
onHide={hide}
|
||||||
backdrop={canClose ? undefined : "static"}
|
onExited={exit}
|
||||||
|
backdrop={closeable ? undefined : "static"}
|
||||||
>
|
>
|
||||||
<Modal.Header closeButton={canClose}>{title}</Modal.Header>
|
<Modal.Header closeButton={closeable}>{title}</Modal.Header>
|
||||||
<Modal.Body>{children}</Modal.Body>
|
<Modal.Body>{children}</Modal.Body>
|
||||||
<Modal.Footer hidden={footer === undefined}>{footer}</Modal.Footer>
|
<Modal.Footer hidden={footer === undefined}>{footer}</Modal.Footer>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { EpisodesApi, MoviesApi, useAsyncRequest } from "../../apis";
|
||||||
import { BlacklistButton } from "../../generic/blacklist";
|
import { BlacklistButton } from "../../generic/blacklist";
|
||||||
import { AsyncOverlay } from "../async";
|
import { AsyncOverlay } from "../async";
|
||||||
import BaseModal, { BaseModalProps } from "./BaseModal";
|
import BaseModal, { BaseModalProps } from "./BaseModal";
|
||||||
import { usePayload } from "./provider";
|
import { usePayload } from "./hooks";
|
||||||
|
|
||||||
export const MovieHistoryModal: FunctionComponent<BaseModalProps> = (props) => {
|
export const MovieHistoryModal: FunctionComponent<BaseModalProps> = (props) => {
|
||||||
const { ...modal } = props;
|
const { ...modal } = props;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { AsyncButton, Selector } from "../";
|
||||||
import { useLanguageProfiles } from "../../@redux/hooks";
|
import { useLanguageProfiles } from "../../@redux/hooks";
|
||||||
import { GetItemId } from "../../utilites";
|
import { GetItemId } from "../../utilites";
|
||||||
import BaseModal, { BaseModalProps } from "./BaseModal";
|
import BaseModal, { BaseModalProps } from "./BaseModal";
|
||||||
import { useCloseModal, usePayload } from "./provider";
|
import { useCloseModal, usePayload } from "./hooks";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
submit: (form: FormType.ModifyItem) => Promise<void>;
|
submit: (form: FormType.ModifyItem) => Promise<void>;
|
||||||
|
|
|
@ -48,7 +48,7 @@ import { isMovie, submodProcessColor } from "../../utilites";
|
||||||
import { log } from "../../utilites/logger";
|
import { log } from "../../utilites/logger";
|
||||||
import { useCustomSelection } from "../tables/plugins";
|
import { useCustomSelection } from "../tables/plugins";
|
||||||
import BaseModal, { BaseModalProps } from "./BaseModal";
|
import BaseModal, { BaseModalProps } from "./BaseModal";
|
||||||
import { useCloseModalUntil } from "./provider";
|
import { useCloseModalUntil } from "./hooks";
|
||||||
import { availableTranslation, colorOptions } from "./toolOptions";
|
import { availableTranslation, colorOptions } from "./toolOptions";
|
||||||
|
|
||||||
type SupportType = Item.Episode | Item.Movie;
|
type SupportType = Item.Episode | Item.Movie;
|
||||||
|
@ -337,12 +337,12 @@ const STM: FunctionComponent<BaseModalProps & STMProps> = ({ ...props }) => {
|
||||||
const [processState, setProcessState] = useState<ProcessState>({});
|
const [processState, setProcessState] = useState<ProcessState>({});
|
||||||
const [selections, setSelections] = useState<TableColumnType[]>([]);
|
const [selections, setSelections] = useState<TableColumnType[]>([]);
|
||||||
|
|
||||||
const closeUntil = useCloseModalUntil(props.modalKey);
|
const closeUntil = useCloseModalUntil();
|
||||||
|
|
||||||
const process = useCallback(
|
const process = useCallback(
|
||||||
async (action: string, override?: Partial<FormType.ModifySubtitle>) => {
|
async (action: string, override?: Partial<FormType.ModifySubtitle>) => {
|
||||||
log("info", "executing action", action);
|
log("info", "executing action", action);
|
||||||
closeUntil();
|
closeUntil(props.modalKey);
|
||||||
setUpdate(true);
|
setUpdate(true);
|
||||||
|
|
||||||
let states = selections.reduce<ProcessState>(
|
let states = selections.reduce<ProcessState>(
|
||||||
|
@ -374,7 +374,7 @@ const STM: FunctionComponent<BaseModalProps & STMProps> = ({ ...props }) => {
|
||||||
}
|
}
|
||||||
setUpdate(false);
|
setUpdate(false);
|
||||||
},
|
},
|
||||||
[closeUntil, selections]
|
[closeUntil, selections, props.modalKey]
|
||||||
);
|
);
|
||||||
|
|
||||||
const showModal = useShowModal();
|
const showModal = useShowModal();
|
||||||
|
|
76
frontend/src/components/modals/hooks.tsx
Normal file
76
frontend/src/components/modals/hooks.tsx
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import { useCallback, useContext, useMemo } from "react";
|
||||||
|
import { useDidUpdate } from "rooks";
|
||||||
|
import { log } from "../../utilites/logger";
|
||||||
|
import { ModalContext } from "./provider";
|
||||||
|
|
||||||
|
export function useShowModal() {
|
||||||
|
const {
|
||||||
|
control: { push },
|
||||||
|
} = useContext(ModalContext);
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
<T,>(key: string, payload?: T) => {
|
||||||
|
log("info", `modal ${key} sending payload`, payload);
|
||||||
|
|
||||||
|
push({ key, payload });
|
||||||
|
},
|
||||||
|
[push]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCloseModal() {
|
||||||
|
const {
|
||||||
|
control: { pop },
|
||||||
|
} = useContext(ModalContext);
|
||||||
|
return pop;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCloseModalUntil() {
|
||||||
|
const {
|
||||||
|
control: { pop, peek },
|
||||||
|
} = useContext(ModalContext);
|
||||||
|
return useCallback(
|
||||||
|
(key: string) => {
|
||||||
|
let modal = peek();
|
||||||
|
while (modal) {
|
||||||
|
if (modal.key === key) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
modal = pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[pop, peek]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useIsModalShow(key: string) {
|
||||||
|
const {
|
||||||
|
control: { peek },
|
||||||
|
} = useContext(ModalContext);
|
||||||
|
const modal = peek();
|
||||||
|
return key === modal?.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useOnModalShow(callback: () => void, key: string) {
|
||||||
|
const isShow = useIsModalShow(key);
|
||||||
|
useDidUpdate(() => {
|
||||||
|
if (isShow) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}, [isShow]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePayload<T>(key: string): T | null {
|
||||||
|
const {
|
||||||
|
control: { peek },
|
||||||
|
} = useContext(ModalContext);
|
||||||
|
return useMemo(() => {
|
||||||
|
const modal = peek();
|
||||||
|
if (modal && modal.key === key) {
|
||||||
|
return modal.payload as T;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, [key, peek]);
|
||||||
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
export * from "./BaseModal";
|
export * from "./BaseModal";
|
||||||
export * from "./HistoryModal";
|
export * from "./HistoryModal";
|
||||||
|
export * from "./hooks";
|
||||||
export { default as ItemEditorModal } from "./ItemEditorModal";
|
export { default as ItemEditorModal } from "./ItemEditorModal";
|
||||||
export { default as MovieUploadModal } from "./MovieUploadModal";
|
export { default as MovieUploadModal } from "./MovieUploadModal";
|
||||||
export * from "./provider";
|
export { default as ModalProvider } from "./provider";
|
||||||
export { default as SeriesUploadModal } from "./SeriesUploadModal";
|
export { default as SeriesUploadModal } from "./SeriesUploadModal";
|
||||||
export { default as SubtitleToolModal } from "./SubtitleToolModal";
|
export { default as SubtitleToolModal } from "./SubtitleToolModal";
|
||||||
|
|
|
@ -1,100 +1,48 @@
|
||||||
import React, {
|
import React, { FunctionComponent, useMemo } from "react";
|
||||||
Dispatch,
|
import { useStackState } from "rooks";
|
||||||
FunctionComponent,
|
|
||||||
useCallback,
|
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useState,
|
|
||||||
} from "react";
|
|
||||||
import { log } from "../../utilites/logger";
|
|
||||||
|
|
||||||
const ModalContext = React.createContext<[string[], Dispatch<string[]>]>([
|
interface Modal {
|
||||||
[],
|
key: string;
|
||||||
(s) => {},
|
payload: any;
|
||||||
]);
|
}
|
||||||
|
|
||||||
const PayloadContext = React.createContext<[any[], Dispatch<any[]>]>([
|
interface ModalControl {
|
||||||
[],
|
push: (modal: Modal) => void;
|
||||||
(p) => {},
|
peek: () => Modal | undefined;
|
||||||
]);
|
pop: () => Modal | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Performance
|
interface ModalContextType {
|
||||||
export function useShowModal() {
|
modals: Modal[];
|
||||||
const [keys, setKeys] = useContext(ModalContext);
|
control: ModalControl;
|
||||||
const [payloads, setPayloads] = useContext(PayloadContext);
|
}
|
||||||
return useCallback(
|
|
||||||
<T,>(key: string, payload?: T) => {
|
|
||||||
log("info", `modal ${key} sending payload`, payload);
|
|
||||||
|
|
||||||
setKeys([...keys, key]);
|
export const ModalContext = React.createContext<ModalContextType>({
|
||||||
setPayloads([...payloads, payload ?? null]);
|
modals: [],
|
||||||
|
control: {
|
||||||
|
push: () => {
|
||||||
|
throw new Error("Unimplemented");
|
||||||
},
|
},
|
||||||
[keys, payloads, setKeys, setPayloads]
|
pop: () => {
|
||||||
|
throw new Error("Unimplemented");
|
||||||
|
},
|
||||||
|
peek: () => {
|
||||||
|
throw new Error("Unimplemented");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const ModalProvider: FunctionComponent = ({ children }) => {
|
||||||
|
const [stack, { push, pop, peek }] = useStackState([]);
|
||||||
|
|
||||||
|
const context = useMemo<ModalContextType>(
|
||||||
|
() => ({ modals: stack, control: { push, pop, peek } }),
|
||||||
|
[stack, push, pop, peek]
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
export function useCloseModal() {
|
|
||||||
const [keys, setKeys] = useContext(ModalContext);
|
|
||||||
const [payloads, setPayloads] = useContext(PayloadContext);
|
|
||||||
return useCallback(() => {
|
|
||||||
const newKey = [...keys];
|
|
||||||
newKey.pop();
|
|
||||||
const newPayload = [...payloads];
|
|
||||||
newPayload.pop();
|
|
||||||
setKeys(newKey);
|
|
||||||
setPayloads(newPayload);
|
|
||||||
}, [keys, payloads, setKeys, setPayloads]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useCloseModalUntil(key: string) {
|
|
||||||
const [keys, setKeys] = useContext(ModalContext);
|
|
||||||
const [payloads, setPayloads] = useContext(PayloadContext);
|
|
||||||
return useCallback(() => {
|
|
||||||
const idx = keys.findIndex((v) => v === key);
|
|
||||||
if (idx !== -1) {
|
|
||||||
const newKey = keys.slice(0, idx + 1);
|
|
||||||
const newPayload = payloads.slice(0, idx + 1);
|
|
||||||
setKeys(newKey);
|
|
||||||
setPayloads(newPayload);
|
|
||||||
} else {
|
|
||||||
log("error", "Cannot close modal, key is unavailable");
|
|
||||||
}
|
|
||||||
}, [keys, payloads, setKeys, setPayloads, key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useIsModalShow(key: string) {
|
|
||||||
const keys = useContext(ModalContext)[0];
|
|
||||||
return key === keys[keys.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useOnModalShow(key: string, show: () => void) {
|
|
||||||
const isShow = useIsModalShow(key);
|
|
||||||
useEffect(() => {
|
|
||||||
if (isShow) {
|
|
||||||
show();
|
|
||||||
}
|
|
||||||
}, [isShow, show]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function usePayload<T>(key: string): T | null {
|
|
||||||
const payloads = useContext(PayloadContext)[0];
|
|
||||||
const keys = useContext(ModalContext)[0];
|
|
||||||
return useMemo(() => {
|
|
||||||
const idx = keys.findIndex((v) => v === key);
|
|
||||||
return idx !== -1 ? payloads[idx] : null;
|
|
||||||
}, [keys, payloads, key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ModalProvider: FunctionComponent = ({ children }) => {
|
|
||||||
const [key, setKey] = useState<string[]>([]);
|
|
||||||
const [payload, setPayload] = useState<any[]>([]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalContext.Provider value={[key, setKey]}>
|
<ModalContext.Provider value={context}>{children}</ModalContext.Provider>
|
||||||
<PayloadContext.Provider value={[payload, setPayload]}>
|
|
||||||
{children}
|
|
||||||
</PayloadContext.Provider>
|
|
||||||
</ModalContext.Provider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default ModalProvider;
|
||||||
|
|
Loading…
Reference in a new issue