Finish base framework
This commit is contained in:
parent
8a6f0e37b3
commit
4ecdac01fc
11
.eslintrc.js
11
.eslintrc.js
@ -14,9 +14,20 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-argument": "off",
|
||||||
"@typescript-eslint/no-unsafe-assignment": "off",
|
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||||
"@typescript-eslint/no-unsafe-call": "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-console": "off",
|
||||||
|
"no-plusplus": "off",
|
||||||
},
|
},
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
|
|||||||
38
src/components/excel/Excel.ts
Normal file
38
src/components/excel/Excel.ts
Normal file
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/components/formula/Formula.ts
Normal file
28
src/components/formula/Formula.ts
Normal file
@ -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 `
|
||||||
|
<div class="info">fx</div>
|
||||||
|
<div class="input" contenteditable spellcheck="false"></div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
onInput(event: Event) {
|
||||||
|
console.log('Formula on input listeners', event);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/components/header/Header.ts
Normal file
19
src/components/header/Header.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { ExcelComponent } from '../../core/ExcelComponent';
|
||||||
|
|
||||||
|
export class Header extends ExcelComponent {
|
||||||
|
static className = 'excel__header';
|
||||||
|
|
||||||
|
toHTML(): string {
|
||||||
|
return `
|
||||||
|
<input type="text" class="input" value="Новая таблица">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="button">
|
||||||
|
<i class="material-icons">delete</i>
|
||||||
|
</div>
|
||||||
|
<div class="button">
|
||||||
|
<i class="material-icons">exit_to_app</i>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/components/table/Table.ts
Normal file
10
src/components/table/Table.ts
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/components/table/table.template.ts
Normal file
45
src/components/table/table.template.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
const CODES = {
|
||||||
|
A: 65,
|
||||||
|
Z: 90,
|
||||||
|
};
|
||||||
|
|
||||||
|
function createCell(cellContent = '') {
|
||||||
|
return `
|
||||||
|
<div class="cell" contenteditable>${cellContent}</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCol(columnContent = '') {
|
||||||
|
return `
|
||||||
|
<div class="column">${columnContent}</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRow(dataContent = '', infoContent = '') {
|
||||||
|
return `
|
||||||
|
<div class="row">
|
||||||
|
<div class="row-info">${infoContent}</div>
|
||||||
|
<div class="row-data">${dataContent}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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('');
|
||||||
|
}
|
||||||
40
src/components/toolbar/Toolbar.ts
Normal file
40
src/components/toolbar/Toolbar.ts
Normal file
@ -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 `
|
||||||
|
<div class="button">
|
||||||
|
<i class="material-icons">format_bold</i>
|
||||||
|
</div>
|
||||||
|
<div class="button">
|
||||||
|
<i class="material-icons">format_italic</i>
|
||||||
|
</div>
|
||||||
|
<div class="button">
|
||||||
|
<i class="material-icons">format_underline</i>
|
||||||
|
</div>
|
||||||
|
<div class="button">
|
||||||
|
<i class="material-icons">format_align_left</i>
|
||||||
|
</div>
|
||||||
|
<div class="button">
|
||||||
|
<i class="material-icons">format_align_center</i>
|
||||||
|
</div>
|
||||||
|
<div class="button">
|
||||||
|
<i class="material-icons">format_align_right</i>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick() {
|
||||||
|
console.log('Toolbar click');
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/core/DomListener.ts
Normal file
41
src/core/DomListener.ts
Normal file
@ -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)}`;
|
||||||
|
}
|
||||||
31
src/core/ExcelComponent.ts
Normal file
31
src/core/ExcelComponent.ts
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
66
src/core/dom.ts
Normal file
66
src/core/dom.ts
Normal file
@ -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);
|
||||||
|
};
|
||||||
5
src/core/utils.ts
Normal file
5
src/core/utils.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export function capitalize(string: string): string {
|
||||||
|
if (!string) return '';
|
||||||
|
|
||||||
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
|
}
|
||||||
11
src/index.ts
11
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';
|
import './styles/index.scss';
|
||||||
|
|
||||||
|
const excel = new Excel('#app', {
|
||||||
|
components: [Header, Toolbar, Formula, Table],
|
||||||
|
});
|
||||||
|
|
||||||
|
excel.render();
|
||||||
|
|||||||
@ -5,7 +5,8 @@
|
|||||||
"target": "es3",
|
"target": "es3",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"esModuleInterop": true
|
"esModuleInterop": true,
|
||||||
|
"sourceMap": true
|
||||||
},
|
},
|
||||||
"ts-node": {
|
"ts-node": {
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
@ -13,5 +14,5 @@
|
|||||||
"strictPropertyInitialization": true
|
"strictPropertyInitialization": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"],
|
||||||
}
|
}
|
||||||
@ -17,7 +17,7 @@ const config = (env: Record<string, any>, argv: Record<string, any>): Configurat
|
|||||||
const isProd = argv.mode === 'production';
|
const isProd = argv.mode === 'production';
|
||||||
|
|
||||||
const getFilename = (ext: string): string => `[name]${isProd ? '-[hash]' : ''}.${ext}`;
|
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 getPluginList = (): any[] => {
|
||||||
const basePluginList = [
|
const basePluginList = [
|
||||||
@ -81,11 +81,11 @@ const config = (env: Record<string, any>, argv: Record<string, any>): Configurat
|
|||||||
clean: true,
|
clean: true,
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.scss'],
|
|
||||||
alias: {
|
alias: {
|
||||||
'@': path.resolve(__dirname, 'src'),
|
'@': path.resolve(__dirname, 'src/'),
|
||||||
'@core': path.resolve(__dirname, 'src', '@core'),
|
'@core': path.resolve(__dirname, 'src/core/'),
|
||||||
},
|
},
|
||||||
|
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.scss'],
|
||||||
},
|
},
|
||||||
plugins: getPluginList(),
|
plugins: getPluginList(),
|
||||||
module: {
|
module: {
|
||||||
@ -110,7 +110,7 @@ const config = (env: Record<string, any>, argv: Record<string, any>): Configurat
|
|||||||
directory: path.join(__dirname, 'static'),
|
directory: path.join(__dirname, 'static'),
|
||||||
},
|
},
|
||||||
compress: true,
|
compress: true,
|
||||||
port: 8080,
|
port: 80,
|
||||||
watchFiles: './src',
|
watchFiles: './src',
|
||||||
},
|
},
|
||||||
performance: {
|
performance: {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user