aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/views
diff options
context:
space:
mode:
Diffstat (limited to 'FrontEnd/src/views')
-rw-r--r--FrontEnd/src/views/admin/Admin.tsx27
-rw-r--r--FrontEnd/src/views/admin/AdminNav.tsx29
-rw-r--r--FrontEnd/src/views/admin/MoreAdmin.tsx7
-rw-r--r--FrontEnd/src/views/admin/UserAdmin.tsx304
-rw-r--r--FrontEnd/src/views/admin/index.css33
-rw-r--r--FrontEnd/src/views/admin/index.tsx7
-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
-rw-r--r--FrontEnd/src/views/common/button/LoadingButton.css2
-rw-r--r--FrontEnd/src/views/search/index.css15
-rw-r--r--FrontEnd/src/views/search/index.tsx131
14 files changed, 1 insertions, 1235 deletions
diff --git a/FrontEnd/src/views/admin/Admin.tsx b/FrontEnd/src/views/admin/Admin.tsx
deleted file mode 100644
index 986c36b4..00000000
--- a/FrontEnd/src/views/admin/Admin.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { Route, Routes } from "react-router-dom";
-import { useTranslation } from "react-i18next";
-
-import AdminNav from "./AdminNav";
-import UserAdmin from "./UserAdmin";
-import MoreAdmin from "./MoreAdmin";
-
-import "./index.css";
-
-const Admin: React.FC = () => {
- useTranslation("admin");
-
- return (
- <>
- <div className="container">
- <AdminNav className="mt-2" />
- <Routes>
- <Route index element={<UserAdmin />} />
- <Route path="user" element={<UserAdmin />} />
- <Route path="more" element={<MoreAdmin />} />
- </Routes>
- </div>
- </>
- );
-};
-
-export default Admin;
diff --git a/FrontEnd/src/views/admin/AdminNav.tsx b/FrontEnd/src/views/admin/AdminNav.tsx
deleted file mode 100644
index b7385e5c..00000000
--- a/FrontEnd/src/views/admin/AdminNav.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { useLocation } from "react-router-dom";
-
-import Tabs from "../common/tab/Tabs";
-
-export function AdminNav({ className }: { className?: string }) {
- const location = useLocation();
- const name = location.pathname.split("/")[2] ?? "user";
-
- return (
- <Tabs
- className={className}
- activeTabName={name}
- tabs={[
- {
- name: "user",
- text: "admin:nav.users",
- link: "/admin/user",
- },
- {
- name: "more",
- text: "admin:nav.more",
- link: "/admin/more",
- },
- ]}
- />
- );
-}
-
-export default AdminNav;
diff --git a/FrontEnd/src/views/admin/MoreAdmin.tsx b/FrontEnd/src/views/admin/MoreAdmin.tsx
deleted file mode 100644
index d49d211f..00000000
--- a/FrontEnd/src/views/admin/MoreAdmin.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import * as React from "react";
-
-const MoreAdmin: React.FC = () => {
- return <>More...</>;
-};
-
-export default MoreAdmin;
diff --git a/FrontEnd/src/views/admin/UserAdmin.tsx b/FrontEnd/src/views/admin/UserAdmin.tsx
deleted file mode 100644
index d5179bf5..00000000
--- a/FrontEnd/src/views/admin/UserAdmin.tsx
+++ /dev/null
@@ -1,304 +0,0 @@
-import { useState, useEffect } from "react";
-import * as React from "react";
-import { Trans, useTranslation } from "react-i18next";
-import classnames from "classnames";
-
-import { getHttpUserClient, HttpUser, kUserPermissionList } from "@/http/user";
-
-import OperationDialog, {
- OperationDialogBoolInput,
-} from "../common/dialog/OperationDialog";
-import Button from "../common/button/Button";
-import Spinner from "../common/Spinner";
-import FlatButton from "../common/button/FlatButton";
-import IconButton from "../common/button/IconButton";
-
-const CreateUserDialog: React.FC<{
- open: boolean;
- close: () => void;
- onSuccess: (user: HttpUser) => void;
-}> = ({ open, close, onSuccess }) => {
- return (
- <OperationDialog
- title="admin:user.dialog.create.title"
- themeColor="success"
- inputPrompt="admin:user.dialog.create.prompt"
- inputScheme={
- [
- { type: "text", label: "admin:user.username" },
- { type: "text", label: "admin:user.password" },
- ] as const
- }
- onProcess={([username, password]) =>
- getHttpUserClient().post({
- username,
- password,
- })
- }
- onClose={close}
- open={open}
- onSuccessAndClose={onSuccess}
- />
- );
-};
-
-const UsernameLabel: React.FC<{ children: React.ReactNode }> = (props) => {
- return <span style={{ color: "blue" }}>{props.children}</span>;
-};
-
-const UserDeleteDialog: React.FC<{
- open: boolean;
- close: () => void;
- user: HttpUser;
- onSuccess: () => void;
-}> = ({ open, close, user, onSuccess }) => {
- return (
- <OperationDialog
- open={open}
- onClose={close}
- title="admin:user.dialog.delete.title"
- themeColor="danger"
- inputPrompt={() => (
- <Trans i18nKey="user.dialog.delete.prompt" ns="admin">
- 0<UsernameLabel>{user.username}</UsernameLabel>2
- </Trans>
- )}
- onProcess={() => getHttpUserClient().delete(user.username)}
- onSuccessAndClose={onSuccess}
- />
- );
-};
-
-const UserModifyDialog: React.FC<{
- open: boolean;
- close: () => void;
- user: HttpUser;
- onSuccess: () => void;
-}> = ({ open, close, user, onSuccess }) => {
- return (
- <OperationDialog
- open={open}
- onClose={close}
- title="admin:user.dialog.modify.title"
- themeColor="danger"
- inputPrompt={() => (
- <Trans i18nKey="admin:user.dialog.modify.prompt">
- 0<UsernameLabel>{user.username}</UsernameLabel>2
- </Trans>
- )}
- inputScheme={
- [
- {
- type: "text",
- label: "admin:user.username",
- initValue: user.username,
- },
- { type: "text", label: "admin:user.password" },
- {
- type: "text",
- label: "admin:user.nickname",
- initValue: user.nickname,
- },
- ] as const
- }
- onProcess={([username, password, nickname]) =>
- getHttpUserClient().patch(user.username, {
- username: username !== user.username ? username : undefined,
- password: password !== "" ? password : undefined,
- nickname: nickname !== user.nickname ? nickname : undefined,
- })
- }
- onSuccessAndClose={onSuccess}
- />
- );
-};
-
-const UserPermissionModifyDialog: React.FC<{
- open: boolean;
- close: () => void;
- user: HttpUser;
- onSuccess: () => void;
-}> = ({ open, close, user, onSuccess }) => {
- const oldPermissionBoolList: boolean[] = kUserPermissionList.map(
- (permission) => user.permissions.includes(permission)
- );
-
- return (
- <OperationDialog
- open={open}
- onClose={close}
- title="admin:user.dialog.modifyPermissions.title"
- themeColor="danger"
- inputPrompt={() => (
- <Trans i18nKey="admin:user.dialog.modifyPermissions.prompt">
- 0<UsernameLabel>{user.username}</UsernameLabel>2
- </Trans>
- )}
- inputScheme={kUserPermissionList.map<OperationDialogBoolInput>(
- (permission, index) => ({
- type: "bool",
- label: { type: "custom", value: permission },
- initValue: oldPermissionBoolList[index],
- })
- )}
- onProcess={async (newPermissionBoolList): Promise<boolean[]> => {
- for (let index = 0; index < kUserPermissionList.length; index++) {
- const oldValue = oldPermissionBoolList[index];
- const newValue = newPermissionBoolList[index];
- const permission = kUserPermissionList[index];
- if (oldValue === newValue) continue;
- if (newValue) {
- await getHttpUserClient().putUserPermission(
- user.username,
- permission
- );
- } else {
- await getHttpUserClient().deleteUserPermission(
- user.username,
- permission
- );
- }
- }
- return newPermissionBoolList;
- }}
- onSuccessAndClose={onSuccess}
- />
- );
-};
-
-interface UserItemProps {
- user: HttpUser;
- onChange: () => void;
-}
-
-const UserItem: React.FC<UserItemProps> = ({ user, onChange }) => {
- const { t } = useTranslation();
-
- const [dialog, setDialog] = useState<
- "delete" | "modify" | "permission" | null
- >(null);
-
- const [editMaskVisible, setEditMaskVisible] = React.useState<boolean>(false);
-
- return (
- <>
- <div className="admin-user-item">
- <IconButton
- icon="pencil-square"
- className="cru-float-right"
- onClick={() => setEditMaskVisible(true)}
- />
- <h5 className="cru-color-primary">{user.username}</h5>
- <small className="d-block cru-color-secondary">
- {t("admin:user.nickname")}
- {user.nickname}
- </small>
- <small className="d-block cru-color-secondary">
- {t("admin:user.uniqueId")}
- {user.uniqueId}
- </small>
- <small className="d-block cru-color-secondary">
- {t("admin:user.permissions")}
- {user.permissions.map((permission) => {
- return (
- <span key={permission} className="cru-color-danger">
- {permission}
- </span>
- );
- })}
- </small>
- <div
- className={classnames("edit-mask", !editMaskVisible && "d-none")}
- onClick={() => setEditMaskVisible(false)}
- >
- <FlatButton
- text="admin:user.modify"
- onClick={() => setDialog("modify")}
- />
- <FlatButton
- text="admin:user.modifyPermissions"
- onClick={() => setDialog("permission")}
- />
- <FlatButton
- text="admin:user.delete"
- color="danger"
- onClick={() => setDialog("delete")}
- />
- </div>
- </div>
- <UserDeleteDialog
- open={dialog === "delete"}
- close={() => setDialog(null)}
- user={user}
- onSuccess={onChange}
- />
- <UserModifyDialog
- open={dialog === "modify"}
- close={() => setDialog(null)}
- user={user}
- onSuccess={onChange}
- />
- <UserPermissionModifyDialog
- open={dialog === "permission"}
- close={() => setDialog(null)}
- user={user}
- onSuccess={onChange}
- />
- </>
- );
-};
-
-const UserAdmin: React.FC = () => {
- const [users, setUsers] = useState<HttpUser[] | null>(null);
- const [dialog, setDialog] = useState<"create" | null>(null);
- const [usersVersion, setUsersVersion] = useState<number>(0);
- const updateUsers = (): void => {
- setUsersVersion(usersVersion + 1);
- };
-
- useEffect(() => {
- let subscribe = true;
- void getHttpUserClient()
- .list()
- .then((us) => {
- if (subscribe) {
- setUsers(us.items);
- }
- });
- return () => {
- subscribe = false;
- };
- }, [usersVersion]);
-
- if (users) {
- const userComponents = users.map((user) => {
- return (
- <UserItem key={user.username} user={user} onChange={updateUsers} />
- );
- });
-
- return (
- <>
- <div className="row justify-content-end my-2">
- <div className="col col-auto">
- <Button
- text="admin:create"
- color="success"
- onClick={() => setDialog("create")}
- />
- </div>
- </div>
- {userComponents}
- <CreateUserDialog
- open={dialog === "create"}
- close={() => setDialog(null)}
- onSuccess={updateUsers}
- />
- </>
- );
- } else {
- return <Spinner />;
- }
-};
-
-export default UserAdmin;
diff --git a/FrontEnd/src/views/admin/index.css b/FrontEnd/src/views/admin/index.css
deleted file mode 100644
index 17e24586..00000000
--- a/FrontEnd/src/views/admin/index.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.admin-user-item {
- position: relative;
- border: var(--cru-primary-color) solid;
- border-width: 1px 1px 0;
- padding: 1em;
-}
-
-.admin-user-item:last-of-type {
- border-bottom-width: 1px;
-}
-
-.admin-user-item .edit-mask {
- position: absolute;
- top: 0;
- left: 0;
- bottom: 0;
- right: 0;
- background: rgba(255, 255, 255, 0.9);
- position: absolute;
- display: flex;
- justify-content: space-around;
- align-items: center;
-}
-
-@media (max-width: 576px) {
- .admin-user-item .edit-mask {
- flex-direction: column;
- }
-}
-
-.admin-user-item .edit-mask button {
- margin: 0.5em 2em;
-}
diff --git a/FrontEnd/src/views/admin/index.tsx b/FrontEnd/src/views/admin/index.tsx
deleted file mode 100644
index 0467711d..00000000
--- a/FrontEnd/src/views/admin/index.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import { lazy } from "react";
-
-const Admin = lazy(
- () => import(/* webpackChunkName: "admin" */ "./Admin")
-);
-
-export default Admin;
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;
diff --git a/FrontEnd/src/views/common/button/LoadingButton.css b/FrontEnd/src/views/common/button/LoadingButton.css
index 2f73116a..0a7e4a3a 100644
--- a/FrontEnd/src/views/common/button/LoadingButton.css
+++ b/FrontEnd/src/views/common/button/LoadingButton.css
@@ -11,4 +11,4 @@
.cru-loading-button-spinner {
margin-left: 0.5em;
-}
+} \ No newline at end of file
diff --git a/FrontEnd/src/views/search/index.css b/FrontEnd/src/views/search/index.css
deleted file mode 100644
index 6ff4d9fa..00000000
--- a/FrontEnd/src/views/search/index.css
+++ /dev/null
@@ -1,15 +0,0 @@
-.timeline-search-result-item {
- border: 1px solid;
- border-color: #e9ecef;
- background: #f8f9fa;
- transition: all 0.3s;
-}
-.timeline-search-result-item:hover {
- border-color: #0d6efd;
-}
-
-.timeline-search-result-item-avatar {
- width: 2em;
- height: 2em;
- border-radius: 50%;
-}
diff --git a/FrontEnd/src/views/search/index.tsx b/FrontEnd/src/views/search/index.tsx
deleted file mode 100644
index 58257465..00000000
--- a/FrontEnd/src/views/search/index.tsx
+++ /dev/null
@@ -1,131 +0,0 @@
-import * as React from "react";
-import { useTranslation } from "react-i18next";
-import { useNavigate, useLocation } from "react-router-dom";
-import { Link } from "react-router-dom";
-
-import { HttpNetworkError } from "@/http/common";
-import { getHttpSearchClient } from "@/http/search";
-import { HttpTimelineInfo } from "@/http/timeline";
-
-import SearchInput from "../common/SearchInput";
-import UserAvatar from "../common/user/UserAvatar";
-
-import "./index.css";
-
-const TimelineSearchResultItemView: React.FC<{
- timeline: HttpTimelineInfo;
-}> = ({ timeline }) => {
- return (
- <div className="timeline-search-result-item my-2 p-3">
- <h4>
- <Link
- to={`/${timeline.owner.username}/${timeline.nameV2}`}
- className="mb-2 text-primary"
- >
- {timeline.title}
- <small className="ms-3 text-secondary">{timeline.nameV2}</small>
- </Link>
- </h4>
- <div>
- <UserAvatar
- username={timeline.owner.username}
- className="timeline-search-result-item-avatar me-2"
- />
- {timeline.owner.nickname}
- <small className="ms-3 text-secondary">
- @{timeline.owner.username}
- </small>
- </div>
- </div>
- );
-};
-
-const SearchPage: React.FC = () => {
- const { t } = useTranslation();
-
- const navigate = useNavigate();
- const location = useLocation();
- const searchParams = new URLSearchParams(location.search);
- const queryParam = searchParams.get("q");
-
- const [searchText, setSearchText] = React.useState<string>("");
- const [state, setState] = React.useState<
- HttpTimelineInfo[] | "init" | "loading" | "network-error" | "error"
- >("init");
-
- const [forceResearchKey, setForceResearchKey] = React.useState<number>(0);
-
- React.useEffect(() => {
- setState("init");
- if (queryParam != null && queryParam.length > 0) {
- setSearchText(queryParam);
- setState("loading");
- void getHttpSearchClient()
- .searchTimelines(queryParam)
- .then(
- (ts) => {
- setState(ts);
- },
- (e) => {
- if (e instanceof HttpNetworkError) {
- setState("network-error");
- } else {
- setState("error");
- }
- }
- );
- }
- }, [queryParam, forceResearchKey]);
-
- return (
- <div className="container my-3">
- <div className="row justify-content-center">
- <SearchInput
- className="col-12 col-sm-9 col-md-6"
- value={searchText}
- onChange={setSearchText}
- loading={state === "loading"}
- onButtonClick={() => {
- if (queryParam === searchText) {
- setForceResearchKey((old) => old + 1);
- } else {
- navigate(`/search?q=${searchText}`);
- }
- }}
- />
- </div>
- {(() => {
- switch (state) {
- case "init": {
- if (queryParam == null || queryParam.length === 0) {
- return <div>{t("searchPage.input")}</div>;
- }
- break;
- }
- case "loading": {
- return <div>{t("searchPage.loading")}</div>;
- }
- case "network-error": {
- return <div className="text-danger">{t("error.network")}</div>;
- }
- case "error": {
- return <div className="text-danger">{t("error.unknown")}</div>;
- }
- default: {
- if (state.length === 0) {
- return <div>{t("searchPage.noResult")}</div>;
- }
- return state.map((t) => (
- <TimelineSearchResultItemView
- key={`${t.owner.username}/${t.nameV2}`}
- timeline={t}
- />
- ));
- }
- }
- })()}
- </div>
- );
-};
-
-export default SearchPage;