aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/views/center
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2023-08-02 02:52:07 +0800
committercrupest <crupest@outlook.com>2023-08-02 02:52:07 +0800
commit645a88e7e35d15cec6106709c42b071bec045e0d (patch)
tree8c34d8ac3ba177f725e31b55bdf689a303cfee9a /FrontEnd/src/views/center
parent0c5c9d51c51d07aecb6f4a01586c81c824141cb2 (diff)
downloadtimeline-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.tsx131
-rw-r--r--FrontEnd/src/views/center/TimelineBoard.tsx390
-rw-r--r--FrontEnd/src/views/center/TimelineCreateDialog.tsx57
-rw-r--r--FrontEnd/src/views/center/index.css43
-rw-r--r--FrontEnd/src/views/center/index.tsx60
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;