Ver código fonte

添加上格云Web基础库.

庞利祥 6 anos atrás
commit
b29ea1d14c

+ 7 - 0
.gitignore

@@ -0,0 +1,7 @@
+/.idea
+*.iml
+coverage
+node_modules
+package-lock.json
+lib
+api

+ 9 - 0
saga-web-base/.editorconfig

@@ -0,0 +1,9 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 4
+end_of_line =lf
+insert_final_newline = true
+trim_trailing_whitespace = true

+ 36 - 0
saga-web-base/.eslintrc.js

@@ -0,0 +1,36 @@
+module.exports = {
+    root: true,
+    parser: "@typescript-eslint/parser",
+    extends: [
+        "plugin:@typescript-eslint/recommended",
+        // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
+        "prettier/@typescript-eslint",
+        // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
+        // 此行必须在最后
+        "plugin:prettier/recommended"
+    ],
+    env: {
+        es6: true,
+        node: true
+    },
+    parserOptions: {
+        // 支持最新 JavaScript
+        ecmaVersion: 2018,
+        sourceType: "module"
+    },
+    rules: {
+        // 注释
+        // 文件
+        "max-classes-per-file": ["error", 1],           // 一个文件中只能定义一个类
+        "max-lines-per-function": ["error", 200],       // 一个函数最多200行代码
+        "max-statements-per-line": ["error", {max: 1}], // 一行只允许有一条语句
+        // 缩进
+        "indent": ["error", 4],                         // 缩进控制4空格
+        "max-len": ["error", 120],                      // 每行字符不超过120
+        "no-mixed-spaces-and-tabs": "error",            // 禁止使用空格和tab混合缩进
+        // 语句
+        "curly": ["error", "multi-line"],               // if、else if、else、for、while强制使用大括号,但允许在单行中省略大括号。
+        "semi": ["error", "always"],                    // 不得省略语句结束的分号
+        "@typescript-eslint/explicit-member-accessibility": ["error", { accessibility: "no-public" }],       // public访问不需加访问修饰符
+    }
+};

+ 12 - 0
saga-web-base/.npmignore

@@ -0,0 +1,12 @@
+# 发布时排除
+__tests__/
+api/
+docs/
+src/
+.editorconfig
+.eslintrc.js
+*.iml
+coverage
+jest.config.js
+tsconfig.json
+typedoc.json

+ 0 - 0
saga-web-base/README.md


+ 12 - 0
saga-web-base/jest.config.js

@@ -0,0 +1,12 @@
+module.exports = {
+    preset: "ts-jest",
+    moduleFileExtensions: ["js", "ts"],
+    transform: {
+        "^.+\\.tsx?$": "ts-jest"
+    },
+    transformIgnorePatterns: ["/node_modules/"],
+    moduleNameMapper: {
+        "^@/(.*)$": "<rootDir>/src/$1"
+    },
+    collectCoverage: true
+};

+ 35 - 0
saga-web-base/package.json

@@ -0,0 +1,35 @@
+{
+    "name": "@sagacloud-web/base",
+    "version": "2.1.7",
+    "description": "上格云Web基础库。",
+    "main": "lib/index.js",
+    "types": "lib/index.d.js",
+    "scripts": {
+        "build": "tsc",
+        "publish": "npm publish",
+        "lint": "eslint src/**/*.{js,ts,tsx}",
+        "test": "jest",
+        "typedoc": "typedoc --hideGenerator src"
+    },
+    "keywords": [
+        "sybotan",
+        "base"
+    ],
+    "author": "庞利祥 (sybotan@126.com)",
+    "license": "ISC",
+    "publishConfig": {
+        "registry": "http://dev.dp.sagacloud.cn:8082/repository/npm-host/"
+    },
+    "devDependencies": {
+        "@typescript-eslint/eslint-plugin": "^1.12.0",
+        "@typescript-eslint/parser": "^1.12.0",
+        "eslint": "^6.0.1",
+        "eslint-config-prettier": "^6.0.0",
+        "eslint-plugin-prettier": "^3.1.0",
+        "prettier": "^1.18.2",
+        "@types/jest": "^24.0.15",
+        "ts-jest": "^24.0.2",
+        "typescript": "^3.5.3"
+    },
+    "dependencies": {}
+}

+ 53 - 0
saga-web-base/src/SMouseEvent.ts

@@ -0,0 +1,53 @@
+/**
+ * 鼠标事件
+ *
+ * @author
+ */
+
+export class SMouseEvent {
+    /** 事件类型 */
+    type: string;
+    /** x坐标 */
+    x: number;
+    /** y坐标 */
+    y: number;
+    /** 屏幕x坐标 */
+    screenX: number;
+    /** 屏幕y坐标 */
+    screenY: number;
+    /** 客户端x坐标 */
+    clientX: number;
+    /** 客户端Y坐标 */
+    clientY: number;
+    /** 是否按下alt键 */
+    altKey: boolean;
+    /** 是否按下ctrl键 */
+    ctrlKey: boolean;
+    /** 鼠标按键 */
+    buttons: number;
+
+    /**
+     * 构造函数
+     *
+     * @param   event   系统鼠标事件
+     */
+    constructor(event: MouseEvent | SMouseEvent) {
+        // let bbox = (this.type = event.type); //event.srcElement.getBoundingClientRect()
+        this.type = event.type;
+
+        this.screenX = event.screenX;
+        this.screenY = event.screenY;
+        this.clientX = event.clientX;
+        this.clientY = event.clientY;
+        this.altKey = event.altKey;
+        this.ctrlKey = event.ctrlKey;
+        this.buttons = event.buttons;
+        if (event instanceof SMouseEvent) {
+            this.x = event.x;
+            this.y = event.y;
+        } else {
+            this.x = event.offsetX; // TODO: PLX  - bbox.left;
+            this.y = event.offsetY; // TODO: PLX  - bbox.top;
+        }
+    } // Function constructor()
+} // Class SMouseEvent

+ 87 - 0
saga-web-base/src/SObject.ts

@@ -0,0 +1,87 @@
+import { SObjectObserver } from "./SObjectObserver";
+
+/**
+ * Sybotan 对象
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+export class SObject {
+    /** 对象名 */
+    name: string = "";
+    /** 监听数组 */
+    private listeners = {};
+
+    /**
+     * 连接信号
+     *
+     * @param   name        信息名称
+     * @param   receiver    信号接收者
+     * @param   callback    回调函数
+     */
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    connect(name: string, receiver: any, callback: Function): void {
+        // @ts-ignore
+        let observerList = this.listeners[name];
+        if (!observerList) {
+            // @ts-ignore
+            this.listeners[name] = [];
+        } else {
+            // 如果已经连接,则返回
+            for (let observer of observerList) {
+                if (observer.compar(receiver)) {
+                    return;
+                }
+            }
+        }
+
+        // @ts-ignore
+        this.listeners[name].push(new SObjectObserver(receiver, callback));
+    } // Function
+
+    /**
+     * 断开连接的信号
+     *
+     * @param   name        信息名称
+     * @param   receiver    信号接收者
+     */
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    disconnect(name: string, receiver: any): void {
+        // @ts-ignore
+        let observerList = this.listeners[name];
+        if (!observerList) {
+            return;
+        }
+
+        let length = observerList.length;
+        for (let i = 0; i < length; i++) {
+            let observer = observerList[i];
+            if (observer.compar(receiver)) {
+                observerList.splice(i, 1);
+                break;
+            }
+        }
+        if (observerList.length == 0) {
+            // @ts-ignore
+            delete this.listeners[name];
+        }
+    } // Function disconnect()
+
+    /**
+     * 发出事件
+     *
+     * @param   name    事件名称
+     * @param   args    参数
+     */
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    $emit(name: string, ...args: any[]): void {
+        // @ts-ignore
+        let observerList = this.listeners[name];
+        if (!observerList) {
+            return;
+        }
+
+        for (let observer of observerList) {
+            observer.notify(this, args);
+        }
+    } // Function $emit()
+} // Class SObject

+ 55 - 0
saga-web-base/src/SObjectObserver.ts

@@ -0,0 +1,55 @@
+export class SObjectObserver {
+    /** 回调函数 */
+    private readonly callback: Function;
+    /** 信号接收者 */
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    private readonly receiver: any = null;
+
+    /**
+     * 构造函数
+     *
+     * @param   receiver    信号接收者
+     * @param   callback    回调函数
+     */
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    constructor(receiver: any, callback: Function) {
+        this.receiver = receiver;
+        this.callback = callback;
+    } // Constructor
+
+    /**
+     * 发送通知
+     *
+     * @param   args    参数
+     */
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    notify(...args: any[]): void {
+        if (args.length == 2) {
+            this.callback.call(this.receiver, args[0], args[1]);
+        } else if (args.length == 3) {
+            this.callback.call(this.receiver, args[0], args[1], args[2]);
+        } else if (args.length == 4) {
+            this.callback.call(this.receiver, args[0], args[1], args[2], args[3]);
+        } else if (args.length == 5) {
+            this.callback.call(this.receiver, args[0], args[1], args[2], args[3], args[4]);
+        } else if (args.length == 6) {
+            this.callback.call(this.receiver, args[0], args[1], args[2], args[3], args[4], args[5]);
+        } else if (args.length == 7) {
+            this.callback.call(this.receiver, args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
+        } else if (args.length == 8) {
+            this.callback.call(this.receiver, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
+        } else {
+            this.callback.call(this.receiver, ...args);
+        }
+    } // Function notify()
+
+    /**
+     * 上信号接收者比较
+     *
+     * @param   receiver        信号接收者
+     */
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    compar(receiver: any): boolean {
+        return receiver == this.receiver;
+    } // Function compar()
+} // Class EventObserver

+ 13 - 0
saga-web-base/src/enums/SMouseButton.ts

@@ -0,0 +1,13 @@
+/**
+ * 鼠标按键枚举
+ *
+ * @author
+ */
+export enum SMouseButton {
+    /** 鼠标左键 */
+    LeftButton = 0x1,
+    /** 鼠标右键 */
+    RightButton = 0x2,
+    /** 鼠标中键 */
+    MidButton = 0x4
+} // Enum SMouseButton

+ 13 - 0
saga-web-base/src/enums/STouchState.ts

@@ -0,0 +1,13 @@
+/**
+ * 手势状态
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+export enum STouchState {
+    /** 标准状态 */
+    None,
+    /** 移动状态 */
+    Move,
+    /** 缩放状态 */
+    Zoom
+} // Enum STouchState

+ 13 - 0
saga-web-base/src/index.ts

@@ -0,0 +1,13 @@
+import { SMouseButton } from "./enums/SMouseButton";
+import { STouchState } from "./enums/STouchState"
+export { SMouseButton, STouchState };
+
+import { SNetUtil } from "./utils/SNetUtil";
+import { SStringBuilder } from "./utils/SStringBuilder";
+import { SStringUtil } from "./utils/SStringUtil";
+export { SNetUtil, SStringBuilder, SStringUtil };
+
+import { SMouseEvent } from "./SMouseEvent";
+import { SObject } from "./SObject";
+import { SObjectObserver } from "./SObjectObserver";
+export { SMouseEvent, SObject, SObjectObserver };

+ 11 - 0
saga-web-base/src/undo/SUndoCommand.ts

@@ -0,0 +1,11 @@
+/**
+ * Undo命令
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+export abstract class SUndoCommand {
+    /** 执行undo操作 */
+    abstract undo(): void;
+    /** 执行redo操作 */
+    abstract redo(): void;
+} // Class SUndoCommand

+ 103 - 0
saga-web-base/src/undo/SUndoStack.ts

@@ -0,0 +1,103 @@
+import { SObject } from "../SObject";
+import { SUndoCommand } from "./SUndoCommand";
+
+/**
+ * Undo操作堆栈
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+export class SUndoStack extends SObject {
+    /** 命令栈 */
+    private cmdStack = Array<SUndoCommand>();
+
+    /** 当前命令索引 */
+    private _index = -1;
+    get index(): number {
+        return this._index;
+    } // Get index
+
+    /** 命令栈是否为空 */
+    get isEmpty(): boolean {
+        return this.cmdStack.length <= 0;
+    } // Get isEmpty
+
+    /**
+     * 执行redo操作
+     */
+    redo(): void {
+        if (!this.canRedo()) {
+            return;
+        }
+
+        this._index++;
+        this.cmdStack[this._index].redo();
+    } // Function redo()
+
+    /**
+     * 执行undo操作
+     */
+    undo(): void {
+        if (!this.canUndo()) {
+            return;
+        }
+
+        this.cmdStack[this._index].undo();
+        this._index--;
+    } // Function undo()
+
+    /**
+     * 是否可以执行Redo操作
+     *
+     * @return  可以执行,返回true;否则返回false。
+     */
+    canRedo(): boolean {
+        return this.index + 1 < this.cmdStack.length;
+    } // Function canRedo()
+
+    /**
+     * 是否可以执行Undo操作
+     *
+     * @return  可以执行,返回true;否则返回false。
+     */
+    canUndo(): boolean {
+        return this.index >= 0;
+    } // Function canUndo()
+
+    /**
+     * 清空堆栈
+     */
+    clear(): void {
+        // this.cmdStack
+    } // Function clear()
+
+    /**
+     * 返回指定索引的命令
+     *
+     * @param   index       命令在栈中的索引
+     */
+    command(index: number): SUndoCommand | null {
+        // 如果索引越界,则返回null
+        if (index < 0 || index >= this.cmdStack.length) {
+            return null;
+        }
+
+        return this.cmdStack[index];
+    } // Function command()
+
+    /**
+     * 统计命令栈中命令的数量
+     */
+    cout(): number {
+        return this.cmdStack.length;
+    } // Function cout()
+
+    /**
+     * 将命令添加到命令栈
+     *
+     * @param   cmd     被添加的命令
+     */
+    push(cmd: SUndoCommand): void {
+        this.cmdStack.push(cmd);
+        this._index = this.cmdStack.length - 1;
+    } // Function push()
+} // Class SUndoStack

+ 17 - 0
saga-web-base/src/utils/SNetUtil.ts

@@ -0,0 +1,17 @@
+export class SNetUtil {
+    /**
+     * 下载
+     *
+     * @param   name    下载文件名称
+     * @param   url     下载url地址
+     */
+    static downLoad(name: string, url: string): void {
+        let oA = document.createElement("a");
+        oA.download = name;
+        oA.href = url;
+        document.body.appendChild(oA);
+        oA.click();
+        // 下载之后把创建的元素删除
+        oA.remove();
+    } // Function downLoad()
+} // Class SNetUtil

+ 39 - 0
saga-web-base/src/utils/SStringBuilder.ts

@@ -0,0 +1,39 @@
+/**
+ * 字符串builder
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+export class SStringBuilder {
+    /** 字符串数组 */
+    private _strArray = new Array<string>();
+
+    /**
+     * 构造函数
+     *
+     * @param   msg     初始字符串
+     */
+    constructor(msg?: string) {
+        if (msg != undefined) {
+            this.append(msg);
+        }
+    } // Constructor()
+
+    /**
+     * 追加字符串
+     *
+     * @param   msg     追加的字符串
+     */
+    append(msg: string): SStringBuilder {
+        this._strArray.push(msg);
+        return this;
+    } // Function append()
+
+    /**
+     * 转换为字符串
+     *
+     * @param   gap     分隔符,默认换行(\n)
+     */
+    toString(gap: string = "\n"): string {
+        return this._strArray.join(gap);
+    } // Function toString()
+} // Class SStringBuilder

+ 36 - 0
saga-web-base/src/utils/SStringUtil.ts

@@ -0,0 +1,36 @@
+export class SStringUtil {
+    /**
+     * 数值转16进制字符串
+     *
+     * @param   n       要转换的数值
+     * @param   len     转换后的长度(默认为2)
+     * @return  转换后的数据
+     */
+    static num2Hex(n: number, len: number = 2): string {
+        let hex = "";
+        let numStr: string[] = [
+            "0",
+            "1",
+            "2",
+            "3",
+            "4",
+            "5",
+            "6",
+            "7",
+            "8",
+            "9",
+            "a",
+            "b",
+            "c",
+            "d",
+            "e",
+            "f"
+        ];
+        while (len > 0) {
+            hex = numStr[n & 0x0f] + hex;
+            n = n >> 4;
+            len--;
+        }
+        return hex;
+    } // Function num2Hex()
+} // Class SStringUtil

+ 16 - 0
saga-web-base/tsconfig.json

@@ -0,0 +1,16 @@
+{
+    "compilerOptions": {
+        "target": "es5",                            // Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'.
+        "module": "commonjs",                       // Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'.
+        "outDir": "./lib",                          // 编译后生成的文件目录
+        "strict": true,                             // 开启严格的类型检测
+        "declaration": true,                        // 生成 `.d.ts` 文件
+        "experimentalDecorators": true,             // 开启装饰器
+        "removeComments": true,                     // 去除注释
+        "noImplicitAny": true,                      // 在表达式和声明上有隐含的 any类型时报错。
+        "esModuleInterop": true,                    // 支持别名导入
+        "moduleResolution": "node"                  // 此处设置为node,才能解析import xx from 'xx'
+    },
+    "include": ["./src"],
+    "exclude": ["node_modules"]
+}

+ 6 - 0
saga-web-base/typedoc.json

@@ -0,0 +1,6 @@
+{
+    "name": "上格云Web基础库",
+    "mode": "file",
+    "out": "doc",
+    "exclude": ["**/*+(index|.test).ts"]
+}