diff options
author | crupest <crupest@outlook.com> | 2020-08-07 18:27:04 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2020-08-07 18:27:04 +0800 |
commit | 00b4f38a4c6f3c1039873cc8e274e24f207d81a7 (patch) | |
tree | 32603eda8592590e869b11f6c95dad9db74ce9b8 /Timeline/ClientApp/src | |
parent | ef5f490cf3155234a12d42f1b43630e38cb49a38 (diff) | |
download | timeline-00b4f38a4c6f3c1039873cc8e274e24f207d81a7.tar.gz timeline-00b4f38a4c6f3c1039873cc8e274e24f207d81a7.tar.bz2 timeline-00b4f38a4c6f3c1039873cc8e274e24f207d81a7.zip |
Create home page for offline.
Diffstat (limited to 'Timeline/ClientApp/src')
-rw-r--r-- | Timeline/ClientApp/src/app/data/timeline.ts | 10 | ||||
-rw-r--r-- | Timeline/ClientApp/src/app/home/BoardWithUser.tsx | 102 | ||||
-rw-r--r-- | Timeline/ClientApp/src/app/home/BoardWithoutUser.tsx | 61 | ||||
-rw-r--r-- | Timeline/ClientApp/src/app/home/Home.tsx | 88 | ||||
-rw-r--r-- | Timeline/ClientApp/src/app/home/OfflineBoard.tsx | 62 | ||||
-rw-r--r-- | Timeline/ClientApp/src/app/home/TimelineBoard.tsx | 28 | ||||
-rw-r--r-- | Timeline/ClientApp/src/app/home/TimelineBoardAreaWithUser.tsx | 36 | ||||
-rw-r--r-- | Timeline/ClientApp/src/app/home/TimelineBoardAreaWithoutUser.tsx | 26 | ||||
-rw-r--r-- | Timeline/ClientApp/src/app/locales/en/translation.ts | 3 | ||||
-rw-r--r-- | Timeline/ClientApp/src/app/locales/scheme.ts | 2 | ||||
-rw-r--r-- | Timeline/ClientApp/src/app/locales/zh/translation.ts | 3 |
11 files changed, 287 insertions, 134 deletions
diff --git a/Timeline/ClientApp/src/app/data/timeline.ts b/Timeline/ClientApp/src/app/data/timeline.ts index c0d2141f..9fc99d59 100644 --- a/Timeline/ClientApp/src/app/data/timeline.ts +++ b/Timeline/ClientApp/src/app/data/timeline.ts @@ -881,3 +881,13 @@ export function usePostList( }, [timelineName]);
return state;
}
+
+export async function getAllCachedTimelineNames(): Promise<string[]> {
+ const keys = await dataStorage.keys();
+ return keys
+ .filter(
+ (key) =>
+ key.startsWith('timeline.') && (key.match(/\./g) ?? []).length === 1
+ )
+ .map((key) => key.substr('timeline.'.length));
+}
diff --git a/Timeline/ClientApp/src/app/home/BoardWithUser.tsx b/Timeline/ClientApp/src/app/home/BoardWithUser.tsx new file mode 100644 index 00000000..3830104f --- /dev/null +++ b/Timeline/ClientApp/src/app/home/BoardWithUser.tsx @@ -0,0 +1,102 @@ +import React from 'react';
+import { Row, Col } from 'reactstrap';
+import { useTranslation } from 'react-i18next';
+
+import { UserWithToken } from '../data/user';
+import { TimelineInfo } from '../data/timeline';
+
+import { getHttpTimelineClient } from '../http/timeline';
+
+import TimelineBoard from './TimelineBoard';
+import OfflineBoard from './OfflineBoard';
+
+const BoardWithUser: React.FC<{ user: UserWithToken }> = ({ user }) => {
+ const { t } = useTranslation();
+
+ const [ownTimelines, setOwnTimelines] = React.useState<
+ TimelineInfo[] | 'offline' | 'loading'
+ >('loading');
+ const [joinTimelines, setJoinTimelines] = React.useState<
+ TimelineInfo[] | 'offline' | 'loading'
+ >('loading');
+
+ React.useEffect(() => {
+ let subscribe = true;
+ if (ownTimelines === 'loading') {
+ void getHttpTimelineClient()
+ .listTimeline({ relate: user.username, relateType: 'own' })
+ .then(
+ (timelines) => {
+ if (subscribe) {
+ setOwnTimelines(timelines);
+ }
+ },
+ () => {
+ setOwnTimelines('offline');
+ }
+ );
+ }
+ return () => {
+ subscribe = false;
+ };
+ }, [user, ownTimelines]);
+
+ React.useEffect(() => {
+ let subscribe = true;
+ if (joinTimelines === 'loading') {
+ void getHttpTimelineClient()
+ .listTimeline({ relate: user.username, relateType: 'join' })
+ .then(
+ (timelines) => {
+ if (subscribe) {
+ setJoinTimelines(timelines);
+ }
+ },
+ () => {
+ setJoinTimelines('offline');
+ }
+ );
+ }
+ return () => {
+ subscribe = false;
+ };
+ }, [user, joinTimelines]);
+
+ return (
+ <Row className="my-2 justify-content-center">
+ {ownTimelines === 'offline' && joinTimelines === 'offline' ? (
+ <Col className="py-2" sm="8" lg="6">
+ <OfflineBoard
+ onReload={() => {
+ setOwnTimelines('loading');
+ setJoinTimelines('loading');
+ }}
+ />
+ </Col>
+ ) : (
+ <>
+ <Col sm="6" lg="5" className="py-2">
+ <TimelineBoard
+ title={t('home.ownTimeline')}
+ timelines={ownTimelines}
+ onReload={() => {
+ setOwnTimelines('loading');
+ }}
+ />
+ </Col>
+ <Col sm="6" lg="5" className="py-2">
+ <TimelineBoard
+ title={t('home.joinTimeline')}
+ timelines={joinTimelines}
+ onReload={() => {
+ setJoinTimelines('loading');
+ }}
+ />
+ </Col>
+ </>
+ )}
+ </Row>
+ );
+};
+
+export default BoardWithUser;
diff --git a/Timeline/ClientApp/src/app/home/BoardWithoutUser.tsx b/Timeline/ClientApp/src/app/home/BoardWithoutUser.tsx new file mode 100644 index 00000000..4b30fcc4 --- /dev/null +++ b/Timeline/ClientApp/src/app/home/BoardWithoutUser.tsx @@ -0,0 +1,61 @@ +import React from 'react';
+import { Row, Col } from 'reactstrap';
+
+import { TimelineInfo } from '../data/timeline';
+
+import { getHttpTimelineClient } from '../http/timeline';
+
+import TimelineBoard from './TimelineBoard';
+import OfflineBoard from './OfflineBoard';
+
+const BoardWithoutUser: React.FC = () => {
+ const [publicTimelines, setPublicTimelines] = React.useState<
+ TimelineInfo[] | 'offline' | 'loading'
+ >('loading');
+
+ React.useEffect(() => {
+ let subscribe = true;
+ if (publicTimelines === 'loading') {
+ void getHttpTimelineClient()
+ .listTimeline({ visibility: 'Public' })
+ .then(
+ (timelines) => {
+ if (subscribe) {
+ setPublicTimelines(timelines);
+ }
+ },
+ () => {
+ setPublicTimelines('offline');
+ }
+ );
+ }
+ return () => {
+ subscribe = false;
+ };
+ }, [publicTimelines]);
+
+ return (
+ <Row className="my-2 justify-content-center">
+ {publicTimelines === 'offline' ? (
+ <Col sm="8" lg="6">
+ <OfflineBoard
+ onReload={() => {
+ setPublicTimelines('loading');
+ }}
+ />
+ </Col>
+ ) : (
+ <Col sm="8" lg="6">
+ <TimelineBoard
+ timelines={publicTimelines}
+ onReload={() => {
+ setPublicTimelines('loading');
+ }}
+ />
+ </Col>
+ )}
+ </Row>
+ );
+};
+
+export default BoardWithoutUser;
diff --git a/Timeline/ClientApp/src/app/home/Home.tsx b/Timeline/ClientApp/src/app/home/Home.tsx index de25d5c1..b759fa50 100644 --- a/Timeline/ClientApp/src/app/home/Home.tsx +++ b/Timeline/ClientApp/src/app/home/Home.tsx @@ -4,16 +4,14 @@ import { Row, Container, Button, Col } from 'reactstrap'; import { useTranslation } from 'react-i18next';
import { useUser } from '../data/user';
-import { TimelineInfo } from '../data/timeline';
-import { getHttpTimelineClient } from '../http/timeline';
import AppBar from '../common/AppBar';
import SearchInput from '../common/SearchInput';
-import TimelineBoardAreaWithoutUser from './TimelineBoardAreaWithoutUser';
-import TimelineBoardAreaWithUser from './TimelineBoardAreaWithUser';
+import BoardWithoutUser from './BoardWithoutUser';
+import BoardWithUser from './BoardWithUser';
import TimelineCreateDialog from './TimelineCreateDialog';
-const Home: React.FC = (_) => {
+const Home: React.FC = () => {
const history = useHistory();
const { t } = useTranslation();
@@ -22,50 +20,6 @@ const Home: React.FC = (_) => { const [navText, setNavText] = React.useState<string>('');
- const [publicTimelines, setPublicTimelines] = React.useState<
- TimelineInfo[] | undefined
- >(undefined);
- const [ownTimelines, setOwnTimelines] = React.useState<
- TimelineInfo[] | undefined
- >(undefined);
- const [joinTimelines, setJoinTimelines] = React.useState<
- TimelineInfo[] | undefined
- >(undefined);
-
- React.useEffect(() => {
- let subscribe = true;
- if (user == null) {
- setOwnTimelines(undefined);
- setJoinTimelines(undefined);
- void getHttpTimelineClient()
- .listTimeline({ visibility: 'Public' })
- .then((timelines) => {
- if (subscribe) {
- setPublicTimelines(timelines);
- }
- });
- } else {
- setPublicTimelines(undefined);
- void getHttpTimelineClient()
- .listTimeline({ relate: user.username, relateType: 'own' })
- .then((timelines) => {
- if (subscribe) {
- setOwnTimelines(timelines);
- }
- });
- void getHttpTimelineClient()
- .listTimeline({ relate: user.username, relateType: 'join' })
- .then((timelines) => {
- if (subscribe) {
- setJoinTimelines(timelines);
- }
- });
- }
- return () => {
- subscribe = false;
- };
- }, [user]);
-
const [dialog, setDialog] = React.useState<'create' | null>(null);
const goto = React.useCallback((): void => {
@@ -78,14 +32,6 @@ const Home: React.FC = (_) => { }
}, [navText, history]);
- const openCreateDialog = React.useCallback(() => {
- setDialog('create');
- }, []);
-
- const closeDialog = React.useCallback(() => {
- setDialog(null);
- }, []);
-
return (
<>
<AppBar />
@@ -101,7 +47,13 @@ const Home: React.FC = (_) => { placeholder="@crupest"
additionalButton={
user != null && (
- <Button color="success" outline onClick={openCreateDialog}>
+ <Button
+ color="success"
+ outline
+ onClick={() => {
+ setDialog('create');
+ }}
+ >
{t('home.createButton')}
</Button>
)
@@ -111,16 +63,9 @@ const Home: React.FC = (_) => { </Row>
{(() => {
if (user == null) {
- return (
- <TimelineBoardAreaWithoutUser publicTimelines={publicTimelines} />
- );
+ return <BoardWithoutUser />;
} else {
- return (
- <TimelineBoardAreaWithUser
- ownTimelines={ownTimelines}
- joinTimelines={joinTimelines}
- />
- );
+ return <BoardWithUser user={user} />;
}
})()}
</Container>
@@ -142,7 +87,14 @@ const Home: React.FC = (_) => { <small className="white-space-no-wrap">公安备案 42112102000124</small>
</a>
</footer>
- {dialog === 'create' && <TimelineCreateDialog open close={closeDialog} />}
+ {dialog === 'create' && (
+ <TimelineCreateDialog
+ open
+ close={() => {
+ setDialog(null);
+ }}
+ />
+ )}
</>
);
};
diff --git a/Timeline/ClientApp/src/app/home/OfflineBoard.tsx b/Timeline/ClientApp/src/app/home/OfflineBoard.tsx new file mode 100644 index 00000000..ca6d2a26 --- /dev/null +++ b/Timeline/ClientApp/src/app/home/OfflineBoard.tsx @@ -0,0 +1,62 @@ +import React from 'react';
+import { Link } from 'react-router-dom';
+import { Trans } from 'react-i18next';
+
+import { getAllCachedTimelineNames } from '../data/timeline';
+
+import UserTimelineLogo from '../common/UserTimelineLogo';
+import TimelineLogo from '../common/TimelineLogo';
+
+export interface OfflineBoardProps {
+ onReload: () => void;
+}
+
+const OfflineBoard: React.FC<OfflineBoardProps> = ({ onReload }) => {
+ const [timelines, setTimelines] = React.useState<string[]>([]);
+
+ React.useEffect(() => {
+ let subscribe = true;
+ void getAllCachedTimelineNames().then((t) => {
+ if (subscribe) setTimelines(t);
+ });
+ return () => {
+ subscribe = false;
+ };
+ });
+
+ return (
+ <>
+ <Trans i18nKey="home.offlinePrompt">
+ 0
+ <a
+ href="#"
+ onClick={(e) => {
+ onReload();
+ e.preventDefault();
+ }}
+ >
+ 1
+ </a>
+ 2
+ </Trans>
+ {timelines.map((timeline) => {
+ const isPersonal = timeline.startsWith('@');
+ const url = isPersonal
+ ? `/users/${timeline.slice(1)}`
+ : `/timelines/${timeline}`;
+ return (
+ <div key={timeline} className="timeline-board-item">
+ {isPersonal ? (
+ <UserTimelineLogo className="icon" />
+ ) : (
+ <TimelineLogo className="icon" />
+ )}
+ <Link to={url}>{timeline}</Link>
+ </div>
+ );
+ })}
+ </>
+ );
+};
+
+export default OfflineBoard;
diff --git a/Timeline/ClientApp/src/app/home/TimelineBoard.tsx b/Timeline/ClientApp/src/app/home/TimelineBoard.tsx index 2e017bf7..8f8f6387 100644 --- a/Timeline/ClientApp/src/app/home/TimelineBoard.tsx +++ b/Timeline/ClientApp/src/app/home/TimelineBoard.tsx @@ -2,6 +2,7 @@ import React from 'react'; import clsx from 'clsx';
import { Link } from 'react-router-dom';
import { Spinner } from 'reactstrap';
+import { Trans } from 'react-i18next';
import { TimelineInfo } from '../data/timeline';
@@ -10,25 +11,44 @@ import UserTimelineLogo from '../common/UserTimelineLogo'; export interface TimelineBoardProps {
title?: string;
- timelines?: TimelineInfo[];
+ timelines: TimelineInfo[] | 'offline' | 'loading';
+ onReload: () => void;
className?: string;
}
-const TimelineBoard: React.FC<TimelineBoardProps> = props => {
+const TimelineBoard: React.FC<TimelineBoardProps> = (props) => {
const { title, timelines, className } = props;
return (
<div className={clsx('timeline-board', className)}>
{title != null && <h3 className="text-center">{title}</h3>}
{(() => {
- if (timelines == null) {
+ if (timelines === 'loading') {
return (
<div className="d-flex flex-grow-1 justify-content-center align-items-center">
<Spinner color="primary" />
</div>
);
+ } else if (timelines === 'offline') {
+ return (
+ <div className="d-flex flex-grow-1 justify-content-center align-items-center">
+ <Trans i18nKey="loadFailReload" parent="div">
+ 0
+ <a
+ href="#"
+ onClick={(e) => {
+ props.onReload();
+ e.preventDefault();
+ }}
+ >
+ 1
+ </a>
+ 2
+ </Trans>
+ </div>
+ );
} else {
- return timelines.map(timeline => {
+ return timelines.map((timeline) => {
const { name } = timeline;
const isPersonal = name.startsWith('@');
const url = isPersonal
diff --git a/Timeline/ClientApp/src/app/home/TimelineBoardAreaWithUser.tsx b/Timeline/ClientApp/src/app/home/TimelineBoardAreaWithUser.tsx deleted file mode 100644 index a8603b9e..00000000 --- a/Timeline/ClientApp/src/app/home/TimelineBoardAreaWithUser.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react';
-import { Row, Col } from 'reactstrap';
-import { useTranslation } from 'react-i18next';
-
-import TimelineBoard from './TimelineBoard';
-import { TimelineInfo } from '../data/timeline';
-
-interface TimelineBoardAreaWithUserProps {
- ownTimelines?: TimelineInfo[];
- joinTimelines?: TimelineInfo[];
-}
-
-const TimelineBoardAreaWithUser: React.FC<TimelineBoardAreaWithUserProps> = (
- props
-) => {
- const { t } = useTranslation();
-
- return (
- <Row className="my-2 justify-content-center">
- <Col sm="6" lg="5" className="py-2">
- <TimelineBoard
- title={t('home.ownTimeline')}
- timelines={props.ownTimelines}
- />
- </Col>
- <Col sm="6" lg="5" className="py-2">
- <TimelineBoard
- title={t('home.joinTimeline')}
- timelines={props.joinTimelines}
- />
- </Col>
- </Row>
- );
-};
-
-export default TimelineBoardAreaWithUser;
diff --git a/Timeline/ClientApp/src/app/home/TimelineBoardAreaWithoutUser.tsx b/Timeline/ClientApp/src/app/home/TimelineBoardAreaWithoutUser.tsx deleted file mode 100644 index dc05ff09..00000000 --- a/Timeline/ClientApp/src/app/home/TimelineBoardAreaWithoutUser.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react';
-import { Row, Col } from 'reactstrap';
-
-import { TimelineInfo } from '../data/timeline';
-
-import TimelineBoard from './TimelineBoard';
-
-interface TimelineBoardAreaWithoutUserProps {
- publicTimelines?: TimelineInfo[];
-}
-
-const TimelineBoardAreaWithoutUser: React.FC<TimelineBoardAreaWithoutUserProps> = (
- props
-) => {
- const { publicTimelines } = props;
-
- return (
- <Row className="my-2 justify-content-center">
- <Col sm="8" lg="6">
- <TimelineBoard timelines={publicTimelines} />
- </Col>
- </Row>
- );
-};
-
-export default TimelineBoardAreaWithoutUser;
diff --git a/Timeline/ClientApp/src/app/locales/en/translation.ts b/Timeline/ClientApp/src/app/locales/en/translation.ts index 2f8fb312..e7f395e1 100644 --- a/Timeline/ClientApp/src/app/locales/en/translation.ts +++ b/Timeline/ClientApp/src/app/locales/en/translation.ts @@ -3,6 +3,7 @@ import TranslationResource from '../scheme'; const translation: TranslationResource = {
welcome: 'Welcome!',
search: 'Search',
+ loadFailReload: 'Load failed, click <1>here</1> to reload.',
serviceWorker: {
availableOffline:
'Timeline is now cached in your computer and you can use it offline. 🎉🎉🎉',
@@ -26,6 +27,8 @@ const translation: TranslationResource = { allTimeline: 'All Timelines',
joinTimeline: 'Joined Timelines',
ownTimeline: 'Owned Timelines',
+ offlinePrompt:
+ 'Oh oh, it seems you are offline. Here list some timelines cached locally. You can view them or click <1>here</1> to refresh.',
createButton: 'Create Timeline',
createDialog: {
title: 'Create Timeline!',
diff --git a/Timeline/ClientApp/src/app/locales/scheme.ts b/Timeline/ClientApp/src/app/locales/scheme.ts index 7aa7e125..bbbc2686 100644 --- a/Timeline/ClientApp/src/app/locales/scheme.ts +++ b/Timeline/ClientApp/src/app/locales/scheme.ts @@ -3,6 +3,7 @@ export default interface TranslationResource { search: string;
chooseImage: string;
loadImageError: string;
+ loadFailReload: string;
serviceWorker: {
availableOffline: string;
upgradePrompt: string;
@@ -21,6 +22,7 @@ export default interface TranslationResource { allTimeline: string;
joinTimeline: string;
ownTimeline: string;
+ offlinePrompt: string;
createButton: string;
createDialog: {
title: string;
diff --git a/Timeline/ClientApp/src/app/locales/zh/translation.ts b/Timeline/ClientApp/src/app/locales/zh/translation.ts index 35cfa38c..2e0eccd5 100644 --- a/Timeline/ClientApp/src/app/locales/zh/translation.ts +++ b/Timeline/ClientApp/src/app/locales/zh/translation.ts @@ -3,6 +3,7 @@ import TranslationResource from '../scheme'; const translation: TranslationResource = {
welcome: '欢迎!',
search: '搜索',
+ loadFailReload: '加载失败,<1>点击</1>重试。',
serviceWorker: {
availableOffline: 'Timeline 已经缓存在本地,你可以离线使用它。🎉🎉🎉',
upgradePrompt: 'App 有新版本!',
@@ -24,6 +25,8 @@ const translation: TranslationResource = { allTimeline: '所有的时间线',
joinTimeline: '加入的时间线',
ownTimeline: '拥有的时间线',
+ offlinePrompt:
+ '你好像处于离线状态。以下是一些缓存在本地的时间线。你可以查看它们或者<1>点击</1>重新获取在线信息。',
createButton: '创建时间线',
createDialog: {
title: '创建时间线!',
|