diff options
author | crupest <crupest@outlook.com> | 2023-08-26 23:49:28 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2023-08-26 23:49:28 +0800 |
commit | 256cc9592a3f31fc392e1ccdb699aa206b7b47ce (patch) | |
tree | fdca6f8b1cdbd5ed264a1af9e48496685f4e9a95 | |
parent | f5dfd52f6efece2f4cad227044ecf4dd66301bbc (diff) | |
download | timeline-256cc9592a3f31fc392e1ccdb699aa206b7b47ce.tar.gz timeline-256cc9592a3f31fc392e1ccdb699aa206b7b47ce.tar.bz2 timeline-256cc9592a3f31fc392e1ccdb699aa206b7b47ce.zip |
...
31 files changed, 72 insertions, 157 deletions
diff --git a/FrontEnd/src/common.ts b/FrontEnd/src/common.ts index 7c053140..1ca796c3 100644 --- a/FrontEnd/src/common.ts +++ b/FrontEnd/src/common.ts @@ -3,8 +3,6 @@ // This error should never occur. If it does, it indicates there is some logic bug in codes. export class UiLogicError extends Error {} -export const highlightTimelineUsername = "crupest"; - export type { I18nText } from "./i18n"; export type { I18nText as Text } from "./i18n"; export { c, convertI18nText } from "./i18n"; diff --git a/FrontEnd/src/components/AppBar.tsx b/FrontEnd/src/components/AppBar.tsx index da3a946f..1a5c1941 100644 --- a/FrontEnd/src/components/AppBar.tsx +++ b/FrontEnd/src/components/AppBar.tsx @@ -2,9 +2,10 @@ import { useState } from "react"; import classnames from "classnames"; import { Link, NavLink } from "react-router-dom"; -import { I18nText, useC, useMobile } from "./common"; import { useUser } from "~src/services/user"; +import { I18nText, useC } from "./common"; +import { useMobile } from "./hooks"; import TimelineLogo from "./TimelineLogo"; import { IconButton } from "./button"; import UserAvatar from "./user/UserAvatar"; diff --git a/FrontEnd/src/components/Card.tsx b/FrontEnd/src/components/Card.tsx index a8f0d3cc..5d3ef630 100644 --- a/FrontEnd/src/components/Card.tsx +++ b/FrontEnd/src/components/Card.tsx @@ -2,6 +2,7 @@ import { ComponentPropsWithoutRef, Ref } from "react"; import classNames from "classnames"; import { ThemeColor } from "./common"; + import "./Card.css"; interface CardProps extends ComponentPropsWithoutRef<"div"> { diff --git a/FrontEnd/src/components/LoadFailReload.tsx b/FrontEnd/src/components/LoadFailReload.tsx deleted file mode 100644 index 81ba1f67..00000000 --- a/FrontEnd/src/components/LoadFailReload.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import * as React from "react"; -import { Trans } from "react-i18next"; - -export interface LoadFailReloadProps { - className?: string; - style?: React.CSSProperties; - onReload: () => void; -} - -const LoadFailReload: React.FC<LoadFailReloadProps> = ({ - onReload, - className, - style, -}) => { - return ( - <Trans - i18nKey="loadFailReload" - parent="div" - className={className} - style={style} - > - 0 - <a - href="#" - onClick={(e) => { - onReload(); - e.preventDefault(); - }} - > - 1 - </a> - 2 - </Trans> - ); -}; - -export default LoadFailReload; diff --git a/FrontEnd/src/components/LoadingPage.tsx b/FrontEnd/src/components/LoadingPage.tsx deleted file mode 100644 index 35ee1aa8..00000000 --- a/FrontEnd/src/components/LoadingPage.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import * as React from "react"; - -import Spinner from "./Spinner"; - -const LoadingPage: React.FC = () => { - return ( - <div className="position-fixed w-100 h-100 d-flex justify-content-center align-items-center"> - <Spinner /> - </div> - ); -}; - -export default LoadingPage; diff --git a/FrontEnd/src/components/SearchInput.tsx b/FrontEnd/src/components/SearchInput.tsx index e3216b86..71820bfa 100644 --- a/FrontEnd/src/components/SearchInput.tsx +++ b/FrontEnd/src/components/SearchInput.tsx @@ -1,7 +1,7 @@ import classNames from "classnames"; import { useC, Text } from "./common"; -import LoadingButton from "./button/LoadingButton"; +import { LoadingButton } from "./button"; import "./SearchInput.css"; diff --git a/FrontEnd/src/components/button/ButtonRowV2.tsx b/FrontEnd/src/components/button/ButtonRowV2.tsx index 3467ad52..5129e7f1 100644 --- a/FrontEnd/src/components/button/ButtonRowV2.tsx +++ b/FrontEnd/src/components/button/ButtonRowV2.tsx @@ -1,13 +1,14 @@ import { ComponentPropsWithoutRef, Ref } from "react"; import classNames from "classnames"; +import { Text, ThemeColor } from "../common"; + import Button from "./Button"; import FlatButton from "./FlatButton"; import IconButton from "./IconButton"; import LoadingButton from "./LoadingButton"; import "./ButtonRow.css"; -import { Text, ThemeColor } from "../common"; interface ButtonRowV2ButtonBase { key: string | number; diff --git a/FrontEnd/src/components/button/LoadingButton.tsx b/FrontEnd/src/components/button/LoadingButton.tsx index 7e7d08e6..d9d41ddb 100644 --- a/FrontEnd/src/components/button/LoadingButton.tsx +++ b/FrontEnd/src/components/button/LoadingButton.tsx @@ -1,7 +1,6 @@ import classNames from "classnames"; import { I18nText, ThemeColor, useC } from "../common"; - import Spinner from "../Spinner"; import "./LoadingButton.css"; diff --git a/FrontEnd/src/components/common.ts b/FrontEnd/src/components/common.ts index e6f7319f..b96388ab 100644 --- a/FrontEnd/src/components/common.ts +++ b/FrontEnd/src/components/common.ts @@ -11,4 +11,3 @@ export const themeColors = [ export type ThemeColor = (typeof themeColors)[number]; export { breakpoints } from "./breakpoints"; -export { useMobile } from "./hooks"; diff --git a/FrontEnd/src/components/dialog/ConfirmDialog.css b/FrontEnd/src/components/dialog/ConfirmDialog.css deleted file mode 100644 index e69de29b..00000000 --- a/FrontEnd/src/components/dialog/ConfirmDialog.css +++ /dev/null diff --git a/FrontEnd/src/components/dialog/ConfirmDialog.tsx b/FrontEnd/src/components/dialog/ConfirmDialog.tsx index 26939c9b..1d997305 100644 --- a/FrontEnd/src/components/dialog/ConfirmDialog.tsx +++ b/FrontEnd/src/components/dialog/ConfirmDialog.tsx @@ -1,5 +1,4 @@ import { useC, Text, ThemeColor } from "../common"; - import Dialog from "./Dialog"; import DialogContainer from "./DialogContainer"; diff --git a/FrontEnd/src/components/dialog/OperationDialog.tsx b/FrontEnd/src/components/dialog/OperationDialog.tsx index e5db7f4f..96766825 100644 --- a/FrontEnd/src/components/dialog/OperationDialog.tsx +++ b/FrontEnd/src/components/dialog/OperationDialog.tsx @@ -2,23 +2,18 @@ import { useState, ReactNode, ComponentProps } from "react"; import classNames from "classnames"; import { useC, Text, ThemeColor } from "../common"; - import { useInputs, InputGroup, Initializer as InputInitializer, - InputValueDict, - InputErrorDict, InputConfirmValueDict, } from "../input"; +import { ButtonRow } from "../button"; import Dialog from "./Dialog"; import DialogContainer from "./DialogContainer"; -import { ButtonRow } from "../button"; import "./OperationDialog.css"; -export type { InputInitializer, InputValueDict, InputErrorDict }; - interface OperationDialogPromptProps { message?: Text; customMessage?: Text; diff --git a/FrontEnd/src/components/dialog/index.ts b/FrontEnd/src/components/dialog/index.ts index 59f15791..17db8fd0 100644 --- a/FrontEnd/src/components/dialog/index.ts +++ b/FrontEnd/src/components/dialog/index.ts @@ -4,6 +4,7 @@ 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"; type DialogMap<D extends string, V> = { [K in D]: V; diff --git a/FrontEnd/src/components/hooks.ts b/FrontEnd/src/components/hooks.ts deleted file mode 100644 index 523a4538..00000000 --- a/FrontEnd/src/components/hooks.ts +++ /dev/null @@ -1,14 +0,0 @@ -// TODO: Migrate hooks - -export { - useIsSmallScreen, - useClickOutside, - useScrollToBottom, -} from "~src/utilities/hooks"; - -import { useMediaQuery } from "react-responsive"; -import { breakpoints } from "./breakpoints"; - -export function useMobile(): boolean { - return useMediaQuery({ maxWidth: breakpoints.sm }); -} diff --git a/FrontEnd/src/components/hooks/index.ts b/FrontEnd/src/components/hooks/index.ts new file mode 100644 index 00000000..3c9859bc --- /dev/null +++ b/FrontEnd/src/components/hooks/index.ts @@ -0,0 +1,3 @@ +export { useMobile } from "./responsive"; +export { default as useClickOutside } from "./useClickOutside"; +export { default as useScrollToBottom } from "./useScrollToBottom"; diff --git a/FrontEnd/src/components/hooks/responsive.ts b/FrontEnd/src/components/hooks/responsive.ts new file mode 100644 index 00000000..6bcce96c --- /dev/null +++ b/FrontEnd/src/components/hooks/responsive.ts @@ -0,0 +1,7 @@ +import { useMediaQuery } from "react-responsive"; + +import { breakpoints } from "../breakpoints"; + +export function useMobile(): boolean { + return useMediaQuery({ maxWidth: breakpoints.sm }); +} diff --git a/FrontEnd/src/utilities/hooks/useClickOutside.ts b/FrontEnd/src/components/hooks/useClickOutside.ts index 6dcbf7b3..828ce7e3 100644 --- a/FrontEnd/src/utilities/hooks/useClickOutside.ts +++ b/FrontEnd/src/components/hooks/useClickOutside.ts @@ -3,7 +3,7 @@ import { useRef, useEffect } from "react"; export default function useClickOutside( element: HTMLElement | null | undefined, onClickOutside: () => void, - nextTick?: boolean + nextTick?: boolean, ): void { const onClickOutsideRef = useRef<() => void>(onClickOutside); diff --git a/FrontEnd/src/utilities/hooks/useScrollToBottom.ts b/FrontEnd/src/components/hooks/useScrollToBottom.ts index 216746f4..79fcda16 100644 --- a/FrontEnd/src/utilities/hooks/useScrollToBottom.ts +++ b/FrontEnd/src/components/hooks/useScrollToBottom.ts @@ -1,6 +1,5 @@ import { useRef, useEffect } from "react"; -import { fromEvent } from "rxjs"; -import { filter, throttleTime } from "rxjs/operators"; +import { fromEvent, filter, throttleTime } from "rxjs"; function useScrollToBottom( handler: () => void, @@ -8,7 +7,7 @@ function useScrollToBottom( option = { maxOffset: 5, throttle: 1000, - } + }, ): void { const handlerRef = useRef<(() => void) | null>(null); @@ -26,9 +25,9 @@ function useScrollToBottom( filter( () => window.scrollY >= - document.body.scrollHeight - window.innerHeight - option.maxOffset + document.body.scrollHeight - window.innerHeight - option.maxOffset, ), - throttleTime(option.throttle) + throttleTime(option.throttle), ) .subscribe(() => { if (enable) { diff --git a/FrontEnd/src/components/input/InputGroup.tsx b/FrontEnd/src/components/input/InputGroup.tsx index 4f487344..47a43b38 100644 --- a/FrontEnd/src/components/input/InputGroup.tsx +++ b/FrontEnd/src/components/input/InputGroup.tsx @@ -72,12 +72,9 @@ export type InputDirtyDict = Record<string, boolean>; // use never so you don't have to cast everywhere export type InputConfirmValueDict = Record<string, never>; -export type GeneralInputErrorDict = - | { - [key: string]: Text | null | undefined; - } - | null - | undefined; +export type GeneralInputErrorDict = { + [key: string]: Text | null | undefined; +}; type MakeInputInfo<I extends Input> = Omit<I, "value" | "error" | "disabled">; @@ -87,8 +84,9 @@ export type InputInfo = { export type Validator = ( values: InputValueDict, + errors: GeneralInputErrorDict, inputs: InputInfo[], -) => GeneralInputErrorDict; +) => void; export type InputScheme = { inputs: InputInfo[]; @@ -157,7 +155,9 @@ function validate( values: InputValueDict, inputs: InputInfo[], ): InputErrorDict { - return cleanObject(validator?.(values, inputs) ?? {}); + const errors: GeneralInputErrorDict = {}; + validator?.(values, errors, inputs); + return cleanObject(errors); } export function useInputs(options: { init: Initializer }): { diff --git a/FrontEnd/src/components/menu/Menu.tsx b/FrontEnd/src/components/menu/Menu.tsx index e8099c76..c01c6cfb 100644 --- a/FrontEnd/src/components/menu/Menu.tsx +++ b/FrontEnd/src/components/menu/Menu.tsx @@ -2,9 +2,9 @@ import { CSSProperties } from "react"; import classNames from "classnames"; import { useC, Text, ThemeColor } from "../common"; +import Icon from "../Icon"; import "./Menu.css"; -import Icon from "../Icon"; export type MenuItem = | { diff --git a/FrontEnd/src/components/menu/PopupMenu.tsx b/FrontEnd/src/components/menu/PopupMenu.tsx index 23a67f79..9d90799d 100644 --- a/FrontEnd/src/components/menu/PopupMenu.tsx +++ b/FrontEnd/src/components/menu/PopupMenu.tsx @@ -3,11 +3,9 @@ import classNames from "classnames"; import { createPortal } from "react-dom"; import { usePopper } from "react-popper"; -import { useClickOutside } from "~src/utilities/hooks"; - -import Menu, { MenuItems } from "./Menu"; - import { ThemeColor } from "../common"; +import { useClickOutside } from "../hooks"; +import Menu, { MenuItems } from "./Menu"; import "./PopupMenu.css"; diff --git a/FrontEnd/src/pages/login/index.tsx b/FrontEnd/src/pages/login/index.tsx index 582ebd0f..39ea3831 100644 --- a/FrontEnd/src/pages/login/index.tsx +++ b/FrontEnd/src/pages/login/index.tsx @@ -6,11 +6,7 @@ import { useUser, userService } from "~src/services/user"; import { useC } from "~src/components/common"; import LoadingButton from "~src/components/button/LoadingButton"; -import { - InputErrorDict, - InputGroup, - useInputs, -} from "~src/components/input/InputGroup"; +import { InputGroup, useInputs } from "~src/components/input/InputGroup"; import Page from "~src/components/Page"; import "./index.css"; @@ -47,15 +43,13 @@ export default function LoginPage() { label: "user.rememberMe", }, ], - validator: ({ username, password }) => { - const result: InputErrorDict = {}; + validator: ({ username, password }, errors) => { if (username === "") { - result["username"] = "login.emptyUsername"; + errors["username"] = "login.emptyUsername"; } if (password === "") { - result["password"] = "login.emptyPassword"; + errors["password"] = "login.emptyPassword"; } - return result; }, }, dataInit: {}, diff --git a/FrontEnd/src/pages/register/index.tsx b/FrontEnd/src/pages/register/index.tsx index 9e478612..fa25c2c2 100644 --- a/FrontEnd/src/pages/register/index.tsx +++ b/FrontEnd/src/pages/register/index.tsx @@ -7,11 +7,7 @@ import { getHttpTokenClient } from "~src/http/token"; import { userService, useUser } from "~src/services/user"; import { LoadingButton } from "~src/components/button"; -import { - useInputs, - InputErrorDict, - InputGroup, -} from "~src/components/input/InputGroup"; +import { useInputs, InputGroup } from "~src/components/input/InputGroup"; import "./index.css"; @@ -51,26 +47,22 @@ export default function RegisterPage() { label: "register.registerCode", }, ], - validator: ({ - username, - password, - confirmPassword, - registerCode, - }) => { - const result: InputErrorDict = {}; + validator: ( + { username, password, confirmPassword, registerCode }, + errors, + ) => { if (username === "") { - result["username"] = "register.error.usernameEmpty"; + errors["username"] = "register.error.usernameEmpty"; } if (password === "") { - result["password"] = "register.error.passwordEmpty"; + errors["password"] = "register.error.passwordEmpty"; } if (confirmPassword !== password) { - result["confirmPassword"] = "register.error.confirmPasswordWrong"; + errors["confirmPassword"] = "register.error.confirmPasswordWrong"; } if (registerCode === "") { - result["registerCode"] = "register.error.registerCodeEmpty"; + errors["registerCode"] = "register.error.registerCodeEmpty"; } - return result; }, }, dataInit: {}, diff --git a/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx b/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx index c34bcf4f..011c5059 100644 --- a/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx +++ b/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx @@ -11,9 +11,8 @@ import ImageCropper, { applyClipToImage, } from "~src/components/ImageCropper"; import BlobImage from "~src/components/BlobImage"; -import ButtonRowV2 from "~src/components/button/ButtonRowV2"; -import Dialog from "~src/components/dialog/Dialog"; -import DialogContainer from "~src/components/dialog/DialogContainer"; +import { ButtonRowV2 } from "~src/components/button"; +import { Dialog, DialogContainer } from "~src/components/dialog"; import "./ChangeAvatarDialog.css"; diff --git a/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx b/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx index bfcea92d..946b9fbe 100644 --- a/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx +++ b/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx @@ -3,9 +3,7 @@ import { useNavigate } from "react-router-dom"; import { userService } from "~src/services/user"; -import OperationDialog, { - InputErrorDict, -} from "~src/components/dialog/OperationDialog"; +import { OperationDialog } from "~src/components/dialog"; interface ChangePasswordDialogProps { open: boolean; @@ -47,21 +45,22 @@ export function ChangePasswordDialog(props: ChangePasswordDialogProps) { password: true, }, ], - validator: ({ oldPassword, newPassword, retypedNewPassword }) => { - const result: InputErrorDict = {}; + validator: ( + { oldPassword, newPassword, retypedNewPassword }, + errors, + ) => { if (oldPassword === "") { - result["oldPassword"] = + errors["oldPassword"] = "settings.dialogChangePassword.errorEmptyOldPassword"; } if (newPassword === "") { - result["newPassword"] = + errors["newPassword"] = "settings.dialogChangePassword.errorEmptyNewPassword"; } if (retypedNewPassword !== newPassword) { - result["retypedNewPassword"] = + errors["retypedNewPassword"] = "settings.dialogChangePassword.errorRetypeNotMatch"; } - return result; }, }} onProcess={async ({ oldPassword, newPassword }) => { diff --git a/FrontEnd/src/pages/setting/index.tsx b/FrontEnd/src/pages/setting/index.tsx index 67416a08..918a77b5 100644 --- a/FrontEnd/src/pages/setting/index.tsx +++ b/FrontEnd/src/pages/setting/index.tsx @@ -4,25 +4,26 @@ import { ReactNode, ComponentPropsWithoutRef, } from "react"; -import { useTranslation } from "react-i18next"; +import { useTranslation } from "react-i18next"; // For change language. import { useNavigate } from "react-router-dom"; import classNames from "classnames"; -import { useC, Text } from "~src/common"; import { useUser, userService } from "~src/services/user"; import { getHttpUserClient } from "~src/http/user"; +import { pushAlert } from "~src/services/alert"; + +import { useC, Text } from "~src/common"; -import { useDialog } from "~src/components/dialog"; -import ConfirmDialog from "~src/components/dialog/ConfirmDialog"; +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"; + import ChangePasswordDialog from "./ChangePasswordDialog"; import ChangeAvatarDialog from "./ChangeAvatarDialog"; import ChangeNicknameDialog from "./ChangeNicknameDialog"; import "./index.css"; -import { pushAlert } from "~src/services/alert"; interface SettingSectionProps extends Omit<ComponentPropsWithoutRef<typeof Card>, "title"> { diff --git a/FrontEnd/src/pages/timeline/Timeline.tsx b/FrontEnd/src/pages/timeline/Timeline.tsx index f266ec9d..caf4f502 100644 --- a/FrontEnd/src/pages/timeline/Timeline.tsx +++ b/FrontEnd/src/pages/timeline/Timeline.tsx @@ -1,6 +1,5 @@ import { useState, useEffect } from "react"; import classnames from "classnames"; -import { useScrollToBottom } from "~src/utilities/hooks"; import { HubConnectionState } from "@microsoft/signalr"; import { @@ -16,6 +15,8 @@ import { import { getTimelinePostUpdate$ } from "~src/services/timeline"; +import { useScrollToBottom } from "~src/components/hooks"; + import TimelinePostList from "./TimelinePostList"; import TimelinePostEdit from "./TimelinePostCreateView"; import TimelineCard from "./TimelineCard"; diff --git a/FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx b/FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx index 7b7b8e8c..a7209e75 100644 --- a/FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx +++ b/FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx @@ -39,9 +39,9 @@ const TimelineDeleteDialog: React.FC<TimelineDeleteDialog> = (props) => { label: "", }, ], - validator: ({ name }) => { + validator: ({ name }, errors) => { if (name !== timeline.nameV2) { - return { name: "timeline.deleteDialog.notMatch" }; + errors.name = "timeline.deleteDialog.notMatch"; } }, }} diff --git a/FrontEnd/src/pages/timeline/TimelinePostView.tsx b/FrontEnd/src/pages/timeline/TimelinePostView.tsx index 2a8c5947..6b87ef2a 100644 --- a/FrontEnd/src/pages/timeline/TimelinePostView.tsx +++ b/FrontEnd/src/pages/timeline/TimelinePostView.tsx @@ -1,11 +1,13 @@ import { useState } from "react"; -import { getHttpTimelineClient, HttpTimelinePostInfo } from "~src/http/timeline"; +import { + getHttpTimelineClient, + HttpTimelinePostInfo, +} from "~src/http/timeline"; import { pushAlert } from "~src/services/alert"; -import { useClickOutside } from "~src/utilities/hooks"; - +import { useClickOutside } from "~src/components/hooks"; import UserAvatar from "~src/components/user/UserAvatar"; import { useDialog } from "~src/components/dialog"; import FlatButton from "~src/components/button/FlatButton"; diff --git a/FrontEnd/src/utilities/hooks.ts b/FrontEnd/src/utilities/hooks.ts deleted file mode 100644 index a59f7167..00000000 --- a/FrontEnd/src/utilities/hooks.ts +++ /dev/null @@ -1,5 +0,0 @@ -import useClickOutside from "./hooks/useClickOutside"; -import useScrollToBottom from "./hooks/useScrollToBottom"; -import { useIsSmallScreen } from "./hooks/mediaQuery"; - -export { useClickOutside, useScrollToBottom, useIsSmallScreen }; diff --git a/FrontEnd/src/utilities/hooks/mediaQuery.ts b/FrontEnd/src/utilities/hooks/mediaQuery.ts deleted file mode 100644 index ad55c3c0..00000000 --- a/FrontEnd/src/utilities/hooks/mediaQuery.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { useMediaQuery } from "react-responsive"; - -export function useIsSmallScreen(): boolean { - return useMediaQuery({ maxWidth: 576 }); -} |