1
0
Fork 0
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:
LASER-Yi 2021-08-17 01:34:26 +08:00
parent 37da3742a0
commit 82a687c8c8
9 changed files with 144 additions and 112 deletions

View file

@ -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) => {

View file

@ -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);

View file

@ -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>

View file

@ -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;

View file

@ -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>;

View file

@ -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();

View 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]);
}

View file

@ -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";

View file

@ -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;