From 6775b254270c8c7aaaee641181ad43e5558c0356 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 3 Sep 2020 21:10:58 +0800 Subject: ... --- Timeline/ClientApp/src/app/index.sass | 4 - .../app/views/timeline-common/CollapseButton.tsx | 23 ++++ .../app/views/timeline-common/SyncStatusBadge.tsx | 58 ++++++++++ .../src/app/views/timeline-common/Timeline.tsx | 5 +- .../src/app/views/timeline-common/TimelineItem.tsx | 58 +++++----- .../timeline-common/TimelinePageTemplateUI.tsx | 128 ++++++--------------- .../app/views/timeline-common/timeline-common.sass | 23 ++-- .../src/app/views/timeline/TimelineInfoCard.tsx | 108 ++++++++++------- .../ClientApp/src/app/views/user/UserInfoCard.tsx | 13 +-- 9 files changed, 226 insertions(+), 194 deletions(-) create mode 100644 Timeline/ClientApp/src/app/views/timeline-common/CollapseButton.tsx create mode 100644 Timeline/ClientApp/src/app/views/timeline-common/SyncStatusBadge.tsx (limited to 'Timeline/ClientApp/src') diff --git a/Timeline/ClientApp/src/app/index.sass b/Timeline/ClientApp/src/app/index.sass index 42a89da5..3322e503 100644 --- a/Timeline/ClientApp/src/app/index.sass +++ b/Timeline/ClientApp/src/app/index.sass @@ -12,10 +12,6 @@ body margin: 0 -#app - display: flex - flex-direction: column - small line-height: 1.2 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 ( + + ); +}; + +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 ( +
+ {(() => { + switch (status) { + case "syncing": { + return ( + <> + + + {t("timeline.postSyncState.syncing")} + + + ); + } + case "synced": { + return ( + <> + + + {t("timeline.postSyncState.synced")} + + + ); + } + case "offline": { + return ( + <> + + + {t("timeline.postSyncState.offline")} + + + ); + } + default: + throw new UiLogicError("Unknown sync state."); + } + })()} +
+ ); +}; + +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 = (props) => { }, [posts, onDelete]); return ( -
+
{(() => { 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 = (props) => { ); return ( - - +
{current &&
} - - - -
- - - {props.post.time.toLocaleString(i18n.languages)} - - - {props.post.author.nickname} - - -
+
+
+
+ + + {props.post.time.toLocaleString(i18n.languages)} + + {props.post.author.nickname} + {more != null ? ( -
- { - more.toggle(); - e.stopPropagation(); - }} - /> -
+ { + more.toggle(); + e.stopPropagation(); + }} + /> ) : null} - -
+
+
= (props) => { } })()}
- +
{more != null && more.isOpen ? ( <>
= (props) => { ) : null} ) : null} - +
); }; 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 ( -
- {(() => { - switch (state) { - case "syncing": { - return ( - <> - - - {t("timeline.postSyncState.syncing")} - - - ); - } - case "synced": { - return ( - <> - - - {t("timeline.postSyncState.synced")} - - - ); - } - case "offline": { - return ( - <> - - - {t("timeline.postSyncState.offline")} - - - ); - } - default: - throw new UiLogicError("Unknown sync state."); - } - })()} -
- ); -}; +import { TimelineSyncStatus } from "./SyncStatusBadge"; export interface TimelineCardComponentProps { timeline: TimelineInfo; onManage?: (item: TManageItems | "property") => void; onMember: () => void; className?: string; + collapse: boolean; + syncStatus: TimelineSyncStatus; + toggleCollapse: () => void; } export interface TimelinePageTemplateUIProps { @@ -216,22 +165,13 @@ export default function TimelinePageTemplateUI( }) ); - const syncState: TimelinePostSyncState = postListState.syncing - ? "syncing" - : postListState.type === "synced" - ? "synced" - : "offline"; - timelineBody = ( -
- - -
+ ); if (props.onPost != null) { timelineBody = ( @@ -255,37 +195,35 @@ export default function TimelinePageTemplateUI(
); } + const { CardComponent } = props; + const syncStatus: TimelineSyncStatus = + postListState == null || postListState.syncing + ? "syncing" + : postListState.type === "synced" + ? "synced" + : "offline"; body = ( <> -
- { - const newState = !infoCardCollapse; - setInfoCardCollapse(newState); + { + 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" - /> - - - -
- + } + }} + /> {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 diff --git a/Timeline/ClientApp/src/app/views/timeline/TimelineInfoCard.tsx b/Timeline/ClientApp/src/app/views/timeline/TimelineInfoCard.tsx index 9f989148..764910aa 100644 --- a/Timeline/ClientApp/src/app/views/timeline/TimelineInfoCard.tsx +++ b/Timeline/ClientApp/src/app/views/timeline/TimelineInfoCard.tsx @@ -8,6 +8,8 @@ import { timelineVisibilityTooltipTranslationMap } from "@/services/timeline"; import BlobImage from "../common/BlobImage"; import { TimelineCardComponentProps } from "../timeline-common/TimelinePageTemplateUI"; +import CollapseButton from "../timeline-common/CollapseButton"; +import SyncStatusBadge from "../timeline-common/SyncStatusBadge"; export type OrdinaryTimelineManageItem = "delete"; @@ -16,55 +18,75 @@ export type TimelineInfoCardProps = TimelineCardComponentProps< >; const TimelineInfoCard: React.FC = (props) => { - const { onMember, onManage } = props; + const { + timeline, + onMember, + onManage, + collapse, + syncStatus, + toggleCollapse, + } = props; const { t } = useTranslation(); - const avatar = useAvatar(props.timeline.owner.username); + const avatar = useAvatar(timeline?.owner?.username); return ( -
-

- {props.timeline.name} -

-
- - {props.timeline.owner.nickname} - - @{props.timeline.owner.username} - +
+
+ +
-

{props.timeline.description}

- - {t(timelineVisibilityTooltipTranslationMap[props.timeline.visibility])} - -
- {onManage != null ? ( - - - {t("timeline.manage")} - - - onManage("property")}> - {t("timeline.manageItem.property")} - - - {t("timeline.manageItem.member")} - - - onManage("delete")} - > - {t("timeline.manageItem.delete")} - - - - ) : ( - - )} + +
+

+ {timeline.name} +

+
+ + {timeline.owner.nickname} + + @{timeline.owner.username} + +
+

{timeline.description}

+ + {t(timelineVisibilityTooltipTranslationMap[timeline.visibility])} + +
+ {onManage != null ? ( + + + {t("timeline.manage")} + + + onManage("property")}> + {t("timeline.manageItem.property")} + + + {t("timeline.manageItem.member")} + + + onManage("delete")} + > + {t("timeline.manageItem.delete")} + + + + ) : ( + + )} +
); diff --git a/Timeline/ClientApp/src/app/views/user/UserInfoCard.tsx b/Timeline/ClientApp/src/app/views/user/UserInfoCard.tsx index cec81421..251e53b4 100644 --- a/Timeline/ClientApp/src/app/views/user/UserInfoCard.tsx +++ b/Timeline/ClientApp/src/app/views/user/UserInfoCard.tsx @@ -1,7 +1,6 @@ import React from "react"; import clsx from "clsx"; import { useTranslation } from "react-i18next"; -import { fromEvent } from "rxjs"; import { Dropdown, Button } from "react-bootstrap"; import { timelineVisibilityTooltipTranslationMap } from "@/services/timeline"; @@ -17,10 +16,10 @@ export type UserInfoCardProps = TimelineCardComponentProps< >; const UserInfoCard: React.FC = (props) => { - const { onManage } = props; + const { onManage, timeline } = props; const { t } = useTranslation(); - const avatar = useAvatar(props.timeline.owner.username); + const avatar = useAvatar(timeline?.owner?.username); return (
@@ -29,14 +28,14 @@ const UserInfoCard: React.FC = (props) => { className="avatar large mr-2 rounded-circle float-left" />
- {props.timeline.owner.nickname} + {timeline.owner.nickname} - @{props.timeline.owner.username} + @{timeline.owner.username}
-

{props.timeline.description}

+

{timeline.description}

- {t(timelineVisibilityTooltipTranslationMap[props.timeline.visibility])} + {t(timelineVisibilityTooltipTranslationMap[timeline.visibility])}
{onManage != null ? ( -- cgit v1.2.3