aboutsummaryrefslogtreecommitdiff
path: root/Timeline/ClientApp/src/app/timeline
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2020-08-04 02:31:59 +0800
committercrupest <crupest@outlook.com>2020-08-04 02:31:59 +0800
commit89806d334fe2c7ef0ea8b3d12c74759e8e3ba860 (patch)
tree47b85b9bfb5026743a4a9984c5c24b8363e8a5af /Timeline/ClientApp/src/app/timeline
parentff94145165d6877e47db7ec11c4d3b802f6f3532 (diff)
downloadtimeline-89806d334fe2c7ef0ea8b3d12c74759e8e3ba860.tar.gz
timeline-89806d334fe2c7ef0ea8b3d12c74759e8e3ba860.tar.bz2
timeline-89806d334fe2c7ef0ea8b3d12c74759e8e3ba860.zip
...
Diffstat (limited to 'Timeline/ClientApp/src/app/timeline')
-rw-r--r--Timeline/ClientApp/src/app/timeline/TimelineInfoCard.tsx9
-rw-r--r--Timeline/ClientApp/src/app/timeline/TimelineItem.tsx24
-rw-r--r--Timeline/ClientApp/src/app/timeline/TimelineMember.tsx13
-rw-r--r--Timeline/ClientApp/src/app/timeline/TimelinePageTemplate.tsx85
-rw-r--r--Timeline/ClientApp/src/app/timeline/TimelinePageTemplateUI.tsx10
5 files changed, 51 insertions, 90 deletions
diff --git a/Timeline/ClientApp/src/app/timeline/TimelineInfoCard.tsx b/Timeline/ClientApp/src/app/timeline/TimelineInfoCard.tsx
index c25b2376..ece7d38a 100644
--- a/Timeline/ClientApp/src/app/timeline/TimelineInfoCard.tsx
+++ b/Timeline/ClientApp/src/app/timeline/TimelineInfoCard.tsx
@@ -10,10 +10,11 @@ import {
import { useTranslation } from 'react-i18next';
import { fromEvent } from 'rxjs';
-import { useAvatarUrl } from '../data/user';
+import { useAvatar } from '../data/user';
import { timelineVisibilityTooltipTranslationMap } from '../data/timeline';
import { TimelineCardComponentProps } from './TimelinePageTemplateUI';
+import BlobImage from '../common/BlobImage';
export type OrdinaryTimelineManageItem = 'delete';
@@ -26,7 +27,7 @@ const TimelineInfoCard: React.FC<TimelineInfoCardProps> = (props) => {
const { t } = useTranslation();
- const avatarUrl = useAvatarUrl(props.timeline.owner.username);
+ const avatar = useAvatar(props.timeline.owner.username);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const containerRef = React.useRef<HTMLDivElement>(null!);
@@ -60,8 +61,8 @@ const TimelineInfoCard: React.FC<TimelineInfoCardProps> = (props) => {
{props.timeline.name}
</h3>
<div className="d-inline-block align-middle">
- <img
- src={avatarUrl}
+ <BlobImage
+ blob={avatar}
onLoad={notifyHeight}
className="avatar small rounded-circle"
/>
diff --git a/Timeline/ClientApp/src/app/timeline/TimelineItem.tsx b/Timeline/ClientApp/src/app/timeline/TimelineItem.tsx
index 11ac9f08..727de1fe 100644
--- a/Timeline/ClientApp/src/app/timeline/TimelineItem.tsx
+++ b/Timeline/ClientApp/src/app/timeline/TimelineItem.tsx
@@ -16,8 +16,10 @@ import Svg from 'react-inlinesvg';
import chevronDownIcon from 'bootstrap-icons/icons/chevron-down.svg';
import trashIcon from 'bootstrap-icons/icons/trash.svg';
-import { useAvatarUrl } from '../data/user';
-import { TimelinePostInfo, usePostDataUrl } from '../data/timeline';
+import BlobImage from '../common/BlobImage';
+
+import { useAvatar } from '../data/user';
+import { TimelinePostInfo } from '../data/timeline';
const TimelinePostDeleteConfirmDialog: React.FC<{
toggle: () => void;
@@ -70,13 +72,7 @@ const TimelineItem: React.FC<TimelineItemProps> = (props) => {
const { more, onResize } = props;
- const avatarUrl = useAvatarUrl(props.post.author.username);
-
- const dataUrl = usePostDataUrl(
- props.post.content.type === 'image',
- props.post.timelineName,
- props.post.id
- );
+ const avatar = useAvatar(props.post.author.username);
const [deleteDialog, setDeleteDialog] = React.useState<boolean>(false);
const toggleDeleteDialog = React.useCallback(
@@ -132,7 +128,11 @@ const TimelineItem: React.FC<TimelineItemProps> = (props) => {
className="float-right float-sm-left mx-2"
to={'/users/' + props.post.author.username}
>
- <img onLoad={onResize} src={avatarUrl} className="avatar rounded" />
+ <BlobImage
+ onLoad={onResize}
+ blob={avatar}
+ className="avatar rounded"
+ />
</Link>
{(() => {
const { content } = props.post;
@@ -140,9 +140,9 @@ const TimelineItem: React.FC<TimelineItemProps> = (props) => {
return content.text;
} else {
return (
- <img
+ <BlobImage
onLoad={onResize}
- src={dataUrl}
+ blob={content.data}
className="timeline-content-image"
/>
);
diff --git a/Timeline/ClientApp/src/app/timeline/TimelineMember.tsx b/Timeline/ClientApp/src/app/timeline/TimelineMember.tsx
index 8c637f46..39af412e 100644
--- a/Timeline/ClientApp/src/app/timeline/TimelineMember.tsx
+++ b/Timeline/ClientApp/src/app/timeline/TimelineMember.tsx
@@ -10,9 +10,10 @@ import {
Button,
} from 'reactstrap';
-import { User, useAvatarUrl } from '../data/user';
+import { User, useAvatar } from '../data/user';
import SearchInput from '../common/SearchInput';
+import BlobImage from '../common/BlobImage';
const TimelineMemberItem: React.FC<{
user: User;
@@ -21,13 +22,13 @@ const TimelineMemberItem: React.FC<{
}> = ({ user, owner, onRemove }) => {
const { t } = useTranslation();
- const avatarUrl = useAvatarUrl(user.username);
+ const avatar = useAvatar(user.username);
return (
<ListGroupItem className="container">
<Row>
<Col className="col-auto">
- <img src={avatarUrl} className="avatar small" />
+ <BlobImage blob={avatar} className="avatar small" />
</Col>
<Col>
<Row>{user.nickname}</Row>
@@ -84,7 +85,7 @@ const TimelineMember: React.FC<TimelineMemberProps> = (props) => {
| { type: 'init' }
>({ type: 'init' });
- const userSearchAvatarUrl = useAvatarUrl(
+ const userSearchAvatar = useAvatar(
userSearchState.type === 'user' ? userSearchState.data.username : undefined
);
@@ -156,8 +157,8 @@ const TimelineMember: React.FC<TimelineMemberProps> = (props) => {
<Container className="mb-3">
<Row>
<Col className="col-auto">
- <img
- src={userSearchAvatarUrl}
+ <BlobImage
+ blob={userSearchAvatar}
className="avatar small"
/>
</Col>
diff --git a/Timeline/ClientApp/src/app/timeline/TimelinePageTemplate.tsx b/Timeline/ClientApp/src/app/timeline/TimelinePageTemplate.tsx
index a68d08c6..afa9464a 100644
--- a/Timeline/ClientApp/src/app/timeline/TimelinePageTemplate.tsx
+++ b/Timeline/ClientApp/src/app/timeline/TimelinePageTemplate.tsx
@@ -1,8 +1,7 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
-import { concat, without } from 'lodash';
import { of } from 'rxjs';
-import { catchError, map } from 'rxjs/operators';
+import { catchError } from 'rxjs/operators';
import { ExcludeKey } from '../utilities/type';
import { pushAlert } from '../common/alert-service';
@@ -10,8 +9,8 @@ import { useUser, userInfoService, UserNotExistError } from '../data/user';
import {
timelineService,
TimelineInfo,
- TimelineNotExistError,
usePostList,
+ useTimelineInfo,
} from '../data/timeline';
import { TimelineDeleteCallback } from './Timeline';
@@ -51,34 +50,22 @@ export default function TimelinePageTemplate<
const [dialog, setDialog] = React.useState<null | 'property' | 'member'>(
null
);
- const [timeline, setTimeline] = React.useState<TimelineInfo | undefined>(
- undefined
- );
- const postListState = usePostList(timeline?.name);
-
- const [error, setError] = React.useState<string | undefined>(undefined);
-
- React.useEffect(() => {
- const subscription = service.getTimeline(name).subscribe(
- (ti) => {
- setTimeline(ti);
- },
- (error) => {
- if (error instanceof TimelineNotExistError) {
- setError(t(props.notFoundI18nKey));
- } else {
- setError(
- // TODO: Convert this to a function.
- (error as { message?: string })?.message ?? 'Unknown error'
- );
- }
- }
- );
- return () => {
- subscription.unsubscribe();
- };
- }, [name, service, user, t, props.dataVersion, props.notFoundI18nKey]);
+ const timelineState = useTimelineInfo(name);
+
+ const timeline = timelineState?.timeline;
+
+ const postListState = usePostList(name);
+
+ const error: string | undefined = (() => {
+ if (timelineState != null) {
+ const { syncState, timeline } = timelineState;
+ if (syncState === 'offline' && timeline == null) return 'Network Error';
+ if (syncState !== 'offline' && timeline == null)
+ return t(props.notFoundI18nKey);
+ }
+ return undefined;
+ })();
const closeDialog = React.useCallback((): void => {
setDialog(null);
@@ -102,14 +89,7 @@ export default function TimelinePageTemplate<
description: timeline.description,
}}
onProcess={(req) => {
- return service
- .changeTimelineProperty(name, req)
- .pipe(
- map((newTimeline) => {
- setTimeline(newTimeline);
- })
- )
- .toPromise();
+ return service.changeTimelineProperty(name, req).toPromise().then();
}}
/>
);
@@ -143,33 +123,10 @@ export default function TimelinePageTemplate<
.toPromise();
},
onAddUser: (u) => {
- return service
- .addMember(name, u.username)
- .pipe(
- map(() => {
- setTimeline({
- ...timeline,
- members: concat(timeline.members, u),
- });
- })
- )
- .toPromise();
+ return service.addMember(name, u.username).toPromise().then();
},
onRemoveUser: (u) => {
- service.removeMember(name, u).subscribe(() => {
- const toDelete = timeline.members.find(
- (m) => m.username === u
- );
- if (toDelete == null) {
- throw new UiLogicError(
- 'The member to delete is not in list.'
- );
- }
- setTimeline({
- ...timeline,
- members: without(timeline.members, toDelete),
- });
- });
+ service.removeMember(name, u);
},
}
: null
@@ -220,7 +177,7 @@ export default function TimelinePageTemplate<
<>
<UiComponent
error={error}
- timeline={timeline}
+ timeline={timeline ?? undefined}
postListState={postListState}
onDelete={onDelete}
onPost={
diff --git a/Timeline/ClientApp/src/app/timeline/TimelinePageTemplateUI.tsx b/Timeline/ClientApp/src/app/timeline/TimelinePageTemplateUI.tsx
index dc5bfda8..42171e13 100644
--- a/Timeline/ClientApp/src/app/timeline/TimelinePageTemplateUI.tsx
+++ b/Timeline/ClientApp/src/app/timeline/TimelinePageTemplateUI.tsx
@@ -12,7 +12,7 @@ import { getAlertHost } from '../common/alert-service';
import { useEventEmiiter, UiLogicError } from '../common';
import {
TimelineInfo,
- TimelinePostListState,
+ TimelinePostsWithSyncState,
timelineService,
} from '../data/timeline';
import { userService } from '../data/user';
@@ -24,8 +24,10 @@ import Timeline, {
import AppBar from '../common/AppBar';
import TimelinePostEdit, { TimelinePostSendCallback } from './TimelinePostEdit';
+type TimelinePostSyncState = 'syncing' | 'synced' | 'offline';
+
const TimelinePostSyncStateBadge: React.FC<{
- state: 'syncing' | 'synced' | 'offline';
+ state: TimelinePostSyncState;
style?: CSSProperties;
className?: string;
}> = ({ state, style, className }) => {
@@ -84,7 +86,7 @@ export interface TimelineCardComponentProps<TManageItems> {
export interface TimelinePageTemplateUIProps<TManageItems> {
avatarKey?: string | number;
timeline?: TimelineInfo;
- postListState?: TimelinePostListState;
+ postListState?: TimelinePostsWithSyncState;
CardComponent: React.ComponentType<TimelineCardComponentProps<TManageItems>>;
onMember: () => void;
onManage?: (item: TManageItems | 'property') => void;
@@ -197,7 +199,7 @@ export default function TimelinePageTemplateUI<TManageItems>(
} else {
if (timeline != null) {
let timelineBody: React.ReactElement;
- if (postListState != null && postListState.state !== 'loading') {
+ if (postListState != null) {
if (postListState.state === 'forbid') {
timelineBody = (
<p className="text-danger">{t('timeline.messageCantSee')}</p>