From 00b4f38a4c6f3c1039873cc8e274e24f207d81a7 Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 7 Aug 2020 18:27:04 +0800 Subject: Create home page for offline. --- Timeline/ClientApp/src/app/data/timeline.ts | 10 ++ Timeline/ClientApp/src/app/home/BoardWithUser.tsx | 102 +++++++++++++++++++++ .../ClientApp/src/app/home/BoardWithoutUser.tsx | 61 ++++++++++++ Timeline/ClientApp/src/app/home/Home.tsx | 88 ++++-------------- Timeline/ClientApp/src/app/home/OfflineBoard.tsx | 62 +++++++++++++ Timeline/ClientApp/src/app/home/TimelineBoard.tsx | 28 +++++- .../src/app/home/TimelineBoardAreaWithUser.tsx | 36 -------- .../src/app/home/TimelineBoardAreaWithoutUser.tsx | 26 ------ .../ClientApp/src/app/locales/en/translation.ts | 3 + Timeline/ClientApp/src/app/locales/scheme.ts | 2 + .../ClientApp/src/app/locales/zh/translation.ts | 3 + 11 files changed, 287 insertions(+), 134 deletions(-) create mode 100644 Timeline/ClientApp/src/app/home/BoardWithUser.tsx create mode 100644 Timeline/ClientApp/src/app/home/BoardWithoutUser.tsx create mode 100644 Timeline/ClientApp/src/app/home/OfflineBoard.tsx delete mode 100644 Timeline/ClientApp/src/app/home/TimelineBoardAreaWithUser.tsx delete mode 100644 Timeline/ClientApp/src/app/home/TimelineBoardAreaWithoutUser.tsx (limited to 'Timeline/ClientApp/src') 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 { + 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 ( + + {ownTimelines === 'offline' && joinTimelines === 'offline' ? ( + + { + setOwnTimelines('loading'); + setJoinTimelines('loading'); + }} + /> + + ) : ( + <> + + { + setOwnTimelines('loading'); + }} + /> + + + { + setJoinTimelines('loading'); + }} + /> + + + )} + + ); +}; + +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 ( + + {publicTimelines === 'offline' ? ( + + { + setPublicTimelines('loading'); + }} + /> + + ) : ( + + { + setPublicTimelines('loading'); + }} + /> + + )} + + ); +}; + +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(''); - 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 ( <> @@ -101,7 +47,13 @@ const Home: React.FC = (_) => { placeholder="@crupest" additionalButton={ user != null && ( - ) @@ -111,16 +63,9 @@ const Home: React.FC = (_) => { {(() => { if (user == null) { - return ( - - ); + return ; } else { - return ( - - ); + return ; } })()} @@ -142,7 +87,14 @@ const Home: React.FC = (_) => { 公安备案 42112102000124 - {dialog === 'create' && } + {dialog === 'create' && ( + { + 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 = ({ onReload }) => { + const [timelines, setTimelines] = React.useState([]); + + React.useEffect(() => { + let subscribe = true; + void getAllCachedTimelineNames().then((t) => { + if (subscribe) setTimelines(t); + }); + return () => { + subscribe = false; + }; + }); + + return ( + <> + + 0 + { + onReload(); + e.preventDefault(); + }} + > + 1 + + 2 + + {timelines.map((timeline) => { + const isPersonal = timeline.startsWith('@'); + const url = isPersonal + ? `/users/${timeline.slice(1)}` + : `/timelines/${timeline}`; + return ( +
+ {isPersonal ? ( + + ) : ( + + )} + {timeline} +
+ ); + })} + + ); +}; + +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 = props => { +const TimelineBoard: React.FC = (props) => { const { title, timelines, className } = props; return (
{title != null &&

{title}

} {(() => { - if (timelines == null) { + if (timelines === 'loading') { return (
); + } else if (timelines === 'offline') { + return ( + + ); } 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 = ( - props -) => { - const { t } = useTranslation(); - - return ( - - - - - - - - - ); -}; - -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 = ( - props -) => { - const { publicTimelines } = props; - - return ( - - - - - - ); -}; - -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 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 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>点击重试。', serviceWorker: { availableOffline: 'Timeline 已经缓存在本地,你可以离线使用它。🎉🎉🎉', upgradePrompt: 'App 有新版本!', @@ -24,6 +25,8 @@ const translation: TranslationResource = { allTimeline: '所有的时间线', joinTimeline: '加入的时间线', ownTimeline: '拥有的时间线', + offlinePrompt: + '你好像处于离线状态。以下是一些缓存在本地的时间线。你可以查看它们或者<1>点击重新获取在线信息。', createButton: '创建时间线', createDialog: { title: '创建时间线!', -- cgit v1.2.3