From a314b5350e269676e8c39eda4cc7842751b1a7fc Mon Sep 17 00:00:00 2001
From: crupest <crupest@outlook.com>
Date: Tue, 1 Sep 2020 02:32:06 +0800
Subject: ...

---
 .../src/app/views/timeline-common/Timeline.tsx     | 88 ++++++++++++++++++++++
 1 file changed, 88 insertions(+)
 create mode 100644 Timeline/ClientApp/src/app/views/timeline-common/Timeline.tsx

(limited to 'Timeline/ClientApp/src/app/views/timeline-common/Timeline.tsx')

diff --git a/Timeline/ClientApp/src/app/views/timeline-common/Timeline.tsx b/Timeline/ClientApp/src/app/views/timeline-common/Timeline.tsx
new file mode 100644
index 00000000..1ad62a51
--- /dev/null
+++ b/Timeline/ClientApp/src/app/views/timeline-common/Timeline.tsx
@@ -0,0 +1,88 @@
+import React from "react";
+import clsx from "clsx";
+
+import { TimelinePostInfo } from "@/services/timeline";
+
+import TimelineItem from "./TimelineItem";
+
+export interface TimelinePostInfoEx extends TimelinePostInfo {
+  deletable: boolean;
+}
+
+export type TimelineDeleteCallback = (index: number, id: number) => void;
+
+export interface TimelineProps {
+  className?: string;
+  posts: TimelinePostInfoEx[];
+  onDelete: TimelineDeleteCallback;
+  onResize?: () => void;
+  containerRef?: React.Ref<HTMLDivElement>;
+}
+
+const Timeline: React.FC<TimelineProps> = (props) => {
+  const { posts, onDelete, onResize } = props;
+
+  const [indexShowDeleteButton, setIndexShowDeleteButton] = React.useState<
+    number
+  >(-1);
+
+  const onItemClick = React.useCallback(() => {
+    setIndexShowDeleteButton(-1);
+  }, []);
+
+  const onToggleDelete = React.useMemo(() => {
+    return posts.map((post, i) => {
+      return post.deletable
+        ? () => {
+            setIndexShowDeleteButton((oldIndexShowDeleteButton) => {
+              return oldIndexShowDeleteButton !== i ? i : -1;
+            });
+          }
+        : undefined;
+    });
+  }, [posts]);
+
+  const onItemDelete = React.useMemo(() => {
+    return posts.map((post, i) => {
+      return () => {
+        onDelete(i, post.id);
+      };
+    });
+  }, [posts, onDelete]);
+
+  return (
+    <div
+      ref={props.containerRef}
+      className={clsx("container-fluid timeline", props.className)}
+    >
+      <div className="timeline-enter-animation-mask" />
+      {(() => {
+        const length = posts.length;
+        return posts.map((post, i) => {
+          const toggleMore = onToggleDelete[i];
+
+          return (
+            <TimelineItem
+              post={post}
+              key={post.id}
+              current={length - 1 === i}
+              more={
+                toggleMore
+                  ? {
+                      isOpen: indexShowDeleteButton === i,
+                      toggle: toggleMore,
+                      onDelete: onItemDelete[i],
+                    }
+                  : undefined
+              }
+              onClick={onItemClick}
+              onResize={onResize}
+            />
+          );
+        });
+      })()}
+    </div>
+  );
+};
+
+export default Timeline;
-- 
cgit v1.2.3


From 01446b3c8306112cd965eeaaa40a0ac573cc374e Mon Sep 17 00:00:00 2001
From: crupest <crupest@outlook.com>
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/app/views/timeline-common/Timeline.tsx')

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 (
+    <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
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<TimelineInfoCardProps> = (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 (
-    <div className={clsx("rounded border p-2 bg-light", props.className)}>
-      <h3 className="text-primary mx-3 d-inline-block align-middle">
-        {props.timeline.name}
-      </h3>
-      <div className="d-inline-block align-middle">
-        <BlobImage blob={avatar} className="avatar small rounded-circle" />
-        {props.timeline.owner.nickname}
-        <small className="ml-3 text-secondary">
-          @{props.timeline.owner.username}
-        </small>
+    <div
+      className={clsx(
+        "rounded border p-2 bg-light",
+        props.className,
+        collapse && "align-self-end"
+      )}
+    >
+      <div className="float-right d-flex align-items-center">
+        <SyncStatusBadge status={syncStatus} className="mr-2" />
+        <CollapseButton collapse={collapse} onClick={toggleCollapse} />
       </div>
-      <p className="mb-0">{props.timeline.description}</p>
-      <small className="mt-1 d-block">
-        {t(timelineVisibilityTooltipTranslationMap[props.timeline.visibility])}
-      </small>
-      <div className="text-right mt-2">
-        {onManage != null ? (
-          <Dropdown>
-            <Dropdown.Toggle variant="outline-primary">
-              {t("timeline.manage")}
-            </Dropdown.Toggle>
-            <Dropdown.Menu>
-              <Dropdown.Item onClick={() => onManage("property")}>
-                {t("timeline.manageItem.property")}
-              </Dropdown.Item>
-              <Dropdown.Item onClick={onMember}>
-                {t("timeline.manageItem.member")}
-              </Dropdown.Item>
-              <Dropdown.Divider />
-              <Dropdown.Item
-                className="text-danger"
-                onClick={() => onManage("delete")}
-              >
-                {t("timeline.manageItem.delete")}
-              </Dropdown.Item>
-            </Dropdown.Menu>
-          </Dropdown>
-        ) : (
-          <Button variant="outline-primary" onClick={onMember}>
-            {t("timeline.memberButton")}
-          </Button>
-        )}
+
+      <div style={{ display: collapse ? "none" : "block" }}>
+        <h3 className="text-primary mx-3 d-inline-block align-middle">
+          {timeline.name}
+        </h3>
+        <div className="d-inline-block align-middle">
+          <BlobImage blob={avatar} className="avatar small rounded-circle" />
+          {timeline.owner.nickname}
+          <small className="ml-3 text-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="text-right mt-2">
+          {onManage != null ? (
+            <Dropdown>
+              <Dropdown.Toggle variant="outline-primary">
+                {t("timeline.manage")}
+              </Dropdown.Toggle>
+              <Dropdown.Menu>
+                <Dropdown.Item onClick={() => onManage("property")}>
+                  {t("timeline.manageItem.property")}
+                </Dropdown.Item>
+                <Dropdown.Item onClick={onMember}>
+                  {t("timeline.manageItem.member")}
+                </Dropdown.Item>
+                <Dropdown.Divider />
+                <Dropdown.Item
+                  className="text-danger"
+                  onClick={() => onManage("delete")}
+                >
+                  {t("timeline.manageItem.delete")}
+                </Dropdown.Item>
+              </Dropdown.Menu>
+            </Dropdown>
+          ) : (
+            <Button variant="outline-primary" onClick={onMember}>
+              {t("timeline.memberButton")}
+            </Button>
+          )}
+        </div>
       </div>
     </div>
   );
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<UserInfoCardProps> = (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 (
     <div className={clsx("rounded border bg-light p-2", props.className)}>
@@ -29,14 +28,14 @@ const UserInfoCard: React.FC<UserInfoCardProps> = (props) => {
         className="avatar large mr-2 rounded-circle float-left"
       />
       <div>
-        {props.timeline.owner.nickname}
+        {timeline.owner.nickname}
         <small className="ml-3 text-secondary">
-          @{props.timeline.owner.username}
+          @{timeline.owner.username}
         </small>
       </div>
-      <p className="mb-0">{props.timeline.description}</p>
+      <p className="mb-0">{timeline.description}</p>
       <small className="mt-1 d-block">
-        {t(timelineVisibilityTooltipTranslationMap[props.timeline.visibility])}
+        {t(timelineVisibilityTooltipTranslationMap[timeline.visibility])}
       </small>
       <div className="text-right mt-2">
         {onManage != null ? (
-- 
cgit v1.2.3


From 5a0adf596988efe8c3e49efcba7594f134a9cb0d Mon Sep 17 00:00:00 2001
From: crupest <crupest@outlook.com>
Date: Thu, 3 Sep 2020 23:05:54 +0800
Subject: Remove animation of timeline entrance.

---
 .../ClientApp/src/app/views/timeline-common/Timeline.tsx   |  1 -
 .../src/app/views/timeline-common/timeline-common.sass     | 14 --------------
 2 files changed, 15 deletions(-)

(limited to 'Timeline/ClientApp/src/app/views/timeline-common/Timeline.tsx')

diff --git a/Timeline/ClientApp/src/app/views/timeline-common/Timeline.tsx b/Timeline/ClientApp/src/app/views/timeline-common/Timeline.tsx
index 1cb15d8e..fd051d45 100644
--- a/Timeline/ClientApp/src/app/views/timeline-common/Timeline.tsx
+++ b/Timeline/ClientApp/src/app/views/timeline-common/Timeline.tsx
@@ -52,7 +52,6 @@ const Timeline: React.FC<TimelineProps> = (props) => {
 
   return (
     <div ref={props.containerRef} className={clsx("timeline", props.className)}>
-      <div className="timeline-enter-animation-mask" />
       {(() => {
         const length = posts.length;
         return posts.map((post, i) => {
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 3bcd4a67..79df9249 100644
--- a/Timeline/ClientApp/src/app/views/timeline-common/timeline-common.sass
+++ b/Timeline/ClientApp/src/app/views/timeline-common/timeline-common.sass
@@ -7,20 +7,6 @@
   &-item
     display: flex
 
-@keyframes timeline-enter-animation-mask-animation
-  to
-    height: 0
-
-.timeline-enter-animation-mask
-  position: absolute
-  left: 0
-  top: 0
-  height: calc(100% + 300px)
-  width: 100%
-  background: linear-gradient(to top, #ffffff00 0, 200px, white 300px, white)
-  z-index: 100
-  animation: timeline-enter-animation-mask-animation 5s 0.3s forwards // Give it 0.3s to load, which I think is reasonable
-
 $timeline-line-width: 7px
 $timeline-line-node-radius: 18px
 $timeline-line-color: $primary
-- 
cgit v1.2.3

