diff --git a/.eslintrc.json b/.eslintrc.json index f4e772a..3c60a16 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -84,8 +84,9 @@ "require-await": "warn", "wrap-iife": ["warn", "inside"], "yoda": "warn", - "no-shadow": "warn", + "no-shadow": "off", "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/no-shadow": "warn", "@typescript-eslint/no-namespace": "off", "@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/no-unused-vars": ["error"], diff --git a/src/app/components/both-menu/BothMenu.tsx b/src/app/components/both-menu/BothMenu.tsx new file mode 100644 index 0000000..a032ce4 --- /dev/null +++ b/src/app/components/both-menu/BothMenu.tsx @@ -0,0 +1,80 @@ +import {AppBar, createStyles, Fab, IconButton, makeStyles, Theme, Toolbar} from '@material-ui/core'; +import React, {memo} from 'react'; +import MoreIcon from '@material-ui/icons/MoreVert'; +import AddIcon from '@material-ui/icons/Add'; +import MoveToInboxIcon from '@material-ui/icons/MoveToInbox'; +import CalendarTodayIcon from '@material-ui/icons/CalendarToday'; +import ListAltIcon from '@material-ui/icons/ListAlt'; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + iconRight: { + marginRight: theme.spacing(2), + }, + appBar: { + top: 'auto', + bottom: 0, + }, + grow: { + flexGrow: 1, + }, + fabButton: { + position: 'absolute', + zIndex: 1, + top: -30, + left: 0, + right: 0, + margin: '0 auto', + }, + }), +); + +const BothMenu: React.FC = () => { + const classes = useStyles(); + + return ( + + + + + + + + + + + +
+ + + + + + + + + ); +}; + +export default memo(BothMenu); diff --git a/src/app/components/both-menu/index.ts b/src/app/components/both-menu/index.ts new file mode 100644 index 0000000..730f41a --- /dev/null +++ b/src/app/components/both-menu/index.ts @@ -0,0 +1 @@ +export {default} from './BothMenu'; diff --git a/src/app/components/page/Page.tsx b/src/app/components/page/Page.tsx index 5b4efd0..4e9868e 100644 --- a/src/app/components/page/Page.tsx +++ b/src/app/components/page/Page.tsx @@ -1,5 +1,5 @@ -import React, {memo} from 'react'; -import {HashRouter, Route, Switch} from 'react-router-dom'; +import React, {Fragment, memo} from 'react'; +import {Route, Switch} from 'react-router-dom'; import mainPageRouter from '_pages/main/routing'; import chaosBoxPageRouter from '_pages/chaos-box/routing'; import calendarPageRouter from '_pages/calendar/routing'; @@ -8,28 +8,32 @@ import projectsPageRouter from '_pages/projects/routing'; import settingsPageRouter from '_pages/settings/routing'; import signInPageRouter from '_pages/sign-in/routing'; import tagsPageRouter from '_pages/tags/routing'; -import NotFoundPage from '_pages/not-found/components/page/Page'; -import TopMenu from '../top-menu/TopMenu'; +import NotFoundPage from '_pages/not-found/components/page'; +import TopMenu from '../top-menu'; import './Page.scss'; +import BothMenu from '../both-menu'; const Page: React.FC = () => { return ( - + - - {mainPageRouter} - {chaosBoxPageRouter} - {calendarPageRouter} - {informationPageRouter} - {projectsPageRouter} - {settingsPageRouter} - {signInPageRouter} - {tagsPageRouter} - - - - - +
+ + {mainPageRouter} + {chaosBoxPageRouter} + {calendarPageRouter} + {informationPageRouter} + {projectsPageRouter} + {settingsPageRouter} + {signInPageRouter} + {tagsPageRouter} + + + + +
+ + ); }; diff --git a/src/app/components/page/index.ts b/src/app/components/page/index.ts new file mode 100644 index 0000000..ff9a33b --- /dev/null +++ b/src/app/components/page/index.ts @@ -0,0 +1 @@ +export {default} from './Page'; diff --git a/src/app/components/top-menu/MenuList.tsx b/src/app/components/top-menu/MenuList.tsx deleted file mode 100644 index ec505c4..0000000 --- a/src/app/components/top-menu/MenuList.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import {List, ListItem as MaterialListItem, ListItemIcon, ListItemText} from '@material-ui/core'; -import React, {memo} from 'react'; -import {Link} from 'react-router-dom'; -import InboxIcon from '@material-ui/icons/MoveToInbox'; -import {ListItem} from '_types/common'; - -type Props = { - list: ListItem[]; -}; - -const MenuList: React.FC = ({list}) => { - return ( - - {list.map(({title, url}) => ( - - - - - - - - - ))} - - ); -}; - -export default memo(MenuList); diff --git a/src/app/components/top-menu/TopMenu.tsx b/src/app/components/top-menu/TopMenu.tsx index 1911c2f..63ccefd 100644 --- a/src/app/components/top-menu/TopMenu.tsx +++ b/src/app/components/top-menu/TopMenu.tsx @@ -1,32 +1,72 @@ import React, {memo} from 'react'; -import {createStyles, makeStyles, Theme} from '@material-ui/core/styles'; +import {useHistory} from 'react-router-dom'; +import {createStyles, makeStyles} from '@material-ui/core/styles'; import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; import Typography from '@material-ui/core/Typography'; +import IconButton from '@material-ui/core/IconButton'; +import ArrowBackIosIcon from '@material-ui/icons/ArrowBackIos'; +import SearchIcon from '@material-ui/icons/Search'; +import {Avatar} from '@material-ui/core'; +import {useParams} from '_hooks/useParams'; +import {PageType} from '_enums/common'; +import {PAGE_TITLE, ROUTES} from '_consts/common'; -const useStyles = makeStyles((theme: Theme) => +const NO_NAME_AVATAR = 'https://d.newsweek.com/en/full/425257/02-10-putin-economy.jpg'; + +const useStyles = makeStyles(() => createStyles({ root: { flexGrow: 1, }, - menuButton: { - marginRight: theme.spacing(2), - }, title: { flexGrow: 1, + display: 'flex', + justifyContent: 'center', }, }), ); const TopMenu: React.FC = () => { const classes = useStyles(); + const {pageType} = useParams(); + const history = useHistory(); + + const handleGoRoot = () => { + history.push(ROUTES.MAIN); + }; + + const title = PAGE_TITLE[pageType]; return (
- - Free your brain + {pageType === PageType.Main && ( + + + + )} + {pageType !== PageType.Main && ( + + + + )} + + + {title} + +
diff --git a/src/app/components/top-menu/index.ts b/src/app/components/top-menu/index.ts new file mode 100644 index 0000000..af53da1 --- /dev/null +++ b/src/app/components/top-menu/index.ts @@ -0,0 +1 @@ +export {default} from './TopMenu'; diff --git a/src/app/index.tsx b/src/app/index.tsx index f1f65a8..f236334 100644 --- a/src/app/index.tsx +++ b/src/app/index.tsx @@ -1,10 +1,13 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import App from './components/page/Page'; +import {HashRouter} from 'react-router-dom'; +import App from './components/page'; ReactDOM.render( - + + + , document.getElementById('root') ); diff --git a/src/core/consts/common.ts b/src/core/consts/common.ts index d5232d2..47b25b2 100644 --- a/src/core/consts/common.ts +++ b/src/core/consts/common.ts @@ -1,3 +1,5 @@ +import {PageType} from '../enums/common'; + export const ROUTES = { MAIN: '/', CHAOS_BOX: '/chaos-box', @@ -8,3 +10,14 @@ export const ROUTES = { SETTINGS: '/settings', SIGN_IN: '/sign-in', }; + +export const PAGE_TITLE = { + [PageType.Main]: 'Free your brain', + [PageType.ChaosBox]: 'Chaos box', + [PageType.Calendar]: 'Calendar', + [PageType.Information]: 'Information', + [PageType.Tags]: 'Tags', + [PageType.Projects]: 'Projects', + [PageType.Settings]: 'Settings', + [PageType.SigIn]: 'SigIn', +}; diff --git a/src/core/enums/common.ts b/src/core/enums/common.ts new file mode 100644 index 0000000..59d1bb2 --- /dev/null +++ b/src/core/enums/common.ts @@ -0,0 +1,21 @@ +export const enum TaskStatus { + Progress = 'progress', + Removed = 'removed', + Done = 'done', +} + +export const enum FolderType { + Project = 'project', + Information = 'information', +} + +export enum PageType { + Main = '', + ChaosBox = 'chaos-box', + Projects = 'projects', + Information = 'information', + Tags = 'tags', + Calendar = 'calendar', + Settings = 'settings', + SigIn = 'sign-in', +} diff --git a/src/core/hooks/useParams.ts b/src/core/hooks/useParams.ts new file mode 100644 index 0000000..f2149cc --- /dev/null +++ b/src/core/hooks/useParams.ts @@ -0,0 +1,25 @@ +import {useMemo} from 'react'; +import {useLocation, useParams as useReactParams} from 'react-router-dom'; +import {PageType} from '../enums/common'; +import {getPageType} from '../utils/common'; + +type ParamsParser = (value?: string) => T; +export type ParamsParsers = Partial<{[K in keyof T]: ParamsParser}>; + +export function useParams(paramParsers: ParamsParsers = {}) { + const params = useReactParams>(); + const {pathname} = useLocation(); + + return useMemo(() => { + return Object.keys(paramParsers).reduce((memo, key) => { + const parser = paramParsers[key]; + + return { + ...memo, + [key]: parser?.(params[key]), + }; + }, { + pageType: getPageType(pathname), + } as T & {pageType: PageType}); + }, [params, paramParsers, pathname]); +} diff --git a/src/core/hooks/useQuery.ts b/src/core/hooks/useQuery.ts index 3b93622..99b4dbb 100644 --- a/src/core/hooks/useQuery.ts +++ b/src/core/hooks/useQuery.ts @@ -49,16 +49,14 @@ export function booleanParser(defaultValue?: boolean) { // Array parser (должен уметь с enum) -export function useQuery(): ParsedUrlQuery; -export function useQuery(queryParsers: QueryParsers): Partial; export function useQuery( - queryParsers?: QueryParsers + queryParsers: QueryParsers ): ParsedUrlQuery | Partial { const {search} = useLocation(); return useMemo(() => { const query = parse(search.slice(1)); - return queryParsers ? Object.keys(query).reduce>((memo, key) => { + return queryParsers ? Object.keys(queryParsers).reduce((memo, key) => { if (key in queryParsers) { const parser = queryParsers[key]; return { @@ -67,6 +65,6 @@ export function useQuery( }; } return memo; - }, {}) : query; + }, {} as T) : query; }, [search, queryParsers]); } diff --git a/src/core/referers/common.ts b/src/core/referers/common.ts new file mode 100644 index 0000000..e96ae14 --- /dev/null +++ b/src/core/referers/common.ts @@ -0,0 +1,5 @@ +import {PageType} from '../enums/common'; + +export const isPageType = (value?: string): value is PageType => ( + Object.values(PageType).some(pageType => pageType === value) +); diff --git a/src/core/services/AuthService.ts b/src/core/services/AuthService.ts deleted file mode 100644 index a62b3d0..0000000 --- a/src/core/services/AuthService.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {createAdapter} from '@most/adapter'; -import {state} from 'fp-ts/lib/State'; - -export namespace AuthService { - type State = { - isAuth: boolean; - }; - - export const initState: State = { - isAuth: false, - }; - - const [changeState, stream$] = createAdapter(); - - export const handleChangeAuth = (isAuth: boolean): void => changeState({ - ...state, - isAuth, - }); - export const state$ = stream$; -} diff --git a/src/core/services/LocalStorageService.ts b/src/core/services/LocalStorageService.ts new file mode 100644 index 0000000..ddc19b1 --- /dev/null +++ b/src/core/services/LocalStorageService.ts @@ -0,0 +1,21 @@ +export const makeLocalStorageService = (init: T, stateName: string) => { + if (!localStorage.getItem(stateName)) { + localStorage.setItem(stateName, JSON.stringify(init)); + } + + return { + set: (updatedState: T) => { + localStorage.setItem(stateName, JSON.stringify(updatedState)); + return updatedState; + }, + get: (): T => { + const stringValue = localStorage.getItem(stateName) || ''; + + try { + return JSON.parse(stringValue); + } catch (e) { + return init; + } + }, + }; +}; diff --git a/src/core/services/TasksService.ts b/src/core/services/TasksService.ts new file mode 100644 index 0000000..f9d94a2 --- /dev/null +++ b/src/core/services/TasksService.ts @@ -0,0 +1,36 @@ +import {v4} from 'uuid'; +import {TaskStatus} from '../enums/common'; +import {Task} from '../types/common'; +import {createService} from '../utils/createService'; +import {makeLocalStorageService} from './LocalStorageService'; + +const TASK_STORAGE_NAME = 'FYB_TASK_STORAGE'; + +const INIT_TASKS: Task[] = [ + { + id: v4(), + status: TaskStatus.Progress, + created_at: '2021-03-01T13:00+03:00', + title: 'Первая таска', + body: 'Описание таски', + }, + { + id: v4(), + status: TaskStatus.Progress, + created_at: '2021-03-01T13:00+03:00', + title: 'Вторая таска', + body: 'Описание таски', + }, + { + id: v4(), + status: TaskStatus.Progress, + created_at: '2021-03-01T13:00+03:00', + title: 'Третья таска', + body: 'Описание таски', + }, +]; + +const taskListService = makeLocalStorageService(INIT_TASKS, TASK_STORAGE_NAME); + +export const tasksService = createService(taskListService.get(), { +}); diff --git a/src/core/services/service1.ts b/src/core/services/service1.ts deleted file mode 100644 index 4ba4894..0000000 --- a/src/core/services/service1.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {createAdapter} from '@most/adapter'; - -const arr: Array = []; -let inc = 0; - -const [handler, stream$] = createAdapter>(); - -export const list$ = stream$; -setInterval(() => { - arr.push(inc += 1); - handler(arr); -}, 500); - diff --git a/src/core/types/common.ts b/src/core/types/common.ts index a489251..105d324 100644 --- a/src/core/types/common.ts +++ b/src/core/types/common.ts @@ -1,4 +1,49 @@ -export type ListItem = { - title: string; - url: string; +import {FolderType, TaskStatus} from '../enums/common'; + +export type Task = { + /** + * Идентификатор + */ + id: string; + title?: string; + body?: string; + created_at: string; + start_at?: string; + end_at?: string; + /** + * Контекст выполнения, теги + */ + tags?: string[]; + /** + * Папка, проект, список + */ + folder?: string; + status: TaskStatus; +}; + +export type Folder = { + /** + * Идентификатор + */ + id: string; + name: string; + type: FolderType; + /** + * Папка, проект + */ + folder?: string; + removed?: boolean; +}; + +export type Tag = { + /** + * Идентификатор + */ + id: string; + name: string; + /** + * Цвет тега в формате #FFFFFF + */ + color?: string; + removed?: boolean; }; diff --git a/src/core/utils/common.ts b/src/core/utils/common.ts index 6892327..adbac1f 100644 --- a/src/core/utils/common.ts +++ b/src/core/utils/common.ts @@ -1 +1,9 @@ +import {PageType} from '../enums/common'; +import {isPageType} from '../referers/common'; + export const numberToString = (num: number): string => num.toString(); + +export const getPageType = (pathname?: string): PageType => { + const path = pathname?.startsWith('/') ? pathname.slice(1) : pathname ?? ''; + return isPageType(path) ? path : PageType.Main; +}; diff --git a/src/pages/chaos-box/components/page/index.ts b/src/pages/chaos-box/components/page/index.ts index cb97cc3..ff9a33b 100644 --- a/src/pages/chaos-box/components/page/index.ts +++ b/src/pages/chaos-box/components/page/index.ts @@ -1 +1 @@ -export {default as Page} from './Page'; +export {default} from './Page'; diff --git a/src/pages/chaos-box/routing.tsx b/src/pages/chaos-box/routing.tsx index 08438e2..6a15a05 100644 --- a/src/pages/chaos-box/routing.tsx +++ b/src/pages/chaos-box/routing.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {Route} from 'react-router-dom'; import {ROUTES} from '_consts/common'; -import Page from './components/page/Page'; +import Page from './components/page'; export default ( diff --git a/src/pages/information/components/page/index.ts b/src/pages/information/components/page/index.ts index cb97cc3..ff9a33b 100644 --- a/src/pages/information/components/page/index.ts +++ b/src/pages/information/components/page/index.ts @@ -1 +1 @@ -export {default as Page} from './Page'; +export {default} from './Page'; diff --git a/src/pages/information/routing.tsx b/src/pages/information/routing.tsx index 6b49337..3cd5135 100644 --- a/src/pages/information/routing.tsx +++ b/src/pages/information/routing.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {Route} from 'react-router-dom'; import {ROUTES} from '_consts/common'; -import Page from './components/page/Page'; +import Page from './components/page'; export default ( diff --git a/src/pages/main/components/component-stream/ComponentStream.tsx b/src/pages/main/components/component-stream/ComponentStream.tsx deleted file mode 100644 index 3aa4974..0000000 --- a/src/pages/main/components/component-stream/ComponentStream.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React, {FC, memo} from 'react'; -import {chain, fromPromise, map} from '@most/core'; -import {pipe} from 'fp-ts/lib/pipeable'; -import {useStream} from '_utils/useStream'; -import {list$} from '_services/service1'; - -const promise1: (id: number) => Promise = (id: number) => new Promise(res => { - setTimeout(() => res(`${id} 123123`), 6000); -}); - -const getStreamFromPromise = (id: number) => fromPromise(promise1(id)); - -const ComponentStream: FC = () => { - const data = useStream( - pipe( - list$, - map(arr => { - return arr.length; - }), - chain(id => getStreamFromPromise(id)) - ), - '' - ); - - return ( -
-
{data}
-
- ); -}; - -export default memo(ComponentStream); diff --git a/src/pages/main/components/page/Page.tsx b/src/pages/main/components/page/Page.tsx index 51826c7..0837b40 100644 --- a/src/pages/main/components/page/Page.tsx +++ b/src/pages/main/components/page/Page.tsx @@ -1,41 +1,26 @@ import React, {memo} from 'react'; -import {AuthService} from '_services/AuthService'; import {useStream} from '_utils/useStream'; -import {createService} from '_utils/createService'; -import ComponentStream from '../component-stream/ComponentStream'; - -const service = createService(1, { - changeWithStr: (state: number, val: string) => { - const parsedNumber = Number(val); - if (Number.isNaN(val)) { - return state; - } - - return parsedNumber; - }, - add: (state: number) => { - return state + 1; - }, - sub: (state: number) => { - return state - 1; - } -}); +import {tasksService} from '_services/TasksService'; +import {List, ListItem, ListItemIcon, ListItemText} from '@material-ui/core'; +import InboxIcon from '@material-ui/icons/Inbox'; const MainPage: React.FC = () => { - const {isAuth} = useStream(AuthService.state$, AuthService.initState); - const toggle = () => AuthService.handleChangeAuth(!isAuth); - const data = useStream(service.stream$, 0); + const taskList = useStream(tasksService.stream$, []); return ( -
- Main Page - Auth: {isAuth ? 'yes' : 'no'} - - -
{data}
- - -
+ + {taskList.map(task => ( + + + + + + + ))} + ); }; diff --git a/src/pages/not-found/components/page/index.ts b/src/pages/not-found/components/page/index.ts new file mode 100644 index 0000000..ff9a33b --- /dev/null +++ b/src/pages/not-found/components/page/index.ts @@ -0,0 +1 @@ +export {default} from './Page'; diff --git a/src/pages/projects/components/page/index.ts b/src/pages/projects/components/page/index.ts index cb97cc3..ff9a33b 100644 --- a/src/pages/projects/components/page/index.ts +++ b/src/pages/projects/components/page/index.ts @@ -1 +1 @@ -export {default as Page} from './Page'; +export {default} from './Page'; diff --git a/src/pages/projects/routing.tsx b/src/pages/projects/routing.tsx index 76a6c31..055d811 100644 --- a/src/pages/projects/routing.tsx +++ b/src/pages/projects/routing.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {Route} from 'react-router-dom'; import {ROUTES} from '_consts/common'; -import Page from './components/page/Page'; +import Page from './components/page'; export default ( diff --git a/src/pages/settings/components/page/index.ts b/src/pages/settings/components/page/index.ts index cb97cc3..ff9a33b 100644 --- a/src/pages/settings/components/page/index.ts +++ b/src/pages/settings/components/page/index.ts @@ -1 +1 @@ -export {default as Page} from './Page'; +export {default} from './Page'; diff --git a/src/pages/settings/routing.tsx b/src/pages/settings/routing.tsx index f899fca..3b83e2d 100644 --- a/src/pages/settings/routing.tsx +++ b/src/pages/settings/routing.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {Route} from 'react-router-dom'; import {ROUTES} from '_consts/common'; -import Page from './components/page/Page'; +import Page from './components/page'; export default ( diff --git a/src/pages/sign-in/components/page/index.ts b/src/pages/sign-in/components/page/index.ts index cb97cc3..ff9a33b 100644 --- a/src/pages/sign-in/components/page/index.ts +++ b/src/pages/sign-in/components/page/index.ts @@ -1 +1 @@ -export {default as Page} from './Page'; +export {default} from './Page'; diff --git a/src/pages/sign-in/routing.tsx b/src/pages/sign-in/routing.tsx index ce6ad55..d819779 100644 --- a/src/pages/sign-in/routing.tsx +++ b/src/pages/sign-in/routing.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {Route} from 'react-router-dom'; import {ROUTES} from '_consts/common'; -import Page from './components/page/Page'; +import Page from './components/page'; export default ( diff --git a/src/pages/tags/components/page/index.ts b/src/pages/tags/components/page/index.ts index cb97cc3..ff9a33b 100644 --- a/src/pages/tags/components/page/index.ts +++ b/src/pages/tags/components/page/index.ts @@ -1 +1 @@ -export {default as Page} from './Page'; +export {default} from './Page'; diff --git a/src/pages/tags/routing.tsx b/src/pages/tags/routing.tsx index 4a59c96..0828b73 100644 --- a/src/pages/tags/routing.tsx +++ b/src/pages/tags/routing.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {Route} from 'react-router-dom'; import {ROUTES} from '_consts/common'; -import Page from './components/page/Page'; +import Page from './components/page'; export default ( diff --git a/tsconfig.json b/tsconfig.json index 12927bd..cf587db 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,6 +30,7 @@ "_utils/*": ["./src/core/utils/*"], "_enums/*": ["./src/core/enums/*"], "_pages/*": ["./src/pages/*"], + "_referers/*": ["./src/referers/*"], } }, "include": [ diff --git a/webpack.config.js b/webpack.config.js index b246158..1aa9ba8 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -5,6 +5,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const webpack = require('webpack'); module.exports = { + mode: 'development', entry: { app: { import: './src/app/index.tsx', @@ -38,6 +39,7 @@ module.exports = { _utils: path.resolve(__dirname, 'src/core/utils/'), _enums: path.resolve(__dirname, 'src/core/enums/'), _pages: path.resolve(__dirname, 'src/pages/'), + _referers: path.resolve(__dirname, 'src/referers/'), } }, optimization: {