add excel state, initial table column size

This commit is contained in:
Sergey Krylov 2022-06-29 10:26:13 +05:00
parent c74c0490c1
commit a8b9cbfb9f
13 changed files with 226 additions and 65 deletions

View File

@ -16,6 +16,7 @@ module.exports = {
"@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/lines-between-class-members": "off", "@typescript-eslint/lines-between-class-members": "off",
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-floating-promises": "off",
"@typescript-eslint/no-unnecessary-type-assertion": "off", "@typescript-eslint/no-unnecessary-type-assertion": "off",
"@typescript-eslint/no-unsafe-argument": "off", "@typescript-eslint/no-unsafe-argument": "off",
"@typescript-eslint/no-unsafe-assignment": "off", "@typescript-eslint/no-unsafe-assignment": "off",
@ -28,9 +29,10 @@ module.exports = {
"class-methods-use-this": "off", "class-methods-use-this": "off",
"func-names": "off", "func-names": "off",
"import/prefer-default-export": "off", "import/prefer-default-export": "off",
"linebreak-style": "off",
"max-len": "off",
"no-console": "off", "no-console": "off",
"no-plusplus": "off", "no-plusplus": "off",
"max-len": "off",
}, },
env: { env: {
browser: true, browser: true,

View File

@ -1,20 +1,24 @@
import { Store } from '../../core/createStore';
import { $, Dom } from '../../core/dom'; import { $, Dom } from '../../core/dom';
import { Emitter } from '../../core/Emitter'; import { Emitter } from '../../core/Emitter';
import { ExcelComponent } from '../../core/ExcelComponent'; import { ExcelComponent } from '../../core/ExcelComponent';
interface ExcelOptionsType { interface ExcelOptionsType {
components: any[] components: any[],
store: any,
} }
export class Excel { export class Excel {
$el: HTMLElement | Dom; $el: HTMLElement | Dom;
components: any[]; components: any[];
emitter: Emitter; emitter: Emitter;
store: Store;
constructor(selector: string, options: ExcelOptionsType) { constructor(selector: string, options: ExcelOptionsType) {
this.$el = $(selector); this.$el = $(selector);
this.components = options.components; this.components = options.components;
this.emitter = new Emitter(); this.emitter = new Emitter();
this.store = options.store;
console.log(`Created new Excel class in ${selector} with options: ${options}`); console.log(`Created new Excel class in ${selector} with options: ${options}`);
} }
@ -24,6 +28,7 @@ export class Excel {
const componentOptions = { const componentOptions = {
emitter: this.emitter, emitter: this.emitter,
store: this.store,
}; };
this.components = this.components.map(Component => { this.components = this.components.map(Component => {

View File

@ -1,9 +1,10 @@
import { DomClass } from '../../core/dom'; import * as actions from '../../redux/actions';
import { $, DomClass } from '../../core/dom';
import { ExcelComponent } from '../../core/ExcelComponent'; import { ExcelComponent } from '../../core/ExcelComponent';
import { selectHandler } from './handlers/table.select.handler';
import { TableSelection } from './TableSelection'; import { TableSelection } from './TableSelection';
import { createTable } from './table.template'; import { createTable } from './table.template';
import { resizeHandler } from './handlers/table.resize'; import { resizeHandler } from './handlers/table.resize';
import { selectHandler } from './handlers/table.select.handler';
export class Table extends ExcelComponent { export class Table extends ExcelComponent {
static className = 'excel__table'; static className = 'excel__table';
@ -41,15 +42,38 @@ export class Table extends ExcelComponent {
this.$on('formula:enter-press', () => { this.$on('formula:enter-press', () => {
this.selection.current.focus(); this.selection.current.focus();
}); });
// this.$subscribe(state => {
// console.log('State in Table', state);
// });
this.initTableSize();
}
initTableSize() {
const colSizes = this.store.getState()?.colState;
Object.keys(colSizes).forEach(key => {
const cols = this.$root.findAll(`[data-col="${key}"]`);
cols.forEach(el => $(el as HTMLElement).css({ width: `${colSizes[key]}px` }));
});
} }
emitSelectCallback() { emitSelectCallback() {
this.$emit('table:select-cell', this.selection.current.text); this.$emit('table:select-cell', this.selection.current.text);
} }
async resizeTable(event: MouseEvent) {
try {
const resizeData = await resizeHandler(this.$root, event);
this.$dispatch(actions.tableResize({ resizeData }));
} catch (e) {
console.warn('Resize error', e.message);
}
}
onMousedown(event: MouseEvent) { onMousedown(event: MouseEvent) {
selectHandler(event, this.selection, this.emitSelectCallback.bind(this)); selectHandler(event, this.selection, this.emitSelectCallback.bind(this));
resizeHandler(this.$root, event); this.resizeTable(event);
} }
onKeydown(event: KeyboardEvent) { onKeydown(event: KeyboardEvent) {

View File

@ -1,8 +1,10 @@
import { $, Dom } from '../../../core/dom'; import { $, Dom } from '../../../core/dom';
type CustomElementType = Element & { css: any }; type CustomElementType = Element & { css: any };
type ResizeReturnDataType = { value: number, id: string };
export function resizeHandler($root: Dom, event: MouseEvent) { export function resizeHandler($root: Dom, event: MouseEvent) {
return new Promise<ResizeReturnDataType>(res => {
const { target } = event; const { target } = event;
if (!(target as HTMLElement).dataset.resize) return; if (!(target as HTMLElement).dataset.resize) return;
@ -57,21 +59,31 @@ export function resizeHandler($root: Dom, event: MouseEvent) {
document.onmouseup = null; document.onmouseup = null;
document.body.style.userSelect = null; document.body.style.userSelect = null;
let value: number;
switch (type) { switch (type) {
case 'col': { case 'col': {
$parent.css({ width: `${coords.width + delta}px` }); value = coords.width + delta;
$parent.css({ width: `${value}px` });
allCols.forEach((el: CustomElementType) => el.css({ width: `${coords.width + delta}px` })); allCols.forEach((el: CustomElementType) => el.css({ width: `${coords.width + delta}px` }));
break; break;
} }
case 'row': { case 'row': {
$parent.css({ height: `${coords.height + (delta)}px` }); value = coords.height + delta;
$parent.css({ height: `${value}px` });
break; break;
} }
default: break; default: break;
} }
res({
value,
id: type === 'col' ? $parent.data.col : null,
});
$resizer.css({ opacity: 0, bottom: 0, right: 0 }); $resizer.css({ opacity: 0, bottom: 0, right: 0 });
}; };
});
} }

View File

@ -1,3 +1,5 @@
import { ActionType, StateType, SubscribeType } from '../redux/types';
import { Store } from './createStore';
import { DomListener } from './DomListener'; import { DomListener } from './DomListener';
import { Emitter } from './Emitter'; import { Emitter } from './Emitter';
@ -10,17 +12,21 @@ type OptionsType = {
listeners?: string[]; listeners?: string[];
name: string; name: string;
emitter?: Emitter; emitter?: Emitter;
store: Store;
}; };
export class ExcelComponent extends DomListener implements ExcelComponentClass { export class ExcelComponent extends DomListener implements ExcelComponentClass {
name: string; name: string;
emitter: Emitter; emitter: Emitter;
store: Store;
storeSub: SubscribeType;
private unsubscribers: ((args?: any) => any)[]; private unsubscribers: ((args?: any) => any)[];
constructor($root: any, options: OptionsType) { constructor($root: any, options: OptionsType) {
super($root, options?.listeners); super($root, options?.listeners);
this.name = options?.name; this.name = options?.name;
this.emitter = options?.emitter; this.emitter = options?.emitter;
this.store = options?.store;
this.unsubscribers = []; this.unsubscribers = [];
this.prepare(); this.prepare();
} }
@ -42,6 +48,14 @@ export class ExcelComponent extends DomListener implements ExcelComponentClass {
this.unsubscribers.push(unsub); this.unsubscribers.push(unsub);
} }
$dispatch(action: ActionType) {
this.store.dispatch(action);
}
$subscribe(fn: (state?: StateType) => void) {
this.storeSub = this.store.subscribe(fn);
}
init() { init() {
this.initDOMListeners(); this.initDOMListeners();
} }
@ -49,5 +63,6 @@ export class ExcelComponent extends DomListener implements ExcelComponentClass {
destroy() { destroy() {
this.removeDOMListeners(); this.removeDOMListeners();
this.unsubscribers.forEach(unsub => unsub()); this.unsubscribers.forEach(unsub => unsub());
this.storeSub.unsubscribe();
} }
} }

40
src/core/createStore.ts Normal file
View File

@ -0,0 +1,40 @@
import {
ActionType, ReducerType, StateType, SubscribeType,
} from '../redux/types';
import { storage } from './utils';
const initialState: StateType = {
colState: {},
rowState: {},
...storage('excel-state'),
};
export class Store {
static initialState = {};
state: StateType;
listeners: ((args?: any) => void)[];
constructor(private reducer: ReducerType) {
this.state = reducer({ ...initialState }, { type: '__INIT__' });
this.listeners = [];
}
subscribe(fn: (state?: StateType) => void): SubscribeType {
this.listeners.push(fn);
return {
unsubscribe() {
this.listeners = this.listeners.filter((l: any) => l !== fn);
},
};
}
dispatch(action: ActionType) {
this.state = this.reducer(this.state, action);
this.listeners.forEach(listener => listener(this.state));
}
getState() {
return this.state;
}
}

View File

@ -3,3 +3,11 @@ export function capitalize(string: string): string {
return string.charAt(0).toUpperCase() + string.slice(1); return string.charAt(0).toUpperCase() + string.slice(1);
} }
export function storage(key: string, data: any = null): any {
if (!data) return JSON.parse(localStorage.getItem(key));
localStorage.setItem(key, JSON.stringify(data));
return true;
}

View File

@ -4,9 +4,19 @@ import { Header } from './components/header/Header';
import { Table } from './components/table/Table'; import { Table } from './components/table/Table';
import { Toolbar } from './components/toolbar/Toolbar'; import { Toolbar } from './components/toolbar/Toolbar';
import './styles/index.scss'; import './styles/index.scss';
import { Store } from './core/createStore';
import { storage } from './core/utils';
import { rootReducer } from './redux/rootReducer';
const store = new Store(rootReducer);
store.subscribe(state => {
storage('excel-state', state);
});
const excel = new Excel('#app', { const excel = new Excel('#app', {
components: [Header, Toolbar, Formula, Table], components: [Header, Toolbar, Formula, Table],
store,
}); });
excel.render(); excel.render();

8
src/redux/actions-types.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
export type ResizePayloadType = {
colState: {
[k in number]: number
},
rowState: {
[k in number]: number
},
};

8
src/redux/actions.ts Normal file
View File

@ -0,0 +1,8 @@
import { TABLE_RESIZE } from './constants';
export function tableResize(resizeData: any) {
return {
type: TABLE_RESIZE,
...resizeData,
};
}

1
src/redux/constants.ts Normal file
View File

@ -0,0 +1 @@
export const TABLE_RESIZE = 'TABLE_RESIZE';

14
src/redux/rootReducer.ts Normal file
View File

@ -0,0 +1,14 @@
import { TABLE_RESIZE } from './constants';
import { ActionType, StateType } from './types';
export function rootReducer(state: StateType, action: ActionType) {
switch (action.type) {
case TABLE_RESIZE: {
const prevState = state.colState || {};
prevState[action.resizeData?.id] = action.resizeData?.value;
return { ...state, colState: prevState };
}
default: return state;
}
}

14
src/redux/types.d.ts vendored Normal file
View File

@ -0,0 +1,14 @@
export type ActionType = {
type: string
[k: string]: any
};
export type StateType = {
[k: string]: any
};
export type ReducerType = (state: StateType, action: ActionType) => StateType;
export type SubscribeType = {
unsubscribe: () => void
};