aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2023-09-21 15:35:27 +0800
committercrupest <crupest@outlook.com>2023-09-21 15:35:27 +0800
commitc7d2545ec7bbcdba30b775453d53df5a359410bd (patch)
tree768668b9d1b2cd835212c3a3567dd8f54700a7fa /FrontEnd/src
parentf836d77e73f3ea0af45c5f71dae7268143d6d86f (diff)
downloadtimeline-c7d2545ec7bbcdba30b775453d53df5a359410bd.tar.gz
timeline-c7d2545ec7bbcdba30b775453d53df5a359410bd.tar.bz2
timeline-c7d2545ec7bbcdba30b775453d53df5a359410bd.zip
Revert dialog.
Diffstat (limited to 'FrontEnd/src')
-rw-r--r--FrontEnd/src/components/dialog/ConfirmDialog.tsx13
-rw-r--r--FrontEnd/src/components/dialog/Dialog.css32
-rw-r--r--FrontEnd/src/components/dialog/Dialog.tsx68
-rw-r--r--FrontEnd/src/components/dialog/DialogProvider.tsx95
-rw-r--r--FrontEnd/src/components/dialog/FullPageDialog.css15
-rw-r--r--FrontEnd/src/components/dialog/FullPageDialog.tsx65
-rw-r--r--FrontEnd/src/components/dialog/OperationDialog.tsx15
-rw-r--r--FrontEnd/src/components/dialog/index.tsx65
-rw-r--r--FrontEnd/src/components/menu/PopupMenu.tsx40
-rw-r--r--FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx28
-rw-r--r--FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx10
-rw-r--r--FrontEnd/src/pages/setting/ChangePasswordDialog.tsx10
-rw-r--r--FrontEnd/src/pages/setting/index.tsx73
-rw-r--r--FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx12
-rw-r--r--FrontEnd/src/pages/timeline/TimelineInfoCard.tsx58
-rw-r--r--FrontEnd/src/pages/timeline/TimelinePostView.tsx52
-rw-r--r--FrontEnd/src/pages/timeline/TimelinePropertyChangeDialog.tsx7
17 files changed, 340 insertions, 318 deletions
diff --git a/FrontEnd/src/components/dialog/ConfirmDialog.tsx b/FrontEnd/src/components/dialog/ConfirmDialog.tsx
index 8b0a4219..4ee0ec03 100644
--- a/FrontEnd/src/components/dialog/ConfirmDialog.tsx
+++ b/FrontEnd/src/components/dialog/ConfirmDialog.tsx
@@ -2,14 +2,17 @@ import { useC, Text, ThemeColor } from "../common";
import Dialog from "./Dialog";
import DialogContainer from "./DialogContainer";
-import { useCloseDialog } from "./DialogProvider";
export default function ConfirmDialog({
+ open,
+ onClose,
onConfirm,
title,
body,
color,
}: {
+ open: boolean;
+ onClose: () => void;
onConfirm: () => void;
title: Text;
body: Text;
@@ -18,10 +21,8 @@ export default function ConfirmDialog({
}) {
const c = useC();
- const closeDialog = useCloseDialog();
-
return (
- <Dialog color={color ?? "danger"}>
+ <Dialog open={open} onClose={onClose} color={color ?? "danger"}>
<DialogContainer
title={title}
titleColor={color ?? "danger"}
@@ -32,7 +33,7 @@ export default function ConfirmDialog({
action: "minor",
text: "operationDialog.cancel",
- onClick: closeDialog,
+ onClick: onClose,
},
{
key: "confirm",
@@ -42,7 +43,7 @@ export default function ConfirmDialog({
color: "danger",
onClick: () => {
onConfirm();
- closeDialog();
+ onClose();
},
},
]}
diff --git a/FrontEnd/src/components/dialog/Dialog.css b/FrontEnd/src/components/dialog/Dialog.css
index 23b663db..6b34091a 100644
--- a/FrontEnd/src/components/dialog/Dialog.css
+++ b/FrontEnd/src/components/dialog/Dialog.css
@@ -7,6 +7,7 @@
bottom: 0;
overflow: auto;
padding: 20vh 1em;
+ user-select: none;
}
.cru-dialog-background {
@@ -30,6 +31,7 @@
border-radius: 5px;
padding: 1.5em;
background-color: var(--cru-dialog-container-background-color);
+ user-select: unset;
}
@media (min-width: 576px) {
@@ -37,3 +39,33 @@
max-width: 800px;
}
}
+
+.cru-dialog-enter .cru-dialog-container {
+ opacity: 0;
+ transform: scale(0, 0);
+ transform-origin: center;
+}
+
+.cru-dialog-enter-active .cru-dialog-container {
+ opacity: 1;
+ transform: scale(1, 1);
+ transform-origin: center;
+ transition:
+ transform 0.3s,
+ opacity 0.3s;
+}
+
+.cru-dialog-exit .cru-dialog-container {
+ opacity: 1;
+ transform: scale(1, 1);
+ transform-origin: center;
+}
+
+.cru-dialog-exit-active .cru-dialog-container {
+ opacity: 0;
+ transform: scale(0, 0);
+ transform-origin: center;
+ transition:
+ transform 0.3s,
+ opacity 0.3s;
+}
diff --git a/FrontEnd/src/components/dialog/Dialog.tsx b/FrontEnd/src/components/dialog/Dialog.tsx
index 043a8eec..15c898f1 100644
--- a/FrontEnd/src/components/dialog/Dialog.tsx
+++ b/FrontEnd/src/components/dialog/Dialog.tsx
@@ -1,55 +1,59 @@
import { ReactNode, useRef } from "react";
-import ReactDOM from "react-dom";
import classNames from "classnames";
+import { CSSTransition } from "react-transition-group";
-import { ThemeColor, UiLogicError } from "../common";
-
-import { useCloseDialog } from "./DialogProvider";
+import { ThemeColor } from "../common";
import "./Dialog.css";
-const optionalPortalElement = document.getElementById("portal");
-if (optionalPortalElement == null) {
- throw new UiLogicError();
-}
-const portalElement = optionalPortalElement;
-
interface DialogProps {
+ open: boolean;
+ onClose: () => void;
color?: ThemeColor;
children?: ReactNode;
disableCloseOnClickOnOverlay?: boolean;
}
export default function Dialog({
+ open,
+ onClose,
color,
children,
disableCloseOnClickOnOverlay,
}: DialogProps) {
- const closeDialog = useCloseDialog();
-
+ const nodeRef = useRef(null);
const lastPointerDownIdRef = useRef<number | null>(null);
- return ReactDOM.createPortal(
- <div
- className={classNames(
- `cru-theme-${color ?? "primary"}`,
- "cru-dialog-overlay",
- )}
+ return (
+ <CSSTransition
+ nodeRef={nodeRef}
+ mountOnEnter
+ unmountOnExit
+ in={open}
+ timeout={300}
+ classNames="cru-dialog"
>
<div
- className="cru-dialog-background"
- onPointerDown={(e) => {
- lastPointerDownIdRef.current = e.pointerId;
- }}
- onPointerUp={(e) => {
- if (lastPointerDownIdRef.current === e.pointerId) {
- if (!disableCloseOnClickOnOverlay) closeDialog();
- }
- lastPointerDownIdRef.current = null;
- }}
- />
- <div className="cru-dialog-container">{children}</div>
- </div>,
- portalElement,
+ ref={nodeRef}
+ className={classNames(
+ `cru-theme-${color ?? "primary"}`,
+ "cru-dialog-overlay",
+ )}
+ >
+ <div
+ className="cru-dialog-background"
+ onPointerDown={(e) => {
+ lastPointerDownIdRef.current = e.pointerId;
+ }}
+ onPointerUp={(e) => {
+ if (lastPointerDownIdRef.current === e.pointerId) {
+ if (!disableCloseOnClickOnOverlay) onClose();
+ }
+ lastPointerDownIdRef.current = null;
+ }}
+ />
+ <div className="cru-dialog-container">{children}</div>
+ </div>
+ </CSSTransition>
);
}
diff --git a/FrontEnd/src/components/dialog/DialogProvider.tsx b/FrontEnd/src/components/dialog/DialogProvider.tsx
deleted file mode 100644
index bb85e4cf..00000000
--- a/FrontEnd/src/components/dialog/DialogProvider.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import { useState, useContext, createContext, ReactNode } from "react";
-
-import { UiLogicError } from "../common";
-
-type DialogMap<D extends string> = {
- [K in D]: ReactNode;
-};
-
-interface DialogController<D extends string> {
- currentDialog: D | null;
- currentDialogReactNode: ReactNode;
- canSwitchDialog: boolean;
- switchDialog: (newDialog: D | null) => void;
- setCanSwitchDialog: (enable: boolean) => void;
- closeDialog: () => void;
- forceSwitchDialog: (newDialog: D | null) => void;
- forceCloseDialog: () => void;
-}
-
-export function useDialog<D extends string>(
- dialogs: DialogMap<D>,
- options?: {
- initDialog?: D | null;
- onClose?: {
- [K in D]?: () => void;
- };
- },
-): {
- controller: DialogController<D>;
- switchDialog: (newDialog: D | null) => void;
- forceSwitchDialog: (newDialog: D | null) => void;
- createDialogSwitch: (newDialog: D | null) => () => void;
-} {
- const [canSwitchDialog, setCanSwitchDialog] = useState<boolean>(true);
- const [dialog, setDialog] = useState<D | null>(options?.initDialog ?? null);
-
- const forceSwitchDialog = (newDialog: D | null) => {
- if (dialog != null) {
- options?.onClose?.[dialog]?.();
- }
- setDialog(newDialog);
- setCanSwitchDialog(true);
- };
-
- const switchDialog = (newDialog: D | null) => {
- if (canSwitchDialog) {
- forceSwitchDialog(newDialog);
- }
- };
-
- const controller: DialogController<D> = {
- currentDialog: dialog,
- currentDialogReactNode: dialog == null ? null : dialogs[dialog],
- canSwitchDialog,
- switchDialog,
- setCanSwitchDialog,
- closeDialog: () => switchDialog(null),
- forceSwitchDialog,
- forceCloseDialog: () => forceSwitchDialog(null),
- };
-
- return {
- controller,
- switchDialog,
- forceSwitchDialog,
- createDialogSwitch: (newDialog: D | null) => () => switchDialog(newDialog),
- };
-}
-
-const DialogControllerContext = createContext<DialogController<string> | null>(
- null,
-);
-
-export function useDialogController(): DialogController<string> {
- const controller = useContext(DialogControllerContext);
- if (controller == null) throw new UiLogicError("not in dialog provider");
- return controller;
-}
-
-export function useCloseDialog(): () => void {
- const controller = useDialogController();
- return controller.closeDialog;
-}
-
-export function DialogProvider<D extends string>({
- controller,
-}: {
- controller: DialogController<D>;
-}) {
- return (
- <DialogControllerContext.Provider value={controller as never}>
- {controller.currentDialogReactNode}
- </DialogControllerContext.Provider>
- );
-}
diff --git a/FrontEnd/src/components/dialog/FullPageDialog.css b/FrontEnd/src/components/dialog/FullPageDialog.css
index ce07c6ac..3ba8ba15 100644
--- a/FrontEnd/src/components/dialog/FullPageDialog.css
+++ b/FrontEnd/src/components/dialog/FullPageDialog.css
@@ -28,3 +28,18 @@
.cru-dialog-full-page-back-button {
margin-left: 0.5em;
}
+
+.cru-dialog-full-page-enter {
+ transform: translate(100%, 0);
+}
+
+.cru-dialog-full-page-enter-active {
+ transform: none;
+ transition: transform 0.3s;
+}
+
+.cru-dialog-full-page-exit-active {
+ transition: transform 0.3s;
+ transform: translate(100%, 0);
+}
+
diff --git a/FrontEnd/src/components/dialog/FullPageDialog.tsx b/FrontEnd/src/components/dialog/FullPageDialog.tsx
index 575abf7f..ad5abfde 100644
--- a/FrontEnd/src/components/dialog/FullPageDialog.tsx
+++ b/FrontEnd/src/components/dialog/FullPageDialog.tsx
@@ -1,52 +1,59 @@
-import { ReactNode } from "react";
-import { createPortal } from "react-dom";
+import { ReactNode, useRef } from "react";
import classNames from "classnames";
+import { CSSTransition } from "react-transition-group";
-import { ThemeColor, UiLogicError } from "../common";
+import { ThemeColor } from "../common";
import { IconButton } from "../button";
-import { useCloseDialog } from "./DialogProvider";
-
import "./FullPageDialog.css";
-const optionalPortalElement = document.getElementById("portal");
-if (optionalPortalElement == null) {
- throw new UiLogicError();
-}
-const portalElement = optionalPortalElement;
-
interface FullPageDialogProps {
+ open: boolean;
+ onClose: () => void;
color?: ThemeColor;
contentContainerClassName?: string;
children: ReactNode;
}
export default function FullPageDialog({
+ open,
+ onClose,
color,
children,
contentContainerClassName,
}: FullPageDialogProps) {
- const closeDialog = useCloseDialog();
+ const nodeRef = useRef(null);
- return createPortal(
- <div className={`cru-dialog-full-page cru-theme-${color ?? "primary"}`}>
- <div className="cru-dialog-full-page-top-bar">
- <IconButton
- icon="arrow-left"
- color="light"
- className="cru-dialog-full-page-back-button"
- onClick={closeDialog}
- />
- </div>
+ return (
+ <CSSTransition
+ nodeRef={nodeRef}
+ mountOnEnter
+ unmountOnExit
+ in={open}
+ timeout={300}
+ classNames="cru-dialog-full-page"
+ >
<div
- className={classNames(
- "cru-dialog-full-page-content-container",
- contentContainerClassName,
- )}
+ ref={nodeRef}
+ className={`cru-dialog-full-page cru-theme-${color ?? "primary"}`}
>
- {children}
+ <div className="cru-dialog-full-page-top-bar">
+ <IconButton
+ icon="arrow-left"
+ color="light"
+ className="cru-dialog-full-page-back-button"
+ onClick={onClose}
+ />
+ </div>
+ <div
+ className={classNames(
+ "cru-dialog-full-page-content-container",
+ contentContainerClassName,
+ )}
+ >
+ {children}
+ </div>
</div>
- </div>,
- portalElement,
+ </CSSTransition>
);
}
diff --git a/FrontEnd/src/components/dialog/OperationDialog.tsx b/FrontEnd/src/components/dialog/OperationDialog.tsx
index 6ca4d0a0..feaf5c79 100644
--- a/FrontEnd/src/components/dialog/OperationDialog.tsx
+++ b/FrontEnd/src/components/dialog/OperationDialog.tsx
@@ -11,7 +11,6 @@ import {
import { ButtonRowV2 } from "../button";
import Dialog from "./Dialog";
import DialogContainer from "./DialogContainer";
-import { useDialogController } from "./DialogProvider";
import "./OperationDialog.css";
@@ -36,6 +35,8 @@ function OperationDialogPrompt(props: OperationDialogPromptProps) {
}
export interface OperationDialogProps<TData> {
+ open: boolean;
+ onClose: () => void;
color?: ThemeColor;
inputColor?: ThemeColor;
title: Text;
@@ -54,6 +55,8 @@ export interface OperationDialogProps<TData> {
function OperationDialog<TData>(props: OperationDialogProps<TData>) {
const {
+ open,
+ onClose,
color,
inputColor,
title,
@@ -92,8 +95,6 @@ function OperationDialog<TData>(props: OperationDialogProps<TData>) {
data: unknown;
};
- const dialogController = useDialogController();
-
const [step, setStep] = useState<Step>({ type: "input" });
const { inputGroupProps, hasErrorAndDirty, setAllDisabled, confirm } =
@@ -103,7 +104,7 @@ function OperationDialog<TData>(props: OperationDialogProps<TData>) {
function close() {
if (step.type !== "process") {
- dialogController.closeDialog();
+ onClose();
if (step.type === "success" && onSuccessAndClose) {
onSuccessAndClose?.(step.data);
}
@@ -116,7 +117,6 @@ function OperationDialog<TData>(props: OperationDialogProps<TData>) {
const result = confirm();
if (result.type === "ok") {
setStep({ type: "process" });
- dialogController.setCanSwitchDialog(false);
setAllDisabled(true);
onProcess(result.values)
.then(
@@ -133,9 +133,6 @@ function OperationDialog<TData>(props: OperationDialogProps<TData>) {
});
},
)
- .finally(() => {
- dialogController.setCanSwitchDialog(true);
- });
}
}
@@ -210,7 +207,7 @@ function OperationDialog<TData>(props: OperationDialogProps<TData>) {
}
return (
- <Dialog color={color}>
+ <Dialog open={open} onClose={close} color={color}>
<DialogContainer title={title} titleColor={color} buttonsV2={buttons}>
{body}
</DialogContainer>
diff --git a/FrontEnd/src/components/dialog/index.tsx b/FrontEnd/src/components/dialog/index.tsx
index 9ca06de2..17db8fd0 100644
--- a/FrontEnd/src/components/dialog/index.tsx
+++ b/FrontEnd/src/components/dialog/index.tsx
@@ -1,12 +1,65 @@
+import { useState } from "react";
+
export { default as Dialog } from "./Dialog";
export { default as FullPageDialog } from "./FullPageDialog";
export { default as OperationDialog } from "./OperationDialog";
export { default as ConfirmDialog } from "./ConfirmDialog";
export { default as DialogContainer } from "./DialogContainer";
-export {
- useDialog,
- useDialogController,
- useCloseDialog,
- DialogProvider,
-} from "./DialogProvider";
+type DialogMap<D extends string, V> = {
+ [K in D]: V;
+};
+
+type DialogKeyMap<D extends string> = DialogMap<D, number>;
+
+type DialogPropsMap<D extends string> = DialogMap<
+ D,
+ { key: number | string; open: boolean; onClose: () => void }
+>;
+
+export function useDialog<D extends string>(
+ dialogs: D[],
+ options?: {
+ initDialog?: D | null;
+ onClose?: {
+ [K in D]?: () => void;
+ };
+ },
+): {
+ dialog: D | null;
+ switchDialog: (newDialog: D | null) => void;
+ dialogPropsMap: DialogPropsMap<D>;
+ createDialogSwitch: (newDialog: D | null) => () => void;
+} {
+ const [dialog, setDialog] = useState<D | null>(options?.initDialog ?? null);
+
+ const [dialogKeys, setDialogKeys] = useState<DialogKeyMap<D>>(
+ () => Object.fromEntries(dialogs.map((d) => [d, 0])) as DialogKeyMap<D>,
+ );
+
+ const switchDialog = (newDialog: D | null) => {
+ if (dialog !== null) {
+ setDialogKeys({ ...dialogKeys, [dialog]: dialogKeys[dialog] + 1 });
+ }
+ setDialog(newDialog);
+ };
+
+ return {
+ dialog,
+ switchDialog,
+ dialogPropsMap: Object.fromEntries(
+ dialogs.map((d) => [
+ d,
+ {
+ key: `${d}-${dialogKeys[d]}`,
+ open: dialog === d,
+ onClose: () => {
+ switchDialog(null);
+ options?.onClose?.[d]?.();
+ },
+ },
+ ]),
+ ) as DialogPropsMap<D>,
+ createDialogSwitch: (newDialog: D | null) => () => switchDialog(newDialog),
+ };
+}
diff --git a/FrontEnd/src/components/menu/PopupMenu.tsx b/FrontEnd/src/components/menu/PopupMenu.tsx
index 7ac2abfe..b00bc2ed 100644
--- a/FrontEnd/src/components/menu/PopupMenu.tsx
+++ b/FrontEnd/src/components/menu/PopupMenu.tsx
@@ -1,6 +1,5 @@
import { useState, CSSProperties, ReactNode } from "react";
import classNames from "classnames";
-import { createPortal } from "react-dom";
import { usePopper } from "react-popper";
import { ThemeColor } from "../common";
@@ -46,27 +45,24 @@ export default function PopupMenu({
onClick={() => setShow(true)}
>
{children}
- {show &&
- createPortal(
- <div
- ref={setPopperElement}
- className={`cru-popup-menu-menu-container cru-clickable-${
- color ?? "primary"
- }`}
- style={styles.popper}
- {...attributes.popper}
- >
- <Menu
- items={items}
- onItemClick={(e) => {
- setShow(false);
- e.stopPropagation();
- }}
- />
- </div>,
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- document.getElementById("portal")!,
- )}
+ {show && (
+ <div
+ ref={setPopperElement}
+ className={`cru-popup-menu-menu-container cru-clickable-${
+ color ?? "primary"
+ }`}
+ style={styles.popper}
+ {...attributes.popper}
+ >
+ <Menu
+ items={items}
+ onItemClick={(e) => {
+ setShow(false);
+ e.stopPropagation();
+ }}
+ />
+ </div>
+ )}
</div>
);
}
diff --git a/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx b/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx
index 0df10411..4cdecbbb 100644
--- a/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx
+++ b/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx
@@ -9,21 +9,21 @@ import { getHttpUserClient } from "~src/http/user";
import { ImageCropper, useImageCrop } from "~src/components/ImageCropper";
import BlobImage from "~src/components/BlobImage";
import { ButtonRowV2 } from "~src/components/button";
-import {
- Dialog,
- DialogContainer,
- useDialogController,
-} from "~src/components/dialog";
+import { Dialog, DialogContainer } from "~src/components/dialog";
import "./ChangeAvatarDialog.css";
-export default function ChangeAvatarDialog() {
+export default function ChangeAvatarDialog({
+ open,
+ onClose,
+}: {
+ open: boolean;
+ onClose: () => void;
+}) {
const c = useC();
const user = useUser();
- const controller = useDialogController();
-
type State =
| "select"
| "crop"
@@ -47,7 +47,9 @@ export default function ChangeAvatarDialog() {
"settings.dialogChangeAvatar.prompt.select",
);
- const close = controller.closeDialog;
+ const close = () => {
+ if (state !== "uploading") onClose();
+ };
const onSelectFile = (e: ChangeEvent<HTMLInputElement>): void => {
const files = e.target.files;
@@ -90,7 +92,6 @@ export default function ChangeAvatarDialog() {
}
setState("uploading");
- controller.setCanSwitchDialog(false);
getHttpUserClient()
.putAvatar(user.username, resultBlob)
.then(
@@ -101,10 +102,7 @@ export default function ChangeAvatarDialog() {
setState("error");
setMessage("operationDialog.error");
},
- )
- .finally(() => {
- controller.setCanSwitchDialog(true);
- });
+ );
};
const cancelButton = {
@@ -177,7 +175,7 @@ export default function ChangeAvatarDialog() {
};
return (
- <Dialog>
+ <Dialog open={open} onClose={close}>
<DialogContainer
title="settings.dialogChangeAvatar.title"
titleColor="primary"
diff --git a/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx b/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx
index 912f554f..964e4c1a 100644
--- a/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx
+++ b/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx
@@ -3,11 +3,19 @@ import { useUserLoggedIn } from "~src/services/user";
import { OperationDialog } from "~src/components/dialog";
-export default function ChangeNicknameDialog() {
+export default function ChangeNicknameDialog({
+ open,
+ onClose,
+}: {
+ open: boolean;
+ onClose: () => void;
+}) {
const user = useUserLoggedIn();
return (
<OperationDialog
+ open={open}
+ onClose={onClose}
title="settings.dialogChangeNickname.title"
inputs={[
{
diff --git a/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx b/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx
index c3111ac8..6fb8589f 100644
--- a/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx
+++ b/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx
@@ -5,13 +5,21 @@ import { userService } from "~src/services/user";
import { OperationDialog } from "~src/components/dialog";
-export function ChangePasswordDialog() {
+export function ChangePasswordDialog({
+ open,
+ onClose,
+}: {
+ open: boolean;
+ onClose: () => void;
+}) {
const navigate = useNavigate();
const [redirect, setRedirect] = useState<boolean>(false);
return (
<OperationDialog
+ open={open}
+ onClose={onClose}
title="settings.dialogChangePassword.title"
color="danger"
inputPrompt="settings.dialogChangePassword.prompt"
diff --git a/FrontEnd/src/pages/setting/index.tsx b/FrontEnd/src/pages/setting/index.tsx
index 88ab5cb2..3fb18e24 100644
--- a/FrontEnd/src/pages/setting/index.tsx
+++ b/FrontEnd/src/pages/setting/index.tsx
@@ -14,11 +14,7 @@ import { getHttpUserClient } from "~src/http/user";
import { useC, Text } from "~src/common";
import { pushAlert } from "~src/components/alert";
-import {
- useDialog,
- DialogProvider,
- ConfirmDialog,
-} from "~src/components/dialog";
+import { useDialog, ConfirmDialog } from "~src/components/dialog";
import Card from "~src/components/Card";
import Spinner from "~src/components/Spinner";
import Page from "~src/components/Page";
@@ -144,22 +140,7 @@ function RegisterCodeSettingItem() {
// undefined: loading
const [registerCode, setRegisterCode] = useState<undefined | null | string>();
- const { controller, createDialogSwitch } = useDialog({
- confirm: (
- <ConfirmDialog
- title="settings.renewRegisterCode"
- body="settings.renewRegisterCodeDesc"
- onConfirm={() => {
- if (user == null) throw new Error();
- void getHttpUserClient()
- .renewRegisterCode(user.username)
- .then(() => {
- setRegisterCode(undefined);
- });
- }}
- />
- ),
- });
+ const { createDialogSwitch, dialogPropsMap } = useDialog(["confirm"]);
useEffect(() => {
setRegisterCode(undefined);
@@ -204,7 +185,19 @@ function RegisterCodeSettingItem() {
</code>
)}
</SettingItemContainer>
- <DialogProvider controller={controller} />
+ <ConfirmDialog
+ title="settings.renewRegisterCode"
+ body="settings.renewRegisterCodeDesc"
+ onConfirm={() => {
+ if (user == null) throw new Error();
+ void getHttpUserClient()
+ .renewRegisterCode(user.username)
+ .then(() => {
+ setRegisterCode(undefined);
+ });
+ }}
+ {...dialogPropsMap["confirm"]}
+ />
</>
);
}
@@ -246,22 +239,12 @@ export default function SettingPage() {
const user = useUser();
const navigate = useNavigate();
- const { controller, createDialogSwitch } = useDialog({
- "change-nickname": <ChangeNicknameDialog />,
- "change-avatar": <ChangeAvatarDialog />,
- "change-password": <ChangePasswordDialog />,
- logout: (
- <ConfirmDialog
- title="settings.dialogConfirmLogout.title"
- body="settings.dialogConfirmLogout.prompt"
- onConfirm={() => {
- void userService.logout().then(() => {
- navigate("/");
- });
- }}
- />
- ),
- });
+ const { createDialogSwitch, dialogPropsMap } = useDialog([
+ "change-nickname",
+ "change-avatar",
+ "change-password",
+ "logout",
+ ]);
return (
<Page noTopPadding>
@@ -286,12 +269,24 @@ export default function SettingPage() {
onClick={createDialogSwitch("logout")}
danger
/>
+ <ChangeNicknameDialog {...dialogPropsMap["change-nickname"]} />
+ <ChangeAvatarDialog {...dialogPropsMap["change-avatar"]} />
+ <ChangePasswordDialog {...dialogPropsMap["change-password"]} />
+ <ConfirmDialog
+ title="settings.dialogConfirmLogout.title"
+ body="settings.dialogConfirmLogout.prompt"
+ onConfirm={() => {
+ void userService.logout().then(() => {
+ navigate("/");
+ });
+ }}
+ {...dialogPropsMap["logout"]}
+ />
</SettingSection>
) : null}
<SettingSection title="settings.subheader.customization">
<LanguageChangeSettingItem />
</SettingSection>
- <DialogProvider controller={controller} />
</Page>
);
}
diff --git a/FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx b/FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx
index d1af364b..f1b741c2 100644
--- a/FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx
+++ b/FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx
@@ -6,14 +6,22 @@ import { getHttpTimelineClient, HttpTimelineInfo } from "~src/http/timeline";
import { OperationDialog } from "~src/components/dialog";
interface TimelineDeleteDialog {
+ open: boolean;
+ onClose: () => void;
timeline: HttpTimelineInfo;
}
-export default function TimelineDeleteDialog({ timeline }: TimelineDeleteDialog) {
+export default function TimelineDeleteDialog({
+ open,
+ onClose,
+ timeline,
+}: TimelineDeleteDialog) {
const navigate = useNavigate();
return (
<OperationDialog
+ open={open}
+ onClose={onClose}
title="timeline.deleteDialog.title"
color="danger"
inputPromptNode={
@@ -49,6 +57,6 @@ export default function TimelineDeleteDialog({ timeline }: TimelineDeleteDialog)
}}
/>
);
-};
+}
TimelineDeleteDialog;
diff --git a/FrontEnd/src/pages/timeline/TimelineInfoCard.tsx b/FrontEnd/src/pages/timeline/TimelineInfoCard.tsx
index 2bc40877..ae58f4aa 100644
--- a/FrontEnd/src/pages/timeline/TimelineInfoCard.tsx
+++ b/FrontEnd/src/pages/timeline/TimelineInfoCard.tsx
@@ -9,12 +9,7 @@ import { getHttpBookmarkClient } from "~src/http/bookmark";
import { pushAlert } from "~src/components/alert";
import { useMobile } from "~src/components/hooks";
import { IconButton } from "~src/components/button";
-import {
- Dialog,
- FullPageDialog,
- DialogProvider,
- useDialog,
-} from "~src/components/dialog";
+import { Dialog, FullPageDialog, useDialog } from "~src/components/dialog";
import UserAvatar from "~src/components/user/UserAvatar";
import PopupMenu from "~src/components/menu/PopupMenu";
import Card from "~src/components/Card";
@@ -57,17 +52,11 @@ function TimelineInfoContent({
}: Omit<TimelineInfoCardProps, "connectionStatus">) {
const user = useUser();
- const { controller, createDialogSwitch } = useDialog({
- member: (
- <Dialog>
- <TimelineMember timeline={timeline} onChange={onReload} />
- </Dialog>
- ),
- property: (
- <TimelinePropertyChangeDialog timeline={timeline} onChange={onReload} />
- ),
- delete: <TimelineDeleteDialog timeline={timeline} />,
- });
+ const { createDialogSwitch, dialogPropsMap } = useDialog([
+ "member",
+ "property",
+ "delete",
+ ]);
return (
<div>
@@ -144,7 +133,17 @@ function TimelineInfoContent({
</PopupMenu>
)}
</div>
- <DialogProvider controller={controller} />
+
+ <Dialog {...dialogPropsMap["member"]}>
+ <TimelineMember timeline={timeline} onChange={onReload} />
+ </Dialog>
+
+ <TimelinePropertyChangeDialog
+ timeline={timeline}
+ onChange={onReload}
+ {...dialogPropsMap["property"]}
+ />
+ <TimelineDeleteDialog timeline={timeline} {...dialogPropsMap["delete"]} />
</div>
);
}
@@ -162,22 +161,13 @@ export default function TimelineInfoCard(props: TimelineInfoCardProps) {
}
});
- const { controller, switchDialog } = useDialog(
- {
- "full-page": (
- <FullPageDialog>
- <TimelineInfoContent timeline={timeline} onReload={onReload} />
- </FullPageDialog>
- ),
- },
- {
- onClose: {
- "full-page": () => {
- setCollapse(true);
- },
+ const { switchDialog, dialogPropsMap } = useDialog(["full-page"], {
+ onClose: {
+ "full-page": () => {
+ setCollapse(true);
},
},
- );
+ });
return (
<Card
@@ -202,7 +192,9 @@ export default function TimelineInfoCard(props: TimelineInfoCardProps) {
{!collapse && !isMobile && (
<TimelineInfoContent timeline={timeline} onReload={onReload} />
)}
- <DialogProvider controller={controller} />
+ <FullPageDialog {...dialogPropsMap["full-page"]}>
+ <TimelineInfoContent timeline={timeline} onReload={onReload} />
+ </FullPageDialog>
</Card>
);
}
diff --git a/FrontEnd/src/pages/timeline/TimelinePostView.tsx b/FrontEnd/src/pages/timeline/TimelinePostView.tsx
index 4f0460ff..2cfe51c4 100644
--- a/FrontEnd/src/pages/timeline/TimelinePostView.tsx
+++ b/FrontEnd/src/pages/timeline/TimelinePostView.tsx
@@ -8,7 +8,7 @@ import {
import { pushAlert } from "~src/components/alert";
import { useClickOutside } from "~src/components/hooks";
import UserAvatar from "~src/components/user/UserAvatar";
-import { DialogProvider, useDialog } from "~src/components/dialog";
+import { useDialog } from "~src/components/dialog";
import FlatButton from "~src/components/button/FlatButton";
import ConfirmDialog from "~src/components/dialog/ConfirmDialog";
import TimelinePostContentView from "./view/TimelinePostContentView";
@@ -32,33 +32,13 @@ export default function TimelinePostView(props: TimelinePostViewProps) {
const [operationMaskVisible, setOperationMaskVisible] =
useState<boolean>(false);
- const { controller, switchDialog } = useDialog(
- {
- delete: (
- <ConfirmDialog
- title="timeline.post.deleteDialog.title"
- body="timeline.post.deleteDialog.prompt"
- onConfirm={() => {
- void getHttpTimelineClient()
- .deletePost(post.timelineOwnerV2, post.timelineNameV2, post.id)
- .then(onDeleted, () => {
- pushAlert({
- color: "danger",
- message: "timeline.deletePostFailed",
- });
- });
- }}
- />
- ),
- },
- {
- onClose: {
- delete: () => {
- setOperationMaskVisible(false);
- },
+ const { switchDialog, dialogPropsMap } = useDialog(["delete"], {
+ onClose: {
+ delete: () => {
+ setOperationMaskVisible(false);
},
},
- );
+ });
const [maskElement, setMaskElement] = useState<HTMLElement | null>(null);
useClickOutside(maskElement, () => setOperationMaskVisible(false));
@@ -114,10 +94,28 @@ export default function TimelinePostView(props: TimelinePostViewProps) {
e.stopPropagation();
}}
/>
+ <ConfirmDialog
+ title="timeline.post.deleteDialog.title"
+ body="timeline.post.deleteDialog.prompt"
+ onConfirm={() => {
+ void getHttpTimelineClient()
+ .deletePost(
+ post.timelineOwnerV2,
+ post.timelineNameV2,
+ post.id,
+ )
+ .then(onDeleted, () => {
+ pushAlert({
+ color: "danger",
+ message: "timeline.deletePostFailed",
+ });
+ });
+ }}
+ {...dialogPropsMap["delete"]}
+ />
</div>
) : null}
</TimelinePostCard>
- <DialogProvider controller={controller} />
</TimelinePostContainer>
);
}
diff --git a/FrontEnd/src/pages/timeline/TimelinePropertyChangeDialog.tsx b/FrontEnd/src/pages/timeline/TimelinePropertyChangeDialog.tsx
index 79838d58..9cf319ca 100644
--- a/FrontEnd/src/pages/timeline/TimelinePropertyChangeDialog.tsx
+++ b/FrontEnd/src/pages/timeline/TimelinePropertyChangeDialog.tsx
@@ -9,6 +9,8 @@ import {
import OperationDialog from "~src/components/dialog/OperationDialog";
interface TimelinePropertyChangeDialogProps {
+ open: boolean;
+ onClose: () => void;
timeline: HttpTimelineInfo;
onChange: () => void;
}
@@ -20,11 +22,15 @@ const labelMap: { [key in TimelineVisibility]: string } = {
};
export default function TimelinePropertyChangeDialog({
+ open,
+ onClose,
timeline,
onChange,
}: TimelinePropertyChangeDialogProps) {
return (
<OperationDialog
+ open={open}
+ onClose={onClose}
title={"timeline.dialogChangeProperty.title"}
inputs={{
scheme: {
@@ -76,4 +82,3 @@ export default function TimelinePropertyChangeDialog({
/>
);
}
-