From 0833654d91dca0ff36cb6352528f6d00556352c0 Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 13 Jan 2021 14:13:02 +0800 Subject: ... --- FrontEnd/src/app/index.sass | 3 + .../src/app/views/timeline-common/Timeline.tsx | 4 +- .../src/app/views/timeline-common/TimelineItem.tsx | 83 +++++----------------- .../src/app/views/timeline-common/TimelineLine.tsx | 33 +++++++++ .../TimelinePostDeleteConfirmDialog.tsx | 37 ++++++++++ .../src/app/views/timeline-common/TimelineTop.tsx | 8 +-- .../app/views/timeline-common/timeline-common.sass | 83 +++++++++++----------- 7 files changed, 134 insertions(+), 117 deletions(-) create mode 100644 FrontEnd/src/app/views/timeline-common/TimelineLine.tsx create mode 100644 FrontEnd/src/app/views/timeline-common/TimelinePostDeleteConfirmDialog.tsx (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/index.sass b/FrontEnd/src/app/index.sass index 87616998..85c2bcdc 100644 --- a/FrontEnd/src/app/index.sass +++ b/FrontEnd/src/app/index.sass @@ -88,3 +88,6 @@ textarea .touch-action-none touch-action: none + +i + line-height: 1 diff --git a/FrontEnd/src/app/views/timeline-common/Timeline.tsx b/FrontEnd/src/app/views/timeline-common/Timeline.tsx index aba868cb..5c8f9195 100644 --- a/FrontEnd/src/app/views/timeline-common/Timeline.tsx +++ b/FrontEnd/src/app/views/timeline-common/Timeline.tsx @@ -16,12 +16,11 @@ export interface TimelineProps { className?: string; style?: React.CSSProperties; posts: TimelinePostInfoEx[]; - onResize?: () => void; containerRef?: React.Ref; } const Timeline: React.FC = (props) => { - const { posts, onResize } = props; + const { posts } = props; const [showMoreIndex, setShowMoreIndex] = React.useState(-1); @@ -51,7 +50,6 @@ const Timeline: React.FC = (props) => { : undefined } onClick={() => setShowMoreIndex(-1)} - onResize={onResize} /> ); }); diff --git a/FrontEnd/src/app/views/timeline-common/TimelineItem.tsx b/FrontEnd/src/app/views/timeline-common/TimelineItem.tsx index 233c81bd..74431402 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelineItem.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelineItem.tsx @@ -2,44 +2,13 @@ import React from "react"; import clsx from "clsx"; import { Link } from "react-router-dom"; import { useTranslation } from "react-i18next"; -import { Modal, Button } from "react-bootstrap"; import { useAvatar } from "@/services/user"; import { TimelinePostInfo } from "@/services/timeline"; import BlobImage from "../common/BlobImage"; - -const TimelinePostDeleteConfirmDialog: React.FC<{ - onClose: () => void; - onConfirm: () => void; -}> = ({ onClose, onConfirm }) => { - const { t } = useTranslation(); - - return ( - - - - {t("timeline.post.deleteDialog.title")} - - - {t("timeline.post.deleteDialog.prompt")} - - - - - - ); -}; +import TimelineLine from "./TimelineLine"; +import TimelinePostDeleteConfirmDialog from "./TimelinePostDeleteConfirmDialog"; export interface TimelineItemProps { post: TimelinePostInfo; @@ -50,7 +19,6 @@ export interface TimelineItemProps { onDelete: () => void; }; onClick?: () => void; - onResize?: () => void; className?: string; style?: React.CSSProperties; } @@ -60,7 +28,7 @@ const TimelineItem: React.FC = (props) => { const current = props.current === true; - const { more, onResize } = props; + const { more } = props; const avatar = useAvatar(props.post.author.username); @@ -68,31 +36,25 @@ const TimelineItem: React.FC = (props) => { return (
-
-
-
-
-
-
-
- {current &&
} -
-
+
-
+
- - {props.post.time.toLocaleString(i18n.languages)} - - {props.post.author.nickname} + + + + + + {props.post.author.nickname} + + + {props.post.time.toLocaleString(i18n.languages)} + + {more != null ? ( = (props) => { ) : null}
- - - {(() => { const { content } = props.post; if (content.type === "text") { @@ -122,7 +74,6 @@ const TimelineItem: React.FC = (props) => { } else { return ( diff --git a/FrontEnd/src/app/views/timeline-common/TimelineLine.tsx b/FrontEnd/src/app/views/timeline-common/TimelineLine.tsx new file mode 100644 index 00000000..fd7dde0a --- /dev/null +++ b/FrontEnd/src/app/views/timeline-common/TimelineLine.tsx @@ -0,0 +1,33 @@ +import clsx from "clsx"; +import React from "react"; + +export interface TimelineLineProps { + current?: boolean; + startSegmentLength?: string | number; + center: "node" | null; + className?: string; + style?: React.CSSProperties; +} + +const TimelineLine: React.FC = ({ + startSegmentLength, + center, + current, + className, + style, +}) => { + return ( +
+
+ {center == "node" ? ( +
+
+
+ ) : null} +
+ {current &&
} +
+ ); +}; + +export default TimelineLine; diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePostDeleteConfirmDialog.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePostDeleteConfirmDialog.tsx new file mode 100644 index 00000000..b2c7a470 --- /dev/null +++ b/FrontEnd/src/app/views/timeline-common/TimelinePostDeleteConfirmDialog.tsx @@ -0,0 +1,37 @@ +import React from "react"; +import { Modal, Button } from "react-bootstrap"; +import { useTranslation } from "react-i18next"; + +const TimelinePostDeleteConfirmDialog: React.FC<{ + onClose: () => void; + onConfirm: () => void; +}> = ({ onClose, onConfirm }) => { + const { t } = useTranslation(); + + return ( + + + + {t("timeline.post.deleteDialog.title")} + + + {t("timeline.post.deleteDialog.prompt")} + + + + + + ); +}; + +export default TimelinePostDeleteConfirmDialog; diff --git a/FrontEnd/src/app/views/timeline-common/TimelineTop.tsx b/FrontEnd/src/app/views/timeline-common/TimelineTop.tsx index 93a2a32c..39cfa426 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelineTop.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelineTop.tsx @@ -1,5 +1,7 @@ import React from "react"; +import TimelineLine from "./TimelineLine"; + export interface TimelineTopProps { height?: number | string; children?: React.ReactElement; @@ -8,11 +10,7 @@ export interface TimelineTopProps { const TimelineTop: React.FC = ({ height, children }) => { return (
-
-
-
-
-
+ {children}
); diff --git a/FrontEnd/src/app/views/timeline-common/timeline-common.sass b/FrontEnd/src/app/views/timeline-common/timeline-common.sass index 1aa5e731..5af3a9ef 100644 --- a/FrontEnd/src/app/views/timeline-common/timeline-common.sass +++ b/FrontEnd/src/app/views/timeline-common/timeline-common.sass @@ -6,10 +6,6 @@ width: 100% overflow-wrap: break-word - &-item - position: relative - padding: 0.5em - $timeline-line-width: 7px $timeline-line-node-radius: 18px $timeline-line-color: $primary @@ -32,29 +28,23 @@ $timeline-line-color-current: #36c2e6 box-shadow: 0 0 20px 3px color.adjust($timeline-line-color-current, $lightness: +10%, $alpha: -0.1) .timeline-line - &-area-container - position: absolute - display: flex - justify-content: flex-end - padding-right: 5px - z-index: 1 + display: flex + flex-direction: column + align-items: center + width: 30px - top: 0em - bottom: 0em - left: 0.5em - width: 60px - transition: width 0.8s + position: absolute + z-index: 1 + left: 2em + top: 0 + bottom: 0 - @include media-breakpoint-down(sm) - width: 40px + transition: left 0.5s - &-area - display: flex - flex-direction: column - align-items: center - width: 30px + @include media-breakpoint-down(sm) + left: 1em - &-segment + .segment width: $timeline-line-width background: $timeline-line-color @@ -70,13 +60,13 @@ $timeline-line-color-current: #36c2e6 flex: 0 0 auto background: linear-gradient($timeline-line-color-current, transparent) - &-node-container + .node-container flex: 0 0 auto position: relative width: $timeline-line-node-radius height: $timeline-line-node-radius - &-node + .node width: $timeline-line-node-radius + 2 height: $timeline-line-node-radius + 2 position: absolute @@ -88,42 +78,49 @@ $timeline-line-color-current: #36c2e6 animation: 1s infinite alternate animation-name: timeline-line-node-noncurrent -.timeline-top - position: relative - text-align: right - - .timeline-line-segment - flex: 1 1 auto - .current &.timeline-item padding-bottom: 2.5em .timeline-line - &-segment - + .segment &.start background: linear-gradient($timeline-line-color, $timeline-line-color-current) - &.end background: $timeline-line-color-current - - &-node + .node animation-name: timeline-line-node-current +.timeline-top + position: relative + text-align: right + +.timeline-item + position: relative + padding: 0.5em + .timeline-item-card @extend .cru-card - @extend .clearfix position: relative - padding: 0.5em 2em 0.5em 60px - transition: background 0.5s, padding-left 0.8s + padding: 0.5em 2em 0.5em 4em + transition: background 0.5s, padding-left 0.5s @include media-breakpoint-down(sm) - padding-left: 40px + padding-left: 3em &:hover background: $gray-200 +.timeline-item-header + display: flex + align-items: center + @extend .mb-2 + +.timeline-avatar + border-radius: 50% + width: 2em + height: 2em + .timeline-item-delete-button position: absolute right: 0 @@ -171,5 +168,5 @@ $timeline-line-color-current: #36c2e6 justify-content: center .timeline - max-width: 100em - flex-grow: 1 \ No newline at end of file + max-width: 80em + flex-grow: 1 -- cgit v1.2.3 From 4a633a0f83a3a7cf18feead821fcdff2453de926 Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 13 Jan 2021 17:06:43 +0800 Subject: ... --- .../src/app/views/timeline-common/Timeline.tsx | 84 ++++++++++++++++------ .../app/views/timeline-common/TimelineDateItem.tsx | 19 +++++ .../src/app/views/timeline-common/TimelineItem.tsx | 23 +++--- .../app/views/timeline-common/timeline-common.sass | 18 ++++- 4 files changed, 105 insertions(+), 39 deletions(-) create mode 100644 FrontEnd/src/app/views/timeline-common/TimelineDateItem.tsx (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/views/timeline-common/Timeline.tsx b/FrontEnd/src/app/views/timeline-common/Timeline.tsx index 5c8f9195..ab658b89 100644 --- a/FrontEnd/src/app/views/timeline-common/Timeline.tsx +++ b/FrontEnd/src/app/views/timeline-common/Timeline.tsx @@ -5,6 +5,15 @@ import { TimelinePostInfo } from "@/services/timeline"; import TimelineItem from "./TimelineItem"; import TimelineTop from "./TimelineTop"; +import TimelineDateItem from "./TimelineDateItem"; + +function dateEqual(left: Date, right: Date): boolean { + return ( + left.getDate() == right.getDate() && + left.getMonth() == right.getMonth() && + left.getFullYear() == right.getFullYear() + ); +} export interface TimelinePostInfoEx extends TimelinePostInfo { onDelete?: () => void; @@ -24,6 +33,31 @@ const Timeline: React.FC = (props) => { const [showMoreIndex, setShowMoreIndex] = React.useState(-1); + const groupedPosts = React.useMemo< + { date: Date; posts: (TimelinePostInfoEx & { index: number })[] }[] + >(() => { + const result: { + date: Date; + posts: (TimelinePostInfoEx & { index: number })[]; + }[] = []; + let index = 0; + for (const post of posts) { + const { time } = post; + if (result.length === 0) { + result.push({ date: time, posts: [{ ...post, index }] }); + } else { + const lastGroup = result[result.length - 1]; + if (dateEqual(lastGroup.date, time)) { + lastGroup.posts.push({ ...post, index }); + } else { + result.push({ date: time, posts: [{ ...post, index }] }); + } + } + index++; + } + return result; + }, [posts]); + return (
= (props) => { className={clsx("timeline", props.className)} > - {(() => { - const length = posts.length; - return posts.map((post, index) => { - return ( - - setShowMoreIndex((old) => (old === index ? -1 : index)), - onDelete: post.onDelete, - } - : undefined - } - onClick={() => setShowMoreIndex(-1)} - /> - ); - }); - })()} + {groupedPosts.map((group) => { + return ( + <> + + {group.posts.map((post) => ( + + setShowMoreIndex((old) => + old === post.index ? -1 : post.index + ), + onDelete: post.onDelete, + } + : undefined + } + onClick={() => setShowMoreIndex(-1)} + /> + ))} + + ); + })}
); }; diff --git a/FrontEnd/src/app/views/timeline-common/TimelineDateItem.tsx b/FrontEnd/src/app/views/timeline-common/TimelineDateItem.tsx new file mode 100644 index 00000000..bcc1530f --- /dev/null +++ b/FrontEnd/src/app/views/timeline-common/TimelineDateItem.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import TimelineLine from "./TimelineLine"; + +export interface TimelineDateItemProps { + date: Date; +} + +const TimelineDateItem: React.FC = ({ date }) => { + return ( +
+ +
+ {date.toLocaleDateString()} +
+
+ ); +}; + +export default TimelineDateItem; diff --git a/FrontEnd/src/app/views/timeline-common/TimelineItem.tsx b/FrontEnd/src/app/views/timeline-common/TimelineItem.tsx index 74431402..c096f890 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelineItem.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelineItem.tsx @@ -1,7 +1,6 @@ import React from "react"; import clsx from "clsx"; import { Link } from "react-router-dom"; -import { useTranslation } from "react-i18next"; import { useAvatar } from "@/services/user"; import { TimelinePostInfo } from "@/services/timeline"; @@ -24,8 +23,6 @@ export interface TimelineItemProps { } const TimelineItem: React.FC = (props) => { - const { i18n } = useTranslation(); - const current = props.current === true; const { more } = props; @@ -42,6 +39,15 @@ const TimelineItem: React.FC = (props) => { >
+ {more != null ? ( + { + more.toggle(); + e.stopPropagation(); + }} + /> + ) : null}
@@ -52,19 +58,10 @@ const TimelineItem: React.FC = (props) => { {props.post.author.nickname} - {props.post.time.toLocaleString(i18n.languages)} + {props.post.time.toLocaleTimeString()} - {more != null ? ( - { - more.toggle(); - e.stopPropagation(); - }} - /> - ) : null}
{(() => { diff --git a/FrontEnd/src/app/views/timeline-common/timeline-common.sass b/FrontEnd/src/app/views/timeline-common/timeline-common.sass index 5af3a9ef..ebaf96b5 100644 --- a/FrontEnd/src/app/views/timeline-common/timeline-common.sass +++ b/FrontEnd/src/app/views/timeline-common/timeline-common.sass @@ -49,7 +49,7 @@ $timeline-line-color-current: #36c2e6 background: $timeline-line-color &.start - height: 1.4em + height: 1.8em flex: 0 0 auto &.end @@ -102,7 +102,7 @@ $timeline-line-color-current: #36c2e6 .timeline-item-card @extend .cru-card position: relative - padding: 0.5em 2em 0.5em 4em + padding: 0.3em 0.5em 1em 4em transition: background 0.5s, padding-left 0.5s @include media-breakpoint-down(sm) @@ -114,7 +114,7 @@ $timeline-line-color-current: #36c2e6 .timeline-item-header display: flex align-items: center - @extend .mb-2 + @extend .my-2 .timeline-avatar border-radius: 50% @@ -133,6 +133,18 @@ $timeline-line-color-current: #36c2e6 max-width: 60% max-height: 200px +.timeline-date-item + position: relative + padding: 0.3em 0 0.3em 4em + +.timeline-date-item-badge + display: inline-block + padding: 0.1em 0.4em + border-radius: 0.4em + background: #7c7c7c + color: white + font-size: 0.8em + .timeline-post-edit-image max-width: 100px max-height: 100px -- cgit v1.2.3