aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/pages
diff options
context:
space:
mode:
Diffstat (limited to 'FrontEnd/src/pages')
-rw-r--r--FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx32
-rw-r--r--FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx11
-rw-r--r--FrontEnd/src/pages/setting/ChangePasswordDialog.tsx11
-rw-r--r--FrontEnd/src/pages/setting/index.tsx130
-rw-r--r--FrontEnd/src/pages/timeline/MarkdownPostEdit.tsx33
-rw-r--r--FrontEnd/src/pages/timeline/TimelineCard.tsx28
-rw-r--r--FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx4
-rw-r--r--FrontEnd/src/pages/timeline/TimelinePostCreateView.tsx2
-rw-r--r--FrontEnd/src/pages/timeline/TimelinePostView.tsx48
-rw-r--r--FrontEnd/src/pages/timeline/TimelinePropertyChangeDialog.tsx4
10 files changed, 141 insertions, 162 deletions
diff --git a/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx b/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx
index 2fcfef2c..96ae971b 100644
--- a/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx
+++ b/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx
@@ -9,23 +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 } from "~src/components/dialog";
+import {
+ Dialog,
+ DialogContainer,
+ useDialogController,
+} from "~src/components/dialog";
import "./ChangeAvatarDialog.css";
-interface ChangeAvatarDialogProps {
- open: boolean;
- onClose: () => void;
-}
-
-export default function ChangeAvatarDialog({
- open,
- onClose,
-}: ChangeAvatarDialogProps) {
+export default function ChangeAvatarDialog() {
const c = useC();
const user = useUser();
+ const controller = useDialogController();
+
type State =
| "select"
| "crop"
@@ -49,11 +47,7 @@ export default function ChangeAvatarDialog({
"settings.dialogChangeAvatar.prompt.select",
);
- const close = (): void => {
- if (state !== "uploading") {
- onClose();
- }
- };
+ const close = controller.closeDialog;
const onSelectFile = (e: ChangeEvent<HTMLInputElement>): void => {
const files = e.target.files;
@@ -96,6 +90,7 @@ export default function ChangeAvatarDialog({
}
setState("uploading");
+ controller.setCanSwitchDialog(false);
getHttpUserClient()
.putAvatar(user.username, resultBlob)
.then(
@@ -106,7 +101,10 @@ export default function ChangeAvatarDialog({
setState("error");
setMessage("operationDialog.error");
},
- );
+ )
+ .finally(() => {
+ controller.setCanSwitchDialog(true);
+ });
};
const cancelButton = {
@@ -181,7 +179,7 @@ export default function ChangeAvatarDialog({
};
return (
- <Dialog open={open} onClose={close}>
+ <Dialog>
<DialogContainer
title="settings.dialogChangeAvatar.title"
titleColor="primary"
diff --git a/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx b/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx
index 843659ef..bd1eaa51 100644
--- a/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx
+++ b/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx
@@ -3,19 +3,11 @@ import { useUserLoggedIn } from "~src/services/user";
import OperationDialog from "~src/components/dialog/OperationDialog";
-export interface ChangeNicknameDialogProps {
- open: boolean;
- onClose: () => void;
-}
-
-export default function ChangeNicknameDialog(props: ChangeNicknameDialogProps) {
- const { open, onClose } = props;
-
+export default function ChangeNicknameDialog() {
const user = useUserLoggedIn();
return (
<OperationDialog
- open={open}
title="settings.dialogChangeNickname.title"
inputs={[
{
@@ -29,7 +21,6 @@ export default function ChangeNicknameDialog(props: ChangeNicknameDialogProps) {
nickname: newNickname,
});
}}
- onClose={onClose}
/>
);
}
diff --git a/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx b/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx
index 946b9fbe..c3111ac8 100644
--- a/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx
+++ b/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx
@@ -5,22 +5,13 @@ import { userService } from "~src/services/user";
import { OperationDialog } from "~src/components/dialog";
-interface ChangePasswordDialogProps {
- open: boolean;
- onClose: () => void;
-}
-
-export function ChangePasswordDialog(props: ChangePasswordDialogProps) {
- const { open, onClose } = props;
-
+export function ChangePasswordDialog() {
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 918a77b5..4d2c28c7 100644
--- a/FrontEnd/src/pages/setting/index.tsx
+++ b/FrontEnd/src/pages/setting/index.tsx
@@ -14,7 +14,11 @@ import { pushAlert } from "~src/services/alert";
import { useC, Text } from "~src/common";
-import { useDialog, ConfirmDialog } from "~src/components/dialog";
+import {
+ useDialog,
+ DialogProvider,
+ ConfirmDialog,
+} from "~src/components/dialog";
import Card from "~src/components/Card";
import Spinner from "~src/components/Spinner";
import Page from "~src/components/Page";
@@ -140,7 +144,22 @@ function RegisterCodeSettingItem() {
// undefined: loading
const [registerCode, setRegisterCode] = useState<undefined | null | string>();
- const [dialogOpen, setDialogOpen] = useState(false);
+ 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);
+ });
+ }}
+ />
+ ),
+ });
useEffect(() => {
setRegisterCode(undefined);
@@ -157,49 +176,34 @@ function RegisterCodeSettingItem() {
}, [user, registerCode]);
return (
- <>
- <SettingItemContainer
- title="settings.myRegisterCode"
- description="settings.myRegisterCodeDesc"
- className="register-code-setting-item"
- onClick={() => setDialogOpen(true)}
- >
- {registerCode === undefined ? (
- <Spinner />
- ) : registerCode === null ? (
- <span>Noop</span>
- ) : (
- <code
- className="register-code"
- onClick={(event) => {
- void navigator.clipboard.writeText(registerCode).then(() => {
- pushAlert({
- type: "create",
- message: "settings.myRegisterCodeCopied",
- });
+ <SettingItemContainer
+ title="settings.myRegisterCode"
+ description="settings.myRegisterCodeDesc"
+ className="register-code-setting-item"
+ onClick={createDialogSwitch("confirm")}
+ >
+ {registerCode === undefined ? (
+ <Spinner />
+ ) : registerCode === null ? (
+ <span>Noop</span>
+ ) : (
+ <code
+ className="register-code"
+ onClick={(event) => {
+ void navigator.clipboard.writeText(registerCode).then(() => {
+ pushAlert({
+ type: "create",
+ message: "settings.myRegisterCodeCopied",
});
- event.stopPropagation();
- }}
- >
- {registerCode}
- </code>
- )}
- </SettingItemContainer>
- <ConfirmDialog
- title="settings.renewRegisterCode"
- body="settings.renewRegisterCodeDesc"
- onClose={() => setDialogOpen(false)}
- open={dialogOpen}
- onConfirm={() => {
- if (user == null) throw new Error();
- void getHttpUserClient()
- .renewRegisterCode(user.username)
- .then(() => {
- setRegisterCode(undefined);
});
- }}
- />{" "}
- </>
+ event.stopPropagation();
+ }}
+ >
+ {registerCode}
+ </code>
+ )}
+ <DialogProvider controller={controller} />
+ </SettingItemContainer>
);
}
@@ -240,12 +244,22 @@ export default function SettingPage() {
const user = useUser();
const navigate = useNavigate();
- const { dialogPropsMap, createDialogSwitch } = useDialog([
- "change-password",
- "change-avatar",
- "change-nickname",
- "logout",
- ]);
+ const { controller, createDialogSwitch } = useDialog({
+ "change-password": <ChangeNicknameDialog />,
+ "change-avatar": <ChangeAvatarDialog />,
+ "change-nickname": <ChangePasswordDialog />,
+ logout: (
+ <ConfirmDialog
+ title="settings.dialogConfirmLogout.title"
+ body="settings.dialogConfirmLogout.prompt"
+ onConfirm={() => {
+ void userService.logout().then(() => {
+ navigate("/");
+ });
+ }}
+ />
+ ),
+ });
return (
<Page noTopPadding>
@@ -275,23 +289,7 @@ export default function SettingPage() {
<SettingSection title="settings.subheader.customization">
<LanguageChangeSettingItem />
</SettingSection>
- <ChangePasswordDialog {...dialogPropsMap["change-password"]} />
- {user && (
- <>
- <ConfirmDialog
- title="settings.dialogConfirmLogout.title"
- body="settings.dialogConfirmLogout.prompt"
- onConfirm={() => {
- void userService.logout().then(() => {
- navigate("/");
- });
- }}
- {...dialogPropsMap["logout"]}
- />
- <ChangeAvatarDialog {...dialogPropsMap["change-avatar"]} />
- <ChangeNicknameDialog {...dialogPropsMap["change-nickname"]} />
- </>
- )}
+ <DialogProvider controller={controller} />
</Page>
);
}
diff --git a/FrontEnd/src/pages/timeline/MarkdownPostEdit.tsx b/FrontEnd/src/pages/timeline/MarkdownPostEdit.tsx
index 43e81d67..fc7b882f 100644
--- a/FrontEnd/src/pages/timeline/MarkdownPostEdit.tsx
+++ b/FrontEnd/src/pages/timeline/MarkdownPostEdit.tsx
@@ -2,7 +2,10 @@ import * as React from "react";
import classnames from "classnames";
import { useTranslation } from "react-i18next";
-import { getHttpTimelineClient, HttpTimelinePostInfo } from "~src/http/timeline";
+import {
+ getHttpTimelineClient,
+ HttpTimelinePostInfo,
+} from "~src/http/timeline";
import TimelinePostBuilder from "~src/services/TimelinePostBuilder";
@@ -13,6 +16,7 @@ import Spinner from "~src/components/Spinner";
import IconButton from "~src/components/button/IconButton";
import "./MarkdownPostEdit.css";
+import { DialogProvider, useDialog } from "~src/components/dialog";
export interface MarkdownPostEditProps {
owner: string;
@@ -39,12 +43,19 @@ const MarkdownPostEdit: React.FC<MarkdownPostEditProps> = ({
const [process, setProcess] = React.useState<boolean>(false);
- const [showLeaveConfirmDialog, setShowLeaveConfirmDialog] =
- React.useState<boolean>(false);
+ const { controller, switchDialog } = useDialog({
+ "leave-confirm": (
+ <ConfirmDialog
+ onConfirm={onClose}
+ title="timeline.dropDraft"
+ body="timeline.confirmLeave"
+ />
+ ),
+ });
const [text, _setText] = React.useState<string>("");
const [images, _setImages] = React.useState<{ file: File; url: string }[]>(
- []
+ [],
);
const [previewHtml, _setPreviewHtml] = React.useState<string>("");
@@ -92,7 +103,7 @@ const MarkdownPostEdit: React.FC<MarkdownPostEditProps> = ({
timelineName,
{
dataList,
- }
+ },
);
onPosted(post);
onClose();
@@ -123,7 +134,7 @@ const MarkdownPostEdit: React.FC<MarkdownPostEditProps> = ({
if (canLeave) {
onClose();
} else {
- setShowLeaveConfirmDialog(true);
+ switchDialog("leave-confirm");
}
}}
/>
@@ -167,7 +178,7 @@ const MarkdownPostEdit: React.FC<MarkdownPostEditProps> = ({
color="danger"
className={classnames(
"timeline-markdown-post-edit-image-delete-button",
- process && "d-none"
+ process && "d-none",
)}
onClick={() => {
getBuilder().deleteImage(index);
@@ -201,13 +212,7 @@ const MarkdownPostEdit: React.FC<MarkdownPostEditProps> = ({
},
]}
/>
- <ConfirmDialog
- onClose={() => setShowLeaveConfirmDialog(false)}
- onConfirm={onClose}
- open={showLeaveConfirmDialog}
- title="timeline.dropDraft"
- body="timeline.confirmLeave"
- />
+ <DialogProvider controller={controller} />
</>
);
};
diff --git a/FrontEnd/src/pages/timeline/TimelineCard.tsx b/FrontEnd/src/pages/timeline/TimelineCard.tsx
index f17a3ce9..133f1ef4 100644
--- a/FrontEnd/src/pages/timeline/TimelineCard.tsx
+++ b/FrontEnd/src/pages/timeline/TimelineCard.tsx
@@ -8,7 +8,7 @@ import { HttpTimelineInfo } from "~src/http/timeline";
import { getHttpBookmarkClient } from "~src/http/bookmark";
import { useMobile } from "~src/components/hooks";
-import { Dialog, useDialog } from "~src/components/dialog";
+import { Dialog, DialogProvider, useDialog } from "~src/components/dialog";
import UserAvatar from "~src/components/user/UserAvatar";
import PopupMenu from "~src/components/menu/PopupMenu";
import FullPageDialog from "~src/components/dialog/FullPageDialog";
@@ -40,11 +40,17 @@ export default function TimelineCard(props: TimelinePageCardProps) {
const isMobile = useMobile();
- const { createDialogSwitch, dialogPropsMap } = useDialog([
- "member",
- "property",
- "delete",
- ]);
+ const { controller, createDialogSwitch } = useDialog({
+ member: (
+ <Dialog>
+ <TimelineMember timeline={timeline} onChange={onReload} />
+ </Dialog>
+ ),
+ property: (
+ <TimelinePropertyChangeDialog timeline={timeline} onChange={onReload} />
+ ),
+ delete: <TimelineDeleteDialog timeline={timeline} />,
+ });
const content = (
<div>
@@ -144,15 +150,7 @@ export default function TimelineCard(props: TimelinePageCardProps) {
) : (
<div style={{ display: collapse ? "none" : "block" }}>{content}</div>
)}
- <Dialog {...dialogPropsMap["member"]}>
- <TimelineMember timeline={timeline} onChange={onReload} />
- </Dialog>
- <TimelinePropertyChangeDialog
- timeline={timeline}
- onChange={onReload}
- {...dialogPropsMap["property"]}
- />
- <TimelineDeleteDialog timeline={timeline} {...dialogPropsMap["delete"]} />
+ <DialogProvider controller={controller} />
</Card>
);
}
diff --git a/FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx b/FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx
index a7209e75..630ce4ca 100644
--- a/FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx
+++ b/FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx
@@ -8,8 +8,6 @@ import OperationDialog from "~src/components/dialog/OperationDialog";
interface TimelineDeleteDialog {
timeline: HttpTimelineInfo;
- open: boolean;
- onClose: () => void;
}
const TimelineDeleteDialog: React.FC<TimelineDeleteDialog> = (props) => {
@@ -19,8 +17,6 @@ const TimelineDeleteDialog: React.FC<TimelineDeleteDialog> = (props) => {
return (
<OperationDialog
- open={props.open}
- onClose={props.onClose}
title="timeline.deleteDialog.title"
color="danger"
inputPromptNode={
diff --git a/FrontEnd/src/pages/timeline/TimelinePostCreateView.tsx b/FrontEnd/src/pages/timeline/TimelinePostCreateView.tsx
index 3c41228a..3bc4dab3 100644
--- a/FrontEnd/src/pages/timeline/TimelinePostCreateView.tsx
+++ b/FrontEnd/src/pages/timeline/TimelinePostCreateView.tsx
@@ -88,7 +88,7 @@ function TimelinePostEditImage(props: TimelinePostEditImageProps) {
/>
{file != null && !error && (
<BlobImage
- blob={file}
+ src={file}
className="timeline-post-create-image"
onLoad={() => onSelect(file)}
onError={() => {
diff --git a/FrontEnd/src/pages/timeline/TimelinePostView.tsx b/FrontEnd/src/pages/timeline/TimelinePostView.tsx
index 6b87ef2a..5de09b28 100644
--- a/FrontEnd/src/pages/timeline/TimelinePostView.tsx
+++ b/FrontEnd/src/pages/timeline/TimelinePostView.tsx
@@ -9,7 +9,7 @@ import { pushAlert } from "~src/services/alert";
import { useClickOutside } from "~src/components/hooks";
import UserAvatar from "~src/components/user/UserAvatar";
-import { useDialog } from "~src/components/dialog";
+import { DialogProvider, useDialog } from "~src/components/dialog";
import FlatButton from "~src/components/button/FlatButton";
import ConfirmDialog from "~src/components/dialog/ConfirmDialog";
import TimelinePostContentView from "./TimelinePostContentView";
@@ -33,13 +33,33 @@ export default function TimelinePostView(props: TimelinePostViewProps) {
const [operationMaskVisible, setOperationMaskVisible] =
useState<boolean>(false);
- const { switchDialog, dialogPropsMap } = useDialog(["delete"], {
- onClose: {
- delete: () => {
- setOperationMaskVisible(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({
+ type: "danger",
+ message: "timeline.deletePostFailed",
+ });
+ });
+ }}
+ />
+ ),
+ },
+ {
+ onClose: {
+ delete: () => {
+ setOperationMaskVisible(false);
+ },
},
},
- });
+ );
const [maskElement, setMaskElement] = useState<HTMLElement | null>(null);
useClickOutside(maskElement, () => setOperationMaskVisible(false));
@@ -98,21 +118,7 @@ export default function TimelinePostView(props: TimelinePostViewProps) {
</div>
) : null}
</TimelinePostCard>
- <ConfirmDialog
- title="timeline.post.deleteDialog.title"
- body="timeline.post.deleteDialog.prompt"
- onConfirm={() => {
- void getHttpTimelineClient()
- .deletePost(post.timelineOwnerV2, post.timelineNameV2, post.id)
- .then(onDeleted, () => {
- pushAlert({
- type: "danger",
- message: "timeline.deletePostFailed",
- });
- });
- }}
- {...dialogPropsMap.delete}
- />
+ <DialogProvider controller={controller} />
</TimelinePostContainer>
);
}
diff --git a/FrontEnd/src/pages/timeline/TimelinePropertyChangeDialog.tsx b/FrontEnd/src/pages/timeline/TimelinePropertyChangeDialog.tsx
index afd83a5f..ee5388cb 100644
--- a/FrontEnd/src/pages/timeline/TimelinePropertyChangeDialog.tsx
+++ b/FrontEnd/src/pages/timeline/TimelinePropertyChangeDialog.tsx
@@ -11,8 +11,6 @@ import {
import OperationDialog from "~src/components/dialog/OperationDialog";
export interface TimelinePropertyChangeDialogProps {
- open: boolean;
- onClose: () => void;
timeline: HttpTimelineInfo;
onChange: () => void;
}
@@ -63,8 +61,6 @@ const TimelinePropertyChangeDialog: React.FC<
},
},
}}
- open={props.open}
- onClose={props.onClose}
onProcess={({ title, visibility, description }) => {
const req: HttpTimelinePatchRequest = {};
if (title !== timeline.title) {