From 8dd3bc0ac8c7035edec7974e76f4e49d7e401ab5 Mon Sep 17 00:00:00 2001
From: crupest <crupest@outlook.com>
Date: Fri, 31 Jul 2020 00:14:32 +0800
Subject: Update post list when create or delete post.

---
 .../src/app/timeline/TimelinePageTemplateUI.tsx    | 32 ++++++++++++++++------
 1 file changed, 24 insertions(+), 8 deletions(-)

(limited to 'Timeline/ClientApp/src/app/timeline/TimelinePageTemplateUI.tsx')

diff --git a/Timeline/ClientApp/src/app/timeline/TimelinePageTemplateUI.tsx b/Timeline/ClientApp/src/app/timeline/TimelinePageTemplateUI.tsx
index 4b3b3096..3c8e312c 100644
--- a/Timeline/ClientApp/src/app/timeline/TimelinePageTemplateUI.tsx
+++ b/Timeline/ClientApp/src/app/timeline/TimelinePageTemplateUI.tsx
@@ -8,6 +8,13 @@ import arrowsAngleContractIcon from 'bootstrap-icons/icons/arrows-angle-contract
 import arrowsAngleExpandIcon from 'bootstrap-icons/icons/arrows-angle-expand.svg';
 
 import { getAlertHost } from '../common/alert-service';
+import { useEventEmiiter } from '../common';
+import {
+  TimelineInfo,
+  TimelinePostListState,
+  timelineService,
+} from '../data/timeline';
+import { userService } from '../data/user';
 
 import Timeline, {
   TimelinePostInfoEx,
@@ -15,8 +22,6 @@ import Timeline, {
 } from './Timeline';
 import AppBar from '../common/AppBar';
 import TimelinePostEdit, { TimelinePostSendCallback } from './TimelinePostEdit';
-import { useEventEmiiter } from '../common';
-import { TimelineInfo } from '../data/timeline';
 
 export interface TimelineCardComponentProps<TManageItems> {
   timeline: TimelineInfo;
@@ -29,7 +34,7 @@ export interface TimelineCardComponentProps<TManageItems> {
 export interface TimelinePageTemplateUIProps<TManageItems> {
   avatarKey?: string | number;
   timeline?: TimelineInfo;
-  posts?: TimelinePostInfoEx[] | 'forbid';
+  postListState?: TimelinePostListState;
   CardComponent: React.ComponentType<TimelineCardComponentProps<TManageItems>>;
   onMember: () => void;
   onManage?: (item: TManageItems | 'property') => void;
@@ -41,7 +46,7 @@ export interface TimelinePageTemplateUIProps<TManageItems> {
 export default function TimelinePageTemplateUI<TManageItems>(
   props: TimelinePageTemplateUIProps<TManageItems>
 ): React.ReactElement | null {
-  const { timeline } = props;
+  const { timeline, postListState } = props;
 
   const { t } = useTranslation();
 
@@ -116,7 +121,7 @@ export default function TimelinePageTemplateUI<TManageItems>(
         subscriptions.forEach((s) => s.unsubscribe());
       };
     }
-  }, [getResizeEvent, triggerResizeEvent, timeline, props.posts]);
+  }, [getResizeEvent, triggerResizeEvent, timeline, postListState]);
 
   const [cardHeight, setCardHeight] = React.useState<number>(0);
 
@@ -142,16 +147,27 @@ export default function TimelinePageTemplateUI<TManageItems>(
   } else {
     if (timeline != null) {
       let timelineBody: React.ReactElement;
-      if (props.posts != null) {
-        if (props.posts === 'forbid') {
+      if (postListState != null) {
+        if (postListState.state === 'forbid') {
           timelineBody = (
             <p className="text-danger">{t('timeline.messageCantSee')}</p>
           );
         } else {
+          const posts: TimelinePostInfoEx[] = postListState.posts.map(
+            (post) => ({
+              ...post,
+              deletable: timelineService.hasModifyPostPermission(
+                userService.currentUser,
+                timeline,
+                post
+              ),
+            })
+          );
+
           timelineBody = (
             <Timeline
               containerRef={timelineRef}
-              posts={props.posts}
+              posts={posts}
               onDelete={props.onDelete}
               onResize={triggerResizeEvent}
             />
-- 
cgit v1.2.3


From 28aae259f0cdb774e72a2ecf79e3addfe0db990a Mon Sep 17 00:00:00 2001
From: crupest <crupest@outlook.com>
Date: Fri, 31 Jul 2020 00:57:22 +0800
Subject: Add sync state badge.

---
 .../ClientApp/src/app/locales/en/translation.ts    |  5 ++
 Timeline/ClientApp/src/app/locales/scheme.ts       |  5 ++
 .../ClientApp/src/app/locales/zh/translation.ts    |  5 ++
 Timeline/ClientApp/src/app/timeline/Timeline.tsx   |  5 +-
 .../src/app/timeline/TimelinePageTemplateUI.tsx    | 66 +++++++++++++++++++---
 .../ClientApp/src/app/timeline/timeline-ui.sass    | 18 ++++++
 Timeline/ClientApp/src/app/timeline/timeline.sass  |  6 ++
 7 files changed, 98 insertions(+), 12 deletions(-)

(limited to 'Timeline/ClientApp/src/app/timeline/TimelinePageTemplateUI.tsx')

diff --git a/Timeline/ClientApp/src/app/locales/en/translation.ts b/Timeline/ClientApp/src/app/locales/en/translation.ts
index 6abe910e..2f8fb312 100644
--- a/Timeline/ClientApp/src/app/locales/en/translation.ts
+++ b/Timeline/ClientApp/src/app/locales/en/translation.ts
@@ -92,6 +92,11 @@ const translation: TranslationResource = {
         'This is a dangerous action. If you are sure to delete timeline<1>{{name}}</1>, please input its name below and click confirm button.',
       notMatch: 'Name does not match.',
     },
+    postSyncState: {
+      syncing: 'Syncing',
+      synced: 'Synced',
+      offline: 'Offline',
+    },
     post: {
       deleteDialog: {
         title: 'Confirm Delete',
diff --git a/Timeline/ClientApp/src/app/locales/scheme.ts b/Timeline/ClientApp/src/app/locales/scheme.ts
index 19ac6c31..7aa7e125 100644
--- a/Timeline/ClientApp/src/app/locales/scheme.ts
+++ b/Timeline/ClientApp/src/app/locales/scheme.ts
@@ -83,6 +83,11 @@ export default interface TranslationResource {
       inputPrompt: string;
       notMatch: string;
     };
+    postSyncState: {
+      syncing: string;
+      synced: string;
+      offline: string;
+    };
     post: {
       deleteDialog: {
         title: string;
diff --git a/Timeline/ClientApp/src/app/locales/zh/translation.ts b/Timeline/ClientApp/src/app/locales/zh/translation.ts
index 372979c0..35cfa38c 100644
--- a/Timeline/ClientApp/src/app/locales/zh/translation.ts
+++ b/Timeline/ClientApp/src/app/locales/zh/translation.ts
@@ -88,6 +88,11 @@ const translation: TranslationResource = {
         '这是一个危险的操作。如果您确认要删除时间线<1>{{name}}</1>，请在下面输入它的名字并点击确认。',
       notMatch: '名字不匹配',
     },
+    postSyncState: {
+      syncing: '同步中',
+      synced: '同步成功',
+      offline: '离线',
+    },
     post: {
       deleteDialog: {
         title: '确认删除',
diff --git a/Timeline/ClientApp/src/app/timeline/Timeline.tsx b/Timeline/ClientApp/src/app/timeline/Timeline.tsx
index 849933cf..7c3a93fb 100644
--- a/Timeline/ClientApp/src/app/timeline/Timeline.tsx
+++ b/Timeline/ClientApp/src/app/timeline/Timeline.tsx
@@ -53,10 +53,7 @@ const Timeline: React.FC<TimelineProps> = (props) => {
   return (
     <div
       ref={props.containerRef}
-      className={clsx(
-        'container-fluid d-flex flex-column position-relative',
-        props.className
-      )}
+      className={clsx('container-fluid timeline', props.className)}
     >
       <div className="timeline-enter-animation-mask" />
       {(() => {
diff --git a/Timeline/ClientApp/src/app/timeline/TimelinePageTemplateUI.tsx b/Timeline/ClientApp/src/app/timeline/TimelinePageTemplateUI.tsx
index 3c8e312c..0d8ad278 100644
--- a/Timeline/ClientApp/src/app/timeline/TimelinePageTemplateUI.tsx
+++ b/Timeline/ClientApp/src/app/timeline/TimelinePageTemplateUI.tsx
@@ -8,7 +8,7 @@ import arrowsAngleContractIcon from 'bootstrap-icons/icons/arrows-angle-contract
 import arrowsAngleExpandIcon from 'bootstrap-icons/icons/arrows-angle-expand.svg';
 
 import { getAlertHost } from '../common/alert-service';
-import { useEventEmiiter } from '../common';
+import { useEventEmiiter, UiLogicError } from '../common';
 import {
   TimelineInfo,
   TimelinePostListState,
@@ -23,6 +23,53 @@ import Timeline, {
 import AppBar from '../common/AppBar';
 import TimelinePostEdit, { TimelinePostSendCallback } from './TimelinePostEdit';
 
+const TimelinePostSyncStateBadge: React.FC<{
+  state: 'syncing' | 'synced' | 'offline';
+}> = ({ state }) => {
+  const { t } = useTranslation();
+
+  return (
+    <div className="timeline-sync-state-badge">
+      {(() => {
+        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>
+  );
+};
+
 export interface TimelineCardComponentProps<TManageItems> {
   timeline: TimelineInfo;
   onManage?: (item: TManageItems | 'property') => void;
@@ -147,7 +194,7 @@ export default function TimelinePageTemplateUI<TManageItems>(
   } else {
     if (timeline != null) {
       let timelineBody: React.ReactElement;
-      if (postListState != null) {
+      if (postListState != null && postListState.state !== 'loading') {
         if (postListState.state === 'forbid') {
           timelineBody = (
             <p className="text-danger">{t('timeline.messageCantSee')}</p>
@@ -165,12 +212,15 @@ export default function TimelinePageTemplateUI<TManageItems>(
           );
 
           timelineBody = (
-            <Timeline
-              containerRef={timelineRef}
-              posts={posts}
-              onDelete={props.onDelete}
-              onResize={triggerResizeEvent}
-            />
+            <div className="position-relative">
+              <TimelinePostSyncStateBadge state={postListState.state} />
+              <Timeline
+                containerRef={timelineRef}
+                posts={posts}
+                onDelete={props.onDelete}
+                onResize={triggerResizeEvent}
+              />
+            </div>
           );
           if (props.onPost != null) {
             timelineBody = (
diff --git a/Timeline/ClientApp/src/app/timeline/timeline-ui.sass b/Timeline/ClientApp/src/app/timeline/timeline-ui.sass
index b92327bd..952e4659 100644
--- a/Timeline/ClientApp/src/app/timeline/timeline-ui.sass
+++ b/Timeline/ClientApp/src/app/timeline/timeline-ui.sass
@@ -16,3 +16,21 @@
 .timeline-page-top-space
   transition: height 0.5s
 
+.timeline-sync-state-badge
+  position: absolute
+  top: 0
+  right: 0
+  z-index: 1
+  font-size: 0.8em
+  margin-top: 4px
+  padding: 3px 8px
+  border-radius: 5px
+  background: #e8fbff
+
+.timeline-sync-state-badge-pin
+  display: inline-block
+  width: 0.4em
+  height: 0.4em
+  border-radius: 50%
+  vertical-align: middle
+  margin-right: 0.6em
diff --git a/Timeline/ClientApp/src/app/timeline/timeline.sass b/Timeline/ClientApp/src/app/timeline/timeline.sass
index 4f69295b..b224e973 100644
--- a/Timeline/ClientApp/src/app/timeline/timeline.sass
+++ b/Timeline/ClientApp/src/app/timeline/timeline.sass
@@ -1,5 +1,11 @@
 @use 'sass:color'
 
+.timeline
+  display: flex
+  flex-direction: column
+  z-index: 0
+  position: relative
+
 @keyframes timeline-enter-animation-mask-animation
   to
     height: 0
-- 
cgit v1.2.3


From b8cf8379358958c1155442d0526bbfc8c4d65ded Mon Sep 17 00:00:00 2001
From: crupest <crupest@outlook.com>
Date: Fri, 31 Jul 2020 01:11:13 +0800
Subject: Make sync state badge fixed.

---
 .../src/app/timeline/TimelinePageTemplateUI.tsx      | 20 +++++++++++++++-----
 Timeline/ClientApp/src/app/timeline/timeline-ui.sass |  3 +--
 2 files changed, 16 insertions(+), 7 deletions(-)

(limited to 'Timeline/ClientApp/src/app/timeline/TimelinePageTemplateUI.tsx')

diff --git a/Timeline/ClientApp/src/app/timeline/TimelinePageTemplateUI.tsx b/Timeline/ClientApp/src/app/timeline/TimelinePageTemplateUI.tsx
index 0d8ad278..dc5bfda8 100644
--- a/Timeline/ClientApp/src/app/timeline/TimelinePageTemplateUI.tsx
+++ b/Timeline/ClientApp/src/app/timeline/TimelinePageTemplateUI.tsx
@@ -1,8 +1,9 @@
-import React from 'react';
+import React, { CSSProperties } from 'react';
 import { Spinner } from 'reactstrap';
 import { useTranslation } from 'react-i18next';
 import { fromEvent } from 'rxjs';
 import Svg from 'react-inlinesvg';
+import clsx from 'clsx';
 
 import arrowsAngleContractIcon from 'bootstrap-icons/icons/arrows-angle-contract.svg';
 import arrowsAngleExpandIcon from 'bootstrap-icons/icons/arrows-angle-expand.svg';
@@ -25,11 +26,13 @@ import TimelinePostEdit, { TimelinePostSendCallback } from './TimelinePostEdit';
 
 const TimelinePostSyncStateBadge: React.FC<{
   state: 'syncing' | 'synced' | 'offline';
-}> = ({ state }) => {
+  style?: CSSProperties;
+  className?: string;
+}> = ({ state, style, className }) => {
   const { t } = useTranslation();
 
   return (
-    <div className="timeline-sync-state-badge">
+    <div style={style} className={clsx('timeline-sync-state-badge', className)}>
       {(() => {
         switch (state) {
           case 'syncing': {
@@ -211,9 +214,16 @@ export default function TimelinePageTemplateUI<TManageItems>(
             })
           );
 
+          const topHeight: string = infoCardCollapse
+            ? 'calc(68px + 1.5em)'
+            : `${cardHeight + 60}px`;
+
           timelineBody = (
-            <div className="position-relative">
-              <TimelinePostSyncStateBadge state={postListState.state} />
+            <div>
+              <TimelinePostSyncStateBadge
+                style={{ top: topHeight }}
+                state={postListState.state}
+              />
               <Timeline
                 containerRef={timelineRef}
                 posts={posts}
diff --git a/Timeline/ClientApp/src/app/timeline/timeline-ui.sass b/Timeline/ClientApp/src/app/timeline/timeline-ui.sass
index 952e4659..667c1da9 100644
--- a/Timeline/ClientApp/src/app/timeline/timeline-ui.sass
+++ b/Timeline/ClientApp/src/app/timeline/timeline-ui.sass
@@ -17,12 +17,11 @@
   transition: height 0.5s
 
 .timeline-sync-state-badge
-  position: absolute
+  position: fixed
   top: 0
   right: 0
   z-index: 1
   font-size: 0.8em
-  margin-top: 4px
   padding: 3px 8px
   border-radius: 5px
   background: #e8fbff
-- 
cgit v1.2.3

