add excel state, initial table column size
This commit is contained in:
parent
c74c0490c1
commit
a8b9cbfb9f
@ -16,6 +16,7 @@ module.exports = {
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/lines-between-class-members": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-floating-promises": "off",
|
||||
"@typescript-eslint/no-unnecessary-type-assertion": "off",
|
||||
"@typescript-eslint/no-unsafe-argument": "off",
|
||||
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||
@ -28,9 +29,10 @@ module.exports = {
|
||||
"class-methods-use-this": "off",
|
||||
"func-names": "off",
|
||||
"import/prefer-default-export": "off",
|
||||
"linebreak-style": "off",
|
||||
"max-len": "off",
|
||||
"no-console": "off",
|
||||
"no-plusplus": "off",
|
||||
"max-len": "off",
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
|
||||
@ -1,20 +1,24 @@
|
||||
import { Store } from '../../core/createStore';
|
||||
import { $, Dom } from '../../core/dom';
|
||||
import { Emitter } from '../../core/Emitter';
|
||||
import { ExcelComponent } from '../../core/ExcelComponent';
|
||||
|
||||
interface ExcelOptionsType {
|
||||
components: any[]
|
||||
components: any[],
|
||||
store: any,
|
||||
}
|
||||
|
||||
export class Excel {
|
||||
$el: HTMLElement | Dom;
|
||||
components: any[];
|
||||
emitter: Emitter;
|
||||
store: Store;
|
||||
|
||||
constructor(selector: string, options: ExcelOptionsType) {
|
||||
this.$el = $(selector);
|
||||
this.components = options.components;
|
||||
this.emitter = new Emitter();
|
||||
this.store = options.store;
|
||||
|
||||
console.log(`Created new Excel class in ${selector} with options: ${options}`);
|
||||
}
|
||||
@ -24,6 +28,7 @@ export class Excel {
|
||||
|
||||
const componentOptions = {
|
||||
emitter: this.emitter,
|
||||
store: this.store,
|
||||
};
|
||||
|
||||
this.components = this.components.map(Component => {
|
||||
|
||||
@ -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 { selectHandler } from './handlers/table.select.handler';
|
||||
import { TableSelection } from './TableSelection';
|
||||
import { createTable } from './table.template';
|
||||
import { resizeHandler } from './handlers/table.resize';
|
||||
import { selectHandler } from './handlers/table.select.handler';
|
||||
|
||||
export class Table extends ExcelComponent {
|
||||
static className = 'excel__table';
|
||||
@ -41,15 +42,38 @@ export class Table extends ExcelComponent {
|
||||
this.$on('formula:enter-press', () => {
|
||||
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() {
|
||||
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) {
|
||||
selectHandler(event, this.selection, this.emitSelectCallback.bind(this));
|
||||
resizeHandler(this.$root, event);
|
||||
this.resizeTable(event);
|
||||
}
|
||||
|
||||
onKeydown(event: KeyboardEvent) {
|
||||
|
||||
@ -1,77 +1,89 @@
|
||||
import { $, Dom } from '../../../core/dom';
|
||||
|
||||
type CustomElementType = Element & { css: any };
|
||||
type ResizeReturnDataType = { value: number, id: string };
|
||||
|
||||
export function resizeHandler($root: Dom, event: MouseEvent) {
|
||||
const { target } = event;
|
||||
if (!(target as HTMLElement).dataset.resize) return;
|
||||
return new Promise<ResizeReturnDataType>(res => {
|
||||
const { target } = event;
|
||||
if (!(target as HTMLElement).dataset.resize) return;
|
||||
|
||||
const $resizer = $(target as HTMLElement);
|
||||
const $parent = $resizer.closest('[data-type="resizable"]');
|
||||
const coords = $parent.getCoords();
|
||||
const type = $resizer.data.resize;
|
||||
const $resizer = $(target as HTMLElement);
|
||||
const $parent = $resizer.closest('[data-type="resizable"]');
|
||||
const coords = $parent.getCoords();
|
||||
const type = $resizer.data.resize;
|
||||
|
||||
const allCols = $root.findAll(`[data-col="${$parent.data.col}"]`);
|
||||
const allCols = $root.findAll(`[data-col="${$parent.data.col}"]`);
|
||||
|
||||
let delta: number;
|
||||
let delta: number;
|
||||
|
||||
(Element.prototype as CustomElementType).css = function (styles: any) {
|
||||
Object.keys(styles).forEach((key: any) => {
|
||||
this.style[key] = styles[key];
|
||||
});
|
||||
};
|
||||
(Element.prototype as CustomElementType).css = function (styles: any) {
|
||||
Object.keys(styles).forEach((key: any) => {
|
||||
this.style[key] = styles[key];
|
||||
});
|
||||
};
|
||||
|
||||
$resizer.css({ opacity: 1 });
|
||||
$resizer.css({ opacity: 1 });
|
||||
|
||||
document.onmousemove = e => {
|
||||
document.body.style.userSelect = 'none';
|
||||
document.onmousemove = e => {
|
||||
document.body.style.userSelect = 'none';
|
||||
|
||||
switch (type) {
|
||||
case 'col': {
|
||||
delta = e.pageX - coords.right;
|
||||
$resizer.css({
|
||||
right: `${-delta}px`,
|
||||
bottom: '-100vh',
|
||||
});
|
||||
switch (type) {
|
||||
case 'col': {
|
||||
delta = e.pageX - coords.right;
|
||||
$resizer.css({
|
||||
right: `${-delta}px`,
|
||||
bottom: '-100vh',
|
||||
});
|
||||
|
||||
break;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'row': {
|
||||
delta = e.pageY - coords.bottom;
|
||||
|
||||
$resizer.css({
|
||||
bottom: `${-delta}px`,
|
||||
right: '-100vw',
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default: break;
|
||||
}
|
||||
};
|
||||
|
||||
document.onmouseup = () => {
|
||||
document.onmousemove = null;
|
||||
document.onmouseup = null;
|
||||
document.body.style.userSelect = null;
|
||||
|
||||
let value: number;
|
||||
|
||||
switch (type) {
|
||||
case 'col': {
|
||||
value = coords.width + delta;
|
||||
$parent.css({ width: `${value}px` });
|
||||
allCols.forEach((el: CustomElementType) => el.css({ width: `${coords.width + delta}px` }));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'row': {
|
||||
value = coords.height + delta;
|
||||
$parent.css({ height: `${value}px` });
|
||||
break;
|
||||
}
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
case 'row': {
|
||||
delta = e.pageY - coords.bottom;
|
||||
res({
|
||||
value,
|
||||
id: type === 'col' ? $parent.data.col : null,
|
||||
});
|
||||
|
||||
$resizer.css({
|
||||
bottom: `${-delta}px`,
|
||||
right: '-100vw',
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default: break;
|
||||
}
|
||||
};
|
||||
|
||||
document.onmouseup = () => {
|
||||
document.onmousemove = null;
|
||||
document.onmouseup = null;
|
||||
document.body.style.userSelect = null;
|
||||
|
||||
switch (type) {
|
||||
case 'col': {
|
||||
$parent.css({ width: `${coords.width + delta}px` });
|
||||
allCols.forEach((el: CustomElementType) => el.css({ width: `${coords.width + delta}px` }));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'row': {
|
||||
$parent.css({ height: `${coords.height + (delta)}px` });
|
||||
break;
|
||||
}
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
$resizer.css({ opacity: 0, bottom: 0, right: 0 });
|
||||
};
|
||||
$resizer.css({ opacity: 0, bottom: 0, right: 0 });
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { ActionType, StateType, SubscribeType } from '../redux/types';
|
||||
import { Store } from './createStore';
|
||||
import { DomListener } from './DomListener';
|
||||
import { Emitter } from './Emitter';
|
||||
|
||||
@ -10,17 +12,21 @@ type OptionsType = {
|
||||
listeners?: string[];
|
||||
name: string;
|
||||
emitter?: Emitter;
|
||||
store: Store;
|
||||
};
|
||||
|
||||
export class ExcelComponent extends DomListener implements ExcelComponentClass {
|
||||
name: string;
|
||||
emitter: Emitter;
|
||||
store: Store;
|
||||
storeSub: SubscribeType;
|
||||
private unsubscribers: ((args?: any) => any)[];
|
||||
|
||||
constructor($root: any, options: OptionsType) {
|
||||
super($root, options?.listeners);
|
||||
this.name = options?.name;
|
||||
this.emitter = options?.emitter;
|
||||
this.store = options?.store;
|
||||
this.unsubscribers = [];
|
||||
this.prepare();
|
||||
}
|
||||
@ -42,6 +48,14 @@ export class ExcelComponent extends DomListener implements ExcelComponentClass {
|
||||
this.unsubscribers.push(unsub);
|
||||
}
|
||||
|
||||
$dispatch(action: ActionType) {
|
||||
this.store.dispatch(action);
|
||||
}
|
||||
|
||||
$subscribe(fn: (state?: StateType) => void) {
|
||||
this.storeSub = this.store.subscribe(fn);
|
||||
}
|
||||
|
||||
init() {
|
||||
this.initDOMListeners();
|
||||
}
|
||||
@ -49,5 +63,6 @@ export class ExcelComponent extends DomListener implements ExcelComponentClass {
|
||||
destroy() {
|
||||
this.removeDOMListeners();
|
||||
this.unsubscribers.forEach(unsub => unsub());
|
||||
this.storeSub.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
40
src/core/createStore.ts
Normal file
40
src/core/createStore.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -3,3 +3,11 @@ export function capitalize(string: string): string {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
10
src/index.ts
10
src/index.ts
@ -4,9 +4,19 @@ 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 { 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', {
|
||||
components: [Header, Toolbar, Formula, Table],
|
||||
store,
|
||||
});
|
||||
|
||||
excel.render();
|
||||
|
||||
8
src/redux/actions-types.d.ts
vendored
Normal file
8
src/redux/actions-types.d.ts
vendored
Normal 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
8
src/redux/actions.ts
Normal 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
1
src/redux/constants.ts
Normal file
@ -0,0 +1 @@
|
||||
export const TABLE_RESIZE = 'TABLE_RESIZE';
|
||||
14
src/redux/rootReducer.ts
Normal file
14
src/redux/rootReducer.ts
Normal 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
14
src/redux/types.d.ts
vendored
Normal 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
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user