diff --git a/.eslintrc.js b/.eslintrc.js
index 75d8cd0..4ff7eb6 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -14,9 +14,20 @@ module.exports = {
},
rules: {
"@typescript-eslint/no-explicit-any": "off",
+ "@typescript-eslint/no-unsafe-argument": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-call": "off",
+ "@typescript-eslint/no-unsafe-member-access": "off",
+ "@typescript-eslint/restrict-template-expressions": "off",
+ "@typescript-eslint/lines-between-class-members": "off",
+ "@typescript-eslint/no-unsafe-return": "off",
+ "@typescript-eslint/no-use-before-define": "off",
+ "@typescript-eslint/ban-ts-comment": "off",
+ "arrow-parens": "off",
+ "class-methods-use-this": "off",
+ "import/prefer-default-export": "off",
"no-console": "off",
+ "no-plusplus": "off",
},
env: {
browser: true,
diff --git a/src/components/excel/Excel.ts b/src/components/excel/Excel.ts
new file mode 100644
index 0000000..e7761f8
--- /dev/null
+++ b/src/components/excel/Excel.ts
@@ -0,0 +1,38 @@
+import { $, Dom } from '../../core/dom';
+
+interface ExcelOptionsType {
+ components: any[]
+}
+
+export class Excel {
+ $el: HTMLElement | Dom;
+ components: any[];
+
+ constructor(selector: string, options: ExcelOptionsType) {
+ this.$el = $(selector);
+ this.components = options.components;
+
+ console.log(`Created new Excel class in ${selector} with options: ${options}`);
+ }
+
+ getRoot() {
+ const $root = $.create('div', 'excel');
+
+ this.components = this.components.map(Component => {
+ const $el = $.create('div', Component.className);
+ const component: any = new Component($el);
+
+ $el.html(component.toHTML());
+ $root.append($el.$el);
+
+ return component;
+ });
+
+ return $root.$el;
+ }
+
+ render() {
+ this.$el.append(this.getRoot());
+ this.components.forEach(component => component.init());
+ }
+}
diff --git a/src/components/formula/Formula.ts b/src/components/formula/Formula.ts
new file mode 100644
index 0000000..eb224a7
--- /dev/null
+++ b/src/components/formula/Formula.ts
@@ -0,0 +1,28 @@
+import { DomClass } from '../../core/dom';
+import { ExcelComponent } from '../../core/ExcelComponent';
+
+export class Formula extends ExcelComponent {
+ static className = 'excel__formula';
+
+ constructor($root: DomClass) {
+ super($root, {
+ listeners: ['input', 'click'],
+ name: 'Formula',
+ });
+ }
+
+ toHTML(): string {
+ return `
+
+
+ delete
+
+
+ exit_to_app
+
+ `;
+ }
+}
diff --git a/src/components/table/Table.ts b/src/components/table/Table.ts
new file mode 100644
index 0000000..78a0e17
--- /dev/null
+++ b/src/components/table/Table.ts
@@ -0,0 +1,10 @@
+import { ExcelComponent } from '../../core/ExcelComponent';
+import { createTable } from './table.template';
+
+export class Table extends ExcelComponent {
+ static className = 'excel__table';
+
+ toHTML(): string {
+ return createTable(50, 24);
+ }
+}
diff --git a/src/components/table/table.template.ts b/src/components/table/table.template.ts
new file mode 100644
index 0000000..153f713
--- /dev/null
+++ b/src/components/table/table.template.ts
@@ -0,0 +1,45 @@
+const CODES = {
+ A: 65,
+ Z: 90,
+};
+
+function createCell(cellContent = '') {
+ return `
+
${cellContent}
+ `;
+}
+
+function createCol(columnContent = '') {
+ return `
+
${columnContent}
+ `;
+}
+
+function createRow(dataContent = '', infoContent = '') {
+ return `
+
+
${infoContent}
+
${dataContent}
+
+ `;
+}
+
+export function createTable(rowsCount = 10, columnCount = 10) {
+ const colsCount = Math.min(CODES.Z - CODES.A + 1, columnCount);
+ const rows: string[] = [];
+
+ const cols = new Array(colsCount)
+ .fill('')
+ .map((el, index) => String.fromCharCode(CODES.A + index))
+ .map((el) => createCol(el))
+ .join('');
+
+ rows.push(createRow(cols));
+
+ for (let i = 0; i < rowsCount; i++) {
+ const cells = new Array(colsCount).fill(createCell()).join('');
+ rows.push(createRow(cells, `${i + 1}`));
+ }
+
+ return rows.join('');
+}
diff --git a/src/components/toolbar/Toolbar.ts b/src/components/toolbar/Toolbar.ts
new file mode 100644
index 0000000..a60f7c4
--- /dev/null
+++ b/src/components/toolbar/Toolbar.ts
@@ -0,0 +1,40 @@
+import { Dom } from '../../core/dom';
+import { ExcelComponent } from '../../core/ExcelComponent';
+
+export class Toolbar extends ExcelComponent {
+ static className = 'excel__toolbar';
+
+ constructor($root: Dom) {
+ super($root, {
+ name: 'Toolbar',
+ listeners: ['click'],
+ });
+ }
+
+ toHTML(): string {
+ return `
+
+ format_bold
+
+
+ format_italic
+
+
+ format_underline
+
+
+ format_align_left
+
+
+ format_align_center
+
+
+ format_align_right
+
+ `;
+ }
+
+ onClick() {
+ console.log('Toolbar click');
+ }
+}
diff --git a/src/core/DomListener.ts b/src/core/DomListener.ts
new file mode 100644
index 0000000..125130e
--- /dev/null
+++ b/src/core/DomListener.ts
@@ -0,0 +1,41 @@
+import { Dom } from './dom';
+import { capitalize } from './utils';
+
+export class DomListener {
+ $root: Dom;
+ listeners: string[];
+
+ constructor($root: Dom, listeners?: string[]) {
+ if (!$root) throw new Error('Не передали корневой элемент');
+
+ this.$root = $root;
+ this.listeners = listeners;
+ }
+
+ initDOMListeners() {
+ if (!this.listeners) return;
+
+ this.listeners.forEach((listener: string) => {
+ const method: any = getMethodName(listener);
+ // @ts-ignore FIXME:
+ this[method] = this[method]?.bind(this);
+ // @ts-ignore FIXME:
+ if (!this[method]) throw new Error(`Отсутствует метод ${method} в компоненте ${this?.name}`);
+ // @ts-ignore FIXME:
+ this.$root.on(listener, this[method]);
+ });
+ }
+
+ removeDOMListeners() {
+ this.listeners.forEach(listener => {
+ // @ts-ignore FIXME:
+ const method: any = getMethodName(listener);
+ // @ts-ignore FIXME:
+ 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
new file mode 100644
index 0000000..9c9a987
--- /dev/null
+++ b/src/core/ExcelComponent.ts
@@ -0,0 +1,31 @@
+import { DomListener } from './DomListener';
+
+interface ExcelComponentClass {
+ toHTML: () => string;
+}
+
+type OptionsType = {
+ listeners?: string[];
+ name: string;
+};
+
+export class ExcelComponent extends DomListener implements ExcelComponentClass {
+ name: string;
+
+ constructor($root: any, options: OptionsType) {
+ super($root, options?.listeners);
+ this.name = options?.name;
+ }
+
+ toHTML() {
+ return '';
+ }
+
+ init() {
+ this.initDOMListeners();
+ }
+
+ destroy() {
+ this.removeDOMListeners();
+ }
+}
diff --git a/src/core/dom.ts b/src/core/dom.ts
new file mode 100644
index 0000000..c1499a8
--- /dev/null
+++ b/src/core/dom.ts
@@ -0,0 +1,66 @@
+type SelectorType = string | HTMLElement;
+
+export interface DomClass {
+ html(html?: string): string | DomClass;
+ clear(): DomClass;
+ append(node: Node | string | DomClass): DomClass
+}
+
+export class Dom implements DomClass {
+ $el: HTMLElement;
+
+ constructor(selector: SelectorType) {
+ this.$el = typeof selector === 'string'
+ ? document.querySelector(selector)
+ : selector;
+ }
+
+ html(html = '') {
+ if (typeof html === 'string') {
+ this.$el.innerHTML = html;
+ return this;
+ }
+
+ return this.$el.outerHTML.trim();
+ }
+
+ clear() {
+ this.html('');
+
+ return this;
+ }
+
+ // FIXME: any
+ append(node: any) {
+ let child = node;
+
+ if (node instanceof Dom) child = node.$el;
+
+ if (Element.prototype.append) {
+ this.$el.append(child);
+ } else {
+ this.$el.appendChild(child);
+ }
+
+ return this;
+ }
+
+ on(eventType: string, callback: any) {
+ this.$el.addEventListener(eventType, callback);
+ }
+
+ off(eventType: string, callback: any) {
+ this.$el.removeEventListener(eventType, callback);
+ }
+}
+
+export function $(selector: SelectorType) {
+ return new Dom(selector);
+}
+
+$.create = (tagName: string, classes = '') => {
+ const el: HTMLElement = document.createElement(tagName);
+ if (classes) el.classList.add(classes);
+
+ return $(el);
+};
diff --git a/src/core/utils.ts b/src/core/utils.ts
new file mode 100644
index 0000000..53c7860
--- /dev/null
+++ b/src/core/utils.ts
@@ -0,0 +1,5 @@
+export function capitalize(string: string): string {
+ if (!string) return '';
+
+ return string.charAt(0).toUpperCase() + string.slice(1);
+}
diff --git a/src/index.ts b/src/index.ts
index 8579212..f1e0e26 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1 +1,12 @@
+import { Excel } from './components/excel/Excel';
+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 './styles/index.scss';
+
+const excel = new Excel('#app', {
+ components: [Header, Toolbar, Formula, Table],
+});
+
+excel.render();
diff --git a/tsconfig.json b/tsconfig.json
index 44d292b..61ee365 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -5,7 +5,8 @@
"target": "es3",
"allowJs": true,
"moduleResolution": "node",
- "esModuleInterop": true
+ "esModuleInterop": true,
+ "sourceMap": true
},
"ts-node": {
"compilerOptions": {
@@ -13,5 +14,5 @@
"strictPropertyInitialization": true
}
},
- "exclude": ["node_modules"]
+ "exclude": ["node_modules"],
}
\ No newline at end of file
diff --git a/webpack.config.ts b/webpack.config.ts
index a1ee3dc..e031ab5 100644
--- a/webpack.config.ts
+++ b/webpack.config.ts
@@ -17,7 +17,7 @@ const config = (env: Record
, argv: Record): Configurat
const isProd = argv.mode === 'production';
const getFilename = (ext: string): string => `[name]${isProd ? '-[hash]' : ''}.${ext}`;
- const getSourceMap = () => (isProd ? false : 'source-map');
+ const getSourceMap = () => (isProd ? false : 'inline-source-map');
const getPluginList = (): any[] => {
const basePluginList = [
@@ -81,11 +81,11 @@ const config = (env: Record, argv: Record): Configurat
clean: true,
},
resolve: {
- extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.scss'],
alias: {
- '@': path.resolve(__dirname, 'src'),
- '@core': path.resolve(__dirname, 'src', '@core'),
+ '@': path.resolve(__dirname, 'src/'),
+ '@core': path.resolve(__dirname, 'src/core/'),
},
+ extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.scss'],
},
plugins: getPluginList(),
module: {
@@ -110,7 +110,7 @@ const config = (env: Record, argv: Record): Configurat
directory: path.join(__dirname, 'static'),
},
compress: true,
- port: 8080,
+ port: 80,
watchFiles: './src',
},
performance: {