From a168336c0761b263ee5371218cbf6da236c0acce Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 26 Jun 2021 19:13:19 +0800 Subject: ... --- FrontEnd/src/views/admin/UserAdmin.tsx | 9 +- FrontEnd/src/views/common/Menu.tsx | 26 ++--- FrontEnd/src/views/common/SearchInput.tsx | 13 +-- FrontEnd/src/views/common/Spinner.tsx | 13 +++ FrontEnd/src/views/common/TabPages.tsx | 20 +--- FrontEnd/src/views/common/alert/AlertHost.tsx | 11 +- FrontEnd/src/views/common/button/LoadingButton.tsx | 23 ++--- .../src/views/common/dailog/OperationDialog.tsx | 71 ++++++------- FrontEnd/src/views/login/index.tsx | 114 ++++++++++----------- .../src/views/timeline-common/MarkdownPostEdit.tsx | 8 +- .../src/views/timeline-common/TimelineMember.tsx | 21 ++-- .../views/timeline-common/TimelinePageTemplate.tsx | 1 - .../TimelinePostDeleteConfirmDialog.tsx | 40 -------- .../src/views/timeline-common/TimelinePostEdit.tsx | 1 - .../src/views/timeline-common/TimelinePostView.tsx | 6 +- 15 files changed, 142 insertions(+), 235 deletions(-) create mode 100644 FrontEnd/src/views/common/Spinner.tsx delete mode 100644 FrontEnd/src/views/timeline-common/TimelinePostDeleteConfirmDialog.tsx diff --git a/FrontEnd/src/views/admin/UserAdmin.tsx b/FrontEnd/src/views/admin/UserAdmin.tsx index 125219c3..481db1cc 100644 --- a/FrontEnd/src/views/admin/UserAdmin.tsx +++ b/FrontEnd/src/views/admin/UserAdmin.tsx @@ -15,6 +15,7 @@ import { import { Trans, useTranslation } from "react-i18next"; import Button from "../common/button/Button"; import TextButton from "../common/button/TextButton"; +import Spinner from "../common/Spinner"; interface DialogProps { open: boolean; @@ -203,7 +204,7 @@ const UserItem: React.FC = ({ user, on }) => { const [editMaskVisible, setEditMaskVisible] = React.useState(false); return ( - +
setEditMaskVisible(true)} @@ -242,7 +243,7 @@ const UserItem: React.FC = ({ user, on }) => { onClick={on[kDelete]} />
-
+ ); }; @@ -251,8 +252,6 @@ interface UserAdminProps { } const UserAdmin: React.FC = () => { - const { t } = useTranslation(); - type DialogInfo = | null | { @@ -390,7 +389,7 @@ const UserAdmin: React.FC = () => { ); } else { - return ; + return ; } }; diff --git a/FrontEnd/src/views/common/Menu.tsx b/FrontEnd/src/views/common/Menu.tsx index ae73a331..a5d2ec2c 100644 --- a/FrontEnd/src/views/common/Menu.tsx +++ b/FrontEnd/src/views/common/Menu.tsx @@ -1,9 +1,9 @@ import React from "react"; import classnames from "classnames"; -import { OverlayTrigger, OverlayTriggerProps, Popover } from "react-bootstrap"; import { useTranslation } from "react-i18next"; -import { BootstrapThemeColor, convertI18nText, I18nText } from "@/common"; +import { convertI18nText, I18nText } from "@/common"; +import { PaletteColorType } from "@/palette"; export type MenuItem = | { @@ -13,7 +13,7 @@ export type MenuItem = type: "button"; text: I18nText; iconClassName?: string; - color?: BootstrapThemeColor; + color?: PaletteColorType; onClick: () => void; }; @@ -67,26 +67,14 @@ export default Menu; export interface PopupMenuProps { items: MenuItems; - children: OverlayTriggerProps["children"]; + children: React.ReactElement; } export const PopupMenu: React.FC = ({ items, children }) => { const [show, setShow] = React.useState(false); const toggle = (): void => setShow(!show); - return ( - - setShow(false)} /> - - } - show={show} - onToggle={toggle} - > - {children} - - ); + // TODO: + + return setShow(false)} />; }; diff --git a/FrontEnd/src/views/common/SearchInput.tsx b/FrontEnd/src/views/common/SearchInput.tsx index 79eb2732..fff11b95 100644 --- a/FrontEnd/src/views/common/SearchInput.tsx +++ b/FrontEnd/src/views/common/SearchInput.tsx @@ -1,7 +1,8 @@ import React, { useCallback } from "react"; import classnames from "classnames"; import { useTranslation } from "react-i18next"; -import { Spinner, Form, Button } from "react-bootstrap"; + +import LoadingButton from "./button/LoadingButton"; export interface SearchInputProps { value: string; @@ -64,13 +65,9 @@ const SearchInput: React.FC = (props) => { "flex-shrink-0" )} > - {props.loading ? ( - - ) : ( - - )} + + {props.buttonText ?? t("search")} + ); diff --git a/FrontEnd/src/views/common/Spinner.tsx b/FrontEnd/src/views/common/Spinner.tsx new file mode 100644 index 00000000..783c9be2 --- /dev/null +++ b/FrontEnd/src/views/common/Spinner.tsx @@ -0,0 +1,13 @@ +import { PaletteColorType } from "@/palette"; +import React from "react"; + +export interface SpinnerProps { + size?: "sm" | "md" | "lg" | number; + color?: PaletteColorType; +} + +export default function Spinner( + props: SpinnerProps +): React.ReactElement | null { + return ; +} diff --git a/FrontEnd/src/views/common/TabPages.tsx b/FrontEnd/src/views/common/TabPages.tsx index 2b1d91cb..b7a9fb36 100644 --- a/FrontEnd/src/views/common/TabPages.tsx +++ b/FrontEnd/src/views/common/TabPages.tsx @@ -1,5 +1,4 @@ import React from "react"; -import { Nav } from "react-bootstrap"; import { useTranslation } from "react-i18next"; import { convertI18nText, I18nText, UiLogicError } from "@/common"; @@ -31,6 +30,8 @@ const TabPages: React.FC = ({ pageContainerClassName, pageContainerStyle, }) => { + // TODO: + if (pages.length === 0) { throw new UiLogicError("Page list can't be empty."); } @@ -47,23 +48,6 @@ const TabPages: React.FC = ({ return (
-
{currentPage.page}
diff --git a/FrontEnd/src/views/common/alert/AlertHost.tsx b/FrontEnd/src/views/common/alert/AlertHost.tsx index 949be7ed..21b9882d 100644 --- a/FrontEnd/src/views/common/alert/AlertHost.tsx +++ b/FrontEnd/src/views/common/alert/AlertHost.tsx @@ -1,7 +1,6 @@ import React from "react"; import without from "lodash/without"; import { useTranslation } from "react-i18next"; -import { Alert } from "react-bootstrap"; import { alertService, @@ -52,13 +51,7 @@ export const AutoCloseAlert: React.FC = (props) => { }; return ( - +
{(() => { const { message } = alert; if (typeof message === "function") { @@ -66,7 +59,7 @@ export const AutoCloseAlert: React.FC = (props) => { return ; } else return convertI18nText(message, t); })()} - +
); }; diff --git a/FrontEnd/src/views/common/button/LoadingButton.tsx b/FrontEnd/src/views/common/button/LoadingButton.tsx index fd1c19b3..aee83aa2 100644 --- a/FrontEnd/src/views/common/button/LoadingButton.tsx +++ b/FrontEnd/src/views/common/button/LoadingButton.tsx @@ -1,26 +1,19 @@ import React from "react"; -const LoadingButton: React.FC<{ loading?: boolean } & ButtonProps> = ({ +import { CommonButtonProps } from "./common"; +import Button from "./Button"; +import Spinner from "../Spinner"; + +const LoadingButton: React.FC<{ loading?: boolean } & CommonButtonProps> = ({ loading, - variant, disabled, + color, ...otherProps }) => { return ( - ); }; diff --git a/FrontEnd/src/views/common/dailog/OperationDialog.tsx b/FrontEnd/src/views/common/dailog/OperationDialog.tsx index 129e85d5..1e765276 100644 --- a/FrontEnd/src/views/common/dailog/OperationDialog.tsx +++ b/FrontEnd/src/views/common/dailog/OperationDialog.tsx @@ -5,6 +5,8 @@ import moment from "moment"; import { convertI18nText, I18nText, UiLogicError } from "@/common"; +import { PaletteColorType } from "@/palette"; + import Button from "../button/Button"; import LoadingButton from "../button/LoadingButton"; import Dialog from "./Dialog"; @@ -144,7 +146,7 @@ export interface OperationDialogProps< open: boolean; onClose: () => void; title: I18nText | (() => React.ReactNode); - themeColor?: "danger" | "success" | string; + themeColor?: PaletteColorType; onProcess: ( inputs: MapOperationInputInfoValueTypeList ) => Promise; @@ -290,50 +292,42 @@ const OperationDialog = < if (item.type === "text") { return ( - +
{item.label && ( - {convertI18nText(item.label, t)} + )} - { const v = e.target.value; updateValue(index, v); }} - isInvalid={error != null} disabled={process} /> - {error != null && ( - - {error} - - )} - {item.helperText && ( - {t(item.helperText)} - )} - + {error != null &&
{error}
} + {item.helperText &&
{t(item.helperText)}
} +
); } else if (item.type === "bool") { return ( - - +
+ { updateValue(index, event.currentTarget.checked); }} - label={convertI18nText(item.label, t)} disabled={process} /> - + +
); } else if (item.type === "select") { return ( - - {convertI18nText(item.label, t)} - + + +
); } else if (item.type === "color") { return ( - +
{item.canBeNull ? ( - + { @@ -365,42 +359,35 @@ const OperationDialog = < updateValue(index, null); } }} - label={convertI18nText(item.label, t)} disabled={process} /> - ) : ( - {convertI18nText(item.label, t)} - )} + ) : null} + {value !== null && ( updateValue(index, result.hex)} /> )} - +
); } else if (item.type === "datetime") { return ( - +
{item.label && ( - {convertI18nText(item.label, t)} + )} - { const v = e.target.value; updateValue(index, v); }} - isInvalid={error != null} disabled={process} /> - {error != null && ( - - {error} - - )} - + {error != null &&
{error}
} +
); } })} @@ -412,7 +399,7 @@ const OperationDialog = < onClick={close} /> { diff --git a/FrontEnd/src/views/login/index.tsx b/FrontEnd/src/views/login/index.tsx index 6c0aaf67..6d70c64a 100644 --- a/FrontEnd/src/views/login/index.tsx +++ b/FrontEnd/src/views/login/index.tsx @@ -81,67 +81,59 @@ const LoginPage: React.FC = (_) => {

{t("welcome")}

- {t("user.username")} - { - setUsername(e.target.value); - setUsernameDirty(true); - }} - value={username} - isInvalid={usernameDirty && username === ""} - /> - {usernameDirty && username === "" && ( - - {t("login.emptyUsername")} - - )} - {t("user.password")} - { - setPassword(e.target.value); - setPasswordDirty(true); - }} - value={password} - onKeyDown={onEnterPressInPassword} - isInvalid={passwordDirty && password === ""} - /> - {passwordDirty && password === "" && ( - - {t("login.emptyPassword")} - - )} - - - - id="remember-me" - type="checkbox" - checked={rememberMe} - onChange={(e) => { - setRememberMe(e.currentTarget.checked); - }} - label={t("user.rememberMe")} - /> - - {error ?

{t(error)}

: null} -
- { - submit(); - e.preventDefault(); - }} - disabled={username === "" || password === "" ? true : undefined} - > - {t("user.login")} - -
- + + { + setUsername(e.target.value); + setUsernameDirty(true); + }} + value={username} + /> + {usernameDirty && username === "" && ( +
{t("login.emptyUsername")}
+ )} + + { + setPassword(e.target.value); + setPasswordDirty(true); + }} + value={password} + onKeyDown={onEnterPressInPassword} + /> + {passwordDirty && password === "" && ( +
{t("login.emptyPassword")}
+ )} +
+
+ { + setRememberMe(e.currentTarget.checked); + }} + /> + +
+ {error ?

{t(error)}

: null} +
+ { + submit(); + e.preventDefault(); + }} + disabled={username === "" || password === "" ? true : undefined} + > + {t("user.login")} + +
); }; diff --git a/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx b/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx index 6cb64dd3..9f0753b9 100644 --- a/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx +++ b/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx @@ -1,15 +1,17 @@ +/* eslint-disable react/jsx-no-undef */ import React from "react"; import classnames from "classnames"; -import { Form, Spinner } from "react-bootstrap"; import { useTranslation } from "react-i18next"; import { Prompt } from "react-router"; import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline"; +import TimelinePostBuilder from "@/services/TimelinePostBuilder"; + import FlatButton from "../common/button/FlatButton"; import TabPages from "../common/TabPages"; -import TimelinePostBuilder from "@/services/TimelinePostBuilder"; import ConfirmDialog from "../common/dailog/ConfirmDialog"; +import Spinner from "../common/Spinner"; export interface MarkdownPostEditProps { timeline: string; @@ -102,7 +104,7 @@ const MarkdownPostEdit: React.FC = ({ pageContainerClassName="py-2" actions={ process ? ( - + ) : ( <> void; }> = ({ user, add, onAction }) => { - const { t } = useTranslation(); - return ( - +
@@ -42,7 +41,7 @@ const TimelineMemberItem: React.FC<{
) : null}
- +
); }; @@ -109,7 +108,7 @@ const TimelineMemberUserSearch: React.FC<{ return
{t("timeline.member.noUserAvailableToAdd")}
; } else { return ( - +
{users.map((user) => ( ))} - +
); } } else if (userSearchState.type === "error") { @@ -152,7 +151,7 @@ const TimelineMember: React.FC = (props) => { return (
- +
{members.map((member, index) => ( = (props) => { } /> ))} - +
{timeline.manageable ? ( ) : null} @@ -187,8 +186,8 @@ export const TimelineMemberDialog: React.FC = ( props ) => { return ( - + - + ); }; diff --git a/FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx b/FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx index d05f18d4..ea6e8d40 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx +++ b/FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx @@ -1,5 +1,4 @@ import React from "react"; -import { Container } from "react-bootstrap"; import { HubConnectionState } from "@microsoft/signalr"; import { HttpTimelineInfo } from "@/http/timeline"; diff --git a/FrontEnd/src/views/timeline-common/TimelinePostDeleteConfirmDialog.tsx b/FrontEnd/src/views/timeline-common/TimelinePostDeleteConfirmDialog.tsx deleted file mode 100644 index e04bb7e1..00000000 --- a/FrontEnd/src/views/timeline-common/TimelinePostDeleteConfirmDialog.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; - -import Button from "../common/button/Button"; - -const TimelinePostDeleteConfirmDialog: React.FC<{ - onClose: () => void; - onConfirm: () => void; -}> = ({ onClose, onConfirm }) => { - const { t } = useTranslation(); - - return ( - - - - {t("timeline.post.deleteDialog.title")} - - - {t("timeline.post.deleteDialog.prompt")} - - - - - ); -}; - -export default TimelinePostDeleteConfirmDialog; diff --git a/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx b/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx index 5e59bee4..13aacb54 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx +++ b/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx @@ -265,7 +265,6 @@ const TimelinePostEdit: React.FC = (props) => {
= (props) => { ) : null} {dialog === "delete" ? ( - { setDialog(null); setOperationMaskVisible(false); -- cgit v1.2.3