chore: clear code (#654)

* chore: clear code

* chore: remove redundant files

* fix: revert unintended tsconfig change

* fix: eslint
This commit is contained in:
Wenzhao Hu
2023-12-06 11:14:27 +08:00
committed by GitHub
parent 97de35c92f
commit bee5d97f41
34 changed files with 467 additions and 4601 deletions
+2
View File
@@ -5,6 +5,8 @@ local
.turbo
.docusaurus
apps
# Coverage
coverage-report/
@@ -2,7 +2,7 @@ import { createIdentifier } from '@wendellhu/redi';
import type { Nullable } from '../../shared/types';
// NOTE: WARNING!! Do not set per unit config here! You can definitely find a better place to do that.
// WARNING!!! Do not set per unit config here! You can definitely find a better place to do that.
/**
* IConfig provides universal configuration for the whole application.
@@ -86,7 +86,6 @@ export class LocalUndoRedoService extends Disposable implements IUndoRedoService
// redo stack should be cleared when pushing an undo
redoStack.length = 0;
// TODO: undo element maybe merge-able
// TODO: undo redo stack should have a maximum capacity, maybe we should get the config from IConfigService?
undoStack.push(item);
@@ -94,7 +93,6 @@ export class LocalUndoRedoService extends Disposable implements IUndoRedoService
undoStack.splice(0, 1);
}
// TODO: update status with unitID, the UI doesn't have to update perhaps
this._updateStatus();
}
-4
View File
@@ -1,4 +0,0 @@
/**
* Date Tool
*/
export class DateTime {}
File diff suppressed because it is too large Load Diff
@@ -1,4 +0,0 @@
/**
* Date Tool
*/
export class ExcelDateTime {}
-4
View File
@@ -1,4 +0,0 @@
// TODO
export interface GroupModel<T> {
getGroupModel(): T;
}
-11
View File
@@ -4,31 +4,20 @@ export * from './color/color-builder';
export * from './command-enum';
export * from './common';
export * from './compare';
export * from './date-time';
export * from './doc-tool';
export * from './excel-date-time';
export * from './gen-name';
export * from './generate';
export * from './group-model';
export * from './hash-algorithm';
export * from './io-http';
export * from './io-socket';
export * from './lifecycle';
export * from './locale';
export * from './logger';
export * from './lru/index';
export * from './migrate/index';
export * from './object-array';
export * from './object-matrix';
export * from './permission';
export * from './random';
export * from './rectangle';
export { RefAlias } from './ref-alias';
export * from './row-col-iter';
export * from './sequence';
export * from './serialize/index';
export * from './sort-rules';
export * from './tools';
export * from './tuples';
export * from './type-store';
export * from './types';
-265
View File
@@ -1,265 +0,0 @@
import type { IKeyType, IKeyValue } from './types';
function deleteEmpty(object: IKeyValue) {
if (object) {
Object.keys(object).forEach((key) => {
const value = object[key];
if (unDef(value) || value === '') {
delete object[key];
}
});
}
return object;
}
function extend(src: IKeyValue, ...target: IKeyValue[]) {
for (const item of target) {
if (item) {
for (const key in item) {
if (item.hasOwnProperty(key)) {
if (isDef(item[key])) {
src[key] = item[key];
}
}
}
}
}
return src;
}
function objectToUrlSearch(object: IKeyValue) {
if (unDef(object)) return '';
let str = '';
for (const key in object) {
if (object.hasOwnProperty(key) && object[key] !== undefined && object[key] !== null) {
str += `${key}=${object[key]}&`;
}
}
str = str.substring(0, str.length - 1);
return str ? `?${encodeURI(str)}` : '';
}
function isDef(v: unknown) {
return v !== undefined && v !== null;
}
function unDef(v: unknown) {
return v === undefined || v === null;
}
function urlSearchToObject(search: string) {
if (unDef(search)) return {};
search = decodeURI(search);
const index = search.indexOf('?');
const url = search.substring(0, index);
search = search.substring(index);
search = search.replace(/\?/, '');
const array = search.split('&');
const object: IKeyType<string> = {
$$url: url,
};
array.forEach((value) => {
const keyValue = value.split('=');
object[keyValue[0]] = keyValue[1];
});
return object;
}
function isString(value?: any): value is string {
return Object.prototype.toString.apply(value) === '[object String]';
}
function ispPlain(value: any): value is {} {
return value ? Object.getPrototypeOf(value) === Object.getPrototypeOf({}) : false;
}
function isArray(value: any): value is [] {
return Object.prototype.toString.apply(value) === '[object Array]';
}
function isFormData(value?: any): value is FormData {
return value instanceof FormData;
}
const noop = () => {};
const defaultFilter = (data: object) => data;
/**
* Types of Io Http Request
*/
export enum IOHttpRequestType {
POST = 'POST',
GET = 'GET',
PUT = 'PUT',
DELETE = 'DELETE',
}
/**
* Status of Io Http Ready
*/
export enum IOHttpReadyState {
UNSENT = 0,
OPENED = 1,
HEADERS_RECEIVED = 2,
LOADING = 3,
DONE = 4,
}
/**
* Types of Io Http Response
*/
export enum IOHttpResponseType {
ARRAY_BUFFER = 'arraybuffer',
BLOB = 'blob',
TEXT = 'text',
DOCUMENT = 'document',
JSON = 'json',
STREAM = 'stream',
}
/**
* IOHTTP header type
*/
export interface IOHttpHeaderType {
[key: string]: string;
}
/**
* IOHTTP request body type
*/
export type IOHttpRequestBodyType = string | {} | [] | FormData;
/**
* Config of http communication
*/
export interface IOHttpConfig {
type: keyof typeof IOHttpRequestType;
url: string;
dataType?: IOHttpResponseType;
async?: boolean;
header?: IOHttpHeaderType;
contentType?: string;
data?: IOHttpRequestBodyType;
timeout?: number;
username?: string;
password?: string;
beforeSend?: Function;
dataFilter?: Function;
error?: Function;
success?: Function;
complete?: Function;
}
const defaultConfig: IOHttpConfig = {
type: IOHttpRequestType.POST,
url: '',
async: true,
header: {},
dataType: IOHttpResponseType.JSON,
contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
data: {},
username: '',
password: '',
timeout: 500,
error: noop,
beforeSend: defaultFilter,
dataFilter: defaultFilter,
success: noop,
complete: noop,
};
/**
* HTTP request
*
* @param config The available config options for making requests
* @returns
*/
export function IOHttp<T = void>(config: IOHttpConfig): Promise<T> {
return new Promise((resolve, reject) => {
const setting = extend({}, defaultConfig, config) as Required<IOHttpConfig>;
const request = new XMLHttpRequest();
setting.beforeSend(setting);
request.responseType = setting.dataType as XMLHttpRequestResponseType;
request.timeout = setting.timeout;
request.addEventListener('readystatechange', (event) => {
if (request.readyState === IOHttpReadyState.DONE) {
if (request.status === 200) {
const filter = setting.dataFilter(request.response);
setting.success(filter);
setting.complete();
resolve(filter);
} else {
setting.error(event);
reject(event);
}
}
});
request.addEventListener('timeout', (event) => {
setting.error(event);
});
request.open(setting.type, setting.url, setting.async, setting.username, setting.password);
Object.keys(setting.header).forEach((key) => {
const value = setting.header[key];
request.setRequestHeader(key, value);
});
if (isFormData(setting.data)) {
request.setRequestHeader('Content-Type', 'multipart/form-data');
} else if (setting.contentType) {
request.setRequestHeader('Content-Type', setting.contentType);
}
switch (setting.type) {
case IOHttpRequestType.DELETE:
case IOHttpRequestType.GET: {
request.send();
break;
}
case IOHttpRequestType.POST:
case IOHttpRequestType.PUT: {
if (ispPlain(setting.data)) {
request.send(JSON.stringify(setting.data));
return;
}
if (isString(setting.data)) {
request.send(setting.data);
return;
}
if (isArray(setting.data)) {
request.send(JSON.stringify(setting.data));
return;
}
if (isFormData(setting.data)) {
request.send(setting.data);
}
break;
}
}
});
}
/**
* Send a POST request
*
* @param url The server URL that will be used for the request
* @param data The data to be sent as the request body
* @param success Successful callback
*/
export function Post<T = void>(url: string, data: object = {}, success: Function = noop): Promise<T> {
return IOHttp({ url, type: IOHttpRequestType.POST, data, success });
}
/**
* Send a Get request
*
* @param url The server URL that will be used for the request
* @param data The data to be sent as the request body
* @param success Successful callback
*/
export function Get<T = void>(url: string, data: object = {}, success: Function = noop): Promise<T> {
url += objectToUrlSearch(data);
return IOHttp({ url, type: IOHttpRequestType.GET, success });
}
-201
View File
@@ -1,201 +0,0 @@
import type { Nullable } from '../common/type-utils';
import type { IKeyValue } from './types';
function isDef(v: any) {
return v !== undefined && v !== null;
}
function unDef(v: any) {
return v === undefined || v === null;
}
function isBlank(v: string) {
return unDef(v) || v.toString() === '';
}
function extend(src: IKeyValue, ...target: IKeyValue[]) {
for (const item of target) {
if (item) {
for (const key in item) {
if (item.hasOwnProperty(key)) {
if (isDef(item[key])) {
src[key] = item[key];
}
}
}
}
}
return src;
}
/**
* Properties of socket config
*/
export interface IOSocketConfig {
url: string;
heartbeatTime?: number;
}
/**
* Types if Io Socket Listen
*/
export enum IOSocketListenType {
MESSAGE = 'message',
OPEN = 'open',
CLOSE = 'close',
ERROR = 'error',
RETRY = 'retry',
DESTROY = 'destroy',
}
const defaultConfig = {
url: '',
heartbeatTime: 1000 * 30,
};
export const HEART_BEAT_MESSAGE = 'heart_beat_message';
/**
* IOSocket send body
*/
export type IOSocketSendBody = string | ArrayBufferLike | Blob | ArrayBufferView;
/**
* IOSocket type
*/
export class IOSocket {
private _config: IOSocketConfig;
private _listens: Map<IOSocketListenType, Function>;
private _timer: number;
private _socket: Nullable<WebSocket>;
constructor(config: IOSocketConfig) {
const setting = Object.assign(defaultConfig, config);
// const setting = extend(
// {},
// defaultConfig,
// config
// ) as Required<IOSocketConfig>;
if (isBlank(setting.url)) {
throw new Error('url must be input');
}
this._listens = new Map();
this._timer = -1;
this._config = setting;
}
link(): void {
this._create();
this._bind();
}
send(body: IOSocketSendBody): void {
this._socket?.send(body);
}
destroy(): void {
const listen = this._listens.get(IOSocketListenType.DESTROY);
if (listen) {
listen();
}
this._clear();
this._listens.clear();
}
on(type: IOSocketListenType, listen: Function): void {
this._listens.set(type, listen);
}
close(): void {
this._socket?.close();
}
private _create(): void {
const { _config } = this;
this._socket = new WebSocket(_config.url);
}
private _bind(): void {
this._socket?.addEventListener(IOSocketListenType.MESSAGE, this._message.bind(this));
this._socket?.addEventListener(IOSocketListenType.OPEN, this._open.bind(this));
this._socket?.addEventListener(IOSocketListenType.CLOSE, this._close.bind(this));
this._socket?.addEventListener(IOSocketListenType.ERROR, this._error.bind(this));
// this._socket.addEventListener(IOSocketListenType.MESSAGE, (event: Event) => {
// this._message(event);
// });
// this._socket.addEventListener(IOSocketListenType.OPEN, (event: Event) => {
// this._open(event);
// });
// this._socket.addEventListener(IOSocketListenType.CLOSE, (event: Event) => {
// this._close(event);
// });
// this._socket.addEventListener(IOSocketListenType.ERROR, (event: Event) => {
// this._error(event);
// });
}
private _message(event: Event): void {
const listen = this._listens.get(IOSocketListenType.MESSAGE);
if (listen) {
listen(event);
}
}
private _open(event: Event): void {
const listen = this._listens.get(IOSocketListenType.OPEN);
if (listen) {
listen(event);
this._heartbeat();
}
}
private _error(event: Event): void {
const listen = this._listens.get(IOSocketListenType.ERROR);
if (listen) {
listen(event);
this._retry();
}
}
private _close(event: Event): void {
const listen = this._listens.get(IOSocketListenType.CLOSE);
if (listen) {
listen(event);
this.destroy();
}
}
private _clear(): void {
if (this._timer > -1) {
clearInterval(this._timer);
}
this._timer = -1;
this._socket?.removeEventListener(IOSocketListenType.MESSAGE, this._message.bind(this));
this._socket?.removeEventListener(IOSocketListenType.OPEN, this._open.bind(this));
this._socket?.removeEventListener(IOSocketListenType.CLOSE, this._close.bind(this));
this._socket?.removeEventListener(IOSocketListenType.ERROR, this._error.bind(this));
// this._socket.close();
}
private _heartbeat(): void {
const { _socket, _config } = this;
const { heartbeatTime } = _config;
function handle() {
_socket?.send(HEART_BEAT_MESSAGE);
}
this._timer = setInterval(handle, heartbeatTime) as unknown as number;
}
private _retry(): void {
const listen = this._listens.get(IOSocketListenType.RETRY);
if (listen) {
listen();
}
this._clear();
this._create();
this._bind();
}
}
-44
View File
@@ -1,44 +0,0 @@
/**
* Logging tool
*/
export class Logger {
static log(...value: unknown[]): void {
if (console) {
console.log(...value);
}
}
static info(...value: unknown[]): void {
if (console) {
console.info(...value);
}
}
static error(...value: unknown[]): void {
if (console) {
console.error(...value);
}
}
static warn(...value: unknown[]): void {
if (console) {
console.warn(...value);
}
}
static br(): void {
Logger.info('\n');
}
static capsule(env: string, version: string, prefix: string = '', suffix = ''): void {
if (console) {
console.log(
`%c${prefix}%c${env}%c${version}%c${suffix}`,
'',
'padding:3px;color:white;background:#023047',
'padding:3px;color:white;background:#219EBC',
''
);
}
}
}
@@ -1 +0,0 @@
export * from './migrate';
-439
View File
@@ -1,439 +0,0 @@
import type { BooleanNumber } from '../../types/enum';
import { HorizontalAlign, VerticalAlign, WrapStrategy } from '../../types/enum';
import type {
ICellData,
IColumnData,
IRowData,
IStyleData,
ITextRun,
ITextStyle,
IWorkbookData,
IWorksheetData,
} from '../../types/interfaces';
import type { ObjectArrayPrimitiveType } from '../object-array';
import { Tools } from '../tools';
import type { IKeyValue } from '../types';
import { border } from './border';
export function migrate(config: any): Partial<IWorkbookData> {
const newConfig: Partial<IWorkbookData> = {};
newConfig.styles = {};
if (config.hasOwnProperty('lang')) {
newConfig.locale = config.lang;
}
if (config.hasOwnProperty('data')) {
newConfig.sheets = {};
for (const sheet of config.data) {
const newSheet: Partial<IWorksheetData> = {};
// id
// if (sheet.hasOwnProperty('index')) {
// newSheet.id = sheet.index as string;
// } else {
newSheet.id = Tools.generateRandomId(6);
// }
// 缩放比例
if (sheet.hasOwnProperty('zoomRatio')) {
newSheet.zoomRatio = sheet.zoomRatio;
}
// 最大行号
if (sheet.hasOwnProperty('row')) {
newSheet.rowCount = sheet.row;
}
// 最大列号
if (sheet.hasOwnProperty('column')) {
newSheet.columnCount = sheet.column;
}
// 是否激活
if (sheet.hasOwnProperty('status')) {
newSheet.status = parseInt(sheet.status);
}
// sheet 名称
if (sheet.hasOwnProperty('name')) {
newSheet.name = sheet.name;
}
// 激活选区
if (sheet.hasOwnProperty('universheet_select_save')) {
newSheet.selections = [];
sheet.universheet_select_save.forEach((selection: IKeyValue) => {
newSheet.selections?.push({
startRow: selection.row[0],
endRow: selection.row[1],
startColumn: selection.column[0],
endColumn: selection.column[1],
});
});
}
// 横向滚动条位置
if (sheet.hasOwnProperty('scrollLeft')) {
newSheet.scrollLeft = sheet.scrollLeft;
}
// 竖向滚动条位置
if (sheet.hasOwnProperty('scrollTop')) {
newSheet.scrollTop = sheet.scrollTop;
}
if (sheet.hasOwnProperty('config')) {
// 合并单元格
if (sheet.config.merge) {
newSheet.mergeData = [];
for (const key of Object.keys(sheet.config.merge)) {
const merge: {
r: number;
c: number;
rs: number;
cs: number;
} = sheet.config.merge[key];
const mergeReact = {
startRow: merge.r,
endRow: merge.r + merge.rs - 1,
startColumn: merge.c,
endColumn: merge.c + merge.cs - 1,
};
newSheet.mergeData.push(mergeReact);
}
}
// 边框,先按顺序设置到每一个单元格对象中,设置时需要遍历四个方向的相邻单元格做去重,最后单元格样式确定之后,再统一提取到styles上
if (sheet.config.borderInfo) {
border(newSheet, sheet);
}
// 行高
if (sheet.config.rowlen) {
newSheet.rowData = {};
for (const [rowIndex, height] of Object.entries(sheet.config.rowlen)) {
newSheet.rowData[Number(rowIndex)] = {
h: Number(height),
hd: 0,
};
}
}
// 列宽
if (sheet.config.columnlen) {
newSheet.columnData = {};
for (const [colIndex, width] of Object.entries(sheet.config.columnlen)) {
newSheet.columnData[Number(colIndex)] = {
w: Number(width),
hd: 0,
};
}
}
// 隐藏行
if (sheet.config.rowhidden) {
if (!newSheet.rowData) newSheet.rowData = {};
const rowData = newSheet.rowData as ObjectArrayPrimitiveType<Partial<IRowData>>;
for (const [rowIndex, isHidden] of Object.entries(sheet.config.rowhidden)) {
if (!rowData[Number(rowIndex)]) {
rowData[Number(rowIndex)] = {};
}
rowData[Number(rowIndex)].hd = isHidden as BooleanNumber;
}
}
// 隐藏列
if (sheet.config.colhidden) {
if (!newSheet.columnData) newSheet.columnData = {};
const columnData = newSheet.columnData as ObjectArrayPrimitiveType<Partial<IColumnData>>;
for (const [colIndex, isHidden] of Object.entries(sheet.config.colhidden)) {
if (!columnData[Number(colIndex)]) {
columnData[Number(colIndex)] = {};
}
columnData[Number(colIndex)].hd = isHidden as BooleanNumber;
}
}
}
// 单元格数据
if (sheet.hasOwnProperty('celldata')) {
if (!newSheet.cellData) {
newSheet.cellData = {};
}
for (const cellItem of sheet.celldata) {
if (!newSheet.cellData[cellItem.r]) {
newSheet.cellData[cellItem.r] = {};
}
if (!newSheet.cellData[cellItem.r][cellItem.c]) {
newSheet.cellData[cellItem.r][cellItem.c] = {};
}
const cell = cellItem.v;
const newCell: ICellData = newSheet.cellData[cellItem.r][cellItem.c];
if (cell?.ct?.t === 'inlineStr') {
const textRuns: ITextRun[] = [];
let dataStream = '';
cell.ct.s.forEach((element: any) => {
const textStyle: ITextStyle = {};
// 背景颜色
if (element.hasOwnProperty('bg')) {
textStyle.bg = {
rgb: element.fc,
};
}
// 字体
if (element.hasOwnProperty('ff')) {
textStyle.ff = element.ff;
}
// 字体颜色
if (element.hasOwnProperty('fc')) {
textStyle.cl = {
rgb: element.fc,
};
}
// 字体大小
if (element.hasOwnProperty('fs')) {
textStyle.fs = element.fs;
}
// 粗体
if (element.hasOwnProperty('bl')) {
textStyle.bl = element.bl;
}
// 斜体
if (element.hasOwnProperty('it')) {
textStyle.it = element.it;
}
// 删除线
if (element.hasOwnProperty('cl')) {
textStyle.st = {
s: 1,
};
}
// 下划线
if (element.hasOwnProperty('un')) {
textStyle.ul = {
s: element.un,
};
}
const sId = Tools.generateRandomId(6);
dataStream += element.v;
textRuns.push({
sId,
st: 0,
ed: element.v.length - 1,
ts: textStyle,
});
});
dataStream += '\r\n';
newCell.p = {
id: Tools.generateRandomId(6),
body: {
dataStream,
textRuns,
},
documentStyle: {},
};
} else {
// 原始值
if (cell.hasOwnProperty('v')) {
newCell.v = cell.v;
}
const cellStyle: IStyleData = {};
// 背景颜色
if (cell.hasOwnProperty('bg')) {
cellStyle.bg = {
rgb: cell.bg,
};
}
// 字体
if (cell.hasOwnProperty('ff')) {
cellStyle.ff = cell.ff;
}
// 字体颜色
if (cell.hasOwnProperty('fc')) {
cellStyle.cl = {
rgb: cell.fc,
};
}
// 字体大小
if (cell.hasOwnProperty('fs')) {
cellStyle.fs = cell.fs;
}
// 粗体
if (cell.hasOwnProperty('bl')) {
cellStyle.bl = cell.bl;
}
// 斜体
if (cell.hasOwnProperty('it')) {
cellStyle.it = cell.it;
}
// 删除线
if (cell.hasOwnProperty('cl')) {
cellStyle.st = {
s: cell.cl,
};
}
// 下划线
if (cell.hasOwnProperty('un')) {
cellStyle.ul = {
s: cell.un,
};
}
const sMap = {
'0': VerticalAlign.MIDDLE,
'1': VerticalAlign.TOP,
'2': VerticalAlign.BOTTOM,
};
// const sMap = {
// '0': 2,
// '1': 1,
// '2': 3,
// };
// 垂直对齐
if (cell.hasOwnProperty('vt')) {
switch (String(cell.vt)) {
case '0':
cellStyle.vt = VerticalAlign.MIDDLE;
break;
case '1':
cellStyle.vt = VerticalAlign.TOP;
break;
case '2':
cellStyle.vt = VerticalAlign.BOTTOM;
break;
default:
break;
}
// cellStyle.vt = sMap[String(cell.vt)];
}
// 水平对齐
if (cell.hasOwnProperty('ht')) {
switch (String(cell.ht)) {
case '0':
cellStyle.ht = HorizontalAlign.CENTER;
break;
case '1':
cellStyle.ht = HorizontalAlign.LEFT;
break;
case '2':
cellStyle.ht = HorizontalAlign.RIGHT;
break;
default:
break;
}
// cellStyle.ht = sMap[String(cell.ht)];
}
// 竖排文字
if (cell.hasOwnProperty('tr')) {
switch (cell.tr) {
case '0':
cellStyle.tr = {
a: 0,
v: 0,
};
break;
case '1':
cellStyle.tr = {
a: 45,
v: 0,
};
break;
case '2':
cellStyle.tr = {
a: -45,
v: 0,
};
break;
case '3':
cellStyle.tr = {
a: 0,
v: 1,
};
break;
case '4':
cellStyle.tr = {
a: 90,
v: 0,
};
break;
case '5':
cellStyle.tr = {
a: -90,
v: 0,
};
break;
default:
break;
}
}
// 文字旋转角度
if (cell.hasOwnProperty('rt')) {
cellStyle.tr = {
a: cell.tr,
v: 0,
};
}
// 文本换行
if (cell.hasOwnProperty('tb')) {
switch (String(cell.tb)) {
case '0':
cellStyle.tb = WrapStrategy.CLIP;
break;
case '1':
cellStyle.tb = WrapStrategy.OVERFLOW;
break;
case '2':
cellStyle.tb = WrapStrategy.WRAP;
break;
default:
break;
}
//cellStyle.tb = sMap[String(cell.tb)];
}
newCell.s = Object.assign(newCell.s || {}, cellStyle);
}
}
}
newConfig.sheets[newSheet.id] = newSheet;
}
}
return newConfig;
}
-14
View File
@@ -1,14 +0,0 @@
export class Random {
protected _max: number;
protected _min: number;
constructor(max: number, min: number) {
this._max = max;
this._min = min;
}
next(): number {
return Math.trunc(Math.random() * (this._max - this._min) + this._min);
}
}
@@ -1,118 +0,0 @@
import { ESRectTree } from './es-rect-tree';
import { RectTree } from './rect-tree';
export class BorderTree {
rightTree: ESRectTree;
leftTree: ESRectTree;
topTree: ESRectTree;
bottomTree: ESRectTree;
constructor() {
this.leftTree = new ESRectTree();
this.rightTree = new ESRectTree();
this.topTree = new ESRectTree();
this.bottomTree = new ESRectTree();
}
setLeft(node: BorderTree.Node): void {
this.leftTree.destructCrossNode(node);
this.leftTree.insert(node);
}
setRight(node: BorderTree.Node): void {
this.rightTree.destructCrossNode(node);
this.rightTree.insert(node);
}
setTop(node: BorderTree.Node): void {
this.topTree.destructCrossNode(node);
this.topTree.insert(node);
}
setBottom(node: BorderTree.Node): void {
this.bottomTree.destructCrossNode(node);
this.bottomTree.insert(node);
}
setWhole(node: BorderTree.Node): void {
this.setLeft(node);
this.setRight(node);
this.setTop(node);
this.setBottom(node);
}
clearLeft(node: BorderTree.Node): void {
this.leftTree.remove(node, (other) => node.equals(other as BorderTree.Node));
}
clearRight(node: BorderTree.Node): void {
this.rightTree.remove(node, (other) => node.equals(other as BorderTree.Node));
}
clearTop(node: BorderTree.Node): void {
this.topTree.remove(node, (other) => node.equals(other as BorderTree.Node));
}
clearBottom(node: BorderTree.Node): void {
this.bottomTree.remove(node, (other) => node.equals(other as BorderTree.Node));
}
clearWhole(node: BorderTree.Node): void {
this.clearLeft(node);
this.clearRight(node);
this.clearTop(node);
this.clearBottom(node);
}
}
export namespace BorderTree {
export class Node extends RectTree.Node {
type: number;
width: number;
color: number;
stamp: number;
constructor({
minY = Infinity,
minX = Infinity,
maxY = -Infinity,
maxX = -Infinity,
leaf = true,
height = 1,
childrenNodes = [],
type = 0,
width = 0,
color = 0,
stamp = 0,
}: Partial<BorderTree.Node> = {}) {
super({
minY,
minX,
maxY,
maxX,
leaf,
height,
childrenNodes,
});
this.type = type;
this.width = width;
this.color = color;
this.stamp = stamp;
}
equals(other: BorderTree.Node) {
return (
this.minX === other.minX &&
this.minY === other.minY &&
other.maxX === this.maxX &&
other.maxY === this.maxY
);
}
}
}
@@ -1,108 +0,0 @@
import { RectTree } from './rect-tree';
export class ESRectTree extends RectTree {
rowExpandSplitAxis(index: number, count: number = 1): void {
const baseLineNode = new RectTree.Node({
minY: index,
minX: 0,
maxY: index,
maxX: Infinity,
});
const crossNodes = this.search(baseLineNode);
const collectNode = new Array<RectTree.Node>();
for (let i = 0; i < crossNodes.length; i++) {
const node = crossNodes[i];
this.remove(node);
if (node.minY >= index) {
node.minY += count;
}
if (node.maxY >= index) {
node.maxY += count;
}
collectNode.push(node);
}
this.load(collectNode);
}
colExpandSplitAxis(index: number, count: number = 1): void {
const baseLineNode = new RectTree.Node({
minY: 0,
minX: index,
maxY: Infinity,
maxX: index,
});
const crossNodes = this.search(baseLineNode);
const collectNode = new Array<RectTree.Node>();
for (let i = 0; i < crossNodes.length; i++) {
const node = crossNodes[i];
this.remove(node);
if (node.minX >= index) {
node.minX += count;
}
if (node.maxX >= index) {
node.maxX += count;
}
collectNode.push(node);
}
this.load(collectNode);
}
rowShrinkSplitAxis(index: number, count: number = 1): void {
const baseLineNode = new RectTree.Node({
minY: index,
minX: 0,
maxY: index,
maxX: Infinity,
});
const crossNodes = this.search(baseLineNode);
const collectNode = new Array<RectTree.Node>();
for (let i = 0, len = crossNodes.length; i < len; i++) {
const node = crossNodes[i];
this.remove(node);
if (node.minY >= index) {
node.minY -= count;
}
if (node.maxY >= index) {
node.maxY -= count;
}
collectNode.push(node);
}
this.load(collectNode);
}
colShrinkSplitAxis(index: number, count: number = 1): void {
const baseLineNode = new RectTree.Node({
minY: 0,
minX: index,
maxY: Infinity,
maxX: index,
});
const crossNodes = this.search(baseLineNode);
const collectNode = new Array<RectTree.Node>();
for (let i = 0; i < crossNodes.length; i++) {
const node = crossNodes[i];
this.remove(node);
if (node.minX >= index) {
node.minX -= count;
}
if (node.maxX >= index) {
node.maxX -= count;
}
collectNode.push(node);
}
this.load(collectNode);
}
destructCrossNode(master: RectTree.Node): RectTree.Node[] {
const crossNodes = this.search(master);
const collectNode = new Array<RectTree.Node>();
for (const node of crossNodes) {
if (!master.contains(node)) {
master.difference(node, collectNode);
}
this.remove(node);
}
this.load(collectNode);
return crossNodes;
}
}
@@ -1,65 +0,0 @@
function QuickSwap<T>(array: T[], i: number, j: number): void {
const tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
function QuickCompare<T>(a: T, b: T): number {
return a < b ? -1 : a > b ? 1 : 0;
}
function QuickStep<T>(array: T[], k: number, left: number, right: number, compare: Compare<T>): void {
while (right > left) {
if (right - left > 600) {
const n = right - left + 1;
const m = k - left + 1;
const z = Math.log(n);
const s = 0.5 * Math.exp((2 * z) / 3);
const sd = 0.5 * Math.sqrt((z * s * (n - s)) / n) * (m - n / 2 < 0 ? -1 : 1);
const newLeft = Math.max(left, Math.floor(k - (m * s) / n + sd));
const newRight = Math.min(right, Math.floor(k + ((n - m) * s) / n + sd));
QuickStep(array, k, newLeft, newRight, compare);
}
const t = array[k];
let i = left;
let j = right;
QuickSwap(array, left, k);
if (compare(array[right], t) > 0) {
QuickSwap(array, left, right);
}
while (i < j) {
QuickSwap(array, i, j);
i++;
j--;
while (compare(array[i], t) < 0) {
i++;
}
while (compare(array[j], t) > 0) {
j--;
}
}
if (compare(array[left], t) === 0) {
QuickSwap(array, left, j);
} else {
j++;
QuickSwap(array, j, right);
}
if (j <= k) {
left = j + 1;
}
if (k <= j) {
right = j - 1;
}
}
}
export type Compare<T> = (a: T, b: T) => number;
export function QuickSelect<T>(array: T[], k: number, left: number, right: number, compare: Compare<T>) {
QuickStep(array, k, left || 0, right || array.length - 1, compare || QuickCompare);
}
@@ -1,641 +0,0 @@
import type { Nullable } from '../types';
import type { Compare } from './quick-select';
import { QuickSelect } from './quick-select';
const defaultCompareNodeMinX = (a: RectTree.Node, b: RectTree.Node) => a.minX - b.minX;
const defaultCompareNodeMinY = (a: RectTree.Node, b: RectTree.Node) => a.minY - b.minY;
export class RectTree {
private _maxEntries: number;
private _minEntries: number;
private _data: RectTree.Node;
private _compareMinX: Compare<RectTree.Node>;
private _compareMinY: Compare<RectTree.Node>;
constructor(max: number = 9) {
// 默认情况下,节点中的最大条目数为 9; 最小节点填充为 40% 以获得最佳性能
this._maxEntries = Math.max(4, max);
this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
this._data = null as unknown as RectTree.Node;
this._compareMinX = defaultCompareNodeMinX;
this._compareMinY = defaultCompareNodeMinY;
this.clear();
}
static findItem<T>(item: T, items: T[], equalsFn: Nullable<EqualsFunction<T>>) {
if (!equalsFn) {
return items.indexOf(item);
}
for (let i = 0; i < items.length; i++) {
if (equalsFn(item, items[i])) return i;
}
return -1;
}
static multiSelect(
array: RectTree.Node[],
left: number,
right: number,
n: number,
compare: Compare<RectTree.Node>
) {
// 对数组进行排序,以便项目以 n 个未排序项目为一组,各组在彼此之间排序;
// 将选择算法与二元分治法相结合
const stack: number[] = [left, right];
while (stack.length) {
right = stack.pop() as number;
left = stack.pop() as number;
if (right - left <= n) {
continue;
}
const mid = left + Math.ceil((right - left) / n / 2) * n;
QuickSelect(array, mid, left, right, compare);
stack.push(left, mid, mid, right);
}
}
all(): RectTree.Node[] {
return RectTree.Node.expandNode(this._data, []);
}
load(data: RectTree.Node[]): RectTree {
if (!(data && data.length)) {
return this;
}
if (data.length < this._minEntries) {
for (let i = 0; i < data.length; i++) {
this.insert(data[i]);
}
return this;
}
// 使用 OMT 算法从头开始使用给定数据递归构建树
let node = this._build(data.slice(), 0, data.length - 1, 0);
if (!this._data.childrenNodes.length) {
// 如果树为空,则按原样保存
this._data = node;
} else if (this._data.height === node.height) {
// 如果树具有相同的高度,则拆分根
this._splitRoot(this._data, node);
} else {
if (this._data.height < node.height) {
// 如果插入的树更大,则交换树
const tmpNode = this._data;
this._data = node;
node = tmpNode;
}
// 将小树插入大树中适当的层次
this._insert(node, this._data.height - node.height - 1);
}
return this;
}
clear(): RectTree {
this._data = RectTree.Node.makeParentNode([]);
return this;
}
remove(item: RectTree.Node, equals: Nullable<EqualsFunction<RectTree.Node>>): RectTree {
if (!item) {
return this;
}
let node = this._data;
const path: RectTree.Node[] = [];
const indexes: number[] = [];
const bbox = item;
let i = 0;
let goingUp: boolean = false;
let parent: RectTree.Node = null as unknown as RectTree.Node;
// 深度优先迭代树遍历
while (node || path.length) {
if (!node) {
// 往上
node = path.pop() as RectTree.Node;
parent = path[path.length - 1];
i = indexes.pop() as number;
goingUp = true;
}
if (node.leaf) {
// 检查当前节点
const index = RectTree.findItem(item, node.childrenNodes, equals);
if (index !== -1) {
// 找到项目,移除项目并向上压缩树
node.childrenNodes.splice(index, 1);
path.push(node);
this._condense(path);
return this;
}
}
if (!goingUp && !node.leaf && node.contains(bbox)) {
// 往下
path.push(node);
indexes.push(i as number);
i = 0;
parent = node;
node = node.childrenNodes[0];
} else if (parent) {
// 向右走
i++;
node = parent.childrenNodes[i];
goingUp = false;
} else {
// 没有发现
node = null as unknown as RectTree.Node;
}
}
return this;
}
insert(item: RectTree.Node): RectTree {
if (item) {
this._insert(item, this._data.height - 1);
}
return this;
}
collides(bbox: RectTree.Node): boolean {
let node: RectTree.Node = this._data;
if (!bbox.intersects(node)) {
return false;
}
const nodesToSearch = [];
while (node) {
for (let i = 0; i < node.childrenNodes.length; i++) {
const child = node.childrenNodes[i];
const childBBox = child;
if (bbox.intersects(childBBox)) {
if (node.leaf || bbox.contains(childBBox)) {
return true;
}
nodesToSearch.push(child);
}
}
node = nodesToSearch.pop() as RectTree.Node;
}
return false;
}
first(bbox: RectTree.Node): RectTree.Node {
return this.search(bbox)[0];
}
search(bbox: RectTree.Node): RectTree.Node[] {
let node = this._data;
const result: RectTree.Node[] = [];
if (!bbox.intersects(node)) {
return result;
}
const nodesToSearch = [];
while (node) {
for (let i = 0; i < node.childrenNodes.length; i++) {
const child = node.childrenNodes[i];
const childBBox = child;
if (bbox.intersects(childBBox)) {
if (node.leaf) {
result.push(child);
} else if (bbox.contains(childBBox)) {
RectTree.Node.expandNode(child, result);
} else {
nodesToSearch.push(child);
}
}
}
node = nodesToSearch.pop() as RectTree.Node;
}
return result;
}
private _chooseSplitAxis(node: RectTree.Node, m: number, M: number) {
// 按最佳轴对节点子节点进行排序以进行拆分
const compareMinX = node.leaf ? this._compareMinX : defaultCompareNodeMinX;
const compareMinY = node.leaf ? this._compareMinY : defaultCompareNodeMinY;
const xMargin = this._allDistMargin(node, m, M, compareMinX);
const yMargin = this._allDistMargin(node, m, M, compareMinY);
// 如果 x 的总分配边际值最小,则按 minX 排序,
// 否则它已经按 minY 排序
if (xMargin < yMargin) {
node.childrenNodes.sort(compareMinX);
}
}
private _allDistMargin(node: RectTree.Node, m: number, M: number, compare: Compare<RectTree.Node>) {
node.childrenNodes.sort(compare);
const leftBBox = node.distBBox(0, m);
const rightBBox = node.distBBox(M - m, M);
let margin = leftBBox.bboxMargin() + rightBBox.bboxMargin();
for (let i = m; i < M - m; i++) {
const child = node.childrenNodes[i];
leftBBox.extend(child);
margin += leftBBox.bboxMargin();
}
for (let i = M - m - 1; i >= m; i--) {
const child = node.childrenNodes[i];
rightBBox.extend(child);
margin += rightBBox.bboxMargin();
}
return margin;
}
private _split(insertPath: RectTree.Node[], level: number) {
// 将溢出的节点一分为二
const node = insertPath[level];
const M = node.childrenNodes.length;
const m = this._minEntries;
this._chooseSplitAxis(node, m, M);
const splitIndex = this._chooseSplitIndex(node, m, M);
const newNode = RectTree.Node.makeParentNode(
node.childrenNodes.splice(splitIndex, node.childrenNodes.length - splitIndex)
);
newNode.height = node.height;
newNode.leaf = node.leaf;
node.calcBBox();
newNode.calcBBox();
if (level) {
insertPath[level - 1].childrenNodes.push(newNode);
} else {
this._splitRoot(node, newNode);
}
}
private _condense(path: RectTree.Node[]) {
// 遍历路径,删除空节点并更新 bboxes
for (let i = path.length - 1, siblings; i >= 0; i--) {
if (path[i].childrenNodes.length === 0) {
if (i > 0) {
siblings = path[i - 1].childrenNodes;
siblings.splice(siblings.indexOf(path[i]), 1);
} else {
this.clear();
}
} else {
path[i].calcBBox();
}
}
}
private _splitRoot(node: RectTree.Node, newNode: RectTree.Node) {
// 分裂根节点
this._data = RectTree.Node.makeParentNode([node, newNode]);
this._data.height = node.height + 1;
this._data.leaf = false;
this._data.calcBBox();
}
private _insert(item: RectTree.Node, level: number) {
const bbox: RectTree.Node = item;
const insertPath: RectTree.Node[] = [];
// 找到容纳物品的最佳节点,同时保存路径上的所有节点
const node = this._chooseSubtree(bbox, this._data, level, insertPath);
// 将项目放入节点
node.childrenNodes.push(item);
node.extend(bbox);
// 节点溢出时分裂; 必要时向上传播
while (level >= 0) {
if (insertPath[level].childrenNodes.length > this._maxEntries) {
this._split(insertPath, level);
level--;
} else {
break;
}
}
// 沿插入路径调整 bboxes
this._adjustParentBBoxes(bbox, insertPath, level);
}
private _build(items: RectTree.Node[], left: number, right: number, height: number) {
const N = right - left + 1;
let M = this._maxEntries;
let node;
if (N <= M) {
// 达到叶级; 回叶
node = RectTree.Node.makeParentNode(items.slice(left, right + 1));
node.calcBBox();
return node;
}
if (!height) {
// 散装树的目标高度
height = Math.ceil(Math.log(N) / Math.log(M));
// 目标根条目数以最大限度地提高存储利用率
M = Math.ceil(N / M ** (height - 1));
}
node = RectTree.Node.makeParentNode([]);
node.leaf = false;
node.height = height;
// 将项目分成 M 个主要为方形的元素
const N2 = Math.ceil(N / M);
const N1 = N2 * Math.ceil(Math.sqrt(M));
RectTree.multiSelect(items, left, right, N1, defaultCompareNodeMinX);
for (let i = left; i <= right; i += N1) {
const right2 = Math.min(i + N1 - 1, right);
RectTree.multiSelect(items, i, right2, N2, defaultCompareNodeMinY);
for (let j = i; j <= right2; j += N2) {
const right3 = Math.min(j + N2 - 1, right2);
// 递归地打包每个条目
node.childrenNodes.push(this._build(items, j, right3, height - 1));
}
}
node.calcBBox();
return node;
}
private _chooseSplitIndex(node: RectTree.Node, m: number, M: number) {
let index;
let minOverlap = Infinity;
let minArea = Infinity;
for (let i = m; i <= M - m; i++) {
const bbox1 = node.distBBox(0, i);
const bbox2 = node.distBBox(i, M);
const overlap = bbox1.intersectionArea(bbox2);
const area = bbox1.bboxArea() + bbox2.bboxArea();
// 选择重叠最小的分布
if (overlap < minOverlap) {
minOverlap = overlap;
index = i;
minArea = area < minArea ? area : minArea;
} else if (overlap === minOverlap) {
// 否则选择面积最小的分布
if (area < minArea) {
minArea = area;
index = i;
}
}
}
return index || M - m;
}
private _adjustParentBBoxes(bbox: RectTree.Node, path: RectTree.Node[], level: number) {
// 沿给定的树路径调整 bbox
for (let i = level; i >= 0; i--) {
path[i].extend(bbox);
}
}
private _chooseSubtree(bbox: RectTree.Node, node: RectTree.Node, level: number, path: RectTree.Node[]) {
// eslint-disable-next-line no-constant-condition
while (true) {
path.push(node);
if (node.leaf || path.length - 1 === level) {
break;
}
let minArea = Infinity;
let minEnlargement = Infinity;
let targetNode;
for (let i = 0; i < node.childrenNodes.length; i++) {
const child = node.childrenNodes[i];
const area = child.bboxArea();
const enlargement = bbox.enlargedArea(child) - area;
// 选择最小面积扩大的入口
if (enlargement < minEnlargement) {
minEnlargement = enlargement;
minArea = area < minArea ? area : minArea;
targetNode = child;
} else if (enlargement === minEnlargement) {
// 否则选择面积最小的一个
if (area < minArea) {
minArea = area;
targetNode = child;
}
}
}
node = targetNode || node.childrenNodes[0];
}
return node;
}
}
export type EqualsFunction<T> = (a: T, b: T) => boolean;
export namespace RectTree {
export class Node {
minX: number;
minY: number;
maxX: number;
maxY: number;
leaf: boolean;
height: number;
childrenNodes: RectTree.Node[];
constructor({
minY = Infinity, // sri
minX = Infinity, // sci
maxY = -Infinity, // eri
maxX = -Infinity, // eci
leaf = true,
height = 1,
childrenNodes = [],
}: Partial<RectTree.Node> = {}) {
this.minX = minX;
this.minY = minY;
this.maxX = maxX;
this.maxY = maxY;
this.leaf = leaf;
this.height = height;
this.childrenNodes = childrenNodes;
}
static makeParentNode(childrenNodes: RectTree.Node[]) {
return new RectTree.Node({ childrenNodes });
}
static expandNode(node: RectTree.Node, result: RectTree.Node[]): RectTree.Node[] {
const nodesToSearch: RectTree.Node[] = [];
while (node) {
if (node.leaf) {
result.push(...node.childrenNodes);
} else {
nodesToSearch.push(...node.childrenNodes);
}
node = nodesToSearch.pop() as RectTree.Node;
}
return result;
}
difference(other: RectTree.Node, collect: Nullable<RectTree.Node[]>) {
const ret: RectTree.Node[] = collect || [];
const addNode = (minY: number, minX: number, maxY: number, maxX: number) => {
ret.push(
new RectTree.Node({
minY,
minX,
maxY,
maxX,
})
);
};
const { minY, minX, maxY, maxX } = this;
const diffMinY = other.minY - minY;
const diffMinX = other.minX - minX;
const diffMaxY = maxY - other.maxY;
const diffMaxX = maxX - other.maxX;
if (diffMinY > 0) {
addNode(minY, minX, other.minY - 1, maxX);
if (diffMaxY > 0) {
addNode(other.maxY + 1, minX, maxY, maxX);
if (diffMinX > 0) {
addNode(other.minY, minX, other.maxY, other.minX - 1);
}
if (diffMaxX > 0) {
addNode(other.minY, other.maxX + 1, other.maxY, maxX);
}
} else {
if (diffMinX > 0) {
addNode(other.minY, minX, maxY, other.minX - 1);
}
if (diffMaxX > 0) {
addNode(other.minY, other.maxX + 1, maxY, maxX);
}
}
} else if (diffMaxY > 0) {
addNode(other.maxY + 1, minX, maxY, maxX);
if (diffMinX > 0) {
addNode(minY, minX, other.maxY, other.minX - 1);
}
if (diffMaxX > 0) {
addNode(minY, other.maxX + 1, other.maxY, maxX);
}
}
if (diffMinX > 0) {
addNode(minY, minX, maxY, other.minX - 1);
if (diffMaxX > 0) {
addNode(minY, other.maxY + 1, maxY, maxX);
if (diffMinY > 0) {
addNode(minY, other.minX, other.minY - 1, other.maxX);
}
if (diffMaxY > 0) {
addNode(other.minY + 1, other.minX, maxY, other.maxX);
}
} else {
if (diffMinY > 0) {
addNode(minY, other.minX, other.minY - 1, maxX);
}
if (diffMaxY > 0) {
addNode(other.minY + 1, other.minX, maxY, maxX);
}
}
} else if (diffMaxX > 0) {
addNode(minY, other.maxX + 1, maxY, maxX);
if (diffMinY > 0) {
addNode(minY, minX, other.minY - 1, other.maxX);
}
if (diffMaxY > 0) {
addNode(other.maxY + 1, minX, maxY, other.maxX);
}
}
return ret;
}
bboxArea() {
return (this.maxX - this.minX) * (this.maxY - this.minY);
}
bboxMargin() {
return this.maxX - this.minX + (this.maxY - this.minY);
}
calcBBox() {
// 从其子节点的框计算节点框
this.distBBox(0, this.childrenNodes.length, this);
}
distBBox(k: number, p: number, destNode: Nullable<RectTree.Node>) {
// 从 k 到 p-1 的节点子节点的最小边界矩形
if (!destNode) {
destNode = RectTree.Node.makeParentNode(null as unknown as RectTree.Node[]);
}
destNode.minX = Infinity;
destNode.minY = Infinity;
destNode.maxX = -Infinity;
destNode.maxY = -Infinity;
for (let i = k; i < p; i++) {
const child = this.childrenNodes[i];
destNode.extend(child);
}
return destNode;
}
contains(other: RectTree.Node) {
return (
this.minX <= other.minX && this.minY <= other.minY && other.maxX <= this.maxX && other.maxY <= this.maxY
);
}
intersects(other: RectTree.Node) {
return (
other.minX <= this.maxX && other.minY <= this.maxY && other.maxX >= this.minX && other.maxY >= this.minY
);
}
extend(other: RectTree.Node): RectTree.Node {
this.minX = Math.min(this.minX, other.minX);
this.minY = Math.min(this.minY, other.minY);
this.maxX = Math.max(this.maxX, other.maxX);
this.maxY = Math.max(this.maxY, other.maxY);
return this;
}
enlargedArea(other: RectTree.Node) {
return (
(Math.max(other.maxX, this.maxX) - Math.min(other.minX, this.minX)) *
(Math.max(other.maxY, this.maxY) - Math.min(other.minY, this.minY))
);
}
intersectionArea(other: RectTree.Node) {
const minX = Math.max(this.minX, other.minX);
const minY = Math.max(this.minY, other.minY);
const maxX = Math.min(this.maxX, other.maxX);
const maxY = Math.min(this.maxY, other.maxY);
return Math.max(0, maxX - minX) * Math.max(0, maxY - minY);
}
}
}
@@ -1,4 +0,0 @@
export * from './sequence';
export * from './serialize';
export * from './serialize-manager';
export * from './serializer';
@@ -1,3 +0,0 @@
export declare interface Sequence {
className?: string;
}
@@ -1,63 +0,0 @@
import { Tools } from '../tools';
import type { Sequence } from './sequence';
import type { Serialize } from './serialize';
type SerializeClassType<T extends typeof Serialize = typeof Serialize> = T;
type SerializeInstance<I extends Serialize = Serialize> = I;
type SequenceInstance<I extends Sequence = Sequence> = I;
export class SerializeManager {
protected _storage = new Map<string, SerializeClassType>();
addSerializer(clazz: SerializeClassType): void;
addSerializer(clazz: SerializeClassType, name: string): void;
addSerializer(...parameter: any): void {
if (Tools.hasLength(parameter, 1)) {
const clazz = parameter[0];
this._storage.set(clazz.name, clazz);
return;
}
if (Tools.hasLength(parameter, 2)) {
const clazz = parameter[0];
const name = parameter[1];
this._storage.set(name, clazz);
}
}
fromSequence<I extends Serialize>(sequence: object[]): Array<SerializeInstance<I>>;
fromSequence<I extends Serialize>(sequence: object): SerializeInstance<I>;
fromSequence<I extends Serialize>(sequence: object | object[]): Array<SerializeInstance<I>> | SerializeInstance<I> {
if (Tools.isObject<Sequence>(sequence)) {
const Clazz = this._storage.get(sequence.className!) as typeof Serialize;
return Clazz.fromSequence(sequence) as SerializeInstance<I>;
}
if (Tools.isArray<Sequence>(sequence)) {
const result = new Array<SerializeInstance<I>>();
for (const element of sequence) {
result.push(this.fromSequence(element) as SerializeInstance<I>);
}
return result;
}
return [];
}
toSequence(instance: SerializeInstance[]): SequenceInstance[];
toSequence(instance: SerializeInstance): SequenceInstance;
toSequence(instance: SerializeInstance | SerializeInstance[]): SequenceInstance[] | SequenceInstance {
if (Tools.isObject<SerializeInstance>(instance)) {
return instance.toSequence();
}
if (Tools.isArray<SerializeInstance>(instance)) {
const result = new Array<SequenceInstance>();
for (const element of instance) {
result.push(this.toSequence(element));
}
return result;
}
return [];
}
fromStringifyParse(jsonString: string): SerializeInstance[] | SerializeInstance {
return this.fromSequence(JSON.parse(jsonString));
}
}
@@ -1,10 +0,0 @@
import type { Sequence } from './sequence';
export declare class Serialize {
static newInstance(sequence: Sequence): Serialize;
static fromSequence(sequence: Sequence[]): Serialize[];
static fromSequence(sequence: Sequence): Serialize;
toSequence(): Sequence;
}
@@ -1,39 +0,0 @@
import { Tools } from '../tools';
import type { Sequence } from './sequence';
interface SerializerNewInstanceType {
newInstance: (sequence: Sequence) => Serializer;
}
export abstract class Serializer {
static fromSequence<T extends Serializer>(sequence: Sequence[]): T[];
static fromSequence<T extends Serializer>(sequence: Sequence): T;
static fromSequence<T extends Serializer>(sequence: Sequence | Sequence[]): T | T[] {
if (Tools.isObject<Sequence>(sequence)) {
if (Serializer.requiredNewInstance(this)) {
const SerializerClass = this;
return SerializerClass.newInstance(sequence) as T;
}
}
if (Tools.isArray<Sequence>(sequence)) {
if (Serializer.requiredNewInstance(this)) {
const SerializerClass = this;
return sequence.map((element: Sequence) => SerializerClass.newInstance(element)) as T[];
}
}
return [];
}
toSequence(): Sequence {
return { className: Tools.getClassName(this) };
}
}
export namespace Serializer {
export function requiredNewInstance(target: any): target is SerializerNewInstanceType {
if (Tools.isDefine(target.newInstance)) {
return true;
}
throw new Error('sequence newInstance need subclass implement!');
}
}
-66
View File
@@ -1,66 +0,0 @@
import { Tools } from './tools';
/**
* Tool library for detecting data types
*/
export class Tuples {
static STRING_TYPE = 1;
static NUMBER_TYPE = 2;
static BOOLEAN_TYPE = 3;
static FUNCTION_TYPE = 4;
static OBJECT_TYPE = 5;
static checkup(target: IArguments | unknown[], ...types: any[]): boolean {
if (target.length !== types.length) {
return false;
}
const length = target.length;
for (let i = 0; i < length; i++) {
const element = target[i];
const type = types[i];
switch (type) {
case Tuples.BOOLEAN_TYPE: {
if (!Tools.isBoolean(element)) {
return false;
}
break;
}
case Tuples.NUMBER_TYPE: {
if (!Tools.isNumber(element)) {
return false;
}
break;
}
case Tuples.STRING_TYPE: {
if (!Tools.isString(element)) {
return false;
}
break;
}
case Tuples.FUNCTION_TYPE: {
if (!Tools.isFunction(element)) {
return false;
}
break;
}
case Tuples.OBJECT_TYPE: {
if (!Tools.isPlainObject(element)) {
return false;
}
break;
}
default: {
if (!Tools.isAssignableFrom(element, type)) {
return false;
}
break;
}
}
}
return true;
}
}
-13
View File
@@ -1,13 +0,0 @@
/**
* Register, get type
*/
export class TypeStoreBase {
static RegisteredTypes: { [key: string]: Object } = {};
static GetClass(fqdn: string): any {
if (this.RegisteredTypes && this.RegisteredTypes[fqdn]) {
return this.RegisteredTypes[fqdn];
}
return null;
}
}
+1 -1
View File
@@ -521,7 +521,7 @@ export class Viewport {
if (!applyCanvasState && this._renderClipState) {
ctx.beginPath();
// DEPT: left is set by upper views but width and height is not
// DEBT: left is set by upper views but width and height is not
const { scaleX, scaleY } = this._getBoundScale(m[0], m[3]);
ctx.rect(this.left, this.top, (this.width || 0) * scaleX, (this.height || 0) * scaleY);
ctx.clip();
@@ -1,4 +1,3 @@
/* eslint-disable no-magic-numbers */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { RxDisposable } from '@univerjs/core';
@@ -139,8 +138,6 @@ const enum ResponseType {
SUBSCRIBE_COMPLETE = 202,
}
// TODO@wzhudev:
interface IRPCResponse {
/** It should be the same as its corresponding requests' `seq`. */
seq: number;
@@ -1,4 +1,4 @@
import type { IBorderData, IStyleData, IWorksheetData } from '../../types/interfaces';
import type { IBorderData, IStyleData, IWorksheetData } from '@univerjs/core/types/interfaces/index.js';
export function border(newSheet: Partial<IWorksheetData>, sheet: any) {
newSheet.cellData = {};
@@ -1,8 +1,445 @@
import type { IKeyValue } from '@univerjs/core';
import { IUniverInstanceService, migrate, Tools } from '@univerjs/core';
import type {
BooleanNumber,
ICellData,
IColumnData,
IKeyValue,
IRowData,
IStyleData,
ITextRun,
ITextStyle,
IWorkbookData,
IWorksheetData,
ObjectArrayPrimitiveType,
} from '@univerjs/core';
import { HorizontalAlign, IUniverInstanceService, Tools, VerticalAlign, WrapStrategy } from '@univerjs/core';
// @ts-ignore
import * as LuckyExcel from 'luckyexcel'; // no type definition for package luckyexcel
import { border } from './border';
export function migrate(config: any): Partial<IWorkbookData> {
const newConfig: Partial<IWorkbookData> = {};
newConfig.styles = {};
if (config.hasOwnProperty('lang')) {
newConfig.locale = config.lang;
}
if (config.hasOwnProperty('data')) {
newConfig.sheets = {};
for (const sheet of config.data) {
const newSheet: Partial<IWorksheetData> = {};
// id
// if (sheet.hasOwnProperty('index')) {
// newSheet.id = sheet.index as string;
// } else {
newSheet.id = Tools.generateRandomId(6);
// }
// 缩放比例
if (sheet.hasOwnProperty('zoomRatio')) {
newSheet.zoomRatio = sheet.zoomRatio;
}
// 最大行号
if (sheet.hasOwnProperty('row')) {
newSheet.rowCount = sheet.row;
}
// 最大列号
if (sheet.hasOwnProperty('column')) {
newSheet.columnCount = sheet.column;
}
// 是否激活
if (sheet.hasOwnProperty('status')) {
newSheet.status = parseInt(sheet.status);
}
// sheet 名称
if (sheet.hasOwnProperty('name')) {
newSheet.name = sheet.name;
}
// 激活选区
if (sheet.hasOwnProperty('universheet_select_save')) {
newSheet.selections = [];
sheet.universheet_select_save.forEach((selection: IKeyValue) => {
newSheet.selections?.push({
startRow: selection.row[0],
endRow: selection.row[1],
startColumn: selection.column[0],
endColumn: selection.column[1],
});
});
}
// 横向滚动条位置
if (sheet.hasOwnProperty('scrollLeft')) {
newSheet.scrollLeft = sheet.scrollLeft;
}
// 竖向滚动条位置
if (sheet.hasOwnProperty('scrollTop')) {
newSheet.scrollTop = sheet.scrollTop;
}
if (sheet.hasOwnProperty('config')) {
// 合并单元格
if (sheet.config.merge) {
newSheet.mergeData = [];
for (const key of Object.keys(sheet.config.merge)) {
const merge: {
r: number;
c: number;
rs: number;
cs: number;
} = sheet.config.merge[key];
const mergeReact = {
startRow: merge.r,
endRow: merge.r + merge.rs - 1,
startColumn: merge.c,
endColumn: merge.c + merge.cs - 1,
};
newSheet.mergeData.push(mergeReact);
}
}
// 边框,先按顺序设置到每一个单元格对象中,设置时需要遍历四个方向的相邻单元格做去重,最后单元格样式确定之后,再统一提取到styles上
if (sheet.config.borderInfo) {
border(newSheet, sheet);
}
// 行高
if (sheet.config.rowlen) {
newSheet.rowData = {};
for (const [rowIndex, height] of Object.entries(sheet.config.rowlen)) {
newSheet.rowData[Number(rowIndex)] = {
h: Number(height),
hd: 0,
};
}
}
// 列宽
if (sheet.config.columnlen) {
newSheet.columnData = {};
for (const [colIndex, width] of Object.entries(sheet.config.columnlen)) {
newSheet.columnData[Number(colIndex)] = {
w: Number(width),
hd: 0,
};
}
}
// 隐藏行
if (sheet.config.rowhidden) {
if (!newSheet.rowData) newSheet.rowData = {};
const rowData = newSheet.rowData as ObjectArrayPrimitiveType<Partial<IRowData>>;
for (const [rowIndex, isHidden] of Object.entries(sheet.config.rowhidden)) {
if (!rowData[Number(rowIndex)]) {
rowData[Number(rowIndex)] = {};
}
rowData[Number(rowIndex)].hd = isHidden as BooleanNumber;
}
}
// 隐藏列
if (sheet.config.colhidden) {
if (!newSheet.columnData) newSheet.columnData = {};
const columnData = newSheet.columnData as ObjectArrayPrimitiveType<Partial<IColumnData>>;
for (const [colIndex, isHidden] of Object.entries(sheet.config.colhidden)) {
if (!columnData[Number(colIndex)]) {
columnData[Number(colIndex)] = {};
}
columnData[Number(colIndex)].hd = isHidden as BooleanNumber;
}
}
}
// 单元格数据
if (sheet.hasOwnProperty('celldata')) {
if (!newSheet.cellData) {
newSheet.cellData = {};
}
for (const cellItem of sheet.celldata) {
if (!newSheet.cellData[cellItem.r]) {
newSheet.cellData[cellItem.r] = {};
}
if (!newSheet.cellData[cellItem.r][cellItem.c]) {
newSheet.cellData[cellItem.r][cellItem.c] = {};
}
const cell = cellItem.v;
const newCell: ICellData = newSheet.cellData[cellItem.r][cellItem.c];
if (cell?.ct?.t === 'inlineStr') {
const textRuns: ITextRun[] = [];
let dataStream = '';
cell.ct.s.forEach((element: any) => {
const textStyle: ITextStyle = {};
// 背景颜色
if (element.hasOwnProperty('bg')) {
textStyle.bg = {
rgb: element.fc,
};
}
// 字体
if (element.hasOwnProperty('ff')) {
textStyle.ff = element.ff;
}
// 字体颜色
if (element.hasOwnProperty('fc')) {
textStyle.cl = {
rgb: element.fc,
};
}
// 字体大小
if (element.hasOwnProperty('fs')) {
textStyle.fs = element.fs;
}
// 粗体
if (element.hasOwnProperty('bl')) {
textStyle.bl = element.bl;
}
// 斜体
if (element.hasOwnProperty('it')) {
textStyle.it = element.it;
}
// 删除线
if (element.hasOwnProperty('cl')) {
textStyle.st = {
s: 1,
};
}
// 下划线
if (element.hasOwnProperty('un')) {
textStyle.ul = {
s: element.un,
};
}
const sId = Tools.generateRandomId(6);
dataStream += element.v;
textRuns.push({
sId,
st: 0,
ed: element.v.length - 1,
ts: textStyle,
});
});
dataStream += '\r\n';
newCell.p = {
id: Tools.generateRandomId(6),
body: {
dataStream,
textRuns,
},
documentStyle: {},
};
} else {
// 原始值
if (cell.hasOwnProperty('v')) {
newCell.v = cell.v;
}
const cellStyle: IStyleData = {};
// 背景颜色
if (cell.hasOwnProperty('bg')) {
cellStyle.bg = {
rgb: cell.bg,
};
}
// 字体
if (cell.hasOwnProperty('ff')) {
cellStyle.ff = cell.ff;
}
// 字体颜色
if (cell.hasOwnProperty('fc')) {
cellStyle.cl = {
rgb: cell.fc,
};
}
// 字体大小
if (cell.hasOwnProperty('fs')) {
cellStyle.fs = cell.fs;
}
// 粗体
if (cell.hasOwnProperty('bl')) {
cellStyle.bl = cell.bl;
}
// 斜体
if (cell.hasOwnProperty('it')) {
cellStyle.it = cell.it;
}
// 删除线
if (cell.hasOwnProperty('cl')) {
cellStyle.st = {
s: cell.cl,
};
}
// 下划线
if (cell.hasOwnProperty('un')) {
cellStyle.ul = {
s: cell.un,
};
}
const sMap = {
'0': VerticalAlign.MIDDLE,
'1': VerticalAlign.TOP,
'2': VerticalAlign.BOTTOM,
};
// const sMap = {
// '0': 2,
// '1': 1,
// '2': 3,
// };
// 垂直对齐
if (cell.hasOwnProperty('vt')) {
switch (String(cell.vt)) {
case '0':
cellStyle.vt = VerticalAlign.MIDDLE;
break;
case '1':
cellStyle.vt = VerticalAlign.TOP;
break;
case '2':
cellStyle.vt = VerticalAlign.BOTTOM;
break;
default:
break;
}
// cellStyle.vt = sMap[String(cell.vt)];
}
// 水平对齐
if (cell.hasOwnProperty('ht')) {
switch (String(cell.ht)) {
case '0':
cellStyle.ht = HorizontalAlign.CENTER;
break;
case '1':
cellStyle.ht = HorizontalAlign.LEFT;
break;
case '2':
cellStyle.ht = HorizontalAlign.RIGHT;
break;
default:
break;
}
// cellStyle.ht = sMap[String(cell.ht)];
}
// 竖排文字
if (cell.hasOwnProperty('tr')) {
switch (cell.tr) {
case '0':
cellStyle.tr = {
a: 0,
v: 0,
};
break;
case '1':
cellStyle.tr = {
a: 45,
v: 0,
};
break;
case '2':
cellStyle.tr = {
a: -45,
v: 0,
};
break;
case '3':
cellStyle.tr = {
a: 0,
v: 1,
};
break;
case '4':
cellStyle.tr = {
a: 90,
v: 0,
};
break;
case '5':
cellStyle.tr = {
a: -90,
v: 0,
};
break;
default:
break;
}
}
// 文字旋转角度
if (cell.hasOwnProperty('rt')) {
cellStyle.tr = {
a: cell.tr,
v: 0,
};
}
// 文本换行
if (cell.hasOwnProperty('tb')) {
switch (String(cell.tb)) {
case '0':
cellStyle.tb = WrapStrategy.CLIP;
break;
case '1':
cellStyle.tb = WrapStrategy.OVERFLOW;
break;
case '2':
cellStyle.tb = WrapStrategy.WRAP;
break;
default:
break;
}
//cellStyle.tb = sMap[String(cell.tb)];
}
newCell.s = Object.assign(newCell.s || {}, cellStyle);
}
}
}
newConfig.sheets[newSheet.id] = newSheet;
}
}
return newConfig;
}
export class UploadService {
constructor(@IUniverInstanceService private readonly _currentUniverService: IUniverInstanceService) {}
@@ -1,5 +1,3 @@
// TODO@wzhudev: some logic here should be moved to the controller
import type { ICommand } from '@univerjs/core';
import { CommandType, ICommandService, IUndoRedoService, IUniverInstanceService, RANGE_TYPE } from '@univerjs/core';
import type { ISetFrozenMutationParams } from '@univerjs/sheets';
@@ -1,5 +1,3 @@
// TODO@wzhudev: some logic here should be moved to the controller
import type { ICommand } from '@univerjs/core';
import { CommandType, ICommandService, IUndoRedoService, IUniverInstanceService, RANGE_TYPE } from '@univerjs/core';
import type { ISetFrozenMutationParams } from '@univerjs/sheets';
@@ -207,19 +207,17 @@ export class SheetUIController extends Disposable {
this._init();
}
private _init(): void {
// init custom component
private _initCustomComponents(): void {
const componentManager = this._componentManager;
this.disposeWithMe(componentManager.register(MENU_ITEM_INPUT_COMPONENT, MenuItemInput));
this.disposeWithMe(componentManager.register(BORDER_PANEL_COMPONENT, BorderPanel));
this.disposeWithMe(componentManager.register(COLOR_PICKER_COMPONENT, ColorPicker));
this.disposeWithMe(componentManager.register(FONT_FAMILY_COMPONENT, FontFamily));
this.disposeWithMe(componentManager.register(FONT_FAMILY_ITEM_COMPONENT, FontFamilyItem));
this.disposeWithMe(componentManager.register(FONT_SIZE_COMPONENT, FontSize));
}
// FIXME: no dispose logic
componentManager.register(MENU_ITEM_INPUT_COMPONENT, MenuItemInput);
componentManager.register(BORDER_PANEL_COMPONENT, BorderPanel);
componentManager.register(COLOR_PICKER_COMPONENT, ColorPicker);
componentManager.register(FONT_FAMILY_COMPONENT, FontFamily);
componentManager.register(FONT_FAMILY_ITEM_COMPONENT, FontFamilyItem);
componentManager.register(FONT_SIZE_COMPONENT, FontSize);
// init commands
private _initCommands(): void {
[
AddWorksheetMergeAllCommand,
AddWorksheetMergeCommand,
@@ -268,8 +266,9 @@ export class SheetUIController extends Disposable {
].forEach((c) => {
this.disposeWithMe(this._commandService.registerCommand(c));
});
}
// init menus
private _initMenus(): void {
(
[
// context menu
@@ -346,8 +345,9 @@ export class SheetUIController extends Disposable {
).forEach((factory) => {
this.disposeWithMe(this._menuService.addMenuItem(this._injector.invoke(factory)));
});
}
// init shortcuts
private _initShortcuts(): void {
[
// selection shortcuts
MoveSelectionDownShortcutItem,
@@ -400,7 +400,9 @@ export class SheetUIController extends Disposable {
].forEach((item) => {
this.disposeWithMe(this._shortcutService.registerShortcut(item));
});
}
private _initWorkbenchParts(): void {
this.disposeWithMe(
this._uiController.registerHeaderComponent(() => connectInjector(RenderSheetHeader, this._injector))
);
@@ -413,4 +415,12 @@ export class SheetUIController extends Disposable {
this._uiController.registerContentComponent(() => connectInjector(RenderSheetContent, this._injector))
);
}
private _init(): void {
this._initCustomComponents();
this._initCommands();
this._initMenus();
this._initShortcuts();
this._initWorkbenchParts();
}
}