finish version 1.0.0
This commit is contained in:
parent
38b8834de8
commit
72b559b941
1461
package-lock.json
generated
1461
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@ -31,6 +31,8 @@
|
||||
"jest": "^28.1.2",
|
||||
"jest-environment-jsdom": "^28.1.2",
|
||||
"mini-css-extract-plugin": "^2.6.0",
|
||||
"postcss": "^8.4.14",
|
||||
"postcss-loader": "^7.0.0",
|
||||
"sass": "^1.52.2",
|
||||
"sass-loader": "^13.0.0",
|
||||
"ssh2-sftp-client": "^5.3.2",
|
||||
@ -63,11 +65,17 @@
|
||||
},
|
||||
"homepage": "https://bitbucket.org/ksv741/excel-course#readme",
|
||||
"description": "",
|
||||
"browserslist": "> 0.25%, not dead",
|
||||
"browserslist": [
|
||||
"last 2 version",
|
||||
"> 1%",
|
||||
"IE 9"
|
||||
],
|
||||
"module": "true",
|
||||
"dependencies": {
|
||||
"autoprefixer": "^10.4.7",
|
||||
"element-closest": "^3.0.2",
|
||||
"normalize.css": "^8.0.1"
|
||||
"normalize.css": "^8.0.1",
|
||||
"postcss-preset-env": "^7.7.2"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { $, Dom } from 'core/dom';
|
||||
|
||||
export function Loader(): Dom {
|
||||
return $.create('div', 'loader').html('<div class="loader"><div class="lds-circle"><div></div></div></div>');
|
||||
return $.create('div', 'loader')
|
||||
.html('<div class="loader"><div class="lds-circle"><div></div></div></div>');
|
||||
}
|
||||
|
||||
@ -28,7 +28,7 @@ export class Formula extends ExcelComponent {
|
||||
this.formulaInput = this.$root.find('#formula-input');
|
||||
|
||||
this.$on('table:select-cell', text => {
|
||||
this.formulaInput.text = text;
|
||||
this.formulaInput.text = text || '';
|
||||
});
|
||||
}
|
||||
|
||||
@ -36,8 +36,11 @@ export class Formula extends ExcelComponent {
|
||||
this.formulaInput.text = currentText;
|
||||
}
|
||||
|
||||
onInput(event: Event) {
|
||||
const text = (event.target as HTMLElement).textContent.trim();
|
||||
onInput(event: InputEvent) {
|
||||
const { target } = event;
|
||||
if (!target) return;
|
||||
|
||||
const text = (target as HTMLElement).innerText.trim();
|
||||
this.$emit('formula:input', text);
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { startCellId } from 'components/table/table.functions';
|
||||
import * as actions from 'redux/actions';
|
||||
import { $, Dom } from 'core/dom';
|
||||
import { ExcelComponent } from 'core/ExcelComponent';
|
||||
@ -33,7 +34,7 @@ export class Table extends ExcelComponent {
|
||||
init() {
|
||||
super.init();
|
||||
|
||||
const $cell = this.$root.find('[data-id="0:0"]');
|
||||
const $cell = this.$root.find(`[data-id="${startCellId}"]`);
|
||||
this.selection.select($cell);
|
||||
|
||||
this.$emit('table:select-cell', $cell.data.value);
|
||||
@ -86,7 +87,6 @@ export class Table extends ExcelComponent {
|
||||
const tableState = this.store.getState();
|
||||
const tableContent = tableState?.dataState;
|
||||
const tableStyles = tableState?.stylesState;
|
||||
|
||||
Object.keys(tableContent).forEach(cellId => {
|
||||
const $cell = this.$root.find(`[data-id="${cellId}"]`);
|
||||
const styles = tableStyles[cellId];
|
||||
@ -100,7 +100,7 @@ export class Table extends ExcelComponent {
|
||||
emitSelectCallback() {
|
||||
this.$emit('table:select-cell', this.selection.current.data.value);
|
||||
|
||||
const styles = this.selection.current?.getStyles(Object.keys(initialStyleState) as (keyof Partial<CSSStyleDeclaration>)[]);
|
||||
const styles = this.selection.current?.getStyles(Object.keys(initialStyleState));
|
||||
this.$dispatch(changeCurrentStyles(styles));
|
||||
}
|
||||
|
||||
@ -116,7 +116,7 @@ export class Table extends ExcelComponent {
|
||||
updateCurrentTextInStore(text: string) {
|
||||
this.$dispatch(actions.changeText({
|
||||
text,
|
||||
id: this.selection.current.data.id,
|
||||
id: this.selection.current.data.id || startCellId,
|
||||
}));
|
||||
}
|
||||
|
||||
@ -130,6 +130,6 @@ export class Table extends ExcelComponent {
|
||||
}
|
||||
|
||||
onInput(event: InputEvent) {
|
||||
this.updateCurrentTextInStore((event.target as HTMLElement).textContent.trim());
|
||||
this.updateCurrentTextInStore((event.target as HTMLElement).innerText.trim());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { $, Dom } from 'core/dom';
|
||||
import { getParamsFromCellId } from 'components/table/table.functions';
|
||||
import { getParamsFromCellId, startCellId } from 'components/table/table.functions';
|
||||
|
||||
export class TableSelection {
|
||||
static selectedClassName = 'selected';
|
||||
@ -8,7 +8,6 @@ export class TableSelection {
|
||||
|
||||
constructor() {
|
||||
this.group = [];
|
||||
this.current = null;
|
||||
}
|
||||
|
||||
get selectedIds() {
|
||||
@ -42,8 +41,8 @@ export class TableSelection {
|
||||
}
|
||||
|
||||
selectGroup($el: Dom) {
|
||||
const startCellParams = getParamsFromCellId(this.current.data.id);
|
||||
const selectedCellParams = getParamsFromCellId($el.data.id);
|
||||
const startCellParams = getParamsFromCellId(this.current.data.id || startCellId);
|
||||
const selectedCellParams = getParamsFromCellId($el.data.id || startCellId);
|
||||
|
||||
const startCol = Math.min(startCellParams.col, selectedCellParams.col);
|
||||
const endCol = Math.max(startCellParams.col, selectedCellParams.col);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { startCellId } from 'components/table/table.functions';
|
||||
import { $, Dom } from 'core/dom';
|
||||
|
||||
type CustomElementType = Element & { css: any };
|
||||
@ -57,17 +58,17 @@ export function resizeHandler($root: Dom, event: MouseEvent) {
|
||||
document.onmouseup = () => {
|
||||
document.onmousemove = null;
|
||||
document.onmouseup = null;
|
||||
document.body.style.userSelect = null;
|
||||
document.body.style.userSelect = '';
|
||||
|
||||
let value: number;
|
||||
// let id: string;
|
||||
let value = 0;
|
||||
const id = $parent.data[type || ''] || startCellId;
|
||||
|
||||
switch (type) {
|
||||
case 'col': {
|
||||
value = coords.width + delta;
|
||||
|
||||
$parent.css({ width: `${value}px` });
|
||||
allCols.forEach((el: CustomElementType) => el.css({ width: `${coords.width + delta}px` }));
|
||||
allCols.forEach(el => $(el as HTMLElement).css({ width: `${coords.width + delta}px` }));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -78,10 +79,10 @@ export function resizeHandler($root: Dom, event: MouseEvent) {
|
||||
break;
|
||||
}
|
||||
|
||||
default: break;
|
||||
default: return;
|
||||
}
|
||||
|
||||
res({ value, id: $parent.data[type], type });
|
||||
res({ value, id, type });
|
||||
|
||||
$resizer.css({ opacity: 0, bottom: 0, right: 0 });
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { $ } from 'core/dom';
|
||||
import { TableSelection } from 'components/table/TableSelection';
|
||||
import { getParamsFromCellId, isCell } from 'components/table/table.functions';
|
||||
import { getParamsFromCellId, isCell, startCellId } from 'components/table/table.functions';
|
||||
|
||||
export function selectHandler(event: MouseEvent | KeyboardEvent, selection: TableSelection, callback?: () => void) {
|
||||
switch (event.type) {
|
||||
@ -15,7 +15,8 @@ export function selectHandler(event: MouseEvent | KeyboardEvent, selection: Tabl
|
||||
default: break;
|
||||
}
|
||||
|
||||
callback();
|
||||
// Analog callback && callback();
|
||||
callback?.();
|
||||
|
||||
function onMouseDownHandler() {
|
||||
if (isCell(event)) {
|
||||
@ -37,7 +38,8 @@ export function selectHandler(event: MouseEvent | KeyboardEvent, selection: Tabl
|
||||
|
||||
if (!selection?.current || !handleKeys.includes(key)) return;
|
||||
|
||||
const currentCellId = selection.current?.data?.id;
|
||||
// If something goes wrong, go to start line
|
||||
const currentCellId = selection.current.data.id || startCellId;
|
||||
let { row, col } = getParamsFromCellId(currentCellId);
|
||||
|
||||
switch (key) {
|
||||
|
||||
@ -10,3 +10,5 @@ export function getParamsFromCellId(cellId: string) {
|
||||
|
||||
return { col, row };
|
||||
}
|
||||
|
||||
export const startCellId = '0:0';
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { startCellId } from 'components/table/table.functions';
|
||||
import { $, Dom } from 'core/dom';
|
||||
import { ExcelStateComponent } from 'core/ExcelStateComponent';
|
||||
import { OptionsType } from 'core/ExcelComponent';
|
||||
@ -9,10 +10,10 @@ export class Toolbar extends ExcelStateComponent {
|
||||
|
||||
constructor($root: Dom, options: OptionsType) {
|
||||
super($root, {
|
||||
...options,
|
||||
listeners: ['click'],
|
||||
name: 'Toolbar',
|
||||
subscribe: ['currentStyles'],
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
@ -25,7 +26,7 @@ export class Toolbar extends ExcelStateComponent {
|
||||
get toolbarState() {
|
||||
return {
|
||||
...initialStyleState,
|
||||
...this.store?.getState()?.stylesState?.['0:0'],
|
||||
...this.store?.getState()?.stylesState?.[startCellId],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -4,9 +4,7 @@ import { initialStyleState } from 'src/constants';
|
||||
type ButtonConfigType = {
|
||||
icon: string;
|
||||
isActive: boolean;
|
||||
value: {
|
||||
[k: string]: string | number
|
||||
}
|
||||
value: ToolbarStateType;
|
||||
};
|
||||
|
||||
export function createToolbar(state: ToolbarStateType): string {
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { startCellId } from 'components/table/table.functions';
|
||||
import { ToolbarStateType } from 'components/toolbar/toolbar-types';
|
||||
import { StateType } from 'redux/types';
|
||||
import { storage } from 'core/utils';
|
||||
|
||||
export const initialStyleState: Partial<CSSStyleDeclaration> = {
|
||||
export const initialStyleState: ToolbarStateType = {
|
||||
textAlign: 'left',
|
||||
fontWeight: 'normal',
|
||||
textDecoration: 'none',
|
||||
@ -16,9 +18,9 @@ export function getNormalizeInitialState(params: string): StateType {
|
||||
currentText: '',
|
||||
stylesState: {},
|
||||
title: 'New excel table',
|
||||
...storage(`excel:${params}`),
|
||||
currentStyles: { ...storage(`excel:${params}`)?.stylesState?.['0:0'] },
|
||||
id: params,
|
||||
openDate: Date.now(),
|
||||
...storage(`excel:${params}`),
|
||||
currentStyles: { ...storage(`excel:${params}`)?.stylesState?.[startCellId] },
|
||||
};
|
||||
}
|
||||
|
||||
@ -3,20 +3,25 @@ import { storageName } from 'pages/ExcelPage';
|
||||
import { StateType } from 'redux/types';
|
||||
import { getNormalizeInitialState } from 'src/constants';
|
||||
|
||||
export class LocalStorageClient {
|
||||
export interface ClientDataType {
|
||||
save: (state: StateType) => Promise<any>;
|
||||
get: () => Promise<any>;
|
||||
}
|
||||
|
||||
export class LocalStorageClient implements ClientDataType {
|
||||
private name: string;
|
||||
|
||||
constructor(name: string) {
|
||||
this.name = storageName(name);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
save(state: StateType): Promise<void> {
|
||||
storage(this.name, state);
|
||||
storage(storageName(this.name), state);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
get() {
|
||||
const data = storage(this.name) || getNormalizeInitialState(this.name);
|
||||
const data = storage(storageName(this.name)) || getNormalizeInitialState(this.name);
|
||||
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
|
||||
@ -5,7 +5,7 @@ export class DomListener {
|
||||
$root: Dom;
|
||||
listeners: string[];
|
||||
|
||||
constructor($root: Dom, listeners?: string[]) {
|
||||
constructor($root: Dom, listeners: string[]) {
|
||||
if (!$root) throw new Error('Не передали корневой элемент');
|
||||
|
||||
this.$root = $root;
|
||||
|
||||
@ -11,26 +11,26 @@ interface ExcelComponentClass {
|
||||
}
|
||||
|
||||
export type OptionsType = {
|
||||
listeners?: string[];
|
||||
listeners: string[];
|
||||
name: string;
|
||||
emitter?: Emitter;
|
||||
emitter: Emitter;
|
||||
store: Store;
|
||||
subscribe: string[],
|
||||
};
|
||||
|
||||
export abstract class ExcelComponent extends DomListener implements ExcelComponentClass {
|
||||
private name: string | undefined;
|
||||
private emitter: Emitter | undefined;
|
||||
private store: Store | undefined;
|
||||
private subscribe: string[] | undefined;
|
||||
private name: string;
|
||||
private emitter: Emitter;
|
||||
public store: Store;
|
||||
private subscribe: string[];
|
||||
private unsubscribers: ((args?: any) => any)[];
|
||||
|
||||
constructor($root: Dom, options?: OptionsType) {
|
||||
super($root, options?.listeners);
|
||||
this.name = options?.name;
|
||||
this.emitter = options?.emitter;
|
||||
this.store = options?.store;
|
||||
this.subscribe = options?.subscribe;
|
||||
constructor($root: Dom, options: OptionsType) {
|
||||
super($root, options.listeners);
|
||||
this.name = options.name;
|
||||
this.emitter = options.emitter;
|
||||
this.store = options.store;
|
||||
this.subscribe = options.subscribe;
|
||||
|
||||
this.unsubscribers = [];
|
||||
this.prepare();
|
||||
|
||||
@ -1,12 +1,16 @@
|
||||
import { ClientDataType } from 'core/Clients';
|
||||
import { debounce } from 'core/utils';
|
||||
import { StateType } from 'redux/types';
|
||||
|
||||
export class StateProcessor {
|
||||
constructor(client, dalay = 300) {
|
||||
private client: ClientDataType;
|
||||
|
||||
constructor(client: ClientDataType, dalay = 300) {
|
||||
this.client = client;
|
||||
this.listen = debounce(this.listen.bind(this), dalay);
|
||||
}
|
||||
|
||||
listen(state) {
|
||||
listen(state: StateType) {
|
||||
this.client.save(state);
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { ToolbarStateType } from 'components/toolbar/toolbar-types';
|
||||
import { initialStyleState } from 'src/constants';
|
||||
|
||||
export type SelectorType = string | HTMLElement;
|
||||
@ -9,30 +10,33 @@ export interface DomClass {
|
||||
}
|
||||
|
||||
export class Dom implements DomClass {
|
||||
$el: HTMLElement | null;
|
||||
$el: HTMLElement;
|
||||
|
||||
constructor(selector: SelectorType) {
|
||||
this.$el = typeof selector === 'string'
|
||||
? document.querySelector(selector)
|
||||
: selector;
|
||||
// Could not find element with selector in DOM, need a check
|
||||
if (typeof selector === 'string') {
|
||||
const elementFromDOM = document.querySelector(selector);
|
||||
if (!elementFromDOM) throw new Error(`Can't find element with "${selector}" selector`);
|
||||
else this.$el = elementFromDOM as HTMLElement;
|
||||
} else {
|
||||
this.$el = selector;
|
||||
}
|
||||
}
|
||||
|
||||
html(html = '') {
|
||||
if (typeof html === 'string') {
|
||||
this.$el.innerHTML = html;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
return this.$el.outerHTML.trim();
|
||||
}
|
||||
|
||||
set text(text: string) {
|
||||
if (!text) this.$el.textContent = '';
|
||||
this.$el.textContent = text;
|
||||
}
|
||||
|
||||
get text() {
|
||||
if (this.$el?.closest('input')) return (this.$el as HTMLInputElement).value;
|
||||
return this.$el?.textContent;
|
||||
if (this.$el.closest('input')) return (this.$el as HTMLInputElement).value;
|
||||
return (this.$el as HTMLElement).innerText;
|
||||
}
|
||||
|
||||
clear() {
|
||||
@ -47,11 +51,8 @@ export class Dom implements DomClass {
|
||||
|
||||
if (node instanceof Dom) child = node.$el;
|
||||
|
||||
if (Element.prototype.append) {
|
||||
this.$el.append(child);
|
||||
} else {
|
||||
this.$el.appendChild(child);
|
||||
}
|
||||
if (this.$el.append) this.$el.append(child);
|
||||
else this.$el.appendChild(child);
|
||||
|
||||
return this;
|
||||
}
|
||||
@ -69,7 +70,7 @@ export class Dom implements DomClass {
|
||||
}
|
||||
|
||||
get data() {
|
||||
return this.$el.dataset;
|
||||
return this.$el.dataset || '';
|
||||
}
|
||||
|
||||
setData(name: string, value: string) {
|
||||
@ -108,9 +109,9 @@ export class Dom implements DomClass {
|
||||
this.$el?.classList.remove(className);
|
||||
}
|
||||
|
||||
getStyles(styles: (keyof Partial<CSSStyleDeclaration>)[]) {
|
||||
return styles.reduce((res: Partial<CSSStyleDeclaration>, s: any) => {
|
||||
res[s] = this.$el.style[s] || initialStyleState[s];
|
||||
getStyles(styles: any[]) {
|
||||
return styles.reduce((res, s) => {
|
||||
res[s] = this.$el.style[s] || initialStyleState[s as keyof ToolbarStateType];
|
||||
return res;
|
||||
}, {});
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ export class Store {
|
||||
this.listeners = [];
|
||||
}
|
||||
|
||||
subscribe(fn: (state?: StateType) => void): SubscribeType {
|
||||
subscribe(fn: (state: StateType) => void): SubscribeType {
|
||||
this.listeners.push(fn);
|
||||
return {
|
||||
unsubscribe: () => {
|
||||
|
||||
@ -3,5 +3,5 @@ import elementClosest from 'element-closest';
|
||||
try {
|
||||
elementClosest(window); // this is used to reference window.Element
|
||||
} catch (e) {
|
||||
console.log('Error in polyfills', e);
|
||||
console.error('Error in polyfills', e);
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import path from 'path';
|
||||
|
||||
// FIXME:
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import type { Configuration as DevServerConfiguration } from 'webpack-dev-server';
|
||||
import 'webpack-dev-server';
|
||||
import type { Configuration } from 'webpack';
|
||||
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||
@ -95,6 +95,18 @@ const config = (env: Record<string, any>, argv: Record<string, any>): Configurat
|
||||
sourceMap: !isProd,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: "postcss-loader",
|
||||
options: {
|
||||
postcssOptions: {
|
||||
plugins: [
|
||||
[
|
||||
'postcss-preset-env',
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user