add column/row adding, removing
This commit is contained in:
parent
e12efe60b1
commit
3bb80c1ddf
124
src/components/ContextMenu/ContextMenu.ts
Normal file
124
src/components/ContextMenu/ContextMenu.ts
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import { BaseComponentOption } from 'components/excel/Excel';
|
||||||
|
import { $, Dom } from 'core/Dom';
|
||||||
|
import { ExcelComponent } from 'core/ExcelComponent';
|
||||||
|
|
||||||
|
export type ContextSelectType = {
|
||||||
|
event: ContextEventType,
|
||||||
|
target: Dom,
|
||||||
|
};
|
||||||
|
|
||||||
|
type ContextEventType =
|
||||||
|
'add-row-before' |
|
||||||
|
'remove-row' |
|
||||||
|
'add-row-after' |
|
||||||
|
'add-col-before' |
|
||||||
|
'remove-col' |
|
||||||
|
'add-col-after';
|
||||||
|
|
||||||
|
export class ContextMenu extends ExcelComponent {
|
||||||
|
static className = 'excel__contextmenu_layer';
|
||||||
|
private contextMenu: Dom;
|
||||||
|
private contextType: string;
|
||||||
|
private contextFor: Dom;
|
||||||
|
|
||||||
|
constructor($root: Dom, options: BaseComponentOption) {
|
||||||
|
super($root, {
|
||||||
|
...options,
|
||||||
|
name: 'ContextMenu',
|
||||||
|
eventListeners: ['click', 'mouseup'],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$onEventFromObserver('table:contextmenu', this.initContext);
|
||||||
|
this.$onEventFromObserver('table:rerendercontext', this.rerender);
|
||||||
|
|
||||||
|
this.contextType = 'row';
|
||||||
|
}
|
||||||
|
|
||||||
|
toHTML(): string {
|
||||||
|
return this.createContextMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
afterRender() {
|
||||||
|
super.afterRender();
|
||||||
|
|
||||||
|
this.contextMenu = $('#excel__contextmenu');
|
||||||
|
}
|
||||||
|
|
||||||
|
transformContextMenuCoords(coords: { left: number, top: number }) {
|
||||||
|
this.contextMenu.css({
|
||||||
|
left: `${coords.left.toString()}px`,
|
||||||
|
top: `${coords.top.toString()}px`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
hideContextMenu() {
|
||||||
|
this.transformContextMenuCoords({ left: 0, top: -100 });
|
||||||
|
this.$root.removeClass('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
initContext = (event: MouseEvent) => {
|
||||||
|
const $target = $(event.target);
|
||||||
|
if (!$target.closest('[data-header]').isExist) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
this.contextFor = $target;
|
||||||
|
|
||||||
|
if ($target.data.header && $target.data.header !== this.contextType) {
|
||||||
|
this.contextType = $target.data.header;
|
||||||
|
this.$root.html(this.createContextMenu());
|
||||||
|
this.contextMenu = $('#excel__contextmenu');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.transformContextMenuCoords({ top: event.clientY, left: event.clientX });
|
||||||
|
this.$root.addClass('active');
|
||||||
|
};
|
||||||
|
|
||||||
|
emitSelectItem(contextItem: Dom) {
|
||||||
|
if (!contextItem.isExist) return;
|
||||||
|
|
||||||
|
const contextEvent = contextItem?.data.contextitem;
|
||||||
|
if (!contextEvent) return;
|
||||||
|
|
||||||
|
const select = {
|
||||||
|
target: this.contextFor,
|
||||||
|
event: contextEvent,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.$emitEventToObserver('context-menu: select', select);
|
||||||
|
}
|
||||||
|
|
||||||
|
createContextMenu() {
|
||||||
|
if (this.contextType === 'row') {
|
||||||
|
return `
|
||||||
|
<div class="excel__contextmenu" id="excel__contextmenu">
|
||||||
|
<div class="excel__contextmenu_item" tabindex="-1" data-contextitem="add-row-before">Добавить строку сверху</div>
|
||||||
|
<div class="excel__contextmenu_item" tabindex="-1" data-contextitem="remove-row">Удалить строку</div>
|
||||||
|
<div class="excel__contextmenu_item" tabindex="-1" data-contextitem="add-row-after">Добавить строку снизу</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="excel__contextmenu" id="excel__contextmenu">
|
||||||
|
<div class="excel__contextmenu_item" tabindex="-1" data-contextitem="add-col-before">Добавить столбец слева</div>
|
||||||
|
<div class="excel__contextmenu_item" tabindex="-1" data-contextitem="remove-col">Удалить столбец</div>
|
||||||
|
<div class="excel__contextmenu_item" tabindex="-1" data-contextitem="add-col-after">Добавить столбец справа</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick(e: MouseEvent) {
|
||||||
|
const $target = $(e.target);
|
||||||
|
if ($target.closest('#excel__contextmenu').isExist) {
|
||||||
|
this.emitSelectItem($target);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hideContextMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseup() {
|
||||||
|
this.hideContextMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,19 +1,26 @@
|
|||||||
import { $ } from 'core/Dom';
|
import { ContextMenu } from 'components/ContextMenu/ContextMenu';
|
||||||
|
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 { ComponentManager } from 'core/ComponentManager';
|
||||||
import { Observer } from 'core/Observer';
|
import { Observer } from 'core/Observer';
|
||||||
import { ExcelComponent } from 'core/ExcelComponent';
|
|
||||||
import { Store } from 'core/store/Store';
|
import { Store } from 'core/store/Store';
|
||||||
import { StoreSubscriber } from 'core/StoreSubscriber';
|
import { StoreSubscriber } from 'core/StoreSubscriber';
|
||||||
import { updateOpenDate } from 'redux/action-creators';
|
import { updateOpenDate } from 'redux/action-creators';
|
||||||
|
|
||||||
// TODO type for components array
|
// TODO type for components array
|
||||||
interface ExcelOptionsType {
|
interface ExcelOptionsType {
|
||||||
components: any[],
|
components: ComponentType[],
|
||||||
store: Store,
|
store: Store,
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaseComponentOption = {
|
export type ComponentType = typeof Header | typeof Toolbar | typeof Formula | typeof Table | typeof ContextMenu;
|
||||||
|
|
||||||
|
export type BaseComponentOption = {
|
||||||
observer: Observer;
|
observer: Observer;
|
||||||
store: Store;
|
store: Store;
|
||||||
|
componentManager: ComponentManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Excel {
|
export class Excel {
|
||||||
@ -21,44 +28,38 @@ export class Excel {
|
|||||||
observer: Observer;
|
observer: Observer;
|
||||||
store: Store;
|
store: Store;
|
||||||
subscriber: StoreSubscriber;
|
subscriber: StoreSubscriber;
|
||||||
|
componentManage: ComponentManager;
|
||||||
|
|
||||||
constructor(options: ExcelOptionsType) {
|
constructor(options: ExcelOptionsType) {
|
||||||
this.components = options.components;
|
|
||||||
this.observer = new Observer();
|
|
||||||
this.store = options.store;
|
this.store = options.store;
|
||||||
|
this.components = options.components;
|
||||||
|
|
||||||
this.subscriber = new StoreSubscriber(this.store);
|
this.subscriber = new StoreSubscriber(this.store);
|
||||||
|
this.observer = new Observer();
|
||||||
|
this.componentManage = new ComponentManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
getRoot() {
|
getRoot() {
|
||||||
const $root = $.create('div', 'excel');
|
|
||||||
|
|
||||||
const componentOptions: BaseComponentOption = {
|
const componentOptions: BaseComponentOption = {
|
||||||
observer: this.observer,
|
observer: this.observer,
|
||||||
store: this.store,
|
store: this.store,
|
||||||
|
componentManager: this.componentManage,
|
||||||
};
|
};
|
||||||
|
this.components = this.components.map(Component => this.componentManage.createComponent(Component, componentOptions));
|
||||||
|
this.componentManage.addComponentsToRoot(this.components);
|
||||||
|
|
||||||
this.components = this.components.map(Component => {
|
return this.componentManage.$rootExcelElement;
|
||||||
const $el = $.create('div', Component.className);
|
|
||||||
const component: ExcelComponent = new Component($el, componentOptions);
|
|
||||||
|
|
||||||
$el.html(component.toHTML());
|
|
||||||
$root.append($el.$el);
|
|
||||||
|
|
||||||
return component;
|
|
||||||
});
|
|
||||||
|
|
||||||
return $root;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
afterRender() {
|
||||||
this.subscriber.subscribeComponents(this.components);
|
this.subscriber.subscribeComponents(this.components);
|
||||||
this.components.forEach(component => component.init());
|
this.components.forEach(component => component.afterRender());
|
||||||
|
|
||||||
this.store.dispatchToStore(updateOpenDate(Date.now().toString()));
|
this.store.dispatchToStore(updateOpenDate(Date.now().toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.subscriber.unsubscribeFromStore();
|
this.subscriber.unsubscribeFromStore();
|
||||||
this.components.forEach(component => component.destroy());
|
this.componentManage.destroyComponents();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
|
import { BaseComponentOption } from 'components/excel/Excel';
|
||||||
import { Dom } from 'core/Dom';
|
import { Dom } from 'core/Dom';
|
||||||
import { ExcelComponent, ComponentOptionsType } from 'core/ExcelComponent';
|
import { ExcelComponent } from 'core/ExcelComponent';
|
||||||
|
|
||||||
export class Formula extends ExcelComponent {
|
export class Formula extends ExcelComponent {
|
||||||
static className = 'excel__formula';
|
static className = 'excel__formula';
|
||||||
|
|
||||||
private formulaInput: Dom;
|
private formulaInput: Dom;
|
||||||
|
|
||||||
constructor($root: Dom, options: ComponentOptionsType) {
|
constructor($root: Dom, options: BaseComponentOption) {
|
||||||
super($root, {
|
super($root, {
|
||||||
...options,
|
...options,
|
||||||
eventListeners: ['input', 'keydown'],
|
eventListeners: ['input', 'keydown'],
|
||||||
@ -22,8 +23,8 @@ export class Formula extends ExcelComponent {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
afterRender() {
|
||||||
super.init();
|
super.afterRender();
|
||||||
|
|
||||||
this.formulaInput = this.$root.find('#formula-input');
|
this.formulaInput = this.$root.find('#formula-input');
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { ComponentOptionsType, ExcelComponent } from 'core/ExcelComponent';
|
import { BaseComponentOption } from 'components/excel/Excel';
|
||||||
|
import { ExcelComponent } from 'core/ExcelComponent';
|
||||||
import * as actions from 'redux/action-creators';
|
import * as actions from 'redux/action-creators';
|
||||||
import { $, Dom } from 'core/Dom';
|
import { $, Dom } from 'core/Dom';
|
||||||
import { ActiveRoute } from 'core/routes/ActiveRoute';
|
import { ActiveRoute } from 'core/routes/ActiveRoute';
|
||||||
@ -7,7 +8,7 @@ import { deleteTable } from 'redux/action-creators';
|
|||||||
export class Header extends ExcelComponent {
|
export class Header extends ExcelComponent {
|
||||||
static className = 'excel__header';
|
static className = 'excel__header';
|
||||||
|
|
||||||
constructor($root: Dom, options: ComponentOptionsType) {
|
constructor($root: Dom, options: BaseComponentOption) {
|
||||||
super($root, {
|
super($root, {
|
||||||
...options,
|
...options,
|
||||||
name: 'Header',
|
name: 'Header',
|
||||||
|
|||||||
@ -1,11 +1,21 @@
|
|||||||
|
import { ContextSelectType } from 'components/ContextMenu/ContextMenu';
|
||||||
|
import { BaseComponentOption } from 'components/excel/Excel';
|
||||||
import { startCellId } from 'components/table/table.functions';
|
import { startCellId } from 'components/table/table.functions';
|
||||||
import * as actions from 'redux/action-creators';
|
import * as actions from 'redux/action-creators';
|
||||||
import { $, Dom } from 'core/Dom';
|
import { $, Dom } from 'core/Dom';
|
||||||
import { ComponentOptionsType, ExcelComponent } from 'core/ExcelComponent';
|
import { ExcelComponent } from 'core/ExcelComponent';
|
||||||
import { TableSelection } from 'components/table/TableSelection';
|
import { TableSelection } from 'components/table/TableSelection';
|
||||||
import { changeCurrentStyles, changeCurrentText, changeTableSize, removeRowFromTable } from 'redux/action-creators';
|
import {
|
||||||
import { createTable, getNewRowHTML } from 'components/table/table.template';
|
addCol,
|
||||||
import { initialState, initialStyleState } from 'src/constants';
|
addRow,
|
||||||
|
changeCurrentStyles,
|
||||||
|
changeCurrentText,
|
||||||
|
changeTableSize, removeColFromTable,
|
||||||
|
removeRowFromTable,
|
||||||
|
} from 'redux/action-creators';
|
||||||
|
import { createTable } from 'components/table/table.template';
|
||||||
|
import { TableSizeType } from 'redux/types';
|
||||||
|
import { initialStyleState } from 'src/constants';
|
||||||
import { getCellId, parse } from 'core/utils';
|
import { getCellId, parse } from 'core/utils';
|
||||||
import { resizeHandler } from 'components/table/handlers/table.resize';
|
import { resizeHandler } from 'components/table/handlers/table.resize';
|
||||||
import { selectHandler } from 'components/table/handlers/table.select.handler';
|
import { selectHandler } from 'components/table/handlers/table.select.handler';
|
||||||
@ -15,28 +25,39 @@ export class Table extends ExcelComponent {
|
|||||||
|
|
||||||
private selection: TableSelection;
|
private selection: TableSelection;
|
||||||
private isMouseDowned: boolean;
|
private isMouseDowned: boolean;
|
||||||
private tableResizing = false;
|
private tableResizing: boolean;
|
||||||
public tableSize = { row: initialState.tableSize.row, col: initialState.tableSize.col };
|
public tableSize: TableSizeType;
|
||||||
|
|
||||||
constructor($root: Dom, options: ComponentOptionsType) {
|
constructor($root: Dom, options: BaseComponentOption) {
|
||||||
super($root, {
|
super($root, {
|
||||||
...options,
|
...options,
|
||||||
name: 'Table',
|
name: 'Table',
|
||||||
eventListeners: ['mousedown', 'keydown', 'input', 'mouseover', 'mouseup'],
|
eventListeners: ['mousedown', 'keydown', 'input', 'mouseover', 'mouseup', 'contextmenu'],
|
||||||
});
|
});
|
||||||
|
|
||||||
this.isMouseDowned = false;
|
|
||||||
const { col, row } = this.getTableSize();
|
|
||||||
this.tableSize = { col, row };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toHTML(): string {
|
toHTML(): string {
|
||||||
console.log('Table size', this.tableSize);
|
|
||||||
return createTable(this.tableSize.row, this.tableSize.col);
|
return createTable(this.tableSize.row, this.tableSize.col);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepare() {
|
beforeRender() {
|
||||||
this.selection = new TableSelection(this);
|
this.selection = new TableSelection(this);
|
||||||
|
this.tableResizing = false;
|
||||||
|
this.isMouseDowned = false;
|
||||||
|
this.tableSize = this.getTableSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
afterRender() {
|
||||||
|
super.afterRender();
|
||||||
|
|
||||||
|
this.initTable();
|
||||||
|
|
||||||
|
this.$onEventFromObserver('formula:input', this.updateTextInCell);
|
||||||
|
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);
|
||||||
|
this.$onEventFromObserver('context-menu: select', this.contextMenuHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTableSize() {
|
getTableSize() {
|
||||||
@ -49,25 +70,13 @@ export class Table extends ExcelComponent {
|
|||||||
col: Math.max(maxColFromState, col),
|
col: Math.max(maxColFromState, col),
|
||||||
};
|
};
|
||||||
|
|
||||||
if ((maxColFromState !== this.tableSize.col) || (maxRowFromState !== this.tableSize.row)) {
|
if ((maxColFromState !== this.tableSize?.col) || (maxRowFromState !== this.tableSize?.row)) {
|
||||||
this.dispatchToStore(changeTableSize(normalTableSize));
|
this.dispatchToStore(changeTableSize(normalTableSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
return normalTableSize;
|
return normalTableSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
|
||||||
super.init();
|
|
||||||
|
|
||||||
this.initTable();
|
|
||||||
|
|
||||||
this.$onEventFromObserver('formula:input', this.updateTextInCell);
|
|
||||||
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() {
|
initTable() {
|
||||||
this.initTableSize();
|
this.initTableSize();
|
||||||
this.initTableContentAndStyles();
|
this.initTableContentAndStyles();
|
||||||
@ -156,7 +165,6 @@ export class Table extends ExcelComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
updateTextInCell = (text: string, $cell = this.selection.$focusedCell) => {
|
updateTextInCell = (text: string, $cell = this.selection.$focusedCell) => {
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
$cell.attr('data-value', text);
|
$cell.attr('data-value', text);
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
$cell.text = parse(text);
|
$cell.text = parse(text);
|
||||||
@ -165,15 +173,91 @@ export class Table extends ExcelComponent {
|
|||||||
text,
|
text,
|
||||||
id: $cell.data.id || startCellId,
|
id: $cell.data.id || startCellId,
|
||||||
}));
|
}));
|
||||||
|
this.selection.focusToCell($cell);
|
||||||
};
|
};
|
||||||
|
|
||||||
addNewRowHandler = () => {
|
addNewRowHandler = () => {
|
||||||
this.tableSize.row++;
|
this.addNewColRow('row', 'after', 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));
|
addNewColRow(type: 'col' | 'row', position: 'after' | 'before', targetIndex: number) {
|
||||||
|
if (targetIndex === undefined) return;
|
||||||
|
type === 'col'
|
||||||
|
? this.dispatchToStore(addCol({ position, targetIndex }))
|
||||||
|
: this.dispatchToStore(addRow({ position, targetIndex }));
|
||||||
|
|
||||||
|
this.rerender();
|
||||||
|
}
|
||||||
|
|
||||||
|
removeRowHandler = () => {
|
||||||
|
const cell = this.selection.$focusedCell;
|
||||||
|
const cellId = getCellId(cell);
|
||||||
|
if (!cellId) return;
|
||||||
|
|
||||||
|
const { row } = cellId;
|
||||||
|
this.removeColRow('row', +row);
|
||||||
|
};
|
||||||
|
|
||||||
|
removeColRow = (type: 'col' | 'row', index: number) => {
|
||||||
|
if (!type || !index) return;
|
||||||
|
|
||||||
|
type === 'col'
|
||||||
|
? this.dispatchToStore(removeColFromTable(index))
|
||||||
|
: this.dispatchToStore(removeRowFromTable(index));
|
||||||
|
this.rerender();
|
||||||
|
};
|
||||||
|
|
||||||
|
contextMenuHandler = (select: ContextSelectType) => {
|
||||||
|
const { event, target } = select;
|
||||||
|
|
||||||
|
switch (event) {
|
||||||
|
case 'add-row-before': {
|
||||||
|
const idx = target.closest('[data-row]').data.row;
|
||||||
|
if (!idx) return;
|
||||||
|
this.addNewColRow('row', 'before', +idx);
|
||||||
|
// this.selection.selectByCellId({ col: 2, row: 2 });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'add-row-after': {
|
||||||
|
const idx = target.closest('[data-row]').data.row;
|
||||||
|
if (!idx) return;
|
||||||
|
this.addNewColRow('row', 'after', +idx);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'remove-row': {
|
||||||
|
const idx = target.closest('[data-row]').data.row;
|
||||||
|
if (!idx) return;
|
||||||
|
|
||||||
|
this.removeColRow('row', +idx);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'add-col-before': {
|
||||||
|
const idx = target.closest('[data-col]').data.col;
|
||||||
|
if (!idx) return;
|
||||||
|
this.addNewColRow('col', 'before', +idx);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'add-col-after': {
|
||||||
|
const idx = target.closest('[data-col]').data.col;
|
||||||
|
if (!idx) return;
|
||||||
|
this.addNewColRow('col', 'after', +idx);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'remove-col': {
|
||||||
|
const idx = target.closest('[data-col]').data.col;
|
||||||
|
if (!idx) return;
|
||||||
|
|
||||||
|
this.removeColRow('col', +idx);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMousedown(event: MouseEvent) {
|
onMousedown(event: MouseEvent) {
|
||||||
@ -198,16 +282,12 @@ export class Table extends ExcelComponent {
|
|||||||
this.isMouseDowned = false;
|
this.isMouseDowned = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeRowHandler = () => {
|
onContextmenu(event: MouseEvent) {
|
||||||
const cell = this.selection.$focusedCell;
|
const $target = $(event.target);
|
||||||
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.selection.clearSelection();
|
||||||
|
this.selection.selectHeadRowCol($target);
|
||||||
|
|
||||||
this.dispatchToStore(removeRowFromTable(+row));
|
this.$emitEventToObserver('table:contextmenu', event);
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,7 +30,7 @@ export class TableSelection {
|
|||||||
const range = new Range();
|
const range = new Range();
|
||||||
const node = $cell.$el;
|
const node = $cell.$el;
|
||||||
|
|
||||||
range.setStartAfter(node.childNodes[0]);
|
range.setStartAfter(node.childNodes[node.childNodes.length - 1]);
|
||||||
|
|
||||||
window.getSelection()?.removeAllRanges();
|
window.getSelection()?.removeAllRanges();
|
||||||
window.getSelection()?.addRange(range);
|
window.getSelection()?.addRange(range);
|
||||||
@ -143,4 +143,23 @@ export class TableSelection {
|
|||||||
isCellInSelection($cell: Dom) {
|
isCellInSelection($cell: Dom) {
|
||||||
return this.selectedCellsGroup.includes($cell);
|
return this.selectedCellsGroup.includes($cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selectHeadRowCol($target: Dom) {
|
||||||
|
const row = $target.closest('[data-header="row"]');
|
||||||
|
const col = $target.closest('[data-header="col"]');
|
||||||
|
const resizer = $target.closest('[data-resize]');
|
||||||
|
|
||||||
|
if (row.$el && !resizer.$el) {
|
||||||
|
const cells = row.closest('[data-row]').findAll('[data-type="cell"]');
|
||||||
|
const $cells = Array.from(cells).map(cell => $(cell as HTMLElement));
|
||||||
|
|
||||||
|
this.selectGroupies($cells);
|
||||||
|
} else if (col.$el && !resizer.$el) {
|
||||||
|
const colNumber = col.data.col;
|
||||||
|
const columns = this.rootTable.$root.findAll(`[data-col="${colNumber}"]`);
|
||||||
|
const $cells = Array.from(columns).filter(el => el !== col.$el).map(el => $(el as HTMLElement));
|
||||||
|
|
||||||
|
this.selectGroupies($cells);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,22 +33,7 @@ export function selectHandler(event: MouseEvent | KeyboardEvent, selection: Tabl
|
|||||||
else if (event.ctrlKey) selection.addCellToSelection(target);
|
else if (event.ctrlKey) selection.addCellToSelection(target);
|
||||||
else selection.select(target);
|
else selection.select(target);
|
||||||
} else {
|
} else {
|
||||||
const row = target.closest('[data-header="row"]');
|
selection.selectHeadRowCol(target);
|
||||||
const col = target.closest('[data-header="col"]');
|
|
||||||
const resizer = target.closest('[data-resize]');
|
|
||||||
|
|
||||||
if (row.$el && !resizer.$el) {
|
|
||||||
const cells = row.closest('[data-row]').findAll('[data-type="cell"]');
|
|
||||||
const $cells = Array.from(cells).map(cell => $(cell as HTMLElement));
|
|
||||||
|
|
||||||
selection.selectGroupies($cells);
|
|
||||||
} else if (col.$el && !resizer.$el) {
|
|
||||||
const colNumber = col.data.col;
|
|
||||||
const columns = selection.rootTable.$root.findAll(`[data-col="${colNumber}"]`);
|
|
||||||
const $cells = Array.from(columns).filter(el => el !== col.$el).map(el => $(el as HTMLElement));
|
|
||||||
|
|
||||||
selection.selectGroupies($cells);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -62,12 +62,3 @@ export function createTable(rowsCount = 10, columnCount = 10) {
|
|||||||
|
|
||||||
return rows.join('');
|
return rows.join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNewRowHTML(rowIndex: number, colCount: number): string {
|
|
||||||
const cells = new Array(colCount)
|
|
||||||
.fill('')
|
|
||||||
.map((el, colIndex) => createCell('', colIndex, rowIndex - 1))
|
|
||||||
.join('');
|
|
||||||
|
|
||||||
return createRow(cells, rowIndex.toString(), true, rowIndex - 1);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
|
import { BaseComponentOption } from 'components/excel/Excel';
|
||||||
import { startCellId } from 'components/table/table.functions';
|
import { startCellId } from 'components/table/table.functions';
|
||||||
import { $, Dom } from 'core/Dom';
|
import { $, Dom } from 'core/Dom';
|
||||||
import { ExcelComponentState } from 'core/ExcelComponentState';
|
import { ExcelComponentState } from 'core/ExcelComponentState';
|
||||||
import { ComponentOptionsType } from 'core/ExcelComponent';
|
|
||||||
import { createToolbar } from 'components/toolbar/toolbar.template';
|
import { createToolbar } from 'components/toolbar/toolbar.template';
|
||||||
import { StateType } from 'redux/types';
|
import { StateType } from 'redux/types';
|
||||||
import { fontSizes, initialStyleState } from 'src/constants';
|
import { fontSizes, initialStyleState } from 'src/constants';
|
||||||
@ -9,7 +9,7 @@ import { fontSizes, initialStyleState } from 'src/constants';
|
|||||||
export class Toolbar extends ExcelComponentState {
|
export class Toolbar extends ExcelComponentState {
|
||||||
static className = 'excel__toolbar';
|
static className = 'excel__toolbar';
|
||||||
|
|
||||||
constructor($root: Dom, options: ComponentOptionsType) {
|
constructor($root: Dom, options: BaseComponentOption) {
|
||||||
super($root, {
|
super($root, {
|
||||||
...options,
|
...options,
|
||||||
eventListeners: ['click', 'change'],
|
eventListeners: ['click', 'change'],
|
||||||
@ -18,7 +18,7 @@ export class Toolbar extends ExcelComponentState {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
prepare() {
|
beforeRender() {
|
||||||
const currentToolbarState = this.toolbarState;
|
const currentToolbarState = this.toolbarState;
|
||||||
|
|
||||||
this.initComponentState(currentToolbarState);
|
this.initComponentState(currentToolbarState);
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { startCellId } from 'components/table/table.functions';
|
import { startCellId } from 'components/table/table.functions';
|
||||||
import { ToolbarStateType } from 'components/toolbar/toolbar-types';
|
import { ToolbarStateType } from 'components/toolbar/toolbar-types';
|
||||||
import { storageName } from 'pages/ExcelPage';
|
|
||||||
import { StateType } from 'redux/types';
|
import { StateType } from 'redux/types';
|
||||||
import { storage } from 'core/utils';
|
import { storage, storageName } from 'core/utils';
|
||||||
|
|
||||||
export const fontSizes = [
|
export const fontSizes = [
|
||||||
'12px',
|
'12px',
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { storage } from 'core/utils';
|
import { storage, storageName } from 'core/utils';
|
||||||
import { storageName } from 'pages/ExcelPage';
|
|
||||||
import { StateType } from 'redux/types';
|
import { StateType } from 'redux/types';
|
||||||
import { getNormalizeInitialState } from 'src/constants';
|
import { getNormalizeInitialState } from 'src/constants';
|
||||||
|
|
||||||
|
|||||||
58
src/core/ComponentManager.ts
Normal file
58
src/core/ComponentManager.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { BaseComponentOption, ComponentType } from 'components/excel/Excel';
|
||||||
|
import { $, Dom } from 'core/Dom';
|
||||||
|
import { ExcelComponent } from 'core/ExcelComponent';
|
||||||
|
|
||||||
|
export class ComponentManager {
|
||||||
|
private components: ExcelComponent[];
|
||||||
|
private componentConstructors: {
|
||||||
|
[k: string]: ComponentType;
|
||||||
|
};
|
||||||
|
public $rootExcelElement: Dom;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.componentConstructors = {};
|
||||||
|
this.$rootExcelElement = $.create('div', 'excel');
|
||||||
|
this.components = [];
|
||||||
|
// for debug
|
||||||
|
// @ts-ignore
|
||||||
|
window.componentRoots = this.componentConstructors;
|
||||||
|
}
|
||||||
|
|
||||||
|
createComponent(Component: ComponentType, componentOptions: BaseComponentOption) {
|
||||||
|
const $el = $.create('div', Component.className);
|
||||||
|
const componentInstance: ExcelComponent = new Component($el, componentOptions);
|
||||||
|
|
||||||
|
$el.html(componentInstance.toHTML());
|
||||||
|
|
||||||
|
this.componentConstructors[componentInstance.name] = Component;
|
||||||
|
this.components.push(componentInstance);
|
||||||
|
|
||||||
|
return componentInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
addComponentsToRoot(components: ExcelComponent[]) {
|
||||||
|
components.forEach(component => this.$rootExcelElement.append(component.$root));
|
||||||
|
}
|
||||||
|
|
||||||
|
rerenderComponent(component: ExcelComponent) {
|
||||||
|
const componentConstructor = this.findComponentConstructor(component);
|
||||||
|
const componentOptions: BaseComponentOption = {
|
||||||
|
observer: component.observer,
|
||||||
|
store: component.store,
|
||||||
|
componentManager: this,
|
||||||
|
};
|
||||||
|
const $newComponent = this.createComponent(componentConstructor, componentOptions);
|
||||||
|
|
||||||
|
this.$rootExcelElement.replaceChild($newComponent.$root, component.$root);
|
||||||
|
component.destroy();
|
||||||
|
$newComponent.afterRender();
|
||||||
|
}
|
||||||
|
|
||||||
|
findComponentConstructor(component: ExcelComponent): ComponentType {
|
||||||
|
return this.componentConstructors[component.name];
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyComponents() {
|
||||||
|
Object.values(this.components).forEach(component => component.destroy());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -32,8 +32,8 @@ export class Dom implements DomClass {
|
|||||||
set text(text: string) {
|
set text(text: string) {
|
||||||
if (!this.$el) return;
|
if (!this.$el) return;
|
||||||
|
|
||||||
if (!text) this.$el.textContent = '';
|
if (!text) this.$el.innerText = '';
|
||||||
this.$el.textContent = text;
|
this.$el.innerText = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
get text() {
|
get text() {
|
||||||
@ -47,7 +47,6 @@ export class Dom implements DomClass {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: any
|
|
||||||
append(node: HTMLElement | Dom) {
|
append(node: HTMLElement | Dom) {
|
||||||
let child = node;
|
let child = node;
|
||||||
|
|
||||||
@ -126,7 +125,7 @@ export class Dom implements DomClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
attr(name: string, value: string) {
|
attr(name: string, value: string) {
|
||||||
if (value) {
|
if (value !== undefined) {
|
||||||
this.$el.setAttribute(name, value);
|
this.$el.setAttribute(name, value);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -138,6 +137,10 @@ export class Dom implements DomClass {
|
|||||||
this.$el.removeChild($child.$el);
|
this.$el.removeChild($child.$el);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
replaceChild($newChild: Dom, $oldChild: Dom) {
|
||||||
|
this.$el.replaceChild($newChild.$el, $oldChild.$el);
|
||||||
|
}
|
||||||
|
|
||||||
get isExist(): boolean {
|
get isExist(): boolean {
|
||||||
return !!this.$el;
|
return !!this.$el;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { getMethodNameByEventName } from 'core/utils';
|
|||||||
export class DomListener {
|
export class DomListener {
|
||||||
$root: Dom;
|
$root: Dom;
|
||||||
eventListeners: string[];
|
eventListeners: string[];
|
||||||
protected name: string;
|
name: string;
|
||||||
|
|
||||||
constructor($root: Dom, eventNames: string[]) {
|
constructor($root: Dom, eventNames: string[]) {
|
||||||
if (!$root) throw new Error('Не передали корневой элемент');
|
if (!$root) throw new Error('Не передали корневой элемент');
|
||||||
|
|||||||
@ -1,35 +1,36 @@
|
|||||||
|
import { BaseComponentOption } from 'components/excel/Excel';
|
||||||
|
import { ComponentManager } from 'core/ComponentManager';
|
||||||
import { ActionType, CallbackType, StateType } from 'redux/types';
|
import { ActionType, CallbackType, StateType } from 'redux/types';
|
||||||
import { Dom } from 'core/Dom';
|
import { Dom } from 'core/Dom';
|
||||||
import { DomListener } from 'core/DomListener';
|
import { DomListener } from 'core/DomListener';
|
||||||
import { Observer } from 'core/Observer';
|
import { Observer } from 'core/Observer';
|
||||||
import { Store } from 'core/store/Store';
|
import { Store } from 'core/store/Store';
|
||||||
|
|
||||||
export type ComponentOptionsType = {
|
export type ComponentOptionsType = BaseComponentOption & {
|
||||||
eventListeners: string[];
|
eventListeners: string[];
|
||||||
name: string;
|
name: string;
|
||||||
observer: Observer;
|
subscribe?: (keyof StateType)[],
|
||||||
store: Store;
|
|
||||||
subscribe: (keyof StateType)[],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export abstract class ExcelComponent extends DomListener {
|
export abstract class ExcelComponent extends DomListener {
|
||||||
private observer: Observer;
|
observer: Observer;
|
||||||
public store: Store;
|
public store: Store;
|
||||||
private subscribe: (keyof StateType)[];
|
private subscribe: (keyof StateType)[];
|
||||||
private unsubscribers: CallbackType[];
|
private unsubscribers: CallbackType[];
|
||||||
|
private componentManager: ComponentManager;
|
||||||
|
|
||||||
protected constructor($root: Dom, options: ComponentOptionsType) {
|
protected constructor($root: Dom, options: ComponentOptionsType) {
|
||||||
super($root, options.eventListeners);
|
super($root, options.eventListeners);
|
||||||
this.name = options.name;
|
this.name = options.name;
|
||||||
this.observer = options.observer;
|
this.observer = options.observer;
|
||||||
this.store = options.store;
|
this.store = options.store;
|
||||||
this.subscribe = options.subscribe;
|
this.subscribe = options?.subscribe || [];
|
||||||
|
this.componentManager = options.componentManager;
|
||||||
this.unsubscribers = [];
|
this.unsubscribers = [];
|
||||||
this.prepare();
|
this.beforeRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
prepare() {
|
beforeRender() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +59,7 @@ export abstract class ExcelComponent extends DomListener {
|
|||||||
return this.subscribe?.includes(key);
|
return this.subscribe?.includes(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
afterRender() {
|
||||||
this.initDOMListeners();
|
this.initDOMListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,4 +67,8 @@ export abstract class ExcelComponent extends DomListener {
|
|||||||
this.removeDOMListeners();
|
this.removeDOMListeners();
|
||||||
this.unsubscribers.forEach(unsub => unsub());
|
this.unsubscribers.forEach(unsub => unsub());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rerender() {
|
||||||
|
this.componentManager.rerenderComponent(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -68,10 +68,14 @@ export function getMethodNameByEventName(eventName: string): string {
|
|||||||
return `on${capitalize(eventName)}`;
|
return `on${capitalize(eventName)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCellId($cell: Dom): { row: string, col:string } | false {
|
export function getCellId($cell: Dom): { row: string, col: string } | false {
|
||||||
if (!$cell.isExist) return false;
|
if (!$cell.isExist) return false;
|
||||||
const id = $cell.data.id?.split(':');
|
const id = $cell.data.id?.split(':');
|
||||||
if (!Array.isArray(id)) return false;
|
if (!Array.isArray(id)) return false;
|
||||||
const [row, col] = id;
|
const [row, col] = id;
|
||||||
return { row, col };
|
return { row, col };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function storageName(param: string) {
|
||||||
|
return `excel:${param}`;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { ContextMenu } from 'components/ContextMenu/ContextMenu';
|
||||||
import { AbstractPage } from 'pages/AbstractPage';
|
import { AbstractPage } from 'pages/AbstractPage';
|
||||||
import { Excel } from 'components/excel/Excel';
|
import { Excel } from 'components/excel/Excel';
|
||||||
import { Formula } from 'components/formula/Formula';
|
import { Formula } from 'components/formula/Formula';
|
||||||
@ -10,10 +11,6 @@ import { Table } from 'components/table/Table';
|
|||||||
import { Toolbar } from 'components/toolbar/Toolbar';
|
import { Toolbar } from 'components/toolbar/Toolbar';
|
||||||
import { rootReducer } from 'redux/rootReducer';
|
import { rootReducer } from 'redux/rootReducer';
|
||||||
|
|
||||||
export function storageName(param: string) {
|
|
||||||
return `excel:${param}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ExcelPage extends AbstractPage {
|
export class ExcelPage extends AbstractPage {
|
||||||
private excel: Excel;
|
private excel: Excel;
|
||||||
private storeSub: SubscribeType | null;
|
private storeSub: SubscribeType | null;
|
||||||
@ -35,7 +32,7 @@ export class ExcelPage extends AbstractPage {
|
|||||||
this.storeSub = store.subscribeToStore(this.processor.listen);
|
this.storeSub = store.subscribeToStore(this.processor.listen);
|
||||||
|
|
||||||
this.excel = new Excel({
|
this.excel = new Excel({
|
||||||
components: [Header, Toolbar, Formula, Table],
|
components: [Header, Toolbar, Formula, Table, ContextMenu],
|
||||||
store,
|
store,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -43,7 +40,7 @@ export class ExcelPage extends AbstractPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
afterRender() {
|
afterRender() {
|
||||||
this.excel.init();
|
this.excel.afterRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
|
|||||||
@ -8,3 +8,6 @@ export const UPDATE_DATE = 'UPDATE_DATE';
|
|||||||
export const CHANGE_CURRENT_TEXT = 'CHANGE_CURRENT_TEXT';
|
export const CHANGE_CURRENT_TEXT = 'CHANGE_CURRENT_TEXT';
|
||||||
export const CHANGE_TABLE_SIZE = 'CHANGE_TABLE_SIZE';
|
export const CHANGE_TABLE_SIZE = 'CHANGE_TABLE_SIZE';
|
||||||
export const REMOVE_ROW_FROM_TABLE = 'REMOVE_ROW_FROM_TABLE';
|
export const REMOVE_ROW_FROM_TABLE = 'REMOVE_ROW_FROM_TABLE';
|
||||||
|
export const REMOVE_COL_FROM_TABLE = 'REMOVE_COL_FROM_TABLE';
|
||||||
|
export const ADD_ROW_TO_TABLE = 'ADD_ROW_TO_TABLE';
|
||||||
|
export const ADD_COL_TO_TABLE = 'ADD_COL_TO_TABLE';
|
||||||
|
|||||||
@ -7,7 +7,13 @@ import {
|
|||||||
APPLY_STYLES,
|
APPLY_STYLES,
|
||||||
CHANGE_TITLE,
|
CHANGE_TITLE,
|
||||||
DELETE_TABLE,
|
DELETE_TABLE,
|
||||||
UPDATE_DATE, CHANGE_CURRENT_TEXT, CHANGE_TABLE_SIZE, REMOVE_ROW_FROM_TABLE,
|
UPDATE_DATE,
|
||||||
|
CHANGE_CURRENT_TEXT,
|
||||||
|
CHANGE_TABLE_SIZE,
|
||||||
|
REMOVE_ROW_FROM_TABLE,
|
||||||
|
ADD_ROW_TO_TABLE,
|
||||||
|
ADD_COL_TO_TABLE,
|
||||||
|
REMOVE_COL_FROM_TABLE,
|
||||||
} from 'redux/action-constants';
|
} from 'redux/action-constants';
|
||||||
|
|
||||||
export function tableResize(resizeData: ResizeReturnDataType): ActionType {
|
export function tableResize(resizeData: ResizeReturnDataType): ActionType {
|
||||||
@ -73,9 +79,30 @@ export function changeTableSize(data: { col: number, row: number }): ActionType
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function addRow(data: { position: 'after' | 'before', targetIndex: number }): ActionType {
|
||||||
|
return {
|
||||||
|
type: ADD_ROW_TO_TABLE,
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addCol(data: { position: 'after' | 'before', targetIndex: number }): ActionType {
|
||||||
|
return {
|
||||||
|
type: ADD_COL_TO_TABLE,
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function removeRowFromTable(removedRowNumber: number) {
|
export function removeRowFromTable(removedRowNumber: number) {
|
||||||
return {
|
return {
|
||||||
type: REMOVE_ROW_FROM_TABLE,
|
type: REMOVE_ROW_FROM_TABLE,
|
||||||
data: removedRowNumber,
|
data: removedRowNumber,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function removeColFromTable(removedColNumber: number) {
|
||||||
|
return {
|
||||||
|
type: REMOVE_COL_FROM_TABLE,
|
||||||
|
data: removedColNumber,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { ToolbarStateType } from 'components/toolbar/toolbar-types';
|
import { ToolbarStateType } from 'components/toolbar/toolbar-types';
|
||||||
|
import { storageName } from 'core/utils';
|
||||||
import { ActionType, StateType } from 'redux/types';
|
import { ActionType, StateType } from 'redux/types';
|
||||||
import { ActiveRoute } from 'core/routes/ActiveRoute';
|
import { ActiveRoute } from 'core/routes/ActiveRoute';
|
||||||
import { storageName } from 'pages/ExcelPage';
|
|
||||||
import {
|
import {
|
||||||
CHANGE_TEXT,
|
CHANGE_TEXT,
|
||||||
CHANGE_STYLES,
|
CHANGE_STYLES,
|
||||||
@ -9,7 +9,13 @@ import {
|
|||||||
APPLY_STYLES,
|
APPLY_STYLES,
|
||||||
CHANGE_TITLE,
|
CHANGE_TITLE,
|
||||||
DELETE_TABLE,
|
DELETE_TABLE,
|
||||||
UPDATE_DATE, CHANGE_CURRENT_TEXT, CHANGE_TABLE_SIZE, REMOVE_ROW_FROM_TABLE,
|
UPDATE_DATE,
|
||||||
|
CHANGE_CURRENT_TEXT,
|
||||||
|
CHANGE_TABLE_SIZE,
|
||||||
|
REMOVE_ROW_FROM_TABLE,
|
||||||
|
ADD_ROW_TO_TABLE,
|
||||||
|
ADD_COL_TO_TABLE,
|
||||||
|
REMOVE_COL_FROM_TABLE,
|
||||||
} from 'redux/action-constants';
|
} from 'redux/action-constants';
|
||||||
|
|
||||||
export function rootReducer(state: StateType, action: ActionType): StateType {
|
export function rootReducer(state: StateType, action: ActionType): StateType {
|
||||||
@ -75,6 +81,142 @@ export function rootReducer(state: StateType, action: ActionType): StateType {
|
|||||||
return { ...state, tableSize: action.data };
|
return { ...state, tableSize: action.data };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case ADD_ROW_TO_TABLE: {
|
||||||
|
// needs to be redefined next params:
|
||||||
|
// rowState: { [k: number]: number };
|
||||||
|
// dataState: { [k: string]: string };
|
||||||
|
// stylesState: { [k: string]: ToolbarStateType };
|
||||||
|
// tableSize: { col: number, row: number }
|
||||||
|
const newRowStateEntries: [number, number][] = [];
|
||||||
|
const newDataStateEntries: [string, string][] = [];
|
||||||
|
const newStylesStateEntries: [string, ToolbarStateType][] = [];
|
||||||
|
// rowState change
|
||||||
|
Object.entries(state.rowState).forEach(([key, value]) => {
|
||||||
|
switch (true) {
|
||||||
|
case action.data.position === 'before' && +key < action.data.targetIndex:
|
||||||
|
case action.data.position === 'after' && +key <= action.data.targetIndex:
|
||||||
|
newRowStateEntries.push([+key, value]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case action.data.position === 'before' && +key >= action.data.targetIndex:
|
||||||
|
case action.data.position === 'after' && +key > action.data.targetIndex:
|
||||||
|
newRowStateEntries.push([+key + 1, value]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const newRowState = Object.fromEntries(newRowStateEntries);
|
||||||
|
// dataState change
|
||||||
|
Object.entries(state.dataState).forEach(([key, value]) => {
|
||||||
|
const [row, col] = key.split(':');
|
||||||
|
switch (true) {
|
||||||
|
case action.data.position === 'before' && +row < action.data.targetIndex:
|
||||||
|
case action.data.position === 'after' && +row <= action.data.targetIndex:
|
||||||
|
newDataStateEntries.push([key, value.toString()]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case action.data.position === 'before' && +row >= action.data.targetIndex:
|
||||||
|
case action.data.position === 'after' && +row > action.data.targetIndex:
|
||||||
|
newDataStateEntries.push([`${+row + 1}:${col}`, value.toString()]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const newDataState = Object.fromEntries(newDataStateEntries);
|
||||||
|
// stylesState change
|
||||||
|
Object.entries(state.stylesState).forEach(([key, value]) => {
|
||||||
|
const [row, col] = key.split(':');
|
||||||
|
switch (true) {
|
||||||
|
case action.data.position === 'before' && +row < action.data.targetIndex:
|
||||||
|
case action.data.position === 'after' && +row <= action.data.targetIndex:
|
||||||
|
newStylesStateEntries.push([key, value]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case action.data.position === 'before' && +row >= action.data.targetIndex:
|
||||||
|
case action.data.position === 'after' && +row > action.data.targetIndex:
|
||||||
|
newStylesStateEntries.push([`${+row + 1}:${col}`, value]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const newStylesState = Object.fromEntries(newStylesStateEntries);
|
||||||
|
// tableSize
|
||||||
|
const newTableSize = { col: state.tableSize.col, row: state.tableSize.row + 1 };
|
||||||
|
// debugger
|
||||||
|
return { ...state, rowState: newRowState, dataState: newDataState, stylesState: newStylesState, tableSize: newTableSize };
|
||||||
|
}
|
||||||
|
|
||||||
|
case ADD_COL_TO_TABLE: {
|
||||||
|
// needs to be redefined next params:
|
||||||
|
// colState: { [k: number]: number };
|
||||||
|
// dataState: { [k: string]: string };
|
||||||
|
// stylesState: { [k: string]: ToolbarStateType };
|
||||||
|
// tableSize: { col: number, row: number }
|
||||||
|
const newColStateEntries: [number, number][] = [];
|
||||||
|
const newDataStateEntries: [string, string][] = [];
|
||||||
|
const newStylesStateEntries: [string, ToolbarStateType][] = [];
|
||||||
|
// colState change
|
||||||
|
Object.entries(state.colState).forEach(([key, value]) => {
|
||||||
|
switch (true) {
|
||||||
|
case action.data.position === 'before' && +key < action.data.targetIndex:
|
||||||
|
case action.data.position === 'after' && +key <= action.data.targetIndex:
|
||||||
|
newColStateEntries.push([+key, value]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case action.data.position === 'before' && +key >= action.data.targetIndex:
|
||||||
|
case action.data.position === 'after' && +key > action.data.targetIndex:
|
||||||
|
newColStateEntries.push([+key + 1, value]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const newColState = Object.fromEntries(newColStateEntries);
|
||||||
|
// dataState change
|
||||||
|
Object.entries(state.dataState).forEach(([key, value]) => {
|
||||||
|
const [row, col] = key.split(':');
|
||||||
|
switch (true) {
|
||||||
|
case action.data.position === 'before' && +col < action.data.targetIndex:
|
||||||
|
case action.data.position === 'after' && +col <= action.data.targetIndex:
|
||||||
|
newDataStateEntries.push([key, value.toString()]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case action.data.position === 'before' && +col >= action.data.targetIndex:
|
||||||
|
case action.data.position === 'after' && +col > action.data.targetIndex:
|
||||||
|
newDataStateEntries.push([`${row}:${+col + 1}`, value.toString()]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const newDataState = Object.fromEntries(newDataStateEntries);
|
||||||
|
// stylesState change
|
||||||
|
Object.entries(state.stylesState).forEach(([key, value]) => {
|
||||||
|
const [row, col] = key.split(':');
|
||||||
|
switch (true) {
|
||||||
|
case action.data.position === 'before' && +col < action.data.targetIndex:
|
||||||
|
case action.data.position === 'after' && +col <= action.data.targetIndex:
|
||||||
|
newStylesStateEntries.push([key, value]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case action.data.position === 'before' && +col >= action.data.targetIndex:
|
||||||
|
case action.data.position === 'after' && +col > action.data.targetIndex:
|
||||||
|
newStylesStateEntries.push([`${row}:${+col + 1}`, value]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const newStylesState = Object.fromEntries(newStylesStateEntries);
|
||||||
|
// tableSize
|
||||||
|
const newTableSize = { col: state.tableSize.col + 1, row: state.tableSize.row };
|
||||||
|
// debugger
|
||||||
|
return { ...state, colState: newColState, dataState: newDataState, stylesState: newStylesState, tableSize: newTableSize };
|
||||||
|
}
|
||||||
|
|
||||||
case REMOVE_ROW_FROM_TABLE: {
|
case REMOVE_ROW_FROM_TABLE: {
|
||||||
// needs to be redefined next params:
|
// needs to be redefined next params:
|
||||||
// rowState: { [k: number]: number };
|
// rowState: { [k: number]: number };
|
||||||
@ -135,10 +277,72 @@ export function rootReducer(state: StateType, action: ActionType): StateType {
|
|||||||
// tableSize
|
// tableSize
|
||||||
const newTableSize = { col: state.tableSize.col, row: state.tableSize.row - 1 };
|
const newTableSize = { col: state.tableSize.col, row: state.tableSize.row - 1 };
|
||||||
|
|
||||||
// const newRowState = Object.keys(state.rowState).filter(rowIndex => rowIndex.toString() !== action.data.toString()).
|
|
||||||
return { ...state, rowState: newRowState, dataState: newDataState, stylesState: newStylesState, tableSize: newTableSize };
|
return { ...state, rowState: newRowState, dataState: newDataState, stylesState: newStylesState, tableSize: newTableSize };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case REMOVE_COL_FROM_TABLE: {
|
||||||
|
// needs to be redefined next params:
|
||||||
|
// colState: { [k: number]: number };
|
||||||
|
// dataState: { [k: string]: string };
|
||||||
|
// stylesState: { [k: string]: ToolbarStateType };
|
||||||
|
// tableSize: { col: number, row: number }
|
||||||
|
|
||||||
|
const newColStateEntries: [number, number][] = [];
|
||||||
|
const newDataStateEntries: [string, string][] = [];
|
||||||
|
const newStylesStateEntries: [string, ToolbarStateType][] = [];
|
||||||
|
// colState change
|
||||||
|
Object.entries(state.colState).forEach(([key, value]) => {
|
||||||
|
switch (true) {
|
||||||
|
case key < action.data:
|
||||||
|
newColStateEntries.push([+key, value]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case key > action.data:
|
||||||
|
newColStateEntries.push([+key - 1, value]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const newColState = Object.fromEntries(newColStateEntries);
|
||||||
|
// dataState change
|
||||||
|
Object.entries(state.dataState).forEach(([key, value]) => {
|
||||||
|
const [row, col] = key.split(':');
|
||||||
|
switch (true) {
|
||||||
|
case col < action.data:
|
||||||
|
newDataStateEntries.push([key, value.toString()]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case col > action.data:
|
||||||
|
newDataStateEntries.push([`${row}:${+col - 1}`, value.toString()]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const newDataState = Object.fromEntries(newDataStateEntries);
|
||||||
|
// stylesState change
|
||||||
|
Object.entries(state.stylesState).forEach(([key, value]) => {
|
||||||
|
const [row, col] = key.split(':');
|
||||||
|
switch (true) {
|
||||||
|
case col < action.data:
|
||||||
|
newStylesStateEntries.push([key, value]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case col > action.data:
|
||||||
|
newStylesStateEntries.push([`${row}:${+col - 1}`, value]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const newStylesState = Object.fromEntries(newStylesStateEntries);
|
||||||
|
// tableSize
|
||||||
|
const newTableSize = { col: state.tableSize.col - 1, row: state.tableSize.row };
|
||||||
|
|
||||||
|
return { ...state, colState: newColState, dataState: newDataState, stylesState: newStylesState, tableSize: newTableSize };
|
||||||
|
}
|
||||||
|
|
||||||
default: return state;
|
default: return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/redux/types.d.ts
vendored
10
src/redux/types.d.ts
vendored
@ -5,6 +5,11 @@ export type ActionType = {
|
|||||||
[k: string]: any
|
[k: string]: any
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TableSizeType = {
|
||||||
|
col: number;
|
||||||
|
row: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type StateType = {
|
export type StateType = {
|
||||||
colState: { [k: number]: number };
|
colState: { [k: number]: number };
|
||||||
rowState: { [k: number]: number };
|
rowState: { [k: number]: number };
|
||||||
@ -15,10 +20,7 @@ export type StateType = {
|
|||||||
stylesState: { [k: string]: ToolbarStateType };
|
stylesState: { [k: string]: ToolbarStateType };
|
||||||
title: string;
|
title: string;
|
||||||
currentText: string;
|
currentText: string;
|
||||||
tableSize: {
|
tableSize: TableSizeType;
|
||||||
col: number;
|
|
||||||
row: number;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ReducerType = (state: StateType, action: ActionType) => StateType | null;
|
export type ReducerType = (state: StateType, action: ActionType) => StateType | null;
|
||||||
|
|||||||
43
src/styles/components/contextmenu.scss
Normal file
43
src/styles/components/contextmenu.scss
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
@import "../_variables";
|
||||||
|
@import "../_mixins";
|
||||||
|
|
||||||
|
.excel__contextmenu_layer {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 10;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.excel__contextmenu {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 20;
|
||||||
|
left: 0;
|
||||||
|
top: -100px;
|
||||||
|
width: 170px;
|
||||||
|
height: 100px;
|
||||||
|
background: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.excel__contextmenu_item {
|
||||||
|
flex: 1;
|
||||||
|
border: 2px solid #cecece;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 5px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(206, 206, 206, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,6 +9,7 @@
|
|||||||
top: $header-height + $toolbar-height + $formula-height;
|
top: $header-height + $toolbar-height + $formula-height;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
font-size: $default-cell-font-size;
|
font-size: $default-cell-font-size;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
|
||||||
.row{
|
.row{
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -49,7 +50,7 @@
|
|||||||
border: 1px solid #e1e2e3;
|
border: 1px solid #e1e2e3;
|
||||||
border-top: 0;
|
border-top: 0;
|
||||||
border-left: 0;
|
border-left: 0;
|
||||||
white-space: nowrap;
|
white-space: normal;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
outline: none;
|
outline: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -93,7 +94,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
[data-header="row"] {
|
[data-header="row"] {
|
||||||
cursor: e-resize;
|
cursor: pointer;
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
border-right: 2px solid #3c74ff;
|
border-right: 2px solid #3c74ff;
|
||||||
@ -101,15 +102,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
[data-header="col"] {
|
[data-header="col"] {
|
||||||
cursor: n-resize;
|
cursor: pointer;
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
border-bottom: 2px solid #3c74ff;
|
border-bottom: 2px solid #3c74ff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-header="col"].selected, [data-header="row"].selected {
|
[data-header="col"], [data-header="row"] {
|
||||||
font-weight: bold;
|
&.selected {
|
||||||
background: rgba(60, 116, 255, 0.4);
|
font-weight: bold;
|
||||||
|
background: rgba(60, 116, 255, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #ccc;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
@import './components/toolbar';
|
@import './components/toolbar';
|
||||||
@import './components/formula';
|
@import './components/formula';
|
||||||
@import './components/table';
|
@import './components/table';
|
||||||
|
@import './components/contextmenu';
|
||||||
@import './components/dashboard';
|
@import './components/dashboard';
|
||||||
@import './components/loader';
|
@import './components/loader';
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user