aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/views
diff options
context:
space:
mode:
Diffstat (limited to 'FrontEnd/src/views')
-rw-r--r--FrontEnd/src/views/timeline-common/TimelinePageCardTemplate.tsx153
-rw-r--r--FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx90
-rw-r--r--FrontEnd/src/views/timeline/CollapseButton.tsx (renamed from FrontEnd/src/views/timeline-common/CollapseButton.tsx)0
-rw-r--r--FrontEnd/src/views/timeline/ConnectionStatusBadge.css (renamed from FrontEnd/src/views/timeline-common/ConnectionStatusBadge.css)0
-rw-r--r--FrontEnd/src/views/timeline/ConnectionStatusBadge.tsx (renamed from FrontEnd/src/views/timeline-common/ConnectionStatusBadge.tsx)0
-rw-r--r--FrontEnd/src/views/timeline/MarkdownPostEdit.css (renamed from FrontEnd/src/views/timeline-common/MarkdownPostEdit.css)0
-rw-r--r--FrontEnd/src/views/timeline/MarkdownPostEdit.tsx (renamed from FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx)12
-rw-r--r--FrontEnd/src/views/timeline/PostPropertyChangeDialog.tsx (renamed from FrontEnd/src/views/timeline-common/PostPropertyChangeDialog.tsx)11
-rw-r--r--FrontEnd/src/views/timeline/Timeline.tsx (renamed from FrontEnd/src/views/timeline-common/Timeline.tsx)7
-rw-r--r--FrontEnd/src/views/timeline/TimelineCard.tsx210
-rw-r--r--FrontEnd/src/views/timeline/TimelineDateLabel.tsx (renamed from FrontEnd/src/views/timeline-common/TimelineDateLabel.tsx)0
-rw-r--r--FrontEnd/src/views/timeline/TimelineEmptyItem.tsx (renamed from FrontEnd/src/views/timeline-common/TimelineEmptyItem.tsx)0
-rw-r--r--FrontEnd/src/views/timeline/TimelineLine.tsx (renamed from FrontEnd/src/views/timeline-common/TimelineLine.tsx)0
-rw-r--r--FrontEnd/src/views/timeline/TimelineLoading.tsx (renamed from FrontEnd/src/views/timeline-common/TimelineLoading.tsx)0
-rw-r--r--FrontEnd/src/views/timeline/TimelineMember.css (renamed from FrontEnd/src/views/timeline-common/TimelineMember.css)0
-rw-r--r--FrontEnd/src/views/timeline/TimelineMember.tsx (renamed from FrontEnd/src/views/timeline-common/TimelineMember.tsx)0
-rw-r--r--FrontEnd/src/views/timeline/TimelinePagedPostListView.tsx (renamed from FrontEnd/src/views/timeline-common/TimelinePagedPostListView.tsx)0
-rw-r--r--FrontEnd/src/views/timeline/TimelinePostContentView.tsx (renamed from FrontEnd/src/views/timeline-common/TimelinePostContentView.tsx)0
-rw-r--r--FrontEnd/src/views/timeline/TimelinePostEdit.css (renamed from FrontEnd/src/views/timeline-common/TimelinePostEdit.css)0
-rw-r--r--FrontEnd/src/views/timeline/TimelinePostEdit.tsx (renamed from FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx)0
-rw-r--r--FrontEnd/src/views/timeline/TimelinePostEditCard.tsx (renamed from FrontEnd/src/views/timeline-common/TimelinePostEditCard.tsx)0
-rw-r--r--FrontEnd/src/views/timeline/TimelinePostEditNoLogin.tsx (renamed from FrontEnd/src/views/timeline-common/TimelinePostEditNoLogin.tsx)0
-rw-r--r--FrontEnd/src/views/timeline/TimelinePostListView.tsx (renamed from FrontEnd/src/views/timeline-common/TimelinePostListView.tsx)0
-rw-r--r--FrontEnd/src/views/timeline/TimelinePostView.tsx (renamed from FrontEnd/src/views/timeline-common/TimelinePostView.tsx)0
-rw-r--r--FrontEnd/src/views/timeline/TimelinePropertyChangeDialog.tsx (renamed from FrontEnd/src/views/timeline-common/TimelinePropertyChangeDialog.tsx)0
-rw-r--r--FrontEnd/src/views/timeline/index.css (renamed from FrontEnd/src/views/timeline-common/index.css)0
-rw-r--r--FrontEnd/src/views/timeline/index.tsx80
-rw-r--r--FrontEnd/src/views/user/UserCard.tsx53
-rw-r--r--FrontEnd/src/views/user/index.css0
-rw-r--r--FrontEnd/src/views/user/index.tsx36
30 files changed, 254 insertions, 398 deletions
diff --git a/FrontEnd/src/views/timeline-common/TimelinePageCardTemplate.tsx b/FrontEnd/src/views/timeline-common/TimelinePageCardTemplate.tsx
deleted file mode 100644
index eb17a9d0..00000000
--- a/FrontEnd/src/views/timeline-common/TimelinePageCardTemplate.tsx
+++ /dev/null
@@ -1,153 +0,0 @@
-import React from "react";
-import classnames from "classnames";
-import { useTranslation } from "react-i18next";
-
-import { getHttpHighlightClient } from "@/http/highlight";
-import { getHttpBookmarkClient } from "@/http/bookmark";
-
-import { useUser } from "@/services/user";
-import { pushAlert } from "@/services/alert";
-import { timelineVisibilityTooltipTranslationMap } from "@/services/timeline";
-
-import { useIsSmallScreen } from "@/utilities/mediaQuery";
-
-import { TimelinePageCardProps } from "./TimelinePageTemplate";
-
-import CollapseButton from "./CollapseButton";
-import { TimelineMemberDialog } from "./TimelineMember";
-import TimelinePropertyChangeDialog from "./TimelinePropertyChangeDialog";
-import ConnectionStatusBadge from "./ConnectionStatusBadge";
-import { MenuItems } from "../common/menu/Menu";
-import PopupMenu from "../common/menu/PopupMenu";
-import FullPageDialog from "../common/dailog/FullPageDialog";
-import Card from "../common/Card";
-
-export interface TimelineCardTemplateProps extends TimelinePageCardProps {
- infoArea: React.ReactNode;
- manageItems?: MenuItems;
- dialog: string | "property" | "member" | null;
- setDialog: (dialog: "property" | "member" | null) => void;
-}
-
-const TimelinePageCardTemplate: React.FC<TimelineCardTemplateProps> = ({
- timeline,
- collapse,
- toggleCollapse,
- infoArea,
- manageItems,
- connectionStatus,
- onReload,
- className,
- dialog,
- setDialog,
-}) => {
- const { t } = useTranslation();
-
- const isSmallScreen = useIsSmallScreen();
-
- const user = useUser();
-
- const content = (
- <>
- {infoArea}
- <p className="mb-0">{timeline.description}</p>
- <small className="mt-1 d-block">
- {t(timelineVisibilityTooltipTranslationMap[timeline.visibility])}
- </small>
- <div className="mt-2 cru-text-end">
- <i
- className={classnames(
- timeline.isHighlight ? "bi-star-fill" : "bi-star",
- "icon-button cru-color-primary me-3"
- )}
- onClick={
- user?.hasHighlightTimelineAdministrationPermission
- ? () => {
- getHttpHighlightClient()
- [timeline.isHighlight ? "delete" : "put"](timeline.name)
- .then(onReload, () => {
- pushAlert({
- message: timeline.isHighlight
- ? "timeline.removeHighlightFail"
- : "timeline.addHighlightFail",
- type: "danger",
- });
- });
- }
- : undefined
- }
- />
- {user != null ? (
- <i
- className={classnames(
- timeline.isBookmark ? "bi-bookmark-fill" : "bi-bookmark",
- "icon-button cru-color-primary me-3"
- )}
- onClick={() => {
- getHttpBookmarkClient()
- [timeline.isBookmark ? "delete" : "put"](timeline.name)
- .then(onReload, () => {
- pushAlert({
- message: timeline.isBookmark
- ? "timeline.removeBookmarkFail"
- : "timeline.addBookmarkFail",
- type: "danger",
- });
- });
- }}
- />
- ) : null}
- <i
- className={"icon-button bi-people cru-color-primary me-3"}
- onClick={() => setDialog("member")}
- />
- {manageItems != null ? (
- <PopupMenu items={manageItems} containerClassName="d-inline">
- <i className="icon-button bi-three-dots-vertical cru-color-primary" />
- </PopupMenu>
- ) : null}
- </div>
- </>
- );
-
- return (
- <>
- <Card className={classnames("p-2 cru-clearfix", className)}>
- <div
- className={classnames(
- "cru-float-right d-flex align-items-center",
- !collapse && "ms-3"
- )}
- >
- <ConnectionStatusBadge status={connectionStatus} className="me-2" />
- <CollapseButton collapse={collapse} onClick={toggleCollapse} />
- </div>
- {isSmallScreen ? (
- <FullPageDialog
- onBack={toggleCollapse}
- show={!collapse}
- contentContainerClassName="p-2"
- >
- {content}
- </FullPageDialog>
- ) : (
- <div style={{ display: collapse ? "none" : "inline" }}>{content}</div>
- )}
- </Card>
- <TimelineMemberDialog
- timeline={timeline}
- onClose={() => setDialog(null)}
- open={dialog === "member"}
- onChange={onReload}
- />
- <TimelinePropertyChangeDialog
- timeline={timeline}
- close={() => setDialog(null)}
- open={dialog === "property"}
- onChange={onReload}
- />
- </>
- );
-};
-
-export default TimelinePageCardTemplate;
diff --git a/FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx b/FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx
deleted file mode 100644
index ea6e8d40..00000000
--- a/FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import React from "react";
-import { HubConnectionState } from "@microsoft/signalr";
-
-import { HttpTimelineInfo } from "@/http/timeline";
-
-import useReverseScrollPositionRemember from "@/utilities/useReverseScrollPositionRemember";
-
-import { generatePalette, setPalette } from "@/palette";
-
-import Timeline from "./Timeline";
-
-export interface TimelinePageCardProps {
- timeline: HttpTimelineInfo;
- collapse: boolean;
- toggleCollapse: () => void;
- connectionStatus: HubConnectionState;
- className?: string;
- onReload: () => void;
-}
-
-export interface TimelinePageTemplateProps {
- timelineName: string;
- notFoundI18nKey: string;
- reloadKey: number;
- onReload: () => void;
- CardComponent: React.ComponentType<TimelinePageCardProps>;
-}
-
-const TimelinePageTemplate: React.FC<TimelinePageTemplateProps> = (props) => {
- const { timelineName, reloadKey, onReload, CardComponent } = props;
-
- const [timeline, setTimeline] = React.useState<HttpTimelineInfo | null>(null);
-
- const [connectionStatus, setConnectionStatus] =
- React.useState<HubConnectionState>(HubConnectionState.Connecting);
-
- useReverseScrollPositionRemember();
-
- React.useEffect(() => {
- if (timeline != null && timeline.color != null) {
- return setPalette(generatePalette({ primary: timeline.color }));
- }
- }, [timeline]);
-
- const cardCollapseLocalStorageKey = `timeline.${timelineName}.cardCollapse`;
-
- const [cardCollapse, setCardCollapse] = React.useState<boolean>(true);
-
- React.useEffect(() => {
- const savedCollapse = window.localStorage.getItem(
- cardCollapseLocalStorageKey
- );
- setCardCollapse(savedCollapse == null ? true : savedCollapse === "true");
- }, [cardCollapseLocalStorageKey]);
-
- const toggleCardCollapse = (): void => {
- const newState = !cardCollapse;
- setCardCollapse(newState);
- window.localStorage.setItem(
- cardCollapseLocalStorageKey,
- newState.toString()
- );
- };
-
- return (
- <>
- {timeline != null ? (
- <CardComponent
- className="timeline-template-card"
- timeline={timeline}
- collapse={cardCollapse}
- toggleCollapse={toggleCardCollapse}
- onReload={onReload}
- connectionStatus={connectionStatus}
- />
- ) : null}
- <div className="container">
- <Timeline
- timelineName={timelineName}
- reloadKey={reloadKey}
- onReload={onReload}
- onTimelineLoaded={(t) => setTimeline(t)}
- onConnectionStateChanged={setConnectionStatus}
- />
- </div>
- </>
- );
-};
-
-export default TimelinePageTemplate;
diff --git a/FrontEnd/src/views/timeline-common/CollapseButton.tsx b/FrontEnd/src/views/timeline/CollapseButton.tsx
index 31976228..31976228 100644
--- a/FrontEnd/src/views/timeline-common/CollapseButton.tsx
+++ b/FrontEnd/src/views/timeline/CollapseButton.tsx
diff --git a/FrontEnd/src/views/timeline-common/ConnectionStatusBadge.css b/FrontEnd/src/views/timeline/ConnectionStatusBadge.css
index 7fe83b9b..7fe83b9b 100644
--- a/FrontEnd/src/views/timeline-common/ConnectionStatusBadge.css
+++ b/FrontEnd/src/views/timeline/ConnectionStatusBadge.css
diff --git a/FrontEnd/src/views/timeline-common/ConnectionStatusBadge.tsx b/FrontEnd/src/views/timeline/ConnectionStatusBadge.tsx
index c8478557..c8478557 100644
--- a/FrontEnd/src/views/timeline-common/ConnectionStatusBadge.tsx
+++ b/FrontEnd/src/views/timeline/ConnectionStatusBadge.tsx
diff --git a/FrontEnd/src/views/timeline-common/MarkdownPostEdit.css b/FrontEnd/src/views/timeline/MarkdownPostEdit.css
index e36be992..e36be992 100644
--- a/FrontEnd/src/views/timeline-common/MarkdownPostEdit.css
+++ b/FrontEnd/src/views/timeline/MarkdownPostEdit.css
diff --git a/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx b/FrontEnd/src/views/timeline/MarkdownPostEdit.tsx
index a3a8f408..35a2bbf5 100644
--- a/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx
+++ b/FrontEnd/src/views/timeline/MarkdownPostEdit.tsx
@@ -14,6 +14,7 @@ import Spinner from "../common/Spinner";
import "./MarkdownPostEdit.css";
export interface MarkdownPostEditProps {
+ owner: string;
timeline: string;
onPosted: (post: HttpTimelinePostInfo) => void;
onPostError: () => void;
@@ -23,6 +24,7 @@ export interface MarkdownPostEditProps {
}
const MarkdownPostEdit: React.FC<MarkdownPostEditProps> = ({
+ owner: ownerUsername,
timeline: timelineName,
onPosted,
onClose,
@@ -84,9 +86,13 @@ const MarkdownPostEdit: React.FC<MarkdownPostEditProps> = ({
setProcess(true);
try {
const dataList = await getBuilder().build();
- const post = await getHttpTimelineClient().postPost(timelineName, {
- dataList,
- });
+ const post = await getHttpTimelineClient().postPost(
+ ownerUsername,
+ timelineName,
+ {
+ dataList,
+ }
+ );
onPosted(post);
onClose();
} catch (e) {
diff --git a/FrontEnd/src/views/timeline-common/PostPropertyChangeDialog.tsx b/FrontEnd/src/views/timeline/PostPropertyChangeDialog.tsx
index c1dd416c..d000093d 100644
--- a/FrontEnd/src/views/timeline-common/PostPropertyChangeDialog.tsx
+++ b/FrontEnd/src/views/timeline/PostPropertyChangeDialog.tsx
@@ -25,9 +25,14 @@ function PostPropertyChangeDialog(props: {
},
]}
onProcess={([time]) => {
- return getHttpTimelineClient().patchPost(post.timelineName, post.id, {
- time: time === "" ? undefined : new Date(time).toISOString(),
- });
+ return getHttpTimelineClient().patchPost(
+ post.timelineOwnerV2,
+ post.timelineNameV2,
+ post.id,
+ {
+ time: time === "" ? undefined : new Date(time).toISOString(),
+ }
+ );
}}
onSuccessAndClose={onSuccess}
/>
diff --git a/FrontEnd/src/views/timeline-common/Timeline.tsx b/FrontEnd/src/views/timeline/Timeline.tsx
index e028dddc..4738c705 100644
--- a/FrontEnd/src/views/timeline-common/Timeline.tsx
+++ b/FrontEnd/src/views/timeline/Timeline.tsx
@@ -29,6 +29,7 @@ import "./index.css";
export interface TimelineProps {
className?: string;
style?: React.CSSProperties;
+ timelineOwner: string;
timelineName: string;
reloadKey: number;
onReload: () => void;
@@ -37,7 +38,7 @@ export interface TimelineProps {
}
const Timeline: React.FC<TimelineProps> = (props) => {
- const { timelineName, className, style, reloadKey } = props;
+ const { timelineOwner, timelineName, className, style, reloadKey } = props;
const user = useUser();
@@ -82,8 +83,8 @@ const Timeline: React.FC<TimelineProps> = (props) => {
const client = getHttpTimelineClient();
Promise.all([
- client.getTimeline(timelineName),
- client.listPost(timelineName),
+ client.getTimeline(timelineOwner, timelineName),
+ client.listPost(timelineOwner, timelineName),
]).then(
([t, p]) => {
if (subscribe) {
diff --git a/FrontEnd/src/views/timeline/TimelineCard.tsx b/FrontEnd/src/views/timeline/TimelineCard.tsx
index 339fbfa0..156c581e 100644
--- a/FrontEnd/src/views/timeline/TimelineCard.tsx
+++ b/FrontEnd/src/views/timeline/TimelineCard.tsx
@@ -1,62 +1,182 @@
import React from "react";
+import { useTranslation } from "react-i18next";
+import classnames from "classnames";
+import { HubConnectionState } from "@microsoft/signalr";
-import { TimelinePageCardProps } from "../timeline-common/TimelinePageTemplate";
-import TimelinePageCardTemplate from "../timeline-common/TimelinePageCardTemplate";
+import { useIsSmallScreen } from "@/utilities/mediaQuery";
+import { timelineVisibilityTooltipTranslationMap } from "@/services/timeline";
+import { useUser } from "@/services/user";
+import { pushAlert } from "@/services/alert";
+import { HttpTimelineInfo } from "@/http/timeline";
+import { getHttpHighlightClient } from "@/http/highlight";
+import { getHttpBookmarkClient } from "@/http/bookmark";
import UserAvatar from "../common/user/UserAvatar";
+import PopupMenu from "../common/menu/PopupMenu";
+import FullPageDialog from "../common/dailog/FullPageDialog";
+import Card from "../common/Card";
import TimelineDeleteDialog from "./TimelineDeleteDialog";
+import ConnectionStatusBadge from "./ConnectionStatusBadge";
+import CollapseButton from "./CollapseButton";
+import { TimelineMemberDialog } from "./TimelineMember";
+import TimelinePropertyChangeDialog from "./TimelinePropertyChangeDialog";
+
+export interface TimelinePageCardProps {
+ timeline: HttpTimelineInfo;
+ collapse: boolean;
+ toggleCollapse: () => void;
+ connectionStatus: HubConnectionState;
+ className?: string;
+ onReload: () => void;
+}
const TimelineCard: React.FC<TimelinePageCardProps> = (props) => {
- const { timeline } = props;
+ const {
+ timeline,
+ collapse,
+ toggleCollapse,
+ connectionStatus,
+ onReload,
+ className,
+ } = props;
+
+ const { t } = useTranslation();
const [dialog, setDialog] = React.useState<
"member" | "property" | "delete" | null
>(null);
+ const isSmallScreen = useIsSmallScreen();
+
+ const user = useUser();
+
+ const content = (
+ <>
+ <h3 className="cru-color-primary d-inline-block align-middle">
+ {timeline.title}
+ <small className="ms-3 cru-color-secondary">{timeline.name}</small>
+ </h3>
+ <div>
+ <UserAvatar
+ username={timeline.owner.username}
+ className="cru-avatar small cru-round me-3"
+ />
+ {timeline.owner.nickname}
+ <small className="ms-3 cru-color-secondary">
+ @{timeline.owner.username}
+ </small>
+ </div>
+ <p className="mb-0">{timeline.description}</p>
+ <small className="mt-1 d-block">
+ {t(timelineVisibilityTooltipTranslationMap[timeline.visibility])}
+ </small>
+ <div className="mt-2 cru-text-end">
+ <i
+ className={classnames(
+ timeline.isHighlight ? "bi-star-fill" : "bi-star",
+ "icon-button cru-color-primary me-3"
+ )}
+ onClick={
+ user?.hasHighlightTimelineAdministrationPermission
+ ? () => {
+ getHttpHighlightClient()
+ [timeline.isHighlight ? "delete" : "put"](timeline.name)
+ .then(onReload, () => {
+ pushAlert({
+ message: timeline.isHighlight
+ ? "timeline.removeHighlightFail"
+ : "timeline.addHighlightFail",
+ type: "danger",
+ });
+ });
+ }
+ : undefined
+ }
+ />
+ {user != null ? (
+ <i
+ className={classnames(
+ timeline.isBookmark ? "bi-bookmark-fill" : "bi-bookmark",
+ "icon-button cru-color-primary me-3"
+ )}
+ onClick={() => {
+ getHttpBookmarkClient()
+ [timeline.isBookmark ? "delete" : "put"](timeline.name)
+ .then(onReload, () => {
+ pushAlert({
+ message: timeline.isBookmark
+ ? "timeline.removeBookmarkFail"
+ : "timeline.addBookmarkFail",
+ type: "danger",
+ });
+ });
+ }}
+ />
+ ) : null}
+ <i
+ className={"icon-button bi-people cru-color-primary me-3"}
+ onClick={() => setDialog("member")}
+ />
+ {timeline.manageable ? (
+ <PopupMenu
+ items={[
+ {
+ type: "button",
+ text: "timeline.manageItem.property",
+ onClick: () => setDialog("property"),
+ },
+ { type: "divider" },
+ {
+ type: "button",
+ onClick: () => setDialog("delete"),
+ color: "danger",
+ text: "timeline.manageItem.delete",
+ },
+ ]}
+ containerClassName="d-inline"
+ >
+ <i className="icon-button bi-three-dots-vertical cru-color-primary" />
+ </PopupMenu>
+ ) : null}
+ </div>
+ </>
+ );
+
return (
<>
- <TimelinePageCardTemplate
- infoArea={
- <>
- <h3 className="cru-color-primary d-inline-block align-middle">
- {timeline.title}
- <small className="ms-3 cru-color-secondary">
- {timeline.name}
- </small>
- </h3>
- <div>
- <UserAvatar
- username={timeline.owner.username}
- className="cru-avatar small cru-round me-3"
- />
- {timeline.owner.nickname}
- <small className="ms-3 cru-color-secondary">
- @{timeline.owner.username}
- </small>
- </div>
- </>
- }
- manageItems={
- timeline.manageable
- ? [
- {
- type: "button",
- text: "timeline.manageItem.property",
- onClick: () => setDialog("property"),
- },
- { type: "divider" },
- {
- type: "button",
- onClick: () => setDialog("delete"),
- color: "danger",
- text: "timeline.manageItem.delete",
- },
- ]
- : undefined
- }
- dialog={dialog}
- setDialog={setDialog}
- {...props}
+ <Card className={classnames("p-2 cru-clearfix", className)}>
+ <div
+ className={classnames(
+ "cru-float-right d-flex align-items-center",
+ !collapse && "ms-3"
+ )}
+ >
+ <ConnectionStatusBadge status={connectionStatus} className="me-2" />
+ <CollapseButton collapse={collapse} onClick={toggleCollapse} />
+ </div>
+ {isSmallScreen ? (
+ <FullPageDialog
+ onBack={toggleCollapse}
+ show={!collapse}
+ contentContainerClassName="p-2"
+ >
+ {content}
+ </FullPageDialog>
+ ) : (
+ <div style={{ display: collapse ? "none" : "inline" }}>{content}</div>
+ )}
+ </Card>
+ <TimelineMemberDialog
+ timeline={timeline}
+ onClose={() => setDialog(null)}
+ open={dialog === "member"}
+ onChange={onReload}
+ />
+ <TimelinePropertyChangeDialog
+ timeline={timeline}
+ close={() => setDialog(null)}
+ open={dialog === "property"}
+ onChange={onReload}
/>
<TimelineDeleteDialog
timeline={timeline}
diff --git a/FrontEnd/src/views/timeline-common/TimelineDateLabel.tsx b/FrontEnd/src/views/timeline/TimelineDateLabel.tsx
index 80968ee2..80968ee2 100644
--- a/FrontEnd/src/views/timeline-common/TimelineDateLabel.tsx
+++ b/FrontEnd/src/views/timeline/TimelineDateLabel.tsx
diff --git a/FrontEnd/src/views/timeline-common/TimelineEmptyItem.tsx b/FrontEnd/src/views/timeline/TimelineEmptyItem.tsx
index 8638ad46..8638ad46 100644
--- a/FrontEnd/src/views/timeline-common/TimelineEmptyItem.tsx
+++ b/FrontEnd/src/views/timeline/TimelineEmptyItem.tsx
diff --git a/FrontEnd/src/views/timeline-common/TimelineLine.tsx b/FrontEnd/src/views/timeline/TimelineLine.tsx
index 0a828b32..0a828b32 100644
--- a/FrontEnd/src/views/timeline-common/TimelineLine.tsx
+++ b/FrontEnd/src/views/timeline/TimelineLine.tsx
diff --git a/FrontEnd/src/views/timeline-common/TimelineLoading.tsx b/FrontEnd/src/views/timeline/TimelineLoading.tsx
index f55482fe..f55482fe 100644
--- a/FrontEnd/src/views/timeline-common/TimelineLoading.tsx
+++ b/FrontEnd/src/views/timeline/TimelineLoading.tsx
diff --git a/FrontEnd/src/views/timeline-common/TimelineMember.css b/FrontEnd/src/views/timeline/TimelineMember.css
index adb78764..adb78764 100644
--- a/FrontEnd/src/views/timeline-common/TimelineMember.css
+++ b/FrontEnd/src/views/timeline/TimelineMember.css
diff --git a/FrontEnd/src/views/timeline-common/TimelineMember.tsx b/FrontEnd/src/views/timeline/TimelineMember.tsx
index 59d4c371..59d4c371 100644
--- a/FrontEnd/src/views/timeline-common/TimelineMember.tsx
+++ b/FrontEnd/src/views/timeline/TimelineMember.tsx
diff --git a/FrontEnd/src/views/timeline-common/TimelinePagedPostListView.tsx b/FrontEnd/src/views/timeline/TimelinePagedPostListView.tsx
index 69a5607c..69a5607c 100644
--- a/FrontEnd/src/views/timeline-common/TimelinePagedPostListView.tsx
+++ b/FrontEnd/src/views/timeline/TimelinePagedPostListView.tsx
diff --git a/FrontEnd/src/views/timeline-common/TimelinePostContentView.tsx b/FrontEnd/src/views/timeline/TimelinePostContentView.tsx
index 607b72c9..607b72c9 100644
--- a/FrontEnd/src/views/timeline-common/TimelinePostContentView.tsx
+++ b/FrontEnd/src/views/timeline/TimelinePostContentView.tsx
diff --git a/FrontEnd/src/views/timeline-common/TimelinePostEdit.css b/FrontEnd/src/views/timeline/TimelinePostEdit.css
index 4ce98383..4ce98383 100644
--- a/FrontEnd/src/views/timeline-common/TimelinePostEdit.css
+++ b/FrontEnd/src/views/timeline/TimelinePostEdit.css
diff --git a/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx b/FrontEnd/src/views/timeline/TimelinePostEdit.tsx
index cd61b4a7..cd61b4a7 100644
--- a/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx
+++ b/FrontEnd/src/views/timeline/TimelinePostEdit.tsx
diff --git a/FrontEnd/src/views/timeline-common/TimelinePostEditCard.tsx b/FrontEnd/src/views/timeline/TimelinePostEditCard.tsx
index a69d413a..a69d413a 100644
--- a/FrontEnd/src/views/timeline-common/TimelinePostEditCard.tsx
+++ b/FrontEnd/src/views/timeline/TimelinePostEditCard.tsx
diff --git a/FrontEnd/src/views/timeline-common/TimelinePostEditNoLogin.tsx b/FrontEnd/src/views/timeline/TimelinePostEditNoLogin.tsx
index 82834e95..82834e95 100644
--- a/FrontEnd/src/views/timeline-common/TimelinePostEditNoLogin.tsx
+++ b/FrontEnd/src/views/timeline/TimelinePostEditNoLogin.tsx
diff --git a/FrontEnd/src/views/timeline-common/TimelinePostListView.tsx b/FrontEnd/src/views/timeline/TimelinePostListView.tsx
index f6649e9e..f6649e9e 100644
--- a/FrontEnd/src/views/timeline-common/TimelinePostListView.tsx
+++ b/FrontEnd/src/views/timeline/TimelinePostListView.tsx
diff --git a/FrontEnd/src/views/timeline-common/TimelinePostView.tsx b/FrontEnd/src/views/timeline/TimelinePostView.tsx
index 086176f8..086176f8 100644
--- a/FrontEnd/src/views/timeline-common/TimelinePostView.tsx
+++ b/FrontEnd/src/views/timeline/TimelinePostView.tsx
diff --git a/FrontEnd/src/views/timeline-common/TimelinePropertyChangeDialog.tsx b/FrontEnd/src/views/timeline/TimelinePropertyChangeDialog.tsx
index cd5c46da..cd5c46da 100644
--- a/FrontEnd/src/views/timeline-common/TimelinePropertyChangeDialog.tsx
+++ b/FrontEnd/src/views/timeline/TimelinePropertyChangeDialog.tsx
diff --git a/FrontEnd/src/views/timeline-common/index.css b/FrontEnd/src/views/timeline/index.css
index 6929f9ae..6929f9ae 100644
--- a/FrontEnd/src/views/timeline-common/index.css
+++ b/FrontEnd/src/views/timeline/index.css
diff --git a/FrontEnd/src/views/timeline/index.tsx b/FrontEnd/src/views/timeline/index.tsx
index 02d773dc..4faf8af8 100644
--- a/FrontEnd/src/views/timeline/index.tsx
+++ b/FrontEnd/src/views/timeline/index.tsx
@@ -1,28 +1,84 @@
import React from "react";
+import { HubConnectionState } from "@microsoft/signalr";
import { useParams } from "react-router-dom";
import { UiLogicError } from "@/common";
+import { HttpTimelineInfo } from "@/http/timeline";
+import useReverseScrollPositionRemember from "@/utilities/useReverseScrollPositionRemember";
+import { generatePalette, setPalette } from "@/palette";
-import TimelinePageTemplate from "../timeline-common/TimelinePageTemplate";
+import Timeline from "./Timeline";
import TimelineCard from "./TimelineCard";
const TimelinePage: React.FC = () => {
- const { name } = useParams();
+ const { owner: ownerUsername, timeline: timelineNameParam } = useParams();
- if (name == null) {
- throw new UiLogicError("No route param 'name'.");
- }
+ if (ownerUsername == null || ownerUsername == "")
+ throw new UiLogicError("Route param owner is not set.");
+
+ const timelineName =
+ timelineNameParam == null || timelineNameParam === ""
+ ? "self"
+ : timelineNameParam;
+
+ const [timeline, setTimeline] = React.useState<HttpTimelineInfo | null>(null);
const [reloadKey, setReloadKey] = React.useState<number>(0);
+ const reload = (): void => setReloadKey(reloadKey + 1);
+
+ const [connectionStatus, setConnectionStatus] =
+ React.useState<HubConnectionState>(HubConnectionState.Connecting);
+
+ useReverseScrollPositionRemember();
+
+ React.useEffect(() => {
+ if (timeline != null && timeline.color != null) {
+ return setPalette(generatePalette({ primary: timeline.color }));
+ }
+ }, [timeline]);
+
+ const cardCollapseLocalStorageKey = `timeline.${ownerUsername}.${timelineName}.cardCollapse`;
+
+ const [cardCollapse, setCardCollapse] = React.useState<boolean>(true);
+
+ React.useEffect(() => {
+ const savedCollapse = window.localStorage.getItem(
+ cardCollapseLocalStorageKey
+ );
+ setCardCollapse(savedCollapse == null ? true : savedCollapse === "true");
+ }, [cardCollapseLocalStorageKey]);
+
+ const toggleCardCollapse = (): void => {
+ const newState = !cardCollapse;
+ setCardCollapse(newState);
+ window.localStorage.setItem(
+ cardCollapseLocalStorageKey,
+ newState.toString()
+ );
+ };
return (
- <TimelinePageTemplate
- timelineName={name}
- notFoundI18nKey="timeline.timelineNotExist"
- reloadKey={reloadKey}
- CardComponent={TimelineCard}
- onReload={() => setReloadKey(reloadKey + 1)}
- />
+ <>
+ {timeline != null ? (
+ <TimelineCard
+ className="timeline-template-card"
+ timeline={timeline}
+ collapse={cardCollapse}
+ toggleCollapse={toggleCardCollapse}
+ onReload={reload}
+ connectionStatus={connectionStatus}
+ />
+ ) : null}
+ <div className="container">
+ <Timeline
+ timelineName={timelineName}
+ reloadKey={reloadKey}
+ onReload={reload}
+ onTimelineLoaded={(t) => setTimeline(t)}
+ onConnectionStateChanged={setConnectionStatus}
+ />
+ </div>
+ </>
);
};
diff --git a/FrontEnd/src/views/user/UserCard.tsx b/FrontEnd/src/views/user/UserCard.tsx
deleted file mode 100644
index 739d26ee..00000000
--- a/FrontEnd/src/views/user/UserCard.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import React from "react";
-
-import TimelinePageCardTemplate from "../timeline-common/TimelinePageCardTemplate";
-import { TimelinePageCardProps } from "../timeline-common/TimelinePageTemplate";
-import UserAvatar from "../common/user/UserAvatar";
-
-const UserCard: React.FC<TimelinePageCardProps> = (props) => {
- const { timeline } = props;
-
- const [dialog, setDialog] = React.useState<"member" | "property" | null>(
- null
- );
-
- return (
- <>
- <TimelinePageCardTemplate
- infoArea={
- <>
- <h3 className="cru-color-primary d-inline-block">
- {timeline.title}
- <small className="ms-3 cru-color-secondary">
- {timeline.name}
- </small>
- </h3>
- <div>
- <UserAvatar
- username={timeline.owner.username}
- className="cru-avatar small cru-round me-3"
- />
- {timeline.owner.nickname}
- </div>
- </>
- }
- manageItems={
- timeline.manageable
- ? [
- {
- type: "button",
- text: "timeline.manageItem.property",
- onClick: () => setDialog("property"),
- },
- ]
- : undefined
- }
- dialog={dialog}
- setDialog={setDialog}
- {...props}
- />
- </>
- );
-};
-
-export default UserCard;
diff --git a/FrontEnd/src/views/user/index.css b/FrontEnd/src/views/user/index.css
deleted file mode 100644
index e69de29b..00000000
--- a/FrontEnd/src/views/user/index.css
+++ /dev/null
diff --git a/FrontEnd/src/views/user/index.tsx b/FrontEnd/src/views/user/index.tsx
deleted file mode 100644
index 7913b788..00000000
--- a/FrontEnd/src/views/user/index.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import React from "react";
-import { useParams } from "react-router-dom";
-
-import TimelinePageTemplate from "../timeline-common/TimelinePageTemplate";
-import UserCard from "./UserCard";
-
-import { UiLogicError } from "@/common";
-
-import "./index.css";
-
-const UserPage: React.FC = () => {
- const { username } = useParams();
-
- if (username == null) {
- throw new UiLogicError("No route param 'username'.");
- }
-
- const [reloadKey, setReloadKey] = React.useState<number>(0);
-
- let dialogElement: React.ReactElement | undefined;
-
- return (
- <>
- <TimelinePageTemplate
- timelineName={`@${username}`}
- notFoundI18nKey="timeline.userNotExist"
- reloadKey={reloadKey}
- onReload={() => setReloadKey(reloadKey + 1)}
- CardComponent={UserCard}
- />
- {dialogElement}
- </>
- );
-};
-
-export default UserPage;