212 lines
6.6 KiB
TypeScript

import { startCellId } from 'components/table/table.functions';
import * as actions from 'redux/action-creators';
import { $, Dom } from 'core/Dom';
import { ComponentOptionsType, ExcelComponent } from 'core/ExcelComponent';
import { TableSelection } from 'components/table/TableSelection';
import { changeCurrentStyles, changeCurrentText, changeTableSize, removeRowFromTable } from 'redux/action-creators';
import { createTable, getNewRowHTML } from 'components/table/table.template';
import { initialState, initialStyleState } from 'src/constants';
import { getCellId, parse } from 'core/utils';
import { resizeHandler } from 'components/table/handlers/table.resize';
import { selectHandler } from 'components/table/handlers/table.select.handler';
export class Table extends ExcelComponent {
static className = 'excel__table';
private selection: TableSelection;
private isMouseDowned: boolean;
private tableResizing = false;
public tableSize = { row: initialState.tableSize.row, col: initialState.tableSize.col };
constructor($root: Dom, options: ComponentOptionsType) {
super($root, {
...options,
name: 'Table',
eventListeners: ['mousedown', 'keydown', 'input', 'mouseover', 'mouseup'],
});
this.isMouseDowned = false;
const { col, row } = this.getTableSize();
this.tableSize = { col, row };
}
toHTML(): string {
console.log('Table size', this.tableSize);
return createTable(this.tableSize.row, this.tableSize.col);
}
prepare() {
this.selection = new TableSelection(this);
}
getTableSize() {
const { tableSize: { col, row }, colState, rowState } = this.store.getState();
const maxRowFromState = Math.max(...Object.keys(rowState).map(el => +el));
const maxColFromState = Math.max(...Object.keys(colState).map(el => +el));
const normalTableSize = {
row: Math.max(maxRowFromState, row),
col: Math.max(maxColFromState, col),
};
if ((maxColFromState !== this.tableSize.col) || (maxRowFromState !== this.tableSize.row)) {
this.dispatchToStore(changeTableSize(normalTableSize));
}
return normalTableSize;
}
init() {
super.init();
this.initTable();
this.$onEventFromObserver('formula:input', this.updateCurrentText);
this.$onEventFromObserver('formula:enter-press', () => this.selection.$currentCell.focus());
this.$onEventFromObserver('toolbar:applyStyle', this.updateCurrentStyles);
this.$onEventFromObserver('toolbar:add-row', this.addNewRowHandler);
this.$onEventFromObserver('toolbar:remove-row', this.removeRowHandler);
}
initTable() {
this.initTableSize();
this.initTableContentAndStyles();
this.initStartCellFocus();
}
initTableSize() {
const size = {
col: this.store.getState()?.colState,
row: this.store.getState()?.rowState,
};
this.initColSizes(this.$root, size.col);
this.initRowSizes(this.$root, size.row);
}
initRowSizes(rootElem: Dom, rowState = this.store.getState()?.rowState) {
if (!rootElem || !rootElem?.$el) return;
Object.keys(rowState).forEach(key => {
const rows = rootElem.findAll(`[data-row="${key}"]`);
rows.forEach(el => $(el as HTMLElement).css({ height: `${rowState[+key]}px` }));
});
}
initColSizes(rootElem: Dom, colState = this.store.getState()?.colState) {
if (!rootElem || !rootElem?.$el) return;
Object.keys(colState).forEach(key => {
const cols = rootElem.findAll(`[data-col="${key}"]`);
cols.forEach(el => $(el as HTMLElement).css({ width: `${colState[+key]}px` }));
});
}
initTableContentAndStyles() {
const tableState = this.store.getState();
const tableContent = tableState?.dataState;
const tableStyles = tableState?.stylesState;
Object.keys(tableContent).forEach(cellId => {
const $cell = this.$root.find(`[data-id="${cellId}"]`);
const styles = tableStyles[cellId];
if (!$cell.isExist) return;
$cell.text = parse(tableContent[cellId]) || '';
$cell.setData('value', tableContent[cellId]);
$cell.css(styles);
});
}
initStartCellFocus() {
const $cell = this.$root.find(`[data-id="${startCellId}"]`);
this.selection.select($cell);
this.$emitEventToObserver('table:select-cell', $cell);
}
emitSelectCallback() {
this.$emitEventToObserver('table:select-cell', this.selection.$currentCell);
const styles = this.selection.$currentCell?.getStyles(Object.keys(initialStyleState));
this.dispatchToStore(changeCurrentStyles(styles));
this.dispatchToStore(changeCurrentText(this.selection.$currentCell.text));
}
async resizeTable(event: MouseEvent) {
try {
if (!$(event.target).closest('[data-resize]').isExist) return;
this.tableResizing = true;
const resizeData = await resizeHandler(this.$root, event);
this.dispatchToStore(actions.tableResize(resizeData));
this.tableResizing = false;
} catch (e) {
console.warn('Resize error', e.message);
}
}
updateCurrentText = (text: string) => {
this.selection.$currentCell.attr('data-value', text);
this.selection.$currentCell.text = parse(text);
this.dispatchToStore(actions.changeText({
text,
id: this.selection.$currentCell.data.id || startCellId,
}));
};
updateCurrentStyles = (style: Partial<CSSStyleDeclaration>) => {
this.selection.applyStyle(style);
this.dispatchToStore(actions.applyStyle({
value: style,
ids: this.selection.selectedIds,
}));
};
addNewRowHandler = () => {
this.tableSize.row++;
this.$root.$el.insertAdjacentHTML('beforeend', getNewRowHTML(this.tableSize.row, this.tableSize.col));
const $newRow = $(`[data-row="${this.tableSize.row - 1}"]`);
this.initColSizes($newRow);
this.dispatchToStore(changeTableSize(this.tableSize));
};
onMousedown(event: MouseEvent) {
this.isMouseDowned = true;
selectHandler(event, this.selection, this.emitSelectCallback.bind(this));
this.resizeTable(event);
}
onKeydown(event: KeyboardEvent) {
selectHandler(event, this.selection, this.emitSelectCallback.bind(this));
}
onInput(event: InputEvent) {
this.updateCurrentText((event.target as HTMLElement).innerText);
}
onMouseover(event: MouseEvent) {
this.isMouseDowned && !this.tableResizing && selectHandler(event, this.selection);
}
onMouseup() {
this.isMouseDowned = false;
}
removeRowHandler = () => {
const cell = this.selection.$focusedCell;
const cellId = getCellId(cell);
if (!cellId) return;
const { row } = cellId;
const nodeToRemove = this.$root.find(`[data-row='${row}']`);
this.$root.removeChild(nodeToRemove);
this.selection.clearSelection();
this.dispatchToStore(removeRowFromTable(+row));
};
}