aboutsummaryrefslogtreecommitdiff
path: root/Timeline/ClientApp/src/app/views/timeline-common
diff options
context:
space:
mode:
Diffstat (limited to 'Timeline/ClientApp/src/app/views/timeline-common')
-rw-r--r--Timeline/ClientApp/src/app/views/timeline-common/CollapseButton.tsx23
-rw-r--r--Timeline/ClientApp/src/app/views/timeline-common/SyncStatusBadge.tsx58
-rw-r--r--Timeline/ClientApp/src/app/views/timeline-common/Timeline.tsx5
-rw-r--r--Timeline/ClientApp/src/app/views/timeline-common/TimelineItem.tsx58
-rw-r--r--Timeline/ClientApp/src/app/views/timeline-common/TimelinePageTemplateUI.tsx128
-rw-r--r--Timeline/ClientApp/src/app/views/timeline-common/timeline-common.sass23
6 files changed, 155 insertions, 140 deletions
diff --git a/Timeline/ClientApp/src/app/views/timeline-common/CollapseButton.tsx b/Timeline/ClientApp/src/app/views/timeline-common/CollapseButton.tsx
new file mode 100644
index 00000000..3c52150f
--- /dev/null
+++ b/Timeline/ClientApp/src/app/views/timeline-common/CollapseButton.tsx
@@ -0,0 +1,23 @@
+import React from "react";
+import clsx from "clsx";
+import Svg from "react-inlinesvg";
+import arrowsAngleContractIcon from "bootstrap-icons/icons/arrows-angle-contract.svg";
+import arrowsAngleExpandIcon from "bootstrap-icons/icons/arrows-angle-expand.svg";
+
+const CollapseButton: React.FC<{
+ collapse: boolean;
+ onClick: () => void;
+ className?: string;
+ style?: React.CSSProperties;
+}> = ({ collapse, onClick, className, style }) => {
+ return (
+ <Svg
+ src={collapse ? arrowsAngleExpandIcon : arrowsAngleContractIcon}
+ onClick={onClick}
+ className={clsx("text-primary icon-button", className)}
+ style={style}
+ />
+ );
+};
+
+export default CollapseButton;
diff --git a/Timeline/ClientApp/src/app/views/timeline-common/SyncStatusBadge.tsx b/Timeline/ClientApp/src/app/views/timeline-common/SyncStatusBadge.tsx
new file mode 100644
index 00000000..e67cfb43
--- /dev/null
+++ b/Timeline/ClientApp/src/app/views/timeline-common/SyncStatusBadge.tsx
@@ -0,0 +1,58 @@
+import React from "react";
+import clsx from "clsx";
+import { useTranslation } from "react-i18next";
+
+import { UiLogicError } from "@/common";
+
+export type TimelineSyncStatus = "syncing" | "synced" | "offline";
+
+const SyncStatusBadge: React.FC<{
+ status: TimelineSyncStatus;
+ style?: React.CSSProperties;
+ className?: string;
+}> = ({ status, style, className }) => {
+ const { t } = useTranslation();
+
+ return (
+ <div style={style} className={clsx("timeline-sync-state-badge", className)}>
+ {(() => {
+ switch (status) {
+ case "syncing": {
+ return (
+ <>
+ <span className="timeline-sync-state-badge-pin bg-warning" />
+ <span className="text-warning">
+ {t("timeline.postSyncState.syncing")}
+ </span>
+ </>
+ );
+ }
+ case "synced": {
+ return (
+ <>
+ <span className="timeline-sync-state-badge-pin bg-success" />
+ <span className="text-success">
+ {t("timeline.postSyncState.synced")}
+ </span>
+ </>
+ );
+ }
+ case "offline": {
+ return (
+ <>
+ <span className="timeline-sync-state-badge-pin bg-danger" />
+ <span className="text-danger">
+ {t("timeline.postSyncState.offline")}
+ </span>
+ </>
+ );
+ }
+ default:
+ throw new UiLogicError("Unknown sync state.");
+ }
+ })()}
+ </div>
+ );
+};
+
+export default SyncStatusBadge;
diff --git a/Timeline/ClientApp/src/app/views/timeline-common/Timeline.tsx b/Timeline/ClientApp/src/app/views/timeline-common/Timeline.tsx
index 1ad62a51..1cb15d8e 100644
--- a/Timeline/ClientApp/src/app/views/timeline-common/Timeline.tsx
+++ b/Timeline/ClientApp/src/app/views/timeline-common/Timeline.tsx
@@ -51,10 +51,7 @@ const Timeline: React.FC<TimelineProps> = (props) => {
}, [posts, onDelete]);
return (
- <div
- ref={props.containerRef}
- className={clsx("container-fluid timeline", props.className)}
- >
+ <div ref={props.containerRef} className={clsx("timeline", props.className)}>
<div className="timeline-enter-animation-mask" />
{(() => {
const length = posts.length;
diff --git a/Timeline/ClientApp/src/app/views/timeline-common/TimelineItem.tsx b/Timeline/ClientApp/src/app/views/timeline-common/TimelineItem.tsx
index ce371015..09d74d3c 100644
--- a/Timeline/ClientApp/src/app/views/timeline-common/TimelineItem.tsx
+++ b/Timeline/ClientApp/src/app/views/timeline-common/TimelineItem.tsx
@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
import Svg from "react-inlinesvg";
import chevronDownIcon from "bootstrap-icons/icons/chevron-down.svg";
import trashIcon from "bootstrap-icons/icons/trash.svg";
-import { Row, Col, Modal, Button } from "react-bootstrap";
+import { Modal, Button } from "react-bootstrap";
import { useAvatar } from "@/services/user";
import { TimelinePostInfo } from "@/services/timeline";
@@ -74,51 +74,45 @@ const TimelineItem: React.FC<TimelineItemProps> = (props) => {
);
return (
- <Row
+ <div
className={clsx(
- "position-relative flex-nowrap",
+ "timeline-item position-relative",
current && "current",
props.className
)}
onClick={props.onClick}
style={props.style}
>
- <Col className="timeline-line-area">
+ <div className="timeline-line-area">
<div className="timeline-line-segment start"></div>
<div className="timeline-line-node-container">
<div className="timeline-line-node"></div>
</div>
<div className="timeline-line-segment end"></div>
{current && <div className="timeline-line-segment current-end" />}
- </Col>
- <Col className="timeline-pt-start">
- <Row className="flex-nowrap">
- <div className="col-auto flex-shrink-1 px-0">
- <Row className="ml-n3 mr-0 align-items-center">
- <span className="ml-3 text-primary white-space-no-wrap">
- {props.post.time.toLocaleString(i18n.languages)}
- </span>
- <small className="text-dark ml-3">
- {props.post.author.nickname}
- </small>
- </Row>
- </div>
+ </div>
+ <div className="timeline-content-area">
+ <div>
+ <span className="mr-2">
+ <span className="text-primary white-space-no-wrap mr-2">
+ {props.post.time.toLocaleString(i18n.languages)}
+ </span>
+ <small className="text-dark">{props.post.author.nickname}</small>
+ </span>
{more != null ? (
- <div className="col-auto px-2 d-flex justify-content-center align-items-center">
- <Svg
- src={chevronDownIcon}
- className="text-info icon-button"
- onClick={(e: Event) => {
- more.toggle();
- e.stopPropagation();
- }}
- />
- </div>
+ <Svg
+ src={chevronDownIcon}
+ className="text-info icon-button"
+ onClick={(e: Event) => {
+ more.toggle();
+ e.stopPropagation();
+ }}
+ />
) : null}
- </Row>
- <div className="row d-block timeline-content">
+ </div>
+ <div className="timeline-content">
<Link
- className="float-right float-sm-left mx-2"
+ className="float-left m-2"
to={"/users/" + props.post.author.username}
>
<BlobImage
@@ -142,7 +136,7 @@ const TimelineItem: React.FC<TimelineItemProps> = (props) => {
}
})()}
</div>
- </Col>
+ </div>
{more != null && more.isOpen ? (
<>
<div
@@ -169,7 +163,7 @@ const TimelineItem: React.FC<TimelineItemProps> = (props) => {
) : null}
</>
) : null}
- </Row>
+ </div>
);
};
diff --git a/Timeline/ClientApp/src/app/views/timeline-common/TimelinePageTemplateUI.tsx b/Timeline/ClientApp/src/app/views/timeline-common/TimelinePageTemplateUI.tsx
index 4296a5ce..c2d4aeaa 100644
--- a/Timeline/ClientApp/src/app/views/timeline-common/TimelinePageTemplateUI.tsx
+++ b/Timeline/ClientApp/src/app/views/timeline-common/TimelinePageTemplateUI.tsx
@@ -1,11 +1,7 @@
-import React, { CSSProperties } from "react";
-import clsx from "clsx";
+import React from "react";
import { useTranslation } from "react-i18next";
import { fromEvent } from "rxjs";
-import Svg from "react-inlinesvg";
-import { Spinner, Collapse } from "react-bootstrap";
-import arrowsAngleContractIcon from "bootstrap-icons/icons/arrows-angle-contract.svg";
-import arrowsAngleExpandIcon from "bootstrap-icons/icons/arrows-angle-expand.svg";
+import { Spinner } from "react-bootstrap";
import { getAlertHost } from "@/services/alert";
import { useEventEmiiter, UiLogicError } from "@/common";
@@ -21,63 +17,16 @@ import Timeline, {
TimelineDeleteCallback,
} from "./Timeline";
import TimelinePostEdit, { TimelinePostSendCallback } from "./TimelinePostEdit";
-
-type TimelinePostSyncState = "syncing" | "synced" | "offline";
-
-const TimelinePostSyncStateBadge: React.FC<{
- state: TimelinePostSyncState;
- style?: CSSProperties;
- className?: string;
-}> = ({ state, style, className }) => {
- const { t } = useTranslation();
-
- return (
- <div style={style} className={clsx("timeline-sync-state-badge", className)}>
- {(() => {
- switch (state) {
- case "syncing": {
- return (
- <>
- <span className="timeline-sync-state-badge-pin bg-warning" />
- <span className="text-warning">
- {t("timeline.postSyncState.syncing")}
- </span>
- </>
- );
- }
- case "synced": {
- return (
- <>
- <span className="timeline-sync-state-badge-pin bg-success" />
- <span className="text-success">
- {t("timeline.postSyncState.synced")}
- </span>
- </>
- );
- }
- case "offline": {
- return (
- <>
- <span className="timeline-sync-state-badge-pin bg-danger" />
- <span className="text-danger">
- {t("timeline.postSyncState.offline")}
- </span>
- </>
- );
- }
- default:
- throw new UiLogicError("Unknown sync state.");
- }
- })()}
- </div>
- );
-};
+import { TimelineSyncStatus } from "./SyncStatusBadge";
export interface TimelineCardComponentProps<TManageItems> {
timeline: TimelineInfo;
onManage?: (item: TManageItems | "property") => void;
onMember: () => void;
className?: string;
+ collapse: boolean;
+ syncStatus: TimelineSyncStatus;
+ toggleCollapse: () => void;
}
export interface TimelinePageTemplateUIProps<TManageItems> {
@@ -216,22 +165,13 @@ export default function TimelinePageTemplateUI<TManageItems>(
})
);
- const syncState: TimelinePostSyncState = postListState.syncing
- ? "syncing"
- : postListState.type === "synced"
- ? "synced"
- : "offline";
-
timelineBody = (
- <div>
- <TimelinePostSyncStateBadge state={syncState} />
- <Timeline
- containerRef={timelineRef}
- posts={posts}
- onDelete={props.onDelete}
- onResize={triggerResizeEvent}
- />
- </div>
+ <Timeline
+ containerRef={timelineRef}
+ posts={posts}
+ onDelete={props.onDelete}
+ onResize={triggerResizeEvent}
+ />
);
if (props.onPost != null) {
timelineBody = (
@@ -255,37 +195,35 @@ export default function TimelinePageTemplateUI<TManageItems>(
</div>
);
}
+
const { CardComponent } = props;
+ const syncStatus: TimelineSyncStatus =
+ postListState == null || postListState.syncing
+ ? "syncing"
+ : postListState.type === "synced"
+ ? "synced"
+ : "offline";
body = (
<>
- <div className="info-card-container">
- <Svg
- src={
- infoCardCollapse
- ? arrowsAngleExpandIcon
- : arrowsAngleContractIcon
- }
- onClick={() => {
- const newState = !infoCardCollapse;
- setInfoCardCollapse(newState);
+ <CardComponent
+ timeline={timeline}
+ onManage={props.onManage}
+ onMember={props.onMember}
+ className="timeline-info-card"
+ syncStatus={syncStatus}
+ collapse={infoCardCollapse}
+ toggleCollapse={() => {
+ const newState = !infoCardCollapse;
+ setInfoCardCollapse(newState);
+ if (timeline != null) {
window.localStorage.setItem(
genCardCollapseLocalStorageKey(timeline.uniqueId),
newState.toString()
);
- }}
- className="float-right m-1 info-card-collapse-button text-primary icon-button"
- />
- <Collapse in={!infoCardCollapse}>
- <CardComponent
- timeline={timeline}
- onManage={props.onManage}
- onMember={props.onMember}
- className="info-card-content"
- />
- </Collapse>
- </div>
-
+ }
+ }}
+ />
{timelineBody}
</>
);
diff --git a/Timeline/ClientApp/src/app/views/timeline-common/timeline-common.sass b/Timeline/ClientApp/src/app/views/timeline-common/timeline-common.sass
index a7b9af7b..ad024c78 100644
--- a/Timeline/ClientApp/src/app/views/timeline-common/timeline-common.sass
+++ b/Timeline/ClientApp/src/app/views/timeline-common/timeline-common.sass
@@ -1,11 +1,12 @@
@use 'sass:color'
.timeline
- display: flex
- flex-direction: column
z-index: 0
position: relative
+ &-item
+ display: flex
+
@keyframes timeline-enter-animation-mask-animation
to
height: 0
@@ -96,8 +97,9 @@ $timeline-line-color-current: #36c2e6
&-node
animation-name: timeline-line-node-current
-.timeline-pt-start
+.timeline-content-area
padding-top: 18px
+ flex-grow: 1
.timeline-item-delete-button
position: absolute
@@ -123,10 +125,6 @@ $timeline-line-color-current: #36c2e6
transition: height 0.5s
.timeline-sync-state-badge
- position: fixed
- top: 0
- right: 0
- z-index: 1
font-size: 0.8em
padding: 3px 8px
border-radius: 5px
@@ -140,7 +138,14 @@ $timeline-line-color-current: #36c2e6
vertical-align: middle
margin-right: 0.6em
-.info-card-container
+.timeline-info-card
position: sticky
- top: 56px
z-index: 1
+ top: 56px
+ margin: 0.5em
+
+ @include media-breakpoint-down(sm)
+ margin-bottom: 0
+
+ @include media-breakpoint-up(sm)
+ float: right