add some type

This commit is contained in:
Sergey Krylov 2022-07-12 17:00:27 +05:00
parent d674203a4d
commit d1b83a26a3
24 changed files with 121 additions and 90 deletions

View File

@ -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 = {

View File

@ -17,10 +17,11 @@ export class Header extends ExcelComponent {
}
toHTML(): string {
const { title } = this.store.getState();
const { title, id } = this.store.getState();
return `
<input type="text" class="input" value="${title}">
<div>ID: <strong>${id}</strong></div>
<div>
<div class="button" data-button="delete-table">
@ -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));
}

View File

@ -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() {

View File

@ -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));
}
}

View File

@ -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<ResizeReturnDataType>(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' });
};
});
}

View File

@ -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));
}
}

View File

@ -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) {

View File

@ -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 = '';

View File

@ -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 {

View File

@ -4,12 +4,12 @@ import { StateType } from 'redux/types';
import { getNormalizeInitialState } from 'src/constants';
export interface ClientDataType {
save: (state: StateType) => Promise<any>;
get: () => Promise<any>;
save: (state: StateType) => Promise<void>;
get: () => Promise<StateType>;
}
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<StateType> {
return new Promise(resolve => {
setTimeout(() => {
resolve(getNormalizeInitialState(this.name));

View File

@ -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<CSSStyleDeclaration>) {
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<CSSStyleDeclaration> {
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) {

View File

@ -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)}`;
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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<CallbackType>
};
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);

View File

@ -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();
}
}

View File

@ -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');

View File

@ -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));
}

View File

@ -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)}`;
}

View File

@ -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 '<p>Пока не создали ни одной таблицы</p>';

View File

@ -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;

View File

@ -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<CSSStyleDeclaration>): 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,

View File

@ -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 };

View File

@ -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;