diff --git a/src/components/excel/Excel.ts b/src/components/excel/Excel.ts index 4c30f9a..2e72cd2 100644 --- a/src/components/excel/Excel.ts +++ b/src/components/excel/Excel.ts @@ -5,9 +5,10 @@ import { Store } from 'core/store/Store'; import { StoreSubscriber } from 'core/StoreSubscriber'; import { updateOpenDate } from 'redux/action-creators'; +// TODO type for components array interface ExcelOptionsType { components: any[], - store: any, + store: Store, } type BaseComponentOption = { diff --git a/src/components/header/Header.ts b/src/components/header/Header.ts index a291671..0cdb016 100644 --- a/src/components/header/Header.ts +++ b/src/components/header/Header.ts @@ -17,10 +17,11 @@ export class Header extends ExcelComponent { } toHTML(): string { - const { title } = this.store.getState(); + const { title, id } = this.store.getState(); return ` +
ID: ${id}
@@ -33,7 +34,7 @@ export class Header extends ExcelComponent { } onInput(event: InputEvent) { - const $target = $(event.target as HTMLInputElement); + const $target = $(event.target); this.dispatchToStore(actions.changeTitle($target.text)); } diff --git a/src/components/table/Table.ts b/src/components/table/Table.ts index a3c0bd1..88b1ddb 100644 --- a/src/components/table/Table.ts +++ b/src/components/table/Table.ts @@ -15,6 +15,7 @@ export class Table extends ExcelComponent { private selection: TableSelection; private isMouseDowned: boolean; + private tableResizing = false; public tableSize = { row: 50, col: 24, @@ -92,6 +93,8 @@ export class Table extends ExcelComponent { 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); @@ -116,8 +119,11 @@ export class Table extends ExcelComponent { 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.dispatchToStore(actions.tableResize(resizeData)); + this.tableResizing = false; } catch (e) { console.warn('Resize error', e.message); } @@ -133,7 +139,7 @@ export class Table extends ExcelComponent { })); }; - updateCurrentStyles = (style: CSSStyleRule) => { + updateCurrentStyles = (style: CSSStyleDeclaration) => { this.selection.applyStyle(style); this.dispatchToStore(actions.applyStyle({ value: style, @@ -163,7 +169,7 @@ export class Table extends ExcelComponent { } onMouseover(event: MouseEvent) { - this.isMouseDowned && selectHandler(event, this.selection); + this.isMouseDowned && !this.tableResizing && selectHandler(event, this.selection); } onMouseup() { diff --git a/src/components/table/TableSelection.ts b/src/components/table/TableSelection.ts index 2be8dfa..594b892 100644 --- a/src/components/table/TableSelection.ts +++ b/src/components/table/TableSelection.ts @@ -19,13 +19,17 @@ export class TableSelection { // TODO make a focus manager focusToCell($cell: Dom) { - const range = new Range(); - const node = $cell.$el; + try { + const range = new Range(); + const node = $cell.$el; - range.setStartAfter(node.childNodes[0]); + range.setStartAfter(node.childNodes[0]); - window.getSelection()?.removeAllRanges(); - window.getSelection()?.addRange(range); + window.getSelection()?.removeAllRanges(); + window.getSelection()?.addRange(range); + } catch (e) { + console.log('Error focus', e.message); + } } select($el: Dom) { @@ -83,7 +87,7 @@ export class TableSelection { $cell.addClass(TableSelection.selectedClassName); } - applyStyle(style: CSSStyleRule) { + applyStyle(style: CSSStyleDeclaration) { this.group.forEach(el => el.css(style)); } } diff --git a/src/components/table/handlers/table.resize.ts b/src/components/table/handlers/table.resize.ts index 9e88ed6..3660e5d 100644 --- a/src/components/table/handlers/table.resize.ts +++ b/src/components/table/handlers/table.resize.ts @@ -1,8 +1,7 @@ import { startCellId } from 'components/table/table.functions'; import { $, Dom } from 'core/Dom'; -type CustomElementType = Element & { css: any }; -type ResizeReturnDataType = { value: number, id: string, type: string }; +export type ResizeReturnDataType = { value: number, id: string, type: string }; export function resizeHandler($root: Dom, event: MouseEvent) { return new Promise(res => { @@ -18,13 +17,7 @@ export function resizeHandler($root: Dom, event: MouseEvent) { let delta: number; - (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'; @@ -84,7 +77,7 @@ export function resizeHandler($root: Dom, event: MouseEvent) { res({ value, id, type }); - $resizer.css({ opacity: 0, bottom: 0, right: 0 }); + $resizer.css({ opacity: '0', bottom: '0', right: '0' }); }; }); } diff --git a/src/components/table/handlers/table.select.handler.ts b/src/components/table/handlers/table.select.handler.ts index a1fb57e..8511014 100644 --- a/src/components/table/handlers/table.select.handler.ts +++ b/src/components/table/handlers/table.select.handler.ts @@ -26,7 +26,7 @@ export function selectHandler(event: MouseEvent | KeyboardEvent, selection: Tabl callback?.(); function onMouseDownHandler() { - const target = $(event.target as HTMLElement); + const target = $(event.target); if (isCell(event)) { if (event.shiftKey) selection.selectTo(target); @@ -44,8 +44,8 @@ export function selectHandler(event: MouseEvent | KeyboardEvent, selection: Tabl selection.selectGroupies($cells); } else if (col.$el && !resizer.$el) { const colNumber = col.data.col; - const colls = selection.rootTable.$root.findAll(`[data-col="${colNumber}"]`); - const $cells = Array.from(colls).filter(el => el !== col.$el).map(el => $(el as HTMLElement)); + 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); } @@ -109,6 +109,7 @@ export function selectHandler(event: MouseEvent | KeyboardEvent, selection: Tabl function onMouseOverHandler() { if (selection.current.$el) { + // TODO find event type selection.selectTo($((event as any).toElement)); } } diff --git a/src/components/table/table.functions.ts b/src/components/table/table.functions.ts index 66a8104..e9824be 100644 --- a/src/components/table/table.functions.ts +++ b/src/components/table/table.functions.ts @@ -1,7 +1,7 @@ import { $ } from 'core/Dom'; export function isCell(event: Event): boolean { - return $(event.target as HTMLElement).data.type === 'cell'; + return $(event.target).data.type === 'cell'; } export function getParamsFromCellId(cellId: string) { diff --git a/src/components/toolbar/Toolbar.ts b/src/components/toolbar/Toolbar.ts index b972ea7..44e6e62 100644 --- a/src/components/toolbar/Toolbar.ts +++ b/src/components/toolbar/Toolbar.ts @@ -3,6 +3,7 @@ import { $, Dom } from 'core/Dom'; import { ExcelComponentState } from 'core/ExcelComponentState'; import { ComponentOptionsType } from 'core/ExcelComponent'; import { createToolbar } from 'components/toolbar/toolbar.template'; +import { StateType } from 'redux/types'; import { fontSizes, initialStyleState } from 'src/constants'; export class Toolbar extends ExcelComponentState { @@ -38,15 +39,17 @@ export class Toolbar extends ExcelComponentState { return this.template; } - storeChanged(args?: any) { + storeChanged(args: StateType) { + if (!args) return; + this.setComponentState(args.currentStyles); } onClick(event: MouseEvent) { - //TODO refactor, make style handler - const target = $(event.target as HTMLElement); + // TODO refactor, make style handler + const target = $(event.target); - if (target.closest('[data-addbtn]')) { + if (target.closest('[data-addbtn]').isExist) { this.$emitEventToObserver('toolbar:add-row'); return; } @@ -92,6 +95,7 @@ export class Toolbar extends ExcelComponentState { } } + // TODO find EventType onChange(e: any) { const target = $(e.target); let value = ''; diff --git a/src/constants.ts b/src/constants.ts index 2ae2a16..e6dbb8b 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -36,7 +36,8 @@ export const initialState: StateType = { title: 'New excel table', openDate: Date.now(), currentStyles: initialStyleState, - currentText: 'huy', + currentText: 'initial text', + id: '0', }; export function getNormalizeInitialState(params: string): StateType { diff --git a/src/core/Clients.ts b/src/core/Clients.ts index 70e102a..ca9a73c 100644 --- a/src/core/Clients.ts +++ b/src/core/Clients.ts @@ -4,12 +4,12 @@ import { StateType } from 'redux/types'; import { getNormalizeInitialState } from 'src/constants'; export interface ClientDataType { - save: (state: StateType) => Promise; - get: () => Promise; + save: (state: StateType) => Promise; + get: () => Promise; } export class LocalStorageClient implements ClientDataType { - private name: string; + private readonly name: string; constructor(name: string) { this.name = name; @@ -20,7 +20,7 @@ export class LocalStorageClient implements ClientDataType { return Promise.resolve(); } - get() { + get(): Promise { return new Promise(resolve => { setTimeout(() => { resolve(getNormalizeInitialState(this.name)); diff --git a/src/core/Dom.ts b/src/core/Dom.ts index 5209038..ab1b9a6 100644 --- a/src/core/Dom.ts +++ b/src/core/Dom.ts @@ -1,7 +1,7 @@ -import { ToolbarStateType } from 'components/toolbar/toolbar-types'; +import { CallbackType } from 'redux/types'; import { initialStyleState } from 'src/constants'; -export type SelectorType = string | HTMLElement; +export type SelectorType = string | HTMLElement | EventTarget | null; export interface DomClass { html(html?: string): string | DomClass; @@ -19,7 +19,7 @@ export class Dom implements DomClass { if (!elementFromDOM) throw new Error(`Can't find element with "${selector}" selector`); else this.$el = elementFromDOM as HTMLElement; } else { - this.$el = selector; + this.$el = selector as HTMLElement; } } @@ -30,6 +30,8 @@ export class Dom implements DomClass { } set text(text: string) { + if (!this.$el) return; + if (!text) this.$el.textContent = ''; this.$el.textContent = text; } @@ -46,22 +48,22 @@ export class Dom implements DomClass { } // FIXME: any - append(node: any) { + append(node: HTMLElement | Dom) { let child = node; if (node instanceof Dom) child = node.$el; - if (this.$el.append) this.$el.append(child); - else this.$el.appendChild(child); + if (this.$el.append) this.$el.append(child as HTMLElement); + else this.$el.appendChild(child as HTMLElement); return this; } - on(eventType: string, callback: any) { + on(eventType: string, callback: CallbackType) { this.$el.addEventListener(eventType, callback); } - off(eventType: string, callback: any) { + off(eventType: string, callback: CallbackType) { this.$el.removeEventListener(eventType, callback); } @@ -93,11 +95,11 @@ export class Dom implements DomClass { return this.$el.querySelectorAll(selector); } - css(styles: any) { + css(styles: Partial) { if (!styles) return; Object.keys(styles)?.forEach((key: any) => { - this.$el.style[key] = styles[key]; + this.$el.style[key] = styles[key] as string; }); } @@ -113,11 +115,12 @@ export class Dom implements DomClass { this.$el?.classList.remove(className); } - getStyles(styles: any[]) { + getStyles(styles: string[]): Partial { return styles.reduce((res, s) => { // replace all need if case style value have 2 or more word, this.$el.style[s] return ""word value"" // for example font-family - res[s] = this.$el.style[s].replaceAll('"', '') || initialStyleState[s as keyof ToolbarStateType]; + // @ts-ignore + res[s] = this.$el.style[s].replaceAll('"', '') || initialStyleState[s]; return res; }, {}); } @@ -130,6 +133,10 @@ export class Dom implements DomClass { return this.$el.getAttribute(name); } + + get isExist(): boolean { + return !!this.$el; + } } export function $(selector: SelectorType) { diff --git a/src/core/DomListener.ts b/src/core/DomListener.ts index 36e3ac6..78f6dc4 100644 --- a/src/core/DomListener.ts +++ b/src/core/DomListener.ts @@ -1,9 +1,12 @@ import { Dom } from 'core/Dom'; -import { capitalize } from 'core/utils'; +import { getMethodNameByEventName } from 'core/utils'; + +// TODO fix types export class DomListener { $root: Dom; eventListeners: string[]; + protected name: string; constructor($root: Dom, eventNames: string[]) { if (!$root) throw new Error('Не передали корневой элемент'); @@ -16,26 +19,22 @@ export class DomListener { if (!this.eventListeners) return; this.eventListeners.forEach((listener: string) => { - const method: any = getMethodName(listener); - // @ts-ignore FIXME: + const method = getMethodNameByEventName(listener); + // @ts-ignore this[method] = this[method]?.bind(this); - // @ts-ignore FIXME: + // @ts-ignore if (!this[method]) throw new Error(`Отсутствует метод ${method} в компоненте ${this?.name}`); - // @ts-ignore FIXME: + + // @ts-ignore this.$root.on(listener, this[method]); }); } removeDOMListeners() { this.eventListeners.forEach(listener => { - // @ts-ignore FIXME: - const method: any = getMethodName(listener); - // @ts-ignore FIXME: + const method = getMethodNameByEventName(listener); + // @ts-ignore this.$root.off(listener, this[method]); }); } } - -function getMethodName(eventName: string): string { - return `on${capitalize(eventName)}`; -} diff --git a/src/core/ExcelComponent.ts b/src/core/ExcelComponent.ts index c581ab1..70f3912 100644 --- a/src/core/ExcelComponent.ts +++ b/src/core/ExcelComponent.ts @@ -1,4 +1,4 @@ -import { ActionType, StateType } from 'redux/types'; +import { ActionType, CallbackType, StateType } from 'redux/types'; import { Dom } from 'core/Dom'; import { DomListener } from 'core/DomListener'; import { Observer } from 'core/Observer'; @@ -13,13 +13,12 @@ export type ComponentOptionsType = { }; export abstract class ExcelComponent extends DomListener { - private name: string; private observer: Observer; public store: Store; private subscribe: (keyof StateType)[]; - private unsubscribers: ((args?: any) => any)[]; + private unsubscribers: CallbackType[]; - constructor($root: Dom, options: ComponentOptionsType) { + protected constructor($root: Dom, options: ComponentOptionsType) { super($root, options.eventListeners); this.name = options.name; this.observer = options.observer; @@ -51,7 +50,7 @@ export abstract class ExcelComponent extends DomListener { this.store?.dispatchToStore(action); } - storeChanged(args?: any) { + storeChanged(args: StateType) { console.log('CHANGE STORE: ', args, ' in component ', this.name); } diff --git a/src/core/ExcelComponentState.ts b/src/core/ExcelComponentState.ts index 0ca96ee..026cc6e 100644 --- a/src/core/ExcelComponentState.ts +++ b/src/core/ExcelComponentState.ts @@ -21,6 +21,8 @@ export abstract class ExcelComponentState extends ExcelComponent { } setComponentState(newState: ExcelComponentStateType) { + if (!newState) return; + this.componentState = { ...this.componentState, ...newState }; this.$root.html(this.template); } diff --git a/src/core/Observer.ts b/src/core/Observer.ts index 3b46c30..d110fb5 100644 --- a/src/core/Observer.ts +++ b/src/core/Observer.ts @@ -1,13 +1,15 @@ +import { CallbackType } from 'redux/types'; + export class Observer { - private listeners: { - [k: string]: Array<(args?: any) => any> + private readonly listeners: { + [k: string]: Array }; constructor() { this.listeners = {}; } - subscribe(eventName: string, callback: (args?: any) => any) { + subscribe(eventName: string, callback: CallbackType) { this.listeners[eventName] = this.listeners[eventName] || []; this.listeners[eventName].push(callback); diff --git a/src/core/StoreSubscriber.ts b/src/core/StoreSubscriber.ts index 271563c..9fce204 100644 --- a/src/core/StoreSubscriber.ts +++ b/src/core/StoreSubscriber.ts @@ -1,9 +1,9 @@ -import { StateType } from 'redux/types'; +import { StateType, SubscribeType } from 'redux/types'; import { Store } from 'core/store/Store'; import { isEqual } from 'core/utils'; export class StoreSubscriber { - sub: any; + sub: SubscribeType | null; currentState: StateType; constructor(private store: Store) { @@ -31,6 +31,6 @@ export class StoreSubscriber { } unsubscribeFromStore() { - this.sub.unsubscribe(); + this.sub?.unsubscribe(); } } diff --git a/src/core/routes/Router.ts b/src/core/routes/Router.ts index 3274245..095e1e7 100644 --- a/src/core/routes/Router.ts +++ b/src/core/routes/Router.ts @@ -13,7 +13,7 @@ export class Router { private $placeholder: Dom; private routes: RoutesType; private page: DashboardPage | ExcelPage; - private loader: Dom; + private readonly loader: Dom; constructor(selector: SelectorType, routes: RoutesType) { if (!selector) throw new Error('Selector not provided'); diff --git a/src/core/store/Store.ts b/src/core/store/Store.ts index 7849fe5..3e55293 100644 --- a/src/core/store/Store.ts +++ b/src/core/store/Store.ts @@ -1,8 +1,8 @@ -import { ActionType, ReducerType, StateType, SubscribeType } from 'redux/types'; +import { ActionType, CallbackType, ReducerType, StateType, SubscribeType } from 'redux/types'; export class Store { - state: StateType; - listeners: ((args?: any) => void)[]; + state: StateType | null; + listeners: CallbackType[]; constructor(private reducer: ReducerType, initialState: StateType) { this.state = reducer({ ...initialState }, { type: '__INIT__' }); @@ -20,6 +20,8 @@ export class Store { } dispatchToStore(action: ActionType) { + if (!this.state || !action.type) return; + this.state = this.reducer(this.state, action); this.listeners.forEach(listener => listener(this.state)); } diff --git a/src/core/utils.ts b/src/core/utils.ts index 5cfeebf..d2b9f0d 100644 --- a/src/core/utils.ts +++ b/src/core/utils.ts @@ -1,3 +1,4 @@ +import { CallbackType, StateType } from 'redux/types'; import { fontSizes } from 'src/constants'; export function capitalize(string: string): string { @@ -6,7 +7,7 @@ export function capitalize(string: string): string { return string.charAt(0).toUpperCase() + string.slice(1); } -export function storage(key: string, data: any = null): any { +export function storage(key: string, data: StateType | null = null): any { if (!data) { const localData = localStorage.getItem(key); return localData ? JSON.parse(localData) : false; @@ -25,7 +26,7 @@ export function isEqual(a: any, b: any) { return a === b; } -export function debounce(fn: (fnArgs?: any) => void, wait: number) { +export function debounce(fn: CallbackType, wait: number) { let timeout: NodeJS.Timeout; return function (...args: any) { @@ -61,3 +62,7 @@ export function isSmallestFontSize(fontSize?: string): number | boolean { if (!fontSize) return false; return fontSizes.findIndex(el => el === fontSize) === 0; } + +export function getMethodNameByEventName(eventName: string): string { + return `on${capitalize(eventName)}`; +} diff --git a/src/pages/DashboardPage.ts b/src/pages/DashboardPage.ts index a29a345..a71e543 100644 --- a/src/pages/DashboardPage.ts +++ b/src/pages/DashboardPage.ts @@ -1,9 +1,9 @@ -import { $ } from 'core/Dom'; +import { $, Dom } from 'core/Dom'; import { AbstractPage } from 'pages/AbstractPage'; import { storage } from 'core/utils'; export class DashboardPage extends AbstractPage { - getRoot() { + getRoot(): Dom { const id = Date.now().toString(); return $.create('div', 'db').html( @@ -24,7 +24,7 @@ export class DashboardPage extends AbstractPage { } } -function toHtml(key: string) { +function toHtml(key: string): string { const params = +key.split(':')[1]; const state = storage(key); const link = `#excel/${params}`; @@ -38,7 +38,7 @@ function toHtml(key: string) { `; } -export function createRecordsTable() { +export function createRecordsTable(): string { const keys = getAllKeys(); if (!keys.length) return '

Пока не создали ни одной таблицы

'; diff --git a/src/pages/ExcelPage.ts b/src/pages/ExcelPage.ts index 03e466b..c59964f 100644 --- a/src/pages/ExcelPage.ts +++ b/src/pages/ExcelPage.ts @@ -19,7 +19,7 @@ export class ExcelPage extends AbstractPage { private storeSub: SubscribeType | null; private processor: StateProcessor; - constructor(props: any) { + constructor(props: string[]) { super(props); this.storeSub = null; diff --git a/src/redux/action-creators.ts b/src/redux/action-creators.ts index c307e1e..0d5d16e 100644 --- a/src/redux/action-creators.ts +++ b/src/redux/action-creators.ts @@ -1,3 +1,4 @@ +import { ResizeReturnDataType } from 'components/table/handlers/table.resize'; import { ActionType } from 'redux/types'; import { CHANGE_TEXT, @@ -9,42 +10,42 @@ import { UPDATE_DATE, CHANGE_CURRENT_TEXT, } from 'redux/action-constants'; -export function tableResize(resizeData: any) { +export function tableResize(resizeData: ResizeReturnDataType): ActionType { return { type: TABLE_RESIZE, - ...resizeData, + resizeData, }; } -export function changeText(data: { text: string, id: string }) { +export function changeText(data: { text: string, id: string }): ActionType { return { type: CHANGE_TEXT, data, }; } -export function changeCurrentStyles(data: any) { +export function changeCurrentStyles(data: Partial): ActionType { return { type: CHANGE_STYLES, data, }; } -export function applyStyle(data: { ids: (string | undefined)[], value: CSSStyleRule }) { +export function applyStyle(data: { ids: (string | undefined)[], value: CSSStyleDeclaration }): ActionType { return { type: APPLY_STYLES, data, }; } -export function changeTitle(data: string) { +export function changeTitle(data: string): ActionType { return { type: CHANGE_TITLE, data, }; } -export function deleteTable(data: string) { +export function deleteTable(data: string): ActionType { return { type: DELETE_TABLE, data, diff --git a/src/redux/rootReducer.ts b/src/redux/rootReducer.ts index 6d54d7c..87ee050 100644 --- a/src/redux/rootReducer.ts +++ b/src/redux/rootReducer.ts @@ -12,6 +12,7 @@ import { } from 'redux/action-constants'; export function rootReducer(state: StateType, action: ActionType) { +// export const rootReducer: ReducerType = function (state: StateType, action: ActionType) { switch (action.type) { case TABLE_RESIZE: { const newState: StateType = { ...state }; diff --git a/src/redux/types.d.ts b/src/redux/types.d.ts index 7dadd6d..f9c1d34 100644 --- a/src/redux/types.d.ts +++ b/src/redux/types.d.ts @@ -10,15 +10,17 @@ export type StateType = { rowState: { [k: number]: number }; currentStyles: ToolbarStateType; dataState: { [k: string]: string }; - id?: string; + id: string; openDate: number; stylesState: { [k: string]: ToolbarStateType }; title: string; currentText: string; }; -export type ReducerType = (state: StateType, action: ActionType) => StateType; +export type ReducerType = (state: StateType, action: ActionType) => StateType | null; export type SubscribeType = { unsubscribe: () => void }; + +export type CallbackType = (...args: any[]) => void;