add routing, dashboard page, excel table pages
This commit is contained in:
parent
1456c256e3
commit
6f1b9803bd
@ -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",
|
||||
},
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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 {
|
||||
<input type="text" class="input" value="${title}">
|
||||
|
||||
<div>
|
||||
<div class="button">
|
||||
<div class="button" data-button="delete-table">
|
||||
<i class="material-icons">delete</i>
|
||||
</div>
|
||||
<div class="button">
|
||||
<div class="button" data-button="exit">
|
||||
<i class="material-icons">exit_to_app</i>
|
||||
</div>
|
||||
`;
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { $, Dom } from '../../core/dom';
|
||||
import { $, Dom } from 'core/dom';
|
||||
import { getParamsFromCellId } from './table.functions';
|
||||
|
||||
export class TableSelection {
|
||||
|
||||
@ -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 };
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { $ } from '../../../core/dom';
|
||||
import { $ } from 'core/dom';
|
||||
import { getParamsFromCellId, isCell } from '../table.functions';
|
||||
import { TableSelection } from '../TableSelection';
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -8,13 +8,16 @@ export const initialStyleState: Partial<CSSStyleDeclaration> = {
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
19
src/core/Page.ts
Normal file
19
src/core/Page.ts
Normal file
@ -0,0 +1,19 @@
|
||||
export abstract class Page {
|
||||
params: any;
|
||||
|
||||
constructor(params: any) {
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
getRoot() {
|
||||
|
||||
}
|
||||
|
||||
afterRender() {
|
||||
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { StateType } from '../redux/types';
|
||||
import { StateType } from 'redux/types';
|
||||
import { Store } from './createStore';
|
||||
import { isEqual } from './utils';
|
||||
|
||||
|
||||
@ -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);
|
||||
},
|
||||
};
|
||||
|
||||
9
src/core/routes/ActiveRoute.ts
Normal file
9
src/core/routes/ActiveRoute.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export class ActiveRoute {
|
||||
static get path() {
|
||||
return window.location.hash.slice(1);
|
||||
}
|
||||
|
||||
static get param() {
|
||||
return window.location.hash.split('/');
|
||||
}
|
||||
}
|
||||
57
src/core/routes/router.ts
Normal file
57
src/core/routes/router.ts
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
28
src/index.ts
28
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();
|
||||
|
||||
25
src/pages/DashboardPage.ts
Normal file
25
src/pages/DashboardPage.ts
Normal file
@ -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(
|
||||
`
|
||||
<div class="db__header">
|
||||
<h1>Excel Панель управыления</h1>
|
||||
</div>
|
||||
<div class="db__new">
|
||||
<div class="db__view">
|
||||
<a href="#excel/${id}" class="db__create">Новая таблица</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="db__table db__view">
|
||||
${createRecordsTable()}
|
||||
</div>
|
||||
`,
|
||||
);
|
||||
}
|
||||
}
|
||||
46
src/pages/ExcelPage.ts
Normal file
46
src/pages/ExcelPage.ts
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
46
src/pages/dashboard.functions.ts
Normal file
46
src/pages/dashboard.functions.ts
Normal file
@ -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 `
|
||||
<li class="db__record">
|
||||
<a href=${link}>${params.title}</a>
|
||||
<strong>${new Date(date).toLocaleDateString()}</strong>
|
||||
</li>
|
||||
`;
|
||||
}
|
||||
|
||||
export function getAllRecords() {
|
||||
|
||||
}
|
||||
|
||||
export function createRecordsTable() {
|
||||
const keys = getAllKeys();
|
||||
if (!keys.length) return '<p>Пока не создали ни одной таблицы</p>';
|
||||
|
||||
return `
|
||||
<div class="db__list-header">
|
||||
<span>Название</span>
|
||||
<span>Дата открытия</span>
|
||||
</div>
|
||||
|
||||
<ul class="db__list">
|
||||
${keys.map((key) => toHtml(key)).join('')}
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user