diff options
author | crupest <crupest@outlook.com> | 2023-08-02 02:52:07 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2023-08-02 02:52:07 +0800 |
commit | 645a88e7e35d15cec6106709c42b071bec045e0d (patch) | |
tree | 8c34d8ac3ba177f725e31b55bdf689a303cfee9a /FrontEnd/src/views/center | |
parent | 0c5c9d51c51d07aecb6f4a01586c81c824141cb2 (diff) | |
download | timeline-645a88e7e35d15cec6106709c42b071bec045e0d.tar.gz timeline-645a88e7e35d15cec6106709c42b071bec045e0d.tar.bz2 timeline-645a88e7e35d15cec6106709c42b071bec045e0d.zip |
...
Diffstat (limited to 'FrontEnd/src/views/center')
-rw-r--r-- | FrontEnd/src/views/center/CenterBoards.tsx | 131 | ||||
-rw-r--r-- | FrontEnd/src/views/center/TimelineBoard.tsx | 390 | ||||
-rw-r--r-- | FrontEnd/src/views/center/TimelineCreateDialog.tsx | 57 | ||||
-rw-r--r-- | FrontEnd/src/views/center/index.css | 43 | ||||
-rw-r--r-- | FrontEnd/src/views/center/index.tsx | 60 |
5 files changed, 0 insertions, 681 deletions
diff --git a/FrontEnd/src/views/center/CenterBoards.tsx b/FrontEnd/src/views/center/CenterBoards.tsx deleted file mode 100644 index a8be2c29..00000000 --- a/FrontEnd/src/views/center/CenterBoards.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import * as React from "react"; -import { useTranslation } from "react-i18next"; - -import { highlightTimelineUsername } from "@/common"; - -import { pushAlert } from "@/services/alert"; -import { useUserLoggedIn } from "@/services/user"; - -import { getHttpTimelineClient } from "@/http/timeline"; -import { getHttpBookmarkClient } from "@/http/bookmark"; - -import TimelineBoard from "./TimelineBoard"; - -const CenterBoards: React.FC = () => { - const { t } = useTranslation(); - - const user = useUserLoggedIn(); - - return ( - <> - <div className="row justify-content-center"> - <div className="col col-12 col-md-6"> - <div className="row"> - <div className="col col-12 my-2"> - <TimelineBoard - title={t("home.bookmarkTimeline")} - load={() => - getHttpBookmarkClient() - .list(user.username) - .then((p) => p.items) - } - editHandler={{ - onDelete: (owner, timeline) => { - return getHttpBookmarkClient() - .delete(user.username, owner, timeline) - .catch((e) => { - pushAlert({ - message: "home.message.deleteBookmarkFail", - type: "danger", - }); - throw e; - }); - }, - onMove: (owner, timeline, index, offset) => { - return getHttpBookmarkClient() - .move( - user.username, - owner, - timeline, - index + offset + 1 // +1 because backend contract: index starts at 1 - ) - .catch((e) => { - pushAlert({ - message: "home.message.moveBookmarkFail", - type: "danger", - }); - throw e; - }) - .then(); - }, - }} - /> - </div> - <div className="col col-12 my-2"> - <TimelineBoard - title={t("home.highlightTimeline")} - load={() => - getHttpBookmarkClient() - .list(highlightTimelineUsername) - .then((p) => p.items) - } - editHandler={ - user.username === highlightTimelineUsername - ? { - onDelete: (owner, timeline) => { - return getHttpBookmarkClient() - .delete(highlightTimelineUsername, owner, timeline) - .catch((e) => { - pushAlert({ - message: "home.message.deleteHighlightFail", - type: "danger", - }); - throw e; - }); - }, - onMove: (owner, timeline, index, offset) => { - return getHttpBookmarkClient() - .move( - highlightTimelineUsername, - owner, - timeline, - index + offset + 1 // +1 because backend contract: index starts at 1 - ) - .catch((e) => { - pushAlert({ - message: "home.message.moveBookmarkFail", - type: "danger", - }); - throw e; - }) - .then(); - }, - } - : undefined - } - /> - </div> - </div> - </div> - <div className="col-12 col-md-6 my-2"> - <TimelineBoard - title={t("home.relatedTimeline")} - load={() => - getHttpTimelineClient() - .listTimeline({ relate: user.username }) - .then((l) => - l.map((t, index) => ({ - timelineOwner: t.owner.username, - timelineName: t.nameV2, - position: index + 1, - })) - ) - } - /> - </div> - </div> - </> - ); -}; - -export default CenterBoards; diff --git a/FrontEnd/src/views/center/TimelineBoard.tsx b/FrontEnd/src/views/center/TimelineBoard.tsx deleted file mode 100644 index b3ccdf8c..00000000 --- a/FrontEnd/src/views/center/TimelineBoard.tsx +++ /dev/null @@ -1,390 +0,0 @@ -import * as React from "react"; -import classnames from "classnames"; -import { Link } from "react-router-dom"; - -import { TimelineBookmark } from "@/http/bookmark"; - -import TimelineLogo from "../common/TimelineLogo"; -import LoadFailReload from "../common/LoadFailReload"; -import FlatButton from "../common/button/FlatButton"; -import Card from "../common/Card"; -import Spinner from "../common/Spinner"; -import IconButton from "../common/button/IconButton"; - -interface TimelineBoardItemProps { - timeline: TimelineBookmark; - // In height. - offset?: number; - // In px. - arbitraryOffset?: number; - // If not null, will disable navigation on click. - actions?: { - onDelete: () => void; - onMove: { - start: (e: React.PointerEvent) => void; - moving: (e: React.PointerEvent) => void; - end: (e: React.PointerEvent) => void; - }; - }; -} - -const TimelineBoardItem: React.FC<TimelineBoardItemProps> = ({ - timeline, - arbitraryOffset, - offset, - actions, -}) => { - const content = ( - <> - <TimelineLogo className="icon" /> - <span className="title"> - {timeline.timelineOwner}/{timeline.timelineName} - </span> - <span className="flex-grow-1"></span> - {actions != null ? ( - <div className="right"> - <IconButton - icon="trash" - color="danger" - className="px-2" - onClick={actions.onDelete} - /> - <IconButton - icon="grip-vertical" - className="px-2 touch-action-none" - onPointerDown={(e) => { - e.currentTarget.setPointerCapture(e.pointerId); - actions.onMove.start(e); - }} - onPointerUp={(e) => { - actions.onMove.end(e); - try { - e.currentTarget.releasePointerCapture(e.pointerId); - } catch (_) { - void null; - } - }} - onPointerMove={actions.onMove.moving} - /> - </div> - ) : null} - </> - ); - - const offsetStyle: React.CSSProperties = { - transform: - arbitraryOffset != null - ? `translate(0,${arbitraryOffset}px)` - : offset != null - ? `translate(0,${offset * 100}%)` - : undefined, - transition: offset != null ? "transform 0.5s" : undefined, - zIndex: arbitraryOffset != null ? 1 : undefined, - }; - - return actions == null ? ( - <Link - to={`${timeline.timelineOwner}/${timeline.timelineName}`} - className="timeline-board-item" - > - {content} - </Link> - ) : ( - <div style={offsetStyle} className="timeline-board-item"> - {content} - </div> - ); -}; - -interface TimelineBoardItemContainerProps { - timelines: TimelineBookmark[]; - editHandler?: { - // offset may exceed index range plusing index. - onMove: ( - owner: string, - timeline: string, - index: number, - offset: number - ) => void; - onDelete: (owner: string, timeline: string) => void; - }; -} - -const TimelineBoardItemContainer: React.FC<TimelineBoardItemContainerProps> = ({ - timelines, - editHandler, -}) => { - const [moveState, setMoveState] = React.useState<null | { - index: number; - offset: number; - startPointY: number; - }>(null); - - return ( - <> - {timelines.map((timeline, index) => { - const height = 48; - - let offset: number | undefined = undefined; - let arbitraryOffset: number | undefined = undefined; - if (moveState != null) { - if (index === moveState.index) { - arbitraryOffset = moveState.offset; - } else { - if (moveState.offset >= 0) { - const offsetCount = Math.round(moveState.offset / height); - if ( - index > moveState.index && - index <= moveState.index + offsetCount - ) { - offset = -1; - } else { - offset = 0; - } - } else { - const offsetCount = Math.round(-moveState.offset / height); - if ( - index < moveState.index && - index >= moveState.index - offsetCount - ) { - offset = 1; - } else { - offset = 0; - } - } - } - } - - return ( - <TimelineBoardItem - key={timeline.timelineOwner + "/" + timeline.timelineName} - timeline={timeline} - offset={offset} - arbitraryOffset={arbitraryOffset} - actions={ - editHandler != null - ? { - onDelete: () => { - editHandler.onDelete( - timeline.timelineOwner, - timeline.timelineName - ); - }, - onMove: { - start: (e) => { - if (moveState != null) return; - setMoveState({ - index, - offset: 0, - startPointY: e.clientY, - }); - }, - moving: (e) => { - if (moveState == null) return; - setMoveState({ - index, - offset: e.clientY - moveState.startPointY, - startPointY: moveState.startPointY, - }); - }, - end: () => { - if (moveState != null) { - const offsetCount = Math.round( - moveState.offset / height - ); - editHandler.onMove( - timeline.timelineOwner, - timeline.timelineName, - moveState.index, - offsetCount - ); - } - setMoveState(null); - }, - }, - } - : undefined - } - /> - ); - })} - </> - ); -}; - -interface TimelineBoardUIProps { - title?: string | null; - state: "offline" | "loading" | "loaded"; - timelines: TimelineBookmark[]; - onReload: () => void; - className?: string; - editHandler?: { - onMove: ( - owner: string, - timeline: string, - index: number, - offset: number - ) => void; - onDelete: (owner: string, timeline: string) => void; - }; -} - -const TimelineBoardUI: React.FC<TimelineBoardUIProps> = (props) => { - const { title, state, timelines, className, editHandler } = props; - - const editable = editHandler != null; - - const [editing, setEditing] = React.useState<boolean>(false); - - return ( - <Card className={classnames("timeline-board", className)}> - <div className="timeline-board-header"> - {title != null && <h3>{title}</h3>} - {editable && - (editing ? ( - <FlatButton - text="done" - onClick={() => { - setEditing(false); - }} - /> - ) : ( - <FlatButton - text="edit" - onClick={() => { - setEditing(true); - }} - /> - ))} - </div> - {(() => { - if (state === "loading") { - return ( - <div className="d-flex flex-grow-1 justify-content-center align-items-center"> - <Spinner /> - </div> - ); - } else if (state === "offline") { - return ( - <div className="d-flex flex-grow-1 justify-content-center align-items-center"> - <LoadFailReload onReload={props.onReload} /> - </div> - ); - } else { - return ( - <TimelineBoardItemContainer - timelines={timelines} - editHandler={ - editHandler && editing - ? { - onDelete: editHandler.onDelete, - onMove: (owner, timeline, index, offset) => { - if (index + offset >= timelines.length) { - offset = timelines.length - index - 1; - } else if (index + offset < 0) { - offset = -index; - } - editHandler.onMove(owner, timeline, index, offset); - }, - } - : undefined - } - /> - ); - } - })()} - </Card> - ); -}; - -export interface TimelineBoardProps { - title?: string | null; - className?: string; - load: () => Promise<TimelineBookmark[]>; - editHandler?: { - onMove: ( - owner: string, - timeline: string, - index: number, - offset: number - ) => Promise<void>; - onDelete: (owner: string, timeline: string) => Promise<void>; - }; -} - -const TimelineBoard: React.FC<TimelineBoardProps> = ({ - className, - title, - load, - editHandler, -}) => { - const [state, setState] = React.useState<"offline" | "loading" | "loaded">( - "loading" - ); - const [timelines, setTimelines] = React.useState<TimelineBookmark[]>([]); - - React.useEffect(() => { - let subscribe = true; - if (state === "loading") { - void load().then( - (timelines) => { - if (subscribe) { - setState("loaded"); - setTimelines(timelines); - } - }, - () => { - setState("offline"); - } - ); - } - return () => { - subscribe = false; - }; - }, [load, state]); - - return ( - <TimelineBoardUI - title={title} - className={className} - state={state} - timelines={timelines} - onReload={() => { - setState("loaded"); - }} - editHandler={ - typeof timelines === "object" && editHandler != null - ? { - onMove: (owner, timeline, index, offset) => { - const newTimelines = timelines.slice(); - const [t] = newTimelines.splice(index, 1); - newTimelines.splice(index + offset, 0, t); - setTimelines(newTimelines); - editHandler - .onMove(owner, timeline, index, offset) - .then(null, () => { - setTimelines(timelines); - }); - }, - onDelete: (owner, timeline) => { - const newTimelines = timelines.slice(); - newTimelines.splice( - timelines.findIndex( - (t) => - t.timelineOwner === owner && t.timelineName === timeline - ), - 1 - ); - setTimelines(newTimelines); - editHandler.onDelete(owner, timeline).then(null, () => { - setTimelines(timelines); - }); - }, - } - : undefined - } - /> - ); -}; - -export default TimelineBoard; diff --git a/FrontEnd/src/views/center/TimelineCreateDialog.tsx b/FrontEnd/src/views/center/TimelineCreateDialog.tsx deleted file mode 100644 index 63742936..00000000 --- a/FrontEnd/src/views/center/TimelineCreateDialog.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import * as React from "react"; -import { useNavigate } from "react-router-dom"; - -import { validateTimelineName } from "@/services/timeline"; -import { getHttpTimelineClient, HttpTimelineInfo } from "@/http/timeline"; - -import OperationDialog from "../common/dialog/OperationDialog"; -import { useUserLoggedIn } from "@/services/user"; - -interface TimelineCreateDialogProps { - open: boolean; - close: () => void; -} - -const TimelineCreateDialog: React.FC<TimelineCreateDialogProps> = (props) => { - const navigate = useNavigate(); - - const user = useUserLoggedIn(); - - return ( - <OperationDialog - open={props.open} - onClose={props.close} - themeColor="success" - title="home.createDialog.title" - inputScheme={ - [ - { - type: "text", - label: "home.createDialog.name", - helperText: "home.createDialog.nameFormat", - }, - ] as const - } - inputValidator={([name]) => { - if (name.length === 0) { - return { 0: "home.createDialog.noEmpty" }; - } else if (name.length > 26) { - return { 0: "home.createDialog.tooLong" }; - } else if (!validateTimelineName(name)) { - return { 0: "home.createDialog.badFormat" }; - } else { - return null; - } - }} - onProcess={([name]): Promise<HttpTimelineInfo> => - getHttpTimelineClient().postTimeline({ name }) - } - onSuccessAndClose={(timeline: HttpTimelineInfo) => { - navigate(`${user.username}/${timeline.nameV2}`); - }} - failurePrompt={(e) => `${e as string}`} - /> - ); -}; - -export default TimelineCreateDialog; diff --git a/FrontEnd/src/views/center/index.css b/FrontEnd/src/views/center/index.css deleted file mode 100644 index a779ff90..00000000 --- a/FrontEnd/src/views/center/index.css +++ /dev/null @@ -1,43 +0,0 @@ -.timeline-board {
- min-height: 200px;
- height: 100%;
- position: relative;
- padding: 1em 0;
- display: flex;
- flex-direction: column;
-}
-
-.timeline-board-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 0 1em;
-}
-
-.timeline-board-item {
- font-size: 1.1em;
- height: 48px;
- transition: background 0.3s;
- display: flex;
- align-items: center;
- padding: 0 1em;
-}
-
-.timeline-board-item .icon {
- height: 1.3em;
- color: black;
-}
-
-.timeline-board-item:hover {
- background: #dee2e6;
-}
-.timeline-board-item .right {
- display: flex;
- align-items: center;
- flex-shrink: 0;
-}
-.timeline-board-item .title {
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
diff --git a/FrontEnd/src/views/center/index.tsx b/FrontEnd/src/views/center/index.tsx deleted file mode 100644 index 77af2c20..00000000 --- a/FrontEnd/src/views/center/index.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import * as React from "react"; -import { useNavigate } from "react-router-dom"; - -import { useUserLoggedIn } from "@/services/user"; - -import SearchInput from "../common/SearchInput"; -import Button from "../common/button/Button"; -import CenterBoards from "./CenterBoards"; -import TimelineCreateDialog from "./TimelineCreateDialog"; - -import "./index.css"; - -const HomePage: React.FC = () => { - const navigate = useNavigate(); - - const user = useUserLoggedIn(); - - const [navText, setNavText] = React.useState<string>(""); - - const [dialog, setDialog] = React.useState<"create" | null>(null); - - return ( - <> - <div className="container"> - <div className="row my-3 justify-content-center"> - <div className="col col-12 col-md-8"> - <SearchInput - className="justify-content-center" - value={navText} - onChange={setNavText} - onButtonClick={() => { - navigate(`search?q=${navText}`); - }} - additionalButton={ - user != null && ( - <Button - text="home.createButton" - color="success" - onClick={() => { - setDialog("create"); - }} - /> - ) - } - /> - </div> - </div> - <CenterBoards /> - </div> - <TimelineCreateDialog - open={dialog === "create"} - close={() => { - setDialog(null); - }} - /> - </> - ); -}; - -export default HomePage; |