aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/app/views/center
diff options
context:
space:
mode:
Diffstat (limited to 'FrontEnd/src/app/views/center')
-rw-r--r--FrontEnd/src/app/views/center/CenterBoards.tsx107
-rw-r--r--FrontEnd/src/app/views/center/TimelineBoard.tsx370
-rw-r--r--FrontEnd/src/app/views/center/TimelineCreateDialog.tsx53
-rw-r--r--FrontEnd/src/app/views/center/center.sass36
-rw-r--r--FrontEnd/src/app/views/center/index.tsx64
5 files changed, 0 insertions, 630 deletions
diff --git a/FrontEnd/src/app/views/center/CenterBoards.tsx b/FrontEnd/src/app/views/center/CenterBoards.tsx
deleted file mode 100644
index f5200415..00000000
--- a/FrontEnd/src/app/views/center/CenterBoards.tsx
+++ /dev/null
@@ -1,107 +0,0 @@
-import React from "react";
-import { Row, Col } from "react-bootstrap";
-import { useTranslation } from "react-i18next";
-
-import { pushAlert } from "@/services/alert";
-import { useUserLoggedIn } from "@/services/user";
-
-import { getHttpTimelineClient } from "@/http/timeline";
-import { getHttpBookmarkClient } from "@/http/bookmark";
-import { getHttpHighlightClient } from "@/http/highlight";
-
-import TimelineBoard from "./TimelineBoard";
-
-const CenterBoards: React.FC = () => {
- const { t } = useTranslation();
-
- const user = useUserLoggedIn();
-
- return (
- <>
- <Row className="justify-content-center">
- <Col xs="12" md="6">
- <Row>
- <Col xs="12" className="my-2">
- <TimelineBoard
- title={t("home.bookmarkTimeline")}
- load={() => getHttpBookmarkClient().list()}
- editHandler={{
- onDelete: (timeline) => {
- return getHttpBookmarkClient()
- .delete(timeline)
- .catch((e) => {
- pushAlert({
- message: "home.message.deleteBookmarkFail",
- type: "danger",
- });
- throw e;
- });
- },
- onMove: (timeline, index, offset) => {
- return getHttpBookmarkClient()
- .move(
- { timeline, newPosition: index + offset + 1 } // +1 because backend contract: index starts at 1
- )
- .catch((e) => {
- pushAlert({
- message: "home.message.moveBookmarkFail",
- type: "danger",
- });
- throw e;
- });
- },
- }}
- />
- </Col>
- <Col xs="12" className="my-2">
- <TimelineBoard
- title={t("home.highlightTimeline")}
- load={() => getHttpHighlightClient().list()}
- editHandler={
- user.hasHighlightTimelineAdministrationPermission
- ? {
- onDelete: (timeline) => {
- return getHttpHighlightClient()
- .delete(timeline)
- .catch((e) => {
- pushAlert({
- message: "home.message.deleteHighlightFail",
- type: "danger",
- });
- throw e;
- });
- },
- onMove: (timeline, index, offset) => {
- return getHttpHighlightClient()
- .move(
- { timeline, newPosition: index + offset + 1 } // +1 because backend contract: index starts at 1
- )
- .catch((e) => {
- pushAlert({
- message: "home.message.moveHighlightFail",
- type: "danger",
- });
- throw e;
- });
- },
- }
- : undefined
- }
- />
- </Col>
- </Row>
- </Col>
- <Col xs="12" md="6" className="my-2">
- <TimelineBoard
- title={t("home.relatedTimeline")}
- load={() =>
- getHttpTimelineClient().listTimeline({ relate: user.username })
- }
- />
- </Col>
- </Row>
- </>
- );
-};
-
-export default CenterBoards;
diff --git a/FrontEnd/src/app/views/center/TimelineBoard.tsx b/FrontEnd/src/app/views/center/TimelineBoard.tsx
deleted file mode 100644
index 35249f66..00000000
--- a/FrontEnd/src/app/views/center/TimelineBoard.tsx
+++ /dev/null
@@ -1,370 +0,0 @@
-import React from "react";
-import classnames from "classnames";
-import { Link } from "react-router-dom";
-import { useTranslation } from "react-i18next";
-import { Spinner } from "react-bootstrap";
-
-import { HttpTimelineInfo } from "@/http/timeline";
-
-import TimelineLogo from "../common/TimelineLogo";
-import UserTimelineLogo from "../common/UserTimelineLogo";
-import LoadFailReload from "../common/LoadFailReload";
-
-interface TimelineBoardItemProps {
- timeline: HttpTimelineInfo;
- // 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 { name, title } = timeline;
- const isPersonal = name.startsWith("@");
- const url = isPersonal
- ? `/users/${timeline.owner.username}`
- : `/timelines/${name}`;
-
- const content = (
- <>
- {isPersonal ? (
- <UserTimelineLogo className="icon" />
- ) : (
- <TimelineLogo className="icon" />
- )}
- <span className="title">{title}</span>
- <small className="ms-2 text-secondary">{name}</small>
- <span className="flex-grow-1"></span>
- {actions != null ? (
- <div className="right">
- <i
- className="bi-trash icon-button text-danger px-2"
- onClick={actions.onDelete}
- />
- <i
- className="bi-grip-vertical icon-button text-gray 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={url} className="timeline-board-item">
- {content}
- </Link>
- ) : (
- <div style={offsetStyle} className="timeline-board-item">
- {content}
- </div>
- );
-};
-
-interface TimelineBoardItemContainerProps {
- timelines: HttpTimelineInfo[];
- editHandler?: {
- // offset may exceed index range plusing index.
- onMove: (timeline: string, index: number, offset: number) => void;
- onDelete: (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.name}
- timeline={timeline}
- offset={offset}
- arbitraryOffset={arbitraryOffset}
- actions={
- editHandler != null
- ? {
- onDelete: () => {
- editHandler.onDelete(timeline.name);
- },
- 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.name,
- moveState.index,
- offsetCount
- );
- }
- setMoveState(null);
- },
- },
- }
- : undefined
- }
- />
- );
- })}
- </>
- );
-};
-
-interface TimelineBoardUIProps {
- title?: string;
- timelines: HttpTimelineInfo[] | "offline" | "loading";
- onReload: () => void;
- className?: string;
- editHandler?: {
- onMove: (timeline: string, index: number, offset: number) => void;
- onDelete: (timeline: string) => void;
- };
-}
-
-const TimelineBoardUI: React.FC<TimelineBoardUIProps> = (props) => {
- const { title, timelines, className, editHandler } = props;
-
- const { t } = useTranslation();
-
- const editable = editHandler != null;
-
- const [editing, setEditing] = React.useState<boolean>(false);
-
- return (
- <div className={classnames("timeline-board", className)}>
- <div className="timeline-board-header">
- {title != null && <h3>{title}</h3>}
- {editable &&
- (editing ? (
- <div
- className="flat-button text-primary"
- onClick={() => {
- setEditing(false);
- }}
- >
- {t("done")}
- </div>
- ) : (
- <div
- className="flat-button text-primary"
- onClick={() => {
- setEditing(true);
- }}
- >
- {t("edit")}
- </div>
- ))}
- </div>
- {(() => {
- if (timelines === "loading") {
- return (
- <div className="d-flex flex-grow-1 justify-content-center align-items-center">
- <Spinner variant="primary" animation="border" />
- </div>
- );
- } else if (timelines === "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: (timeline, index, offset) => {
- if (index + offset >= timelines.length) {
- offset = timelines.length - index - 1;
- } else if (index + offset < 0) {
- offset = -index;
- }
- editHandler.onMove(timeline, index, offset);
- },
- }
- : undefined
- }
- />
- );
- }
- })()}
- </div>
- );
-};
-
-export interface TimelineBoardProps {
- title?: string;
- className?: string;
- load: () => Promise<HttpTimelineInfo[]>;
- editHandler?: {
- onMove: (timeline: string, index: number, offset: number) => Promise<void>;
- onDelete: (timeline: string) => Promise<void>;
- };
-}
-
-const TimelineBoard: React.FC<TimelineBoardProps> = ({
- className,
- title,
- load,
- editHandler,
-}) => {
- const [timelines, setTimelines] = React.useState<
- HttpTimelineInfo[] | "offline" | "loading"
- >("loading");
-
- React.useEffect(() => {
- let subscribe = true;
- if (timelines === "loading") {
- void load().then(
- (timelines) => {
- if (subscribe) {
- setTimelines(timelines);
- }
- },
- () => {
- setTimelines("offline");
- }
- );
- }
- return () => {
- subscribe = false;
- };
- }, [load, timelines]);
-
- return (
- <TimelineBoardUI
- title={title}
- className={className}
- timelines={timelines}
- onReload={() => {
- setTimelines("loading");
- }}
- editHandler={
- typeof timelines === "object" && editHandler != null
- ? {
- onMove: (timeline, index, offset) => {
- const newTimelines = timelines.slice();
- const [t] = newTimelines.splice(index, 1);
- newTimelines.splice(index + offset, 0, t);
- setTimelines(newTimelines);
- editHandler.onMove(timeline, index, offset).then(null, () => {
- setTimelines(timelines);
- });
- },
- onDelete: (timeline) => {
- const newTimelines = timelines.slice();
- newTimelines.splice(
- timelines.findIndex((t) => t.name === timeline),
- 1
- );
- setTimelines(newTimelines);
- editHandler.onDelete(timeline).then(null, () => {
- setTimelines(timelines);
- });
- },
- }
- : undefined
- }
- />
- );
-};
-
-export default TimelineBoard;
diff --git a/FrontEnd/src/app/views/center/TimelineCreateDialog.tsx b/FrontEnd/src/app/views/center/TimelineCreateDialog.tsx
deleted file mode 100644
index b4e25ba1..00000000
--- a/FrontEnd/src/app/views/center/TimelineCreateDialog.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import React from "react";
-import { useHistory } from "react-router";
-
-import { validateTimelineName } from "@/services/timeline";
-import OperationDialog from "../common/OperationDialog";
-import { getHttpTimelineClient, HttpTimelineInfo } from "@/http/timeline";
-
-interface TimelineCreateDialogProps {
- open: boolean;
- close: () => void;
-}
-
-const TimelineCreateDialog: React.FC<TimelineCreateDialogProps> = (props) => {
- const history = useHistory();
-
- return (
- <OperationDialog
- open={props.open}
- close={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) => {
- history.push(`timelines/${timeline.name}`);
- }}
- failurePrompt={(e) => `${e as string}`}
- />
- );
-};
-
-export default TimelineCreateDialog;
diff --git a/FrontEnd/src/app/views/center/center.sass b/FrontEnd/src/app/views/center/center.sass
deleted file mode 100644
index c0dfb9c0..00000000
--- a/FrontEnd/src/app/views/center/center.sass
+++ /dev/null
@@ -1,36 +0,0 @@
-.timeline-board
- @extend .cru-card
- @extend .d-flex
- @extend .flex-column
- @extend .py-3
- min-height: 200px
- height: 100%
- position: relative
-
-.timeline-board-header
- @extend .px-3
- display: flex
- align-items: center
- justify-content: space-between
-
-.timeline-board-item
- font-size: 1.1em
- @extend .px-3
- height: 48px
- transition: background 0.3s
- display: flex
- align-items: center
- .icon
- height: 1.3em
- color: black
- @extend .me-2
- &:hover
- background: $gray-300
- .right
- display: flex
- align-items: center
- flex-shrink: 0
- .title
- white-space: nowrap
- overflow: hidden
- text-overflow: ellipsis
diff --git a/FrontEnd/src/app/views/center/index.tsx b/FrontEnd/src/app/views/center/index.tsx
deleted file mode 100644
index 0a2abb2c..00000000
--- a/FrontEnd/src/app/views/center/index.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import React from "react";
-import { useHistory } from "react-router";
-import { useTranslation } from "react-i18next";
-import { Row, Container, Button, Col } from "react-bootstrap";
-
-import { useUserLoggedIn } from "@/services/user";
-
-import SearchInput from "../common/SearchInput";
-import CenterBoards from "./CenterBoards";
-import TimelineCreateDialog from "./TimelineCreateDialog";
-
-const HomePage: React.FC = () => {
- const history = useHistory();
-
- const { t } = useTranslation();
-
- const user = useUserLoggedIn();
-
- const [navText, setNavText] = React.useState<string>("");
-
- const [dialog, setDialog] = React.useState<"create" | null>(null);
-
- return (
- <>
- <Container>
- <Row className="my-3 justify-content-center">
- <Col xs={12} sm={8} lg={6}>
- <SearchInput
- className="justify-content-center"
- value={navText}
- onChange={setNavText}
- onButtonClick={() => {
- history.push(`search?q=${navText}`);
- }}
- additionalButton={
- user != null && (
- <Button
- variant="outline-success"
- onClick={() => {
- setDialog("create");
- }}
- >
- {t("home.createButton")}
- </Button>
- )
- }
- />
- </Col>
- </Row>
- <CenterBoards />
- </Container>
- {dialog === "create" && (
- <TimelineCreateDialog
- open
- close={() => {
- setDialog(null);
- }}
- />
- )}
- </>
- );
-};
-
-export default HomePage;