Finish base framework

This commit is contained in:
Sergey Krylov 2022-06-14 16:08:37 +05:00
parent 8a6f0e37b3
commit 4ecdac01fc
14 changed files with 353 additions and 7 deletions

View File

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

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

View 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() {
}
}

View 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>
`;
}
}

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

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

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

View 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
View 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
View File

@ -0,0 +1,5 @@
export function capitalize(string: string): string {
if (!string) return '';
return string.charAt(0).toUpperCase() + string.slice(1);
}

View File

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

View File

@ -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"],
}

View File

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