From 6f1b9803bd13a77c146eecf088c1424d95a307a0 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Wed, 6 Jul 2022 17:53:15 +0500 Subject: [PATCH] add routing, dashboard page, excel table pages --- .eslintrc.js | 2 + src/components/excel/Excel.ts | 21 +++---- src/components/formula/Formula.ts | 4 +- src/components/header/Header.ts | 43 +++++++++++--- src/components/table/Table.ts | 10 ++-- src/components/table/TableSelection.ts | 2 +- src/components/table/handlers/table.resize.ts | 2 +- .../table/handlers/table.select.handler.ts | 2 +- src/components/table/table.functions.ts | 2 +- src/components/toolbar/Toolbar.ts | 14 +++-- src/constants.ts | 23 ++++---- src/core/ExcelComponent.ts | 3 +- src/core/Page.ts | 19 +++++++ src/core/StoreSubscriber.ts | 2 +- src/core/createStore.ts | 5 +- src/core/routes/ActiveRoute.ts | 9 +++ src/core/routes/router.ts | 57 +++++++++++++++++++ src/core/utils.ts | 6 +- src/index.ts | 28 ++------- src/pages/DashboardPage.ts | 25 ++++++++ src/pages/ExcelPage.ts | 46 +++++++++++++++ src/pages/dashboard.functions.ts | 46 +++++++++++++++ src/redux/actions.ts | 9 ++- src/redux/constants.ts | 1 + src/redux/rootReducer.ts | 12 +++- 25 files changed, 315 insertions(+), 78 deletions(-) create mode 100644 src/core/Page.ts create mode 100644 src/core/routes/ActiveRoute.ts create mode 100644 src/core/routes/router.ts create mode 100644 src/pages/DashboardPage.ts create mode 100644 src/pages/ExcelPage.ts create mode 100644 src/pages/dashboard.functions.ts diff --git a/.eslintrc.js b/.eslintrc.js index 014262e..c8eefb9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -26,6 +26,7 @@ module.exports = { "@typescript-eslint/no-use-before-define": "off", "@typescript-eslint/restrict-template-expressions": "off", "@typescript-eslint/unbound-method": "off", + "@typescript-eslint/quotes": "warn", "arrow-parens": "off", "class-methods-use-this": "off", "func-names": "off", @@ -34,6 +35,7 @@ module.exports = { "max-len": "off", "no-console": "off", "no-new": "off", + "no-continue": "off", "no-plusplus": "off", "object-curly-newline": "off", }, diff --git a/src/components/excel/Excel.ts b/src/components/excel/Excel.ts index d856a30..6f070e8 100644 --- a/src/components/excel/Excel.ts +++ b/src/components/excel/Excel.ts @@ -1,8 +1,8 @@ -import { Store } from '../../core/createStore'; -import { $, Dom } from '../../core/dom'; -import { Emitter } from '../../core/Emitter'; -import { ExcelComponent } from '../../core/ExcelComponent'; -import { StoreSubscriber } from '../../core/StoreSubscriber'; +import { Store } from 'core/createStore'; +import { $, Dom } from 'core/dom'; +import { Emitter } from 'core/Emitter'; +import { ExcelComponent } from 'core/ExcelComponent'; +import { StoreSubscriber } from 'core/StoreSubscriber'; interface ExcelOptionsType { components: any[], @@ -16,14 +16,11 @@ export class Excel { store: Store; subscriber: StoreSubscriber; - constructor(selector: string, options: ExcelOptionsType) { - this.$el = $(selector); + constructor(options: ExcelOptionsType) { this.components = options.components; this.emitter = new Emitter(); this.store = options.store; this.subscriber = new StoreSubscriber(this.store); - - console.log(`Created new Excel class in ${selector} with options: ${options}`); } getRoot() { @@ -44,12 +41,10 @@ export class Excel { return component; }); - return $root.$el; + return $root; } - render() { - this.$el.append(this.getRoot()); - + init() { this.subscriber.subscribeComponents(this.components); this.components.forEach(component => component.init()); } diff --git a/src/components/formula/Formula.ts b/src/components/formula/Formula.ts index ea137aa..7a6e738 100644 --- a/src/components/formula/Formula.ts +++ b/src/components/formula/Formula.ts @@ -1,5 +1,5 @@ -import { Dom } from '../../core/dom'; -import { ExcelComponent } from '../../core/ExcelComponent'; +import { Dom } from 'core/dom'; +import { ExcelComponent } from 'core/ExcelComponent'; export class Formula extends ExcelComponent { static className = 'excel__formula'; diff --git a/src/components/header/Header.ts b/src/components/header/Header.ts index 143266c..77e46cc 100644 --- a/src/components/header/Header.ts +++ b/src/components/header/Header.ts @@ -1,14 +1,16 @@ -import { $, Dom } from '../../core/dom'; -import { ExcelComponent } from '../../core/ExcelComponent'; -import * as actions from '../../redux/actions'; +import { $, Dom } from 'core/dom'; +import { ExcelStateComponent } from 'core/ExcelStateComponent'; +import { deleteTable } from 'redux/actions'; +import * as actions from 'redux/actions'; -export class Header extends ExcelComponent { +export class Header extends ExcelStateComponent { static className = 'excel__header'; constructor($root: Dom, options: any) { super($root, { name: 'Header', - listeners: ['input'], + listeners: ['input', 'click'], + subscribe: ['title'], ...options, }); } @@ -20,10 +22,10 @@ export class Header extends ExcelComponent {
-
+
delete
-
+
exit_to_app
`; @@ -34,4 +36,31 @@ export class Header extends ExcelComponent { this.$dispatch(actions.changeTitle($target.text)); } + + onClick(event: MouseEvent) { + const target = event.target as HTMLElement; + if (!target) return; + + const $btn = $(target).closest('[data-button]'); + if (!$btn.$el) return; + + const btnEvent = $btn?.data?.button; + + switch (btnEvent) { + case 'exit': { + window.location.hash = ''; + break; + } + + case 'delete-table': { + this.$dispatch(deleteTable(this.store.getState().id)); + console.log('THIS', this); + this.destroy(); + break; + } + + default: + break; + } + } } diff --git a/src/components/table/Table.ts b/src/components/table/Table.ts index 44cfdd0..48074d4 100644 --- a/src/components/table/Table.ts +++ b/src/components/table/Table.ts @@ -1,9 +1,9 @@ +import { $, Dom } from 'core/dom'; +import { ExcelComponent } from 'core/ExcelComponent'; +import { parse } from 'core/parse'; +import { changeCurrentStyles } from 'redux/actions'; +import * as actions from 'redux/actions'; import { initialStyleState } from '../../constants'; -import { $, Dom } from '../../core/dom'; -import { ExcelComponent } from '../../core/ExcelComponent'; -import { parse } from '../../core/parse'; -import { changeCurrentStyles } from '../../redux/actions'; -import * as actions from '../../redux/actions'; import { resizeHandler } from './handlers/table.resize'; import { selectHandler } from './handlers/table.select.handler'; import { createTable } from './table.template'; diff --git a/src/components/table/TableSelection.ts b/src/components/table/TableSelection.ts index 1619a71..b35af93 100644 --- a/src/components/table/TableSelection.ts +++ b/src/components/table/TableSelection.ts @@ -1,4 +1,4 @@ -import { $, Dom } from '../../core/dom'; +import { $, Dom } from 'core/dom'; import { getParamsFromCellId } from './table.functions'; export class TableSelection { diff --git a/src/components/table/handlers/table.resize.ts b/src/components/table/handlers/table.resize.ts index a845c9a..8dbb20b 100644 --- a/src/components/table/handlers/table.resize.ts +++ b/src/components/table/handlers/table.resize.ts @@ -1,4 +1,4 @@ -import { $, Dom } from '../../../core/dom'; +import { $, Dom } from 'core/dom'; type CustomElementType = Element & { css: any }; type ResizeReturnDataType = { value: number, id: string, type: string }; diff --git a/src/components/table/handlers/table.select.handler.ts b/src/components/table/handlers/table.select.handler.ts index c54d99b..39f9b9c 100644 --- a/src/components/table/handlers/table.select.handler.ts +++ b/src/components/table/handlers/table.select.handler.ts @@ -1,4 +1,4 @@ -import { $ } from '../../../core/dom'; +import { $ } from 'core/dom'; import { getParamsFromCellId, isCell } from '../table.functions'; import { TableSelection } from '../TableSelection'; diff --git a/src/components/table/table.functions.ts b/src/components/table/table.functions.ts index e4e7afb..610ca2f 100644 --- a/src/components/table/table.functions.ts +++ b/src/components/table/table.functions.ts @@ -1,4 +1,4 @@ -import { $ } from '../../core/dom'; +import { $ } from 'core/dom'; export function isCell(event: Event): boolean { return $(event.target as HTMLElement).data.type === 'cell'; diff --git a/src/components/toolbar/Toolbar.ts b/src/components/toolbar/Toolbar.ts index bf8ee19..a009771 100644 --- a/src/components/toolbar/Toolbar.ts +++ b/src/components/toolbar/Toolbar.ts @@ -1,8 +1,8 @@ -import { initialStyleState } from '../../constants'; -import { $, Dom } from '../../core/dom'; -import { OptionsType } from '../../core/ExcelComponent'; -import { ExcelStateComponent } from '../../core/ExcelStateComponent'; +import { $, Dom } from 'core/dom'; +import { ExcelStateComponent } from 'core/ExcelStateComponent'; +import { OptionsType } from 'core/ExcelComponent'; import { createToolbar } from './toolbar.template'; +import { initialStyleState } from '../../constants'; export class Toolbar extends ExcelStateComponent { static className = 'excel__toolbar'; @@ -18,7 +18,6 @@ export class Toolbar extends ExcelStateComponent { prepare() { const currentToolbarState = this.toolbarState; - console.log('Current state', currentToolbarState); this.initState(currentToolbarState); } @@ -44,7 +43,10 @@ export class Toolbar extends ExcelStateComponent { onClick(event: MouseEvent) { const target = $(event.target as HTMLElement); - const value = JSON.parse(target.data.value); + const stringValue = target?.data?.value; + if (!stringValue) return; + + const value = JSON.parse(stringValue); const key = Object.keys(value)[0]; this.$emit('toolbar:applyStyle', value); diff --git a/src/constants.ts b/src/constants.ts index 854ad10..d9824b3 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -8,13 +8,16 @@ export const initialStyleState: Partial = { fontStyle: 'normal', }; -export const initialState: StateType = { - colState: {}, - rowState: {}, - dataState: {}, - currentText: '', - stylesState: {}, - title: 'New excel table', - ...storage('excel-state'), - currentStyles: { ...storage('excel-state')?.stylesState?.['0:0'] }, -}; +export function getNormalizeInitialState(params: string): StateType { + return { + colState: {}, + rowState: {}, + dataState: {}, + currentText: '', + stylesState: {}, + title: 'New excel table', + ...storage(`excel:${params}`), + currentStyles: { ...storage(`excel:${params}`)?.stylesState?.['0:0'] }, + id: params, + }; +} diff --git a/src/core/ExcelComponent.ts b/src/core/ExcelComponent.ts index f092a71..f18f304 100644 --- a/src/core/ExcelComponent.ts +++ b/src/core/ExcelComponent.ts @@ -1,4 +1,4 @@ -import { ActionType, SubscribeType } from '../redux/types'; +import { ActionType, SubscribeType } from 'redux/types'; import { Store } from './createStore'; import { Dom } from './dom'; import { DomListener } from './DomListener'; @@ -73,6 +73,5 @@ export abstract class ExcelComponent extends DomListener implements ExcelCompone destroy() { this.removeDOMListeners(); this.unsubscribers.forEach(unsub => unsub()); - this.storeSub.unsubscribe(); } } diff --git a/src/core/Page.ts b/src/core/Page.ts new file mode 100644 index 0000000..2ad73fd --- /dev/null +++ b/src/core/Page.ts @@ -0,0 +1,19 @@ +export abstract class Page { + params: any; + + constructor(params: any) { + this.params = params; + } + + getRoot() { + + } + + afterRender() { + + } + + destroy() { + + } +} diff --git a/src/core/StoreSubscriber.ts b/src/core/StoreSubscriber.ts index 408b388..a866ef6 100644 --- a/src/core/StoreSubscriber.ts +++ b/src/core/StoreSubscriber.ts @@ -1,4 +1,4 @@ -import { StateType } from '../redux/types'; +import { StateType } from 'redux/types'; import { Store } from './createStore'; import { isEqual } from './utils'; diff --git a/src/core/createStore.ts b/src/core/createStore.ts index cde045e..49e0e33 100644 --- a/src/core/createStore.ts +++ b/src/core/createStore.ts @@ -1,11 +1,10 @@ -import { initialState } from '../constants'; import { ActionType, ReducerType, StateType, SubscribeType } from '../redux/types'; export class Store { state: StateType; listeners: ((args?: any) => void)[]; - constructor(private reducer: ReducerType) { + constructor(private reducer: ReducerType, initialState: StateType) { this.state = reducer({ ...initialState }, { type: '__INIT__' }); this.listeners = []; } @@ -13,7 +12,7 @@ export class Store { subscribe(fn: (state?: StateType) => void): SubscribeType { this.listeners.push(fn); return { - unsubscribe() { + unsubscribe: () => { this.listeners = this.listeners.filter((l: any) => l !== fn); }, }; diff --git a/src/core/routes/ActiveRoute.ts b/src/core/routes/ActiveRoute.ts new file mode 100644 index 0000000..e477fdb --- /dev/null +++ b/src/core/routes/ActiveRoute.ts @@ -0,0 +1,9 @@ +export class ActiveRoute { + static get path() { + return window.location.hash.slice(1); + } + + static get param() { + return window.location.hash.split('/'); + } +} diff --git a/src/core/routes/router.ts b/src/core/routes/router.ts new file mode 100644 index 0000000..a5a4281 --- /dev/null +++ b/src/core/routes/router.ts @@ -0,0 +1,57 @@ +import { DashboardPage } from 'pages/DashboardPage'; +import { ExcelPage } from 'pages/ExcelPage'; +import { $, Dom } from 'core/dom'; +import { ActiveRoute } from './ActiveRoute'; + +export class Router { + private $placeholder: Dom; + private routes: { + dashboard: DashboardPage + excel: ExcelPage + }; + private page: DashboardPage | ExcelPage; + + constructor(selector: string, routes: any) { + if (!selector) throw new Error('Selector not provided'); + + this.$placeholder = $(selector); + this.routes = routes; + this.page = null; + + this.changePageHandler = this.changePageHandler.bind(this); + + this.init(); + } + + init() { + window.addEventListener('hashchange', this.changePageHandler); + this.changePageHandler(); + } + + changePageHandler() { + this.$placeholder.clear(); + this.page?.destroy(); + + let Page; + + switch (true) { + case ActiveRoute.path.includes('excel'): + Page = this.routes.excel; + break; + + case ActiveRoute.path.includes('dashboard'): + default: + Page = this.routes.dashboard; + break; + } + // @ts-ignore + this.page = new Page(ActiveRoute.param); + + this.$placeholder.append(this.page.getRoot()); + this.page.afterRender(); + } + + destroy() { + window.removeEventListener('hashchange', this.changePageHandler); + } +} diff --git a/src/core/utils.ts b/src/core/utils.ts index 128fa08..402e7bc 100644 --- a/src/core/utils.ts +++ b/src/core/utils.ts @@ -5,9 +5,13 @@ export function capitalize(string: string): string { } export function storage(key: string, data: any = null): any { - if (!data) return JSON.parse(localStorage.getItem(key)); + if (!data) { + console.log('RETURN KEY', key, data); + return JSON.parse(localStorage.getItem(key)); + } localStorage.setItem(key, JSON.stringify(data)); + console.log('SET ITEM', key, data); return true; } diff --git a/src/index.ts b/src/index.ts index 6875c68..695e536 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,25 +1,9 @@ -import { Excel } from './components/excel/Excel'; -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 './styles/index.scss'; -import { Store } from './core/createStore'; -import { debounce, storage } from './core/utils'; -import { rootReducer } from './redux/rootReducer'; -import { StateType } from './redux/types'; +import { Router } from 'core/routes/router'; +import { DashboardPage } from 'pages/DashboardPage'; +import { ExcelPage } from 'pages/ExcelPage'; -const store = new Store(rootReducer); - -const stateListener = debounce((state: StateType) => { - storage('excel-state', state); -}, 300); - -store.subscribe(stateListener); - -const excel = new Excel('#app', { - components: [Header, Toolbar, Formula, Table], - store, +new Router('#app', { + dashboard: DashboardPage, + excel: ExcelPage, }); - -excel.render(); diff --git a/src/pages/DashboardPage.ts b/src/pages/DashboardPage.ts new file mode 100644 index 0000000..847e1a9 --- /dev/null +++ b/src/pages/DashboardPage.ts @@ -0,0 +1,25 @@ +import { Page } from 'core/Page'; +import { $ } from 'core/dom'; +import { createRecordsTable } from 'pages/dashboard.functions'; + +export class DashboardPage extends Page { + getRoot() { + const id = Date.now().toString(); + + return $.create('div', 'db').html( + ` +
+

Excel Панель управыления

+
+ +
+ ${createRecordsTable()} +
+ `, + ); + } +} diff --git a/src/pages/ExcelPage.ts b/src/pages/ExcelPage.ts new file mode 100644 index 0000000..f580e12 --- /dev/null +++ b/src/pages/ExcelPage.ts @@ -0,0 +1,46 @@ +import { Excel } from 'components/excel/Excel'; +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 { Store } from 'core/createStore'; +import { Page } from 'core/Page'; +import { debounce, storage } from 'core/utils'; +import { rootReducer } from 'redux/rootReducer'; +import { StateType } from 'redux/types'; +import { getNormalizeInitialState } from '../constants'; + +export function storageName(param: string) { + return `excel:${param}`; +} + +export class ExcelPage extends Page { + private excel: Excel; + + getRoot() { + const params = this.params[1] ? this.params[1] : Date.now().toString(); + const normalizeState = storage(storageName(params)) || getNormalizeInitialState(params); + const store = new Store(rootReducer, normalizeState); + + const stateListener = debounce((state: StateType) => { + storage(storageName(params), state); + }, 300); + + store.subscribe(stateListener); + + this.excel = new Excel({ + components: [Header, Toolbar, Formula, Table], + store, + }); + + return this.excel.getRoot(); + } + + afterRender() { + this.excel.init(); + } + + destroy() { + this.excel.destroy(); + } +} diff --git a/src/pages/dashboard.functions.ts b/src/pages/dashboard.functions.ts new file mode 100644 index 0000000..465c828 --- /dev/null +++ b/src/pages/dashboard.functions.ts @@ -0,0 +1,46 @@ +import { storage } from 'core/utils'; + +function toHtml(key: string) { + const date = +key.split(':')[1]; + const params = storage(key); + const link = `#excel/${date}`; + return ` +
  • + ${params.title} + ${new Date(date).toLocaleDateString()} +
  • + `; +} + +export function getAllRecords() { + +} + +export function createRecordsTable() { + const keys = getAllKeys(); + if (!keys.length) return '

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

    '; + + return ` +
    + Название + Дата открытия +
    + +
      + ${keys.map((key) => toHtml(key)).join('')} +
    + `; +} + +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/redux/actions.ts b/src/redux/actions.ts index 7b3b6dd..6df4d9d 100644 --- a/src/redux/actions.ts +++ b/src/redux/actions.ts @@ -1,4 +1,4 @@ -import { CHANGE_TEXT, CHANGE_STYLES, TABLE_RESIZE, APPLY_STYLES, CHANGE_TITLE } from './constants'; +import { CHANGE_TEXT, CHANGE_STYLES, TABLE_RESIZE, APPLY_STYLES, CHANGE_TITLE, DELETE_TABLE } from './constants'; export function tableResize(resizeData: any) { return { @@ -34,3 +34,10 @@ export function changeTitle(data: string) { data, }; } + +export function deleteTable(data: string) { + return { + type: DELETE_TABLE, + data, + }; +} diff --git a/src/redux/constants.ts b/src/redux/constants.ts index 5cb2615..99a746d 100644 --- a/src/redux/constants.ts +++ b/src/redux/constants.ts @@ -3,3 +3,4 @@ export const CHANGE_TEXT = 'CHANGE_TEXT'; export const APPLY_STYLES = 'APPLY_STYLES'; export const CHANGE_STYLES = 'CURRENT_STYLES'; export const CHANGE_TITLE = 'CHANGE_TITLE'; +export const DELETE_TABLE = 'DELETE_TABLE'; diff --git a/src/redux/rootReducer.ts b/src/redux/rootReducer.ts index 3842298..ebe34e2 100644 --- a/src/redux/rootReducer.ts +++ b/src/redux/rootReducer.ts @@ -1,4 +1,5 @@ -import { CHANGE_TEXT, CHANGE_STYLES, TABLE_RESIZE, APPLY_STYLES, CHANGE_TITLE } from './constants'; +import { storageName } from 'pages/ExcelPage'; +import { CHANGE_TEXT, CHANGE_STYLES, TABLE_RESIZE, APPLY_STYLES, CHANGE_TITLE, DELETE_TABLE } from './constants'; import { ActionType, StateType } from './types'; export function rootReducer(state: StateType, action: ActionType) { @@ -44,6 +45,15 @@ export function rootReducer(state: StateType, action: ActionType) { return { ...state, title: action.data }; } + case DELETE_TABLE: { + const name = storageName(action.data); + console.log('delete table item', name); + localStorage.removeItem(name); + window.location.hash = ''; + + return { ...state }; + } + default: return state; } }