From cf6cfe87b46a2a3eb2913209092ab4c5639e75c3 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 11 Jun 2020 17:27:15 +0800 Subject: feat(front): Service worker is coming! --- Timeline/ClientApp/src/app/home/Home.tsx | 155 +++++++++++++++++++++ Timeline/ClientApp/src/app/home/TimelineBoard.tsx | 54 +++++++ .../src/app/home/TimelineBoardAreaWithUser.tsx | 36 +++++ .../src/app/home/TimelineBoardAreaWithoutUser.tsx | 26 ++++ .../src/app/home/TimelineCreateDialog.tsx | 60 ++++++++ Timeline/ClientApp/src/app/home/home.sass | 13 ++ 6 files changed, 344 insertions(+) create mode 100644 Timeline/ClientApp/src/app/home/Home.tsx create mode 100644 Timeline/ClientApp/src/app/home/TimelineBoard.tsx create mode 100644 Timeline/ClientApp/src/app/home/TimelineBoardAreaWithUser.tsx create mode 100644 Timeline/ClientApp/src/app/home/TimelineBoardAreaWithoutUser.tsx create mode 100644 Timeline/ClientApp/src/app/home/TimelineCreateDialog.tsx create mode 100644 Timeline/ClientApp/src/app/home/home.sass (limited to 'Timeline/ClientApp/src/app/home') diff --git a/Timeline/ClientApp/src/app/home/Home.tsx b/Timeline/ClientApp/src/app/home/Home.tsx new file mode 100644 index 00000000..775a1a87 --- /dev/null +++ b/Timeline/ClientApp/src/app/home/Home.tsx @@ -0,0 +1,155 @@ +import React from 'react'; +import { useHistory } from 'react-router'; +import { Row, Container, Button, Col } from 'reactstrap'; +import { useTranslation } from 'react-i18next'; +import axios from 'axios'; + +import { apiBaseUrl } from '../config'; +import { useUser } from '../data/user'; +import { TimelineInfo } from '../data/timeline'; + +import AppBar from '../common/AppBar'; +import SearchInput from '../common/SearchInput'; +import TimelineBoardAreaWithoutUser from './TimelineBoardAreaWithoutUser'; +import TimelineBoardAreaWithUser from './TimelineBoardAreaWithUser'; +import TimelineCreateDialog from './TimelineCreateDialog'; + +const Home: React.FC = (_) => { + const history = useHistory(); + + const { t } = useTranslation(); + + const user = useUser(); + + 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 axios + .get(`${apiBaseUrl}/timelines?visibility=public`) + .then((res) => { + if (subscribe) { + setPublicTimelines(res.data); + } + }); + } else { + setPublicTimelines(undefined); + void axios + .get( + `${apiBaseUrl}/timelines?relate=${user.username}&relateType=own` + ) + .then((res) => { + if (subscribe) { + setOwnTimelines(res.data); + } + }); + void axios + .get( + `${apiBaseUrl}/timelines?relate=${user.username}&relateType=join` + ) + .then((res) => { + if (subscribe) { + setJoinTimelines(res.data); + } + }); + } + return () => { + subscribe = false; + }; + }, [user]); + + const [dialog, setDialog] = React.useState<'create' | null>(null); + + const goto = React.useCallback((): void => { + if (navText === '') { + history.push('users/crupest'); + } else if (navText.startsWith('@')) { + history.push(`users/${navText.slice(1)}`); + } else { + history.push(`timelines/${navText}`); + } + }, [navText, history]); + + const openCreateDialog = React.useCallback(() => { + setDialog('create'); + }, []); + + const closeDialog = React.useCallback(() => { + setDialog(null); + }, []); + + return ( + <> + + + + + + {t('home.createButton')} + + ) + } + /> + + + {(() => { + if (user == null) { + return ( + + ); + } else { + return ( + + ); + } + })()} + + + {dialog === 'create' && } + + ); +}; + +export default Home; diff --git a/Timeline/ClientApp/src/app/home/TimelineBoard.tsx b/Timeline/ClientApp/src/app/home/TimelineBoard.tsx new file mode 100644 index 00000000..ca77ab43 --- /dev/null +++ b/Timeline/ClientApp/src/app/home/TimelineBoard.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import clsx from 'clsx'; +import { Link } from 'react-router-dom'; +import { Spinner } from 'reactstrap'; + +import { TimelineInfo } from '../data/timeline'; + +import TimelineLogo from '../common/TimelineLogo'; +import UserTimelineLogo from '../common/UserTimelineLogo'; + +export interface TimelineBoardProps { + title?: string; + timelines?: TimelineInfo[]; + className?: string; +} + +const TimelineBoard: React.FC = props => { + const { title, timelines, className } = props; + + return ( +
+ {title != null &&

{title}

} + {(() => { + if (timelines == null) { + return ( +
+ +
+ ); + } else { + return timelines.map(timeline => { + const { name } = timeline; + const isPersonal = name.startsWith('@'); + const url = isPersonal + ? `/users/${timeline.owner.username}` + : `/timelines/${name}`; + return ( +
+ {isPersonal ? ( + + ) : ( + + )} + {name} +
+ ); + }); + } + })()} +
+ ); +}; + +export default TimelineBoard; diff --git a/Timeline/ClientApp/src/app/home/TimelineBoardAreaWithUser.tsx b/Timeline/ClientApp/src/app/home/TimelineBoardAreaWithUser.tsx new file mode 100644 index 00000000..ea1c6beb --- /dev/null +++ b/Timeline/ClientApp/src/app/home/TimelineBoardAreaWithUser.tsx @@ -0,0 +1,36 @@ +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 new file mode 100644 index 00000000..c79558b6 --- /dev/null +++ b/Timeline/ClientApp/src/app/home/TimelineBoardAreaWithoutUser.tsx @@ -0,0 +1,26 @@ +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/home/TimelineCreateDialog.tsx b/Timeline/ClientApp/src/app/home/TimelineCreateDialog.tsx new file mode 100644 index 00000000..925c6c76 --- /dev/null +++ b/Timeline/ClientApp/src/app/home/TimelineCreateDialog.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { useHistory } from 'react-router'; +import axios from 'axios'; + +import { apiBaseUrl } from '../config'; +import { useUserLoggedIn } from '../data/user'; +import { validateTimelineName } from '../data/timeline'; + +import OperationDialog from '../common/OperationDialog'; + +interface TimelineCreateDialogProps { + open: boolean; + close: () => void; +} + +const TimelineCreateDialog: React.FC = (props) => { + const history = useHistory(); + const user = useUserLoggedIn(); + + let nameSaved: string; + + return ( + { + if (name.length === 0) { + return 'home.createDialog.noEmpty'; + } else if (name.length > 26) { + return 'home.createDialog.tooLong'; + } else if (!validateTimelineName(name)) { + return 'home.createDialog.badFormat'; + } else { + return null; + } + }, + }, + ]} + onProcess={([name]) => { + nameSaved = name as string; + return axios.post(`${apiBaseUrl}/timelines?token=${user.token}`, { + name, + }); + }} + onSuccessAndClose={() => { + history.push(`timelines/${nameSaved}`); + }} + failurePrompt={(e) => `${e as string}`} + /> + ); +}; + +export default TimelineCreateDialog; diff --git a/Timeline/ClientApp/src/app/home/home.sass b/Timeline/ClientApp/src/app/home/home.sass new file mode 100644 index 00000000..f5d6ffc3 --- /dev/null +++ b/Timeline/ClientApp/src/app/home/home.sass @@ -0,0 +1,13 @@ +.timeline-board-item + font-size: 1.1em + @extend .my-2 + .icon + height: 1.3em + @extend .mr-2 + +.timeline-board + @extend .cru-card + @extend .d-flex + @extend .flex-column + @extend .p-3 + min-height: 200px -- cgit v1.2.3