aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/components
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2023-08-26 23:49:28 +0800
committercrupest <crupest@outlook.com>2023-08-26 23:49:28 +0800
commit256cc9592a3f31fc392e1ccdb699aa206b7b47ce (patch)
treefdca6f8b1cdbd5ed264a1af9e48496685f4e9a95 /FrontEnd/src/components
parentf5dfd52f6efece2f4cad227044ecf4dd66301bbc (diff)
downloadtimeline-256cc9592a3f31fc392e1ccdb699aa206b7b47ce.tar.gz
timeline-256cc9592a3f31fc392e1ccdb699aa206b7b47ce.tar.bz2
timeline-256cc9592a3f31fc392e1ccdb699aa206b7b47ce.zip
...
Diffstat (limited to 'FrontEnd/src/components')
-rw-r--r--FrontEnd/src/components/AppBar.tsx3
-rw-r--r--FrontEnd/src/components/Card.tsx1
-rw-r--r--FrontEnd/src/components/LoadFailReload.tsx37
-rw-r--r--FrontEnd/src/components/LoadingPage.tsx13
-rw-r--r--FrontEnd/src/components/SearchInput.tsx2
-rw-r--r--FrontEnd/src/components/button/ButtonRowV2.tsx3
-rw-r--r--FrontEnd/src/components/button/LoadingButton.tsx1
-rw-r--r--FrontEnd/src/components/common.ts1
-rw-r--r--FrontEnd/src/components/dialog/ConfirmDialog.css0
-rw-r--r--FrontEnd/src/components/dialog/ConfirmDialog.tsx1
-rw-r--r--FrontEnd/src/components/dialog/OperationDialog.tsx7
-rw-r--r--FrontEnd/src/components/dialog/index.ts1
-rw-r--r--FrontEnd/src/components/hooks.ts14
-rw-r--r--FrontEnd/src/components/hooks/index.ts3
-rw-r--r--FrontEnd/src/components/hooks/responsive.ts7
-rw-r--r--FrontEnd/src/components/hooks/useClickOutside.ts38
-rw-r--r--FrontEnd/src/components/hooks/useScrollToBottom.ts44
-rw-r--r--FrontEnd/src/components/input/InputGroup.tsx16
-rw-r--r--FrontEnd/src/components/menu/Menu.tsx2
-rw-r--r--FrontEnd/src/components/menu/PopupMenu.tsx6
20 files changed, 111 insertions, 89 deletions
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/components/hooks/useClickOutside.ts b/FrontEnd/src/components/hooks/useClickOutside.ts
new file mode 100644
index 00000000..828ce7e3
--- /dev/null
+++ b/FrontEnd/src/components/hooks/useClickOutside.ts
@@ -0,0 +1,38 @@
+import { useRef, useEffect } from "react";
+
+export default function useClickOutside(
+ element: HTMLElement | null | undefined,
+ onClickOutside: () => void,
+ nextTick?: boolean,
+): void {
+ const onClickOutsideRef = useRef<() => void>(onClickOutside);
+
+ useEffect(() => {
+ onClickOutsideRef.current = onClickOutside;
+ }, [onClickOutside]);
+
+ useEffect(() => {
+ if (element != null) {
+ const handler = (event: MouseEvent): void => {
+ let e: HTMLElement | null = event.target as HTMLElement;
+ while (e) {
+ if (e == element) {
+ return;
+ }
+ e = e.parentElement;
+ }
+ onClickOutsideRef.current();
+ };
+ if (nextTick) {
+ setTimeout(() => {
+ document.addEventListener("click", handler);
+ });
+ } else {
+ document.addEventListener("click", handler);
+ }
+ return () => {
+ document.removeEventListener("click", handler);
+ };
+ }
+ }, [element, nextTick]);
+}
diff --git a/FrontEnd/src/components/hooks/useScrollToBottom.ts b/FrontEnd/src/components/hooks/useScrollToBottom.ts
new file mode 100644
index 00000000..79fcda16
--- /dev/null
+++ b/FrontEnd/src/components/hooks/useScrollToBottom.ts
@@ -0,0 +1,44 @@
+import { useRef, useEffect } from "react";
+import { fromEvent, filter, throttleTime } from "rxjs";
+
+function useScrollToBottom(
+ handler: () => void,
+ enable = true,
+ option = {
+ maxOffset: 5,
+ throttle: 1000,
+ },
+): void {
+ const handlerRef = useRef<(() => void) | null>(null);
+
+ useEffect(() => {
+ handlerRef.current = handler;
+
+ return () => {
+ handlerRef.current = null;
+ };
+ }, [handler]);
+
+ useEffect(() => {
+ const subscription = fromEvent(window, "scroll")
+ .pipe(
+ filter(
+ () =>
+ window.scrollY >=
+ document.body.scrollHeight - window.innerHeight - option.maxOffset,
+ ),
+ throttleTime(option.throttle),
+ )
+ .subscribe(() => {
+ if (enable) {
+ handlerRef.current?.();
+ }
+ });
+
+ return () => {
+ subscription.unsubscribe();
+ };
+ }, [enable, option.maxOffset, option.throttle]);
+}
+
+export default useScrollToBottom;
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";