From b897f6a97d2bfc5cc6d7e5c63a880fd8f26073f5 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sun, 10 Jul 2022 00:08:26 +0500 Subject: [PATCH] add loader, local storage client data --- src/components/Loader.ts | 5 +++ src/components/table/Table.ts | 2 +- src/components/toolbar/Toolbar.ts | 4 +- src/core/Clients.ts | 27 ++++++++++++ src/core/ExcelComponent.ts | 16 +++---- src/core/StateProcessor.ts | 16 +++++++ src/core/dom.ts | 6 +-- src/core/parse.ts | 12 ----- src/core/routes/router.ts | 22 ++++++--- src/core/utils.ts | 20 +++++++-- src/{core/Page.ts => pages/AbstractPage.ts} | 4 +- src/pages/DashboardPage.ts | 49 +++++++++++++++++++-- src/pages/ExcelPage.ts | 33 ++++++++------ src/pages/dashboard.functions.ts | 44 ------------------ src/styles/components/loader.scss | 36 +++++++++++++++ src/styles/components/table.scss | 3 ++ src/styles/index.scss | 3 +- 17 files changed, 202 insertions(+), 100 deletions(-) create mode 100644 src/components/Loader.ts create mode 100644 src/core/Clients.ts create mode 100644 src/core/StateProcessor.ts delete mode 100644 src/core/parse.ts rename src/{core/Page.ts => pages/AbstractPage.ts} (56%) delete mode 100644 src/pages/dashboard.functions.ts create mode 100644 src/styles/components/loader.scss diff --git a/src/components/Loader.ts b/src/components/Loader.ts new file mode 100644 index 0000000..fef58f0 --- /dev/null +++ b/src/components/Loader.ts @@ -0,0 +1,5 @@ +import { $, Dom } from 'core/dom'; + +export function Loader(): Dom { + return $.create('div', 'loader').html('
'); +} diff --git a/src/components/table/Table.ts b/src/components/table/Table.ts index 48074d4..af18097 100644 --- a/src/components/table/Table.ts +++ b/src/components/table/Table.ts @@ -1,6 +1,6 @@ import { $, Dom } from 'core/dom'; import { ExcelComponent } from 'core/ExcelComponent'; -import { parse } from 'core/parse'; +import { parse } from 'core/utils'; import { changeCurrentStyles } from 'redux/actions'; import * as actions from 'redux/actions'; import { initialStyleState } from '../../constants'; diff --git a/src/components/toolbar/Toolbar.ts b/src/components/toolbar/Toolbar.ts index a009771..1126053 100644 --- a/src/components/toolbar/Toolbar.ts +++ b/src/components/toolbar/Toolbar.ts @@ -9,8 +9,8 @@ export class Toolbar extends ExcelStateComponent { constructor($root: Dom, options: OptionsType) { super($root, { - name: 'Toolbar', listeners: ['click'], + name: 'Toolbar', subscribe: ['currentStyles'], ...options, }); @@ -25,7 +25,7 @@ export class Toolbar extends ExcelStateComponent { get toolbarState() { return { ...initialStyleState, - ...this.store.getState().stylesState['0:0'], + ...this.store?.getState()?.stylesState?.['0:0'], }; } diff --git a/src/core/Clients.ts b/src/core/Clients.ts new file mode 100644 index 0000000..0ccb6c5 --- /dev/null +++ b/src/core/Clients.ts @@ -0,0 +1,27 @@ +import { storage } from 'core/utils'; +import { storageName } from 'pages/ExcelPage'; +import { StateType } from 'redux/types'; +import { getNormalizeInitialState } from '../constants'; + +export class LocalStorageClient { + private name: string; + + constructor(name: string) { + this.name = storageName(name); + } + + save(state: StateType): Promise { + storage(this.name, state); + return Promise.resolve(); + } + + get() { + const data = storage(this.name) || getNormalizeInitialState(this.name); + + return new Promise(resolve => { + setTimeout(() => { + resolve(data); + }, 1500); + }); + } +} diff --git a/src/core/ExcelComponent.ts b/src/core/ExcelComponent.ts index 8f9deac..9934005 100644 --- a/src/core/ExcelComponent.ts +++ b/src/core/ExcelComponent.ts @@ -19,10 +19,10 @@ export type OptionsType = { }; export abstract class ExcelComponent extends DomListener implements ExcelComponentClass { - name: string; - emitter: Emitter; - store: Store; - subscribe: string[]; + private name: string | undefined; + private emitter: Emitter | undefined; + private store: Store | undefined; + private subscribe: string[] | undefined; private unsubscribers: ((args?: any) => any)[]; constructor($root: Dom, options?: OptionsType) { @@ -45,16 +45,16 @@ export abstract class ExcelComponent extends DomListener implements ExcelCompone } $emit(event: string, args?: any): void { - this.emitter.emit(event, args); + this.emitter?.emit(event, args); } $on(event: string, callback: (args: any) => any) { - const unsub = this.emitter.subscribe(event, callback); - this.unsubscribers.push(unsub); + const unsub = this.emitter?.subscribe(event, callback); + unsub && this.unsubscribers.push(unsub); } $dispatch(action: ActionType) { - this.store.dispatch(action); + this.store?.dispatch(action); } storeChanged(args?: any) { diff --git a/src/core/StateProcessor.ts b/src/core/StateProcessor.ts new file mode 100644 index 0000000..64a859d --- /dev/null +++ b/src/core/StateProcessor.ts @@ -0,0 +1,16 @@ +import { debounce } from 'core/utils'; + +export class StateProcessor { + constructor(client, dalay = 300) { + this.client = client; + this.listen = debounce(this.listen.bind(this), dalay); + } + + listen(state) { + this.client.save(state); + } + + get() { + return this.client.get(); + } +} diff --git a/src/core/dom.ts b/src/core/dom.ts index 9eeddae..224825f 100644 --- a/src/core/dom.ts +++ b/src/core/dom.ts @@ -9,7 +9,7 @@ export interface DomClass { } export class Dom implements DomClass { - $el: HTMLElement; + $el: HTMLElement | null; constructor(selector: SelectorType) { this.$el = typeof selector === 'string' @@ -31,8 +31,8 @@ export class Dom implements DomClass { } get text() { - if (this.$el.closest('input')) return (this.$el as HTMLInputElement).value; - return this.$el.textContent; + if (this.$el?.closest('input')) return (this.$el as HTMLInputElement).value; + return this.$el?.textContent; } clear() { diff --git a/src/core/parse.ts b/src/core/parse.ts deleted file mode 100644 index 3b95b08..0000000 --- a/src/core/parse.ts +++ /dev/null @@ -1,12 +0,0 @@ -export function parse(value: string) { - if (value.startsWith('=')) { - try { - // eslint-disable-next-line no-eval - return eval(value.slice(1)); - } catch (e) { - return value; - } - } - - return value; -} diff --git a/src/core/routes/router.ts b/src/core/routes/router.ts index 8260d59..641bdbb 100644 --- a/src/core/routes/router.ts +++ b/src/core/routes/router.ts @@ -1,17 +1,19 @@ +import { $, Dom, SelectorType } from 'core/dom'; +import { ActiveRoute } from 'core/routes/ActiveRoute'; +import { Loader } from 'components/Loader'; import { DashboardPage } from 'pages/DashboardPage'; import { ExcelPage } from 'pages/ExcelPage'; -import { $, Dom, SelectorType } from 'core/dom'; -import { ActiveRoute } from './ActiveRoute'; type RoutesType = { - dashboard: DashboardPage - excel: ExcelPage + dashboard: typeof DashboardPage + excel: typeof ExcelPage }; export class Router { private $placeholder: Dom; private routes: RoutesType; private page: DashboardPage | ExcelPage | null; + private loader: Dom; constructor(selector: SelectorType, routes: RoutesType) { if (!selector) throw new Error('Selector not provided'); @@ -19,6 +21,7 @@ export class Router { this.$placeholder = $(selector); this.routes = routes; this.page = null; + this.loader = Loader(); this.changePageHandler = this.changePageHandler.bind(this); @@ -26,12 +29,13 @@ export class Router { } init() { + // eslint-disable-next-line @typescript-eslint/no-misused-promises window.addEventListener('hashchange', this.changePageHandler); this.changePageHandler(); } - changePageHandler() { - this.$placeholder.clear(); + async changePageHandler() { + this.$placeholder.clear().append(this.loader); this.page?.destroy(); let Page; @@ -49,11 +53,15 @@ export class Router { // @ts-ignore this.page = new Page(ActiveRoute.param); - this.$placeholder.append(this.page?.getRoot()); + const root = await this.page?.getRoot(); + + this.$placeholder.clear().append(root); + this.page?.afterRender(); } destroy() { + // eslint-disable-next-line @typescript-eslint/no-misused-promises window.removeEventListener('hashchange', this.changePageHandler); } } diff --git a/src/core/utils.ts b/src/core/utils.ts index 402e7bc..e9aefd6 100644 --- a/src/core/utils.ts +++ b/src/core/utils.ts @@ -6,12 +6,11 @@ export function capitalize(string: string): string { export function storage(key: string, data: any = null): any { if (!data) { - console.log('RETURN KEY', key, data); - return JSON.parse(localStorage.getItem(key)); + const localData = localStorage.getItem(key); + return localData ? JSON.parse(localData) : false; } localStorage.setItem(key, JSON.stringify(data)); - console.log('SET ITEM', key, data); return true; } @@ -27,7 +26,7 @@ export function isEqual(a: any, b: any) { export function debounce(fn: (fnArgs?: any) => void, wait: number) { let timeout: NodeJS.Timeout; - return function (...args: any[]) { + return function (...args: any) { const later = () => { clearTimeout(timeout); fn.apply(this, args); @@ -37,3 +36,16 @@ export function debounce(fn: (fnArgs?: any) => void, wait: number) { timeout = setTimeout(later, wait); }; } + +export function parse(value: string) { + if (value.startsWith('=')) { + try { + // eslint-disable-next-line no-eval + return eval(value.slice(1)); + } catch (e) { + return value; + } + } + + return value; +} diff --git a/src/core/Page.ts b/src/pages/AbstractPage.ts similarity index 56% rename from src/core/Page.ts rename to src/pages/AbstractPage.ts index 2ad73fd..8565c75 100644 --- a/src/core/Page.ts +++ b/src/pages/AbstractPage.ts @@ -1,8 +1,8 @@ -export abstract class Page { +export abstract class AbstractPage { params: any; constructor(params: any) { - this.params = params; + this.params = params || Date.now().toString(); } getRoot() { diff --git a/src/pages/DashboardPage.ts b/src/pages/DashboardPage.ts index 847e1a9..7ee81e3 100644 --- a/src/pages/DashboardPage.ts +++ b/src/pages/DashboardPage.ts @@ -1,8 +1,8 @@ -import { Page } from 'core/Page'; import { $ } from 'core/dom'; -import { createRecordsTable } from 'pages/dashboard.functions'; +import { storage } from 'core/utils'; +import { AbstractPage } from 'pages/AbstractPage'; -export class DashboardPage extends Page { +export class DashboardPage extends AbstractPage { getRoot() { const id = Date.now().toString(); @@ -23,3 +23,46 @@ export class DashboardPage extends Page { ); } } + +function toHtml(key: string) { + const params = +key.split(':')[1]; + const state = storage(key); + const link = `#excel/${params}`; + const date = new Date(+state.openDate); + + return ` +
  • + ${state.title} + ${date.toLocaleDateString()} ${date.toLocaleTimeString()} +
  • + `; +} + +export function createRecordsTable() { + const keys = getAllKeys(); + if (!keys.length) return '

    Пока не создали ни одной таблицы

    '; + + return ` +
    + Название + Дата открытия +
    + + + `; +} + +function getAllKeys() { + const keys = []; + + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (!key?.includes('excel')) continue; + + keys.push(key); + } + + return keys; +} diff --git a/src/pages/ExcelPage.ts b/src/pages/ExcelPage.ts index 838d098..07a34ae 100644 --- a/src/pages/ExcelPage.ts +++ b/src/pages/ExcelPage.ts @@ -3,30 +3,36 @@ import { Formula } from 'components/formula/Formula'; import { Header } from 'components/header/Header'; import { Table } from 'components/table/Table'; import { Toolbar } from 'components/toolbar/Toolbar'; +import { LocalStorageClient } from 'core/Clients'; +import { StateProcessor } from 'core/StateProcessor'; import { Store } from 'core/store/createStore'; -import { Page } from 'core/Page'; -import { debounce, storage } from 'core/utils'; +import { AbstractPage } from 'pages/AbstractPage'; import { rootReducer } from 'redux/rootReducer'; -import { StateType } from 'redux/types'; -import { getNormalizeInitialState } from '../constants'; +import { SubscribeType } from 'redux/types'; export function storageName(param: string) { return `excel:${param}`; } -export class ExcelPage extends Page { +export class ExcelPage extends AbstractPage { private excel: Excel; + private storeSub: SubscribeType | null; + private processor: StateProcessor; - getRoot() { - const params = this.params[1] ? this.params[1] : Date.now().toString(); - const normalizeState = storage(storageName(params)) || getNormalizeInitialState(params); + constructor(props: any) { + super(props); + + this.storeSub = null; + this.processor = new StateProcessor( + new LocalStorageClient(this.params[1]), + ); + } + + async getRoot() { + const normalizeState = await this.processor.get(); const store = new Store(rootReducer, normalizeState); - const stateListener = debounce((state: StateType) => { - storage(storageName(params), state); - }, 300); - - store.subscribe(stateListener); + this.storeSub = store.subscribe(this.processor.listen); this.excel = new Excel({ components: [Header, Toolbar, Formula, Table], @@ -42,5 +48,6 @@ export class ExcelPage extends Page { destroy() { this.excel.destroy(); + this.storeSub?.unsubscribe(); } } diff --git a/src/pages/dashboard.functions.ts b/src/pages/dashboard.functions.ts deleted file mode 100644 index 3e00e21..0000000 --- a/src/pages/dashboard.functions.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { storage } from 'core/utils'; - -function toHtml(key: string) { - const params = +key.split(':')[1]; - const state = storage(key); - const link = `#excel/${params}`; - const date = new Date(+state.openDate); - - return ` -
  • - ${state.title} - ${date.toLocaleDateString()} ${date.toLocaleTimeString()} -
  • - `; -} - -export function createRecordsTable() { - const keys = getAllKeys(); - if (!keys.length) return '

    Пока не создали ни одной таблицы

    '; - - return ` -
    - Название - Дата открытия -
    - - - `; -} - -function getAllKeys() { - const keys = []; - - for (let i = 0; i < localStorage.length; i++) { - const key = localStorage.key(i); - if (!key.includes('excel')) continue; - - keys.push(key); - } - - return keys; -} diff --git a/src/styles/components/loader.scss b/src/styles/components/loader.scss new file mode 100644 index 0000000..03b1fe7 --- /dev/null +++ b/src/styles/components/loader.scss @@ -0,0 +1,36 @@ +.loader { + width: 100vw; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; +} + +.lds-circle { + display: inline-block; + transform: translateZ(1px); +} +.lds-circle > div { + display: inline-block; + width: 64px; + height: 64px; + margin: 8px; + border-radius: 50%; + background: #dfc; + animation: lds-circle 2.4s cubic-bezier(0, 0.2, 0.8, 1) infinite; +} +@keyframes lds-circle { + 0%, 100% { + animation-timing-function: cubic-bezier(0.5, 0, 1, 0.5); + } + 0% { + transform: rotateY(0deg); + } + 50% { + transform: rotateY(1800deg); + animation-timing-function: cubic-bezier(0, 0.5, 0.5, 1); + } + 100% { + transform: rotateY(3600deg); + } +} diff --git a/src/styles/components/table.scss b/src/styles/components/table.scss index dce13ee..1c35c7e 100644 --- a/src/styles/components/table.scss +++ b/src/styles/components/table.scss @@ -48,6 +48,9 @@ border-left: 0; white-space: nowrap; outline: none; + &:hover:not(.selected) { + cursor: cell; + } &.selected { border: none; outline: 2px solid $primary-color; diff --git a/src/styles/index.scss b/src/styles/index.scss index b65a60c..f6a19c9 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -6,6 +6,7 @@ @import './components/formula'; @import './components/table'; @import './components/dashboard'; +@import './components/loader'; * { margin: 0; @@ -24,4 +25,4 @@ body { height: 100%; max-width: 100%; font-size: 0.8rem; -} \ No newline at end of file +}