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: {
|
||||
"@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,
|
||||
|
||||
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';
|
||||
|
||||
const excel = new Excel('#app', {
|
||||
components: [Header, Toolbar, Formula, Table],
|
||||
});
|
||||
|
||||
excel.render();
|
||||
|
||||
@ -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"],
|
||||
}
|
||||
@ -17,7 +17,7 @@ const config = (env: Record<string, any>, argv: Record<string, any>): 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<string, any>, argv: Record<string, any>): 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<string, any>, argv: Record<string, any>): Configurat
|
||||
directory: path.join(__dirname, 'static'),
|
||||
},
|
||||
compress: true,
|
||||
port: 8080,
|
||||
port: 80,
|
||||
watchFiles: './src',
|
||||
},
|
||||
performance: {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user