diff --git a/.eslintrc b/.eslintrc index c435e6b66..12defcd8a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -17,7 +17,6 @@ }, "parser": "@typescript-eslint/parser", "parserOptions": { - "project": "./tsconfig.json", "ecmaVersion": 6, "sourceType": "module", "ecmaFeatures": { @@ -26,7 +25,7 @@ }, "plugins": ["@typescript-eslint/eslint-plugin"], "overrides": [{ - "files": ["src/*.js","src/**/*.js", "demo/*.js", "demo/**/*.js", "i18n/**/*.js", "i18n/*.js", "test/**/*.js", "test/*.js"], + "files": ["src/*.js","src/**/*.js", "demo/*.js", "demo/**/*.js", "i18n/**/*.js", "i18n/*.js", "test/**/*.js", "test/*.js", "examples/*.js", "examples/**/*.js"], "extends": "plugin:@fui/es5", "rules": { "no-param-reassign": "off", @@ -34,7 +33,10 @@ "comma-dangle": ["error", "never"] // 多行对象字面量中要求拖尾逗号 } }, { - "files": ["webpack/*.js", "types/*.ts", "typescript/*.ts","typescript/**/*.ts", "./*.js", "lib/**/*.js", "lib/*.js"], + "files": ["webpack/*.js", "./*.js", "lib/**/*.js", "lib/*.js", "./bin/*.js", "./bin/**/*.js"], + "extends": "plugin:@fui/esnext" + }, { + "files": ["types/*.ts", "typescript/*.ts","typescript/**/*.ts"], "extends": "plugin:@fui/typescript" }] } diff --git a/.npmignore b/.npmignore index a6aad7230..0f0f7d2e4 100644 --- a/.npmignore +++ b/.npmignore @@ -38,3 +38,7 @@ !.eslintrc !dist/2.0/jsy.min.css !dist/2.0/bi.min.css +!bin/* +!bin/**/* +!plugins/* +!plugins/**/* diff --git a/bin/cli/cli.js b/bin/cli/cli.js new file mode 100644 index 000000000..732fc1133 --- /dev/null +++ b/bin/cli/cli.js @@ -0,0 +1,44 @@ +#!/usr/bin/env node + +const workerCmd = require('./worker/cli.worker'); + +function getArgs (startIndex = 1) { + const args = {}; + process.argv + .slice(startIndex, process.argv.length) + .forEach(arg => { + // long arg + if (arg.slice(0, 2) === '--') { + const longArg = arg.split('='); + const longArgFlag = longArg[0].slice(2, longArg[0].length); + const longArgValue = longArg.length > 1 ? longArg[1] : true; + args[longArgFlag] = longArgValue; + // flags + } else if (arg[0] === '-') { + const flags = arg.slice(1, arg.length); + args[flags] = true; + } + }); + + return args; +} + +const cmds = new Map([ + ['worker', workerCmd], +]); + +const baseCmd = 'fui-cli'; + +const startIndex = process.argv.findIndex(argv => argv.indexOf(baseCmd) !== -1); + +if (startIndex === -1) { + throw new Error(`Command ${baseCmd} not found in args`); +} + +const cmd = process.argv[startIndex + 1]; + +if (cmds.has(cmd)) { + cmds.get(cmd)?.exec(getArgs(startIndex + 2)); +} else { + throw new Error(`Command ${cmd} not supported`); +} diff --git a/bin/cli/worker/cli.worker.js b/bin/cli/worker/cli.worker.js new file mode 100644 index 000000000..555c99cf0 --- /dev/null +++ b/bin/cli/worker/cli.worker.js @@ -0,0 +1,76 @@ +const fs = require('fs'); +const path = require('path'); + +function first2UpperCase(str) { + return str.toLowerCase().replace(/( |^)[a-z]/g, L => L.toUpperCase()); +} + +function scanAndCreate(structure, workerName, root = process.env.INIT_CWD) { + Object.keys(structure) + .forEach(name => { + if (typeof structure[name] === 'object') { + fs.mkdirSync(path.resolve(root, name)); + + scanAndCreate(structure[name], workerName, path.resolve(root, `./${name}`)); + } else if (structure[name] === '') { + fs.appendFileSync(path.resolve(root, name), ''); + } else if (typeof structure[name] === 'string') { + let content = fs.readFileSync(structure[name]).toString(); + + content = content + .replace(/\${WorkerName}/g, first2UpperCase(workerName)) + .replace(/\${workerName}/g, workerName); + + fs.appendFileSync(path.resolve(root, name), content); + } + }); +} + +module.exports = { + exec: async args => { + if (!args.init) { + throw new Error(`Command init not found in args`); + } + + if (!args.name) { + throw new Error('Command --name=... not found in args'); + } + + const name = args.name; + + const structure = { + [`${name}_worker`]: { + 'main_thread': { + action: { + 'action.worker_ability_test.ts': path.resolve(__dirname, './template/main_thread/action/action.worker_ability_test.t'), + }, + [`${name}_main_thread.ts`]: path.resolve(__dirname, './template/main_thread/main_thread.t'), + }, + utils: { + 'action_type.ts': path.resolve(__dirname, './template/utils/action_type.t'), + 'payload_type.ts': path.resolve(__dirname, './template/utils/payload_type.t'), + }, + 'worker_thread': { + action: { + 'action.worker_ability_test.ts': path.resolve(__dirname, './template/worker_thread/action/action.worker_ability_test.t'), + }, + [`${name}_worker_thread.ts`]: path.resolve(__dirname, './template/worker_thread/worker_thread.t'), + }, + [`${name}_main_thread.helper.ts`]: path.resolve(__dirname, './template/main_thread.helper.t'), + }, + }; + + scanAndCreate(structure, name); + }, +}; + +// 结构 +// -xxx_worker +// -|--main_thread +// -|--|--action +// -|--|--xxx_main_thread.ts +// -|--utils +// -|--|--action_type.ts +// -|--worker_thread +// -|--|--action +// -|--|--worker_main_thread.ts diff --git a/bin/cli/worker/template/main_thread.helper.t b/bin/cli/worker/template/main_thread.helper.t new file mode 100644 index 000000000..af02e5192 --- /dev/null +++ b/bin/cli/worker/template/main_thread.helper.t @@ -0,0 +1,45 @@ +import { ${WorkerName}MainThreadWorker } from './main_thread/${workerName}_main_thread'; +// 不需要一起打包的话则不需要引入这行 +import { workerUrl } from 'fui-worker!./worker_thread/${workerName}_worker_thread'; + +export class ${WorkerName}WorkerHelper { + private worker: ${WorkerName}MainThreadWorker; + + /** + * 拿到helper中的worker + */ + public getWorker() { + if (this.worker) { + return this.worker; + } + + this.worker = BI.Workers.createWorker(${WorkerName}MainThreadWorker, { + workerUrl: this.urlFormatter(workerUrl), + workerName: BI.UUID(), + }); + + return this.worker; + } + + /** + * 格式化worker url,比如补充一些环境信息到参数里 + * @param url worker url + */ + private urlFormatter(url: string) { + return url; + } + + /** + * 终止worker + */ + public terminate() { + this.worker?.terminate(); + } +} + +// 使用示例 +// const workerHelper = new ${WorkerName}WorkerHelper(); + +// workerHelper.getWorker() +// .testCommunication() +// .then(res => console.log(res)); diff --git a/bin/cli/worker/template/main_thread/action/action.worker_ability_test.t b/bin/cli/worker/template/main_thread/action/action.worker_ability_test.t new file mode 100644 index 000000000..42425b859 --- /dev/null +++ b/bin/cli/worker/template/main_thread/action/action.worker_ability_test.t @@ -0,0 +1,13 @@ +import { WorkerAbilityTestActionType } from '../../utils/action_type'; +import { WorkerAbilityTestPayload, WorkerAbilityTestReponse } from '../../utils/payload_type'; + +export class WorkerAbilityTestMainThreadAction extends BI.Workers.WorkerBaseAction { + /** + * 通信能力检测 + */ + public communicationTest(): Promise { + const mainThreadPostTime: WorkerAbilityTestPayload['CommunicationTest'] = Date.now(); + + return this.controller.requestPromise(WorkerAbilityTestActionType.CommunicationTest, mainThreadPostTime); + } +} diff --git a/bin/cli/worker/template/main_thread/main_thread.t b/bin/cli/worker/template/main_thread/main_thread.t new file mode 100644 index 000000000..00fb09177 --- /dev/null +++ b/bin/cli/worker/template/main_thread/main_thread.t @@ -0,0 +1,13 @@ +import { WorkerAbilityTestMainThreadAction } from './action/action.worker_ability_test'; + +export class ${WorkerName}MainThreadWorker extends BI.Workers.MainThreadWorker { + private communicationTest: WorkerAbilityTestMainThreadAction; + + public initActions(): void { + this.communicationTest = this.createAction(WorkerAbilityTestMainThreadAction); + } + + public testCommunication() { + return this.communicationTest.communicationTest(); + } +} diff --git a/bin/cli/worker/template/utils/action_type.t b/bin/cli/worker/template/utils/action_type.t new file mode 100644 index 000000000..c92de897a --- /dev/null +++ b/bin/cli/worker/template/utils/action_type.t @@ -0,0 +1,8 @@ +/* + * Worker 事务标识 + * 每类事务有命名空间, 包含多个具体事务 + */ + +export const enum WorkerAbilityTestActionType { + CommunicationTest = 'CommunicationTest', +} diff --git a/bin/cli/worker/template/utils/payload_type.t b/bin/cli/worker/template/utils/payload_type.t new file mode 100644 index 000000000..6b9a71509 --- /dev/null +++ b/bin/cli/worker/template/utils/payload_type.t @@ -0,0 +1,13 @@ +/** + * 跨线程通信各事务的发送数据类型声明 + */ +export interface WorkerAbilityTestPayload { + CommunicationTest: number; +} + +/** + * 跨线程通信各事务的响应数据类型声明 + */ +export interface WorkerAbilityTestReponse { + CommunicationTest: number; +} diff --git a/bin/cli/worker/template/worker_thread/action/action.worker_ability_test.t b/bin/cli/worker/template/worker_thread/action/action.worker_ability_test.t new file mode 100644 index 000000000..f7d1248f4 --- /dev/null +++ b/bin/cli/worker/template/worker_thread/action/action.worker_ability_test.t @@ -0,0 +1,24 @@ +import { WorkerAbilityTestActionType } from '../../utils/action_type'; +import { WorkerAbilityTestPayload, WorkerAbilityTestReponse } from '../../utils/payload_type'; + +export class WorkerAbilityTestWorkerThreadAction extends BI.Workers.WorkerBaseAction { + protected addActionHandler(): void { + this.controller.addActionHandler( + WorkerAbilityTestActionType.CommunicationTest, + this.communicationTest.bind(this) + ); + } + + /** + * 通信能力检测的处理器 + */ + private communicationTest( + payload: WorkerAbilityTestPayload['CommunicationTest'] + ): WorkerAbilityTestReponse['CommunicationTest'] { + const mainThreadPostTime = payload; + // 收到主线程信息的耗时 + const workerGetMessageDuration = Date.now() - mainThreadPostTime; + + return workerGetMessageDuration; + } +} diff --git a/bin/cli/worker/template/worker_thread/worker_thread.t b/bin/cli/worker/template/worker_thread/worker_thread.t new file mode 100644 index 000000000..f437bbc23 --- /dev/null +++ b/bin/cli/worker/template/worker_thread/worker_thread.t @@ -0,0 +1,12 @@ +// TODO: 这边需要先import fineui资源 +import { WorkerAbilityTestWorkerThreadAction } from './action/action.worker_ability_test'; + +class ${WorkerName}WorkerTreadWorker extends BI.Workers.WorkerThreadWorker { + public communicationTest: WorkerAbilityTestWorkerThreadAction; + + public initActions(): void { + this.communicationTest = this.createAction(WorkerAbilityTestWorkerThreadAction); + } +} + +export const ${workerName}WorkerTreadWorker = BI.Workers.createWorker(${WorkerName}WorkerTreadWorker); diff --git a/demo/js/base/button/demo.button.js b/demo/js/base/button/demo.button.js index 110431afb..d0b14aaff 100644 --- a/demo/js/base/button/demo.button.js +++ b/demo/js/base/button/demo.button.js @@ -4,416 +4,310 @@ Demo.Button = BI.inherit(BI.Widget, { }, render: function () { var items = [{ - el: { - type: "bi.button", - text: "一般按钮", - level: "common", - height: 30 - } - }, { - el: { - type: "bi.button", - text: "表示成功状态按钮", - level: "success", - height: 30 - } - }, { - el: { - type: "bi.button", - text: "表示警告状态的按钮", - level: "warning", - height: 30 - } - }, { - el: { - type: "bi.button", - text: "表示错误状态的按钮", - level: "error", - height: 30 - } - }, { - el: { - type: "bi.button", - text: "表示忽略状态的按钮", - level: "ignore", - height: 30 - } - }, { - el: { - type: "bi.button", - text: "普通灰化按钮", - disabled: true, - level: "success", - height: 30 - } - }, { - el: { - type: "bi.button", - text: "忽略状态灰化按钮", - disabled: true, - level: "ignore", - height: 30 - } - }, { - el: { - type: "bi.button", - text: "带图标的按钮", - // level: 'ignore', - iconCls: "close-font", - height: 30 - } - }, { - el: { - type: "bi.button", - text: "一般按钮", - block: true, - level: "common", - height: 30 - } - }, { - el: { - type: "bi.button", - text: "表示成功状态按钮", - block: true, - level: "success", - height: 30 - } - }, { - el: { - type: "bi.button", - text: "表示警告状态的按钮", - block: true, - level: "warning", - height: 30 - } - }, { - el: { - type: "bi.button", - text: "表示忽略状态的按钮", - block: true, - level: "ignore", - height: 30 - } - }, { - el: { - type: "bi.button", - text: "普通灰化按钮", - block: true, - disabled: true, - level: "success", - height: 30 - } - }, { - el: { - type: "bi.button", - text: "忽略状态灰化按钮", - block: true, - disabled: true, - level: "ignore", - height: 30 - } - }, { - el: { - type: "bi.button", - text: "带图标的按钮", - block: true, - // level: 'ignore', - iconCls: "close-font", - height: 30 - } - }, { - el: { - type: "bi.button", - text: "一般按钮", - clear: true, - level: "common", - height: 30 - } - }, { - el: { - type: "bi.button", - text: "表示成功状态按钮", - clear: true, - level: "success", - height: 30 - } - }, { - el: { - type: "bi.button", - text: "表示警告状态的按钮", - clear: true, - level: "warning", - height: 30 - } - }, { - el: { - type: "bi.button", - text: "表示忽略状态的按钮", - clear: true, - level: "ignore", - height: 30 - } - }, { - el: { - type: "bi.button", - text: "普通灰化按钮", - clear: true, - disabled: true, - level: "success", - height: 30 - } - }, { - el: { - type: "bi.button", - text: "忽略状态灰化按钮", - clear: true, - disabled: true, - level: "ignore", - height: 30 - } - }, { - el: { - type: "bi.button", - text: "带图标的按钮", - clear: true, - // level: 'ignore', - iconCls: "close-font", - height: 30 - } - }, { - el: { - type: "bi.text_button", - text: "文字按钮", - height: 30 - } - }, { - el: { - type: "bi.button", - text: "幽灵按钮(common)", - ghost: true, - height: 30 - } - }, { - el: { - type: "bi.button", - iconCls: "plus-font", - text: "幽灵按钮(common)", - ghost: true, - height: 30 - } - }, { - el: { - type: "bi.button", - iconCls: "plus-font", - text: "幽灵按钮(common)", - ghost: true, - level: "warning", - height: 30 - } - }, { - el: { - type: "bi.button", - iconCls: "plus-font", - text: "幽灵按钮(common)", - ghost: true, - level: "error", - height: 30 - } - }, { - el: { - type: "bi.button", - iconCls: "plus-font", - text: "幽灵按钮(common)", - ghost: true, - level: "success", - height: 30 - } - }, { - el: { - type: "bi.button", - text: "幽灵按钮(common)灰化", - disabled: true, - ghost: true, - height: 30 - } - }, { - el: { - type: "bi.button", - text: "弹出bubble", - bubble: function () { - return BI.parseInt(Math.random() * 100) % 10 + "提示" - }, - handler: function () { - BI.Msg.toast("1111"); - }, - height: 30 - } - }, { - el: { - type: "bi.button", - text: "图标在上面的按钮,而且可以自动撑开高度", - iconCls: "close-font", - iconGap: 24, - iconPosition: "top" - } + type: "bi.button", + text: "一般按钮", + level: "common", + height: 30 + }, { + type: "bi.button", + text: "表示成功状态按钮", + level: "success", + height: 30 + }, { + type: "bi.button", + text: "表示警告状态的按钮", + level: "warning", + height: 30 + }, { + type: "bi.button", + text: "表示错误状态的按钮", + level: "error", + height: 30 + }, { + type: "bi.button", + text: "表示忽略状态的按钮", + level: "ignore", + height: 30 + }, { + type: "bi.button", + text: "普通灰化按钮", + disabled: true, + level: "success", + height: 30 + }, { + type: "bi.button", + text: "忽略状态灰化按钮", + disabled: true, + level: "ignore", + height: 30 + }, { + type: "bi.button", + text: "带图标的按钮", + // level: 'ignore', + iconCls: "close-font", + height: 30 + }, { + type: "bi.button", + text: "一般按钮", + block: true, + level: "common", + height: 30 + }, { + type: "bi.button", + text: "表示成功状态按钮", + block: true, + level: "success", + height: 30 + }, { + type: "bi.button", + text: "表示警告状态的按钮", + block: true, + level: "warning", + height: 30 + }, { + type: "bi.button", + text: "表示忽略状态的按钮", + block: true, + level: "ignore", + height: 30 + }, { + type: "bi.button", + text: "普通灰化按钮", + block: true, + disabled: true, + level: "success", + height: 30 + }, { + type: "bi.button", + text: "忽略状态灰化按钮", + block: true, + disabled: true, + level: "ignore", + height: 30 + }, { + type: "bi.button", + text: "带图标的按钮", + block: true, + // level: 'ignore', + iconCls: "close-font", + height: 30 + }, { + type: "bi.button", + text: "一般按钮", + clear: true, + level: "common", + height: 30 + }, { + type: "bi.button", + text: "表示成功状态按钮", + clear: true, + level: "success", + height: 30 + }, { + type: "bi.button", + text: "表示警告状态的按钮", + clear: true, + level: "warning", + height: 30 + }, { + type: "bi.button", + text: "表示忽略状态的按钮", + clear: true, + level: "ignore", + height: 30 + }, { + type: "bi.button", + text: "普通灰化按钮", + clear: true, + disabled: true, + level: "success", + height: 30 + }, { + type: "bi.button", + text: "忽略状态灰化按钮", + clear: true, + disabled: true, + level: "ignore", + height: 30 + }, { + type: "bi.button", + text: "带图标的按钮", + clear: true, + // level: 'ignore', + iconCls: "close-font", + height: 30 + }, { + type: "bi.text_button", + text: "文字按钮", + height: 30 + }, { + type: "bi.button", + text: "幽灵按钮(common)", + ghost: true, + height: 30 + }, { + type: "bi.button", + iconCls: "plus-font", + text: "幽灵按钮(common)", + ghost: true, + height: 30 + }, { + type: "bi.button", + iconCls: "plus-font", + text: "幽灵按钮(common)", + ghost: true, + level: "warning", + height: 30 + }, { + type: "bi.button", + iconCls: "plus-font", + text: "幽灵按钮(common)", + ghost: true, + level: "error", + height: 30 + }, { + type: "bi.button", + iconCls: "plus-font", + text: "幽灵按钮(common)", + ghost: true, + level: "success", + height: 30 + }, { + type: "bi.button", + text: "幽灵按钮(common)灰化", + disabled: true, + ghost: true, + height: 30 + }, { + type: "bi.button", + text: "弹出bubble", + bubble: function () { + return BI.parseInt(Math.random() * 100) % 10 + "提示" + }, + handler: function () { + BI.Msg.toast("1111"); + }, + height: 30 + }, { + type: "bi.button", + text: "图标在上面的按钮,而且可以自动撑开高度", + iconCls: "close-font", + iconGap: 24, + iconPosition: "top" },{ - el: { - type: "bi.button", - text: "自动撑开高度", - iconCls: "close-font", - textHeight: 32, - iconGap: 24, - vgap: 16, - hgap: 100, - iconPosition: "top" - } - }, { - el: { - type: "bi.button", - text: "图标在下面的按钮", - iconCls: "close-font", - iconPosition: "bottom" - } - }, { - el: { - type: "bi.button", - text: "图标在左边的按钮", - iconCls: "close-font", - iconPosition: "left" - } - }, { - el: { - type: "bi.button", - text: "图标在右边的按钮", - iconCls: "close-font", - iconPosition: "right" - } - }, { - el: { - type: "bi.button", - text: "浅色的一般按钮", - iconCls: "plus-font", - light: true - } - }, { - el: { - type: "bi.button", - text: "浅色的成功按钮", - level: "success", - iconCls: "plus-font", - light: true - } - }, { - el: { - type: "bi.button", - text: "浅色的警告按钮", - level: "warning", - iconCls: "plus-font", - light: true - } - }, { - el: { - type: "bi.button", - iconCls: "plus-font", - text: "浅色的失败按钮", - level: "error", - cls: "hover-mask", - light: true - } - }, { - el: { - type: "bi.button", - iconCls: "plus-font", - text: "朴素的按钮", - level: "common", - plain: true - } - }, { - el: { - type: "bi.button", - iconCls: "plus-font", - text: "朴素的按钮", - level: "success", - plain: true - } - }, { - el: { - type: "bi.button", - iconCls: "plus-font", - text: "朴素的按钮", - level: "error", - plain: true - } - }, { - el: { - type: "bi.button", - iconCls: "plus-font", - text: "朴素的按钮", - level: "warning", - plain: true - } - }, { - el: { - type: "bi.button", - iconCls: "plus-font", - text: "朴素的按钮", - level: "ignore", - plain: true - } - }, { - el: { - type: "bi.button", - iconCls: "plus-font", - text: "朴素的按钮", - level: "ignore", - plain: true, - disabled: true - } - }, { - el: { - type: "bi.button", - iconCls: "plus-font", - text: "朴素的按钮", - level: "error", - plain: true, - disabled: true - } - }, { - el: { - type: "bi.button", - iconCls: "plus-font", - text: "朴素的按钮", - level: "common", - plain: true, - disabled: true - } - }, { - el: { - type: "bi.button", - iconCls: "plus-font", - text: "点我,更改图标", - handler() { - this.i = this.i === undefined ? 0 : ++this.i; - const arr = ["text-background-font", "check-mark-ha-font", "close-font", "search-font", "date-change-h-font"]; - if(this.i >= arr.length) { - this.i = 0; - } - this.setIcon(arr[this.i]); - }, - height: 24 - } + type: "bi.button", + text: "自动撑开高度", + iconCls: "close-font", + textHeight: 32, + iconGap: 24, + vgap: 16, + hgap: 100, + iconPosition: "top" + }, { + type: "bi.button", + text: "图标在下面的按钮", + iconCls: "close-font", + iconPosition: "bottom" + }, { + type: "bi.button", + text: "图标在左边的按钮", + iconCls: "close-font", + iconPosition: "left" + }, { + type: "bi.button", + text: "图标在右边的按钮", + iconCls: "close-font", + iconPosition: "right" + }, { + type: "bi.button", + text: "浅色的一般按钮", + iconCls: "plus-font", + light: true + }, { + type: "bi.button", + text: "浅色的成功按钮", + level: "success", + iconCls: "plus-font", + light: true + }, { + type: "bi.button", + text: "浅色的警告按钮", + level: "warning", + iconCls: "plus-font", + light: true + }, { + type: "bi.button", + iconCls: "plus-font", + text: "浅色的失败按钮", + level: "error", + cls: "hover-mask", + light: true + }, { + type: "bi.button", + iconCls: "plus-font", + text: "朴素的按钮", + level: "common", + plain: true + }, { + type: "bi.button", + iconCls: "plus-font", + text: "朴素的按钮", + level: "success", + plain: true + }, { + type: "bi.button", + iconCls: "plus-font", + text: "朴素的按钮", + level: "error", + plain: true + }, { + type: "bi.button", + iconCls: "plus-font", + text: "朴素的按钮", + level: "warning", + plain: true + }, { + type: "bi.button", + iconCls: "plus-font", + text: "朴素的按钮", + level: "ignore", + plain: true + }, { + type: "bi.button", + iconCls: "plus-font", + plain: true, + level: "error", + }, { + type: "bi.button", + iconCls: "plus-font", + text: "朴素的按钮", + plain: true, + disabled: true + }, { + type: "bi.button", + iconCls: "plus-font", + text: "点我,更改图标", + handler() { + this.i = this.i === undefined ? 0 : ++this.i; + const arr = ["text-background-font", "check-mark-ha-font", "close-font", "search-font", "date-change-h-font"]; + if(this.i >= arr.length) { + this.i = 0; + } + this.setIcon(arr[this.i]); + }, + height: 24 }]; - // BI.each(items, function (i, item) { - // item.el.handler = function () { - // BI.Msg.alert("按钮", this.options.text); - // }; - // }); + return { type: "bi.left", scrolly: true, vgap: 100, hgap: 20, - items: items + items: BI.map(items, function (index, value) { + return { + el: value + } + }) }; } }); diff --git a/demo/js/base/tip/demo.title.js b/demo/js/base/tip/demo.title.js index 44a5bdc23..5a983068d 100644 --- a/demo/js/base/tip/demo.title.js +++ b/demo/js/base/tip/demo.title.js @@ -30,10 +30,30 @@ Demo.Title = BI.inherit(BI.Widget, { warningTitle: "自定义title提示效果", text: "自定义title提示效果", textAlign: "center" + }, { + type: "bi.label", + cls: "layout-bg3", + height: 50, + title: () => "函数返回值作为title提示", + text: "title提示支持函数", + textAlign: "center" + }, { + type: "bi.label", + cls: "layout-bg4", + height: 50, + title: function () { + return { + level: "success", + text: "自定义title\n提示效果", + textAlign: "center" + }; + }, + text: "title提示支持对象,作为bi.tooltip的props", + textAlign: "center" }], hgap: 300, vgap: 20 }; } }); -BI.shortcut("demo.title", Demo.Title); \ No newline at end of file +BI.shortcut("demo.title", Demo.Title); diff --git a/dist/images/2x/icon/dark/tree_solid_collapse_1.png b/dist/images/2x/icon/dark/tree_solid_collapse_1.png index 9bd5051d1..1b39baa92 100644 Binary files a/dist/images/2x/icon/dark/tree_solid_collapse_1.png and b/dist/images/2x/icon/dark/tree_solid_collapse_1.png differ diff --git a/dist/images/2x/icon/dark/tree_solid_collapse_2.png b/dist/images/2x/icon/dark/tree_solid_collapse_2.png index a111825f4..37029e095 100644 Binary files a/dist/images/2x/icon/dark/tree_solid_collapse_2.png and b/dist/images/2x/icon/dark/tree_solid_collapse_2.png differ diff --git a/dist/images/2x/icon/dark/tree_solid_collapse_3.png b/dist/images/2x/icon/dark/tree_solid_collapse_3.png index c16ac4fc3..ff6c4f653 100644 Binary files a/dist/images/2x/icon/dark/tree_solid_collapse_3.png and b/dist/images/2x/icon/dark/tree_solid_collapse_3.png differ diff --git a/dist/images/2x/icon/dark/tree_solid_collapse_4.png b/dist/images/2x/icon/dark/tree_solid_collapse_4.png index f8b7f8ef2..fb8e354d3 100644 Binary files a/dist/images/2x/icon/dark/tree_solid_collapse_4.png and b/dist/images/2x/icon/dark/tree_solid_collapse_4.png differ diff --git a/dist/images/2x/icon/dark/tree_solid_collapse_5.png b/dist/images/2x/icon/dark/tree_solid_collapse_5.png index 5620b0886..b3c53e8a0 100644 Binary files a/dist/images/2x/icon/dark/tree_solid_collapse_5.png and b/dist/images/2x/icon/dark/tree_solid_collapse_5.png differ diff --git a/dist/images/2x/icon/dark/tree_solid_expand_1.png b/dist/images/2x/icon/dark/tree_solid_expand_1.png index edae4d431..05a11cd9c 100644 Binary files a/dist/images/2x/icon/dark/tree_solid_expand_1.png and b/dist/images/2x/icon/dark/tree_solid_expand_1.png differ diff --git a/dist/images/2x/icon/dark/tree_solid_expand_2.png b/dist/images/2x/icon/dark/tree_solid_expand_2.png index 5951e659f..6a744dbb0 100644 Binary files a/dist/images/2x/icon/dark/tree_solid_expand_2.png and b/dist/images/2x/icon/dark/tree_solid_expand_2.png differ diff --git a/dist/images/2x/icon/dark/tree_solid_expand_3.png b/dist/images/2x/icon/dark/tree_solid_expand_3.png index de68cf779..09e0ad07d 100644 Binary files a/dist/images/2x/icon/dark/tree_solid_expand_3.png and b/dist/images/2x/icon/dark/tree_solid_expand_3.png differ diff --git a/dist/images/2x/icon/dark/tree_solid_expand_4.png b/dist/images/2x/icon/dark/tree_solid_expand_4.png index dee6827fb..066a7cb3f 100644 Binary files a/dist/images/2x/icon/dark/tree_solid_expand_4.png and b/dist/images/2x/icon/dark/tree_solid_expand_4.png differ diff --git a/dist/images/2x/icon/dark/tree_solid_expand_5.png b/dist/images/2x/icon/dark/tree_solid_expand_5.png index 6b6a559f9..039b0e1cc 100644 Binary files a/dist/images/2x/icon/dark/tree_solid_expand_5.png and b/dist/images/2x/icon/dark/tree_solid_expand_5.png differ diff --git a/dist/images/2x/icon/dark/tree_solid_vertical_line_1.png b/dist/images/2x/icon/dark/tree_solid_vertical_line_1.png index 2c674dfd9..3bc014f46 100644 Binary files a/dist/images/2x/icon/dark/tree_solid_vertical_line_1.png and b/dist/images/2x/icon/dark/tree_solid_vertical_line_1.png differ diff --git a/dist/images/2x/icon/dark/tree_solid_vertical_line_2.png b/dist/images/2x/icon/dark/tree_solid_vertical_line_2.png index ab5584b5f..22688bcaf 100644 Binary files a/dist/images/2x/icon/dark/tree_solid_vertical_line_2.png and b/dist/images/2x/icon/dark/tree_solid_vertical_line_2.png differ diff --git a/dist/images/2x/icon/dark/tree_solid_vertical_line_3.png b/dist/images/2x/icon/dark/tree_solid_vertical_line_3.png index adf63d3d5..d155fcdad 100644 Binary files a/dist/images/2x/icon/dark/tree_solid_vertical_line_3.png and b/dist/images/2x/icon/dark/tree_solid_vertical_line_3.png differ diff --git a/dist/images/2x/icon/dark/tree_solid_vertical_line_4.png b/dist/images/2x/icon/dark/tree_solid_vertical_line_4.png index 17c39572d..9af333f28 100644 Binary files a/dist/images/2x/icon/dark/tree_solid_vertical_line_4.png and b/dist/images/2x/icon/dark/tree_solid_vertical_line_4.png differ diff --git a/dist/images/2x/icon/tree_solid_collapse_1.png b/dist/images/2x/icon/tree_solid_collapse_1.png index 3aa9463ad..94ed363d4 100644 Binary files a/dist/images/2x/icon/tree_solid_collapse_1.png and b/dist/images/2x/icon/tree_solid_collapse_1.png differ diff --git a/dist/images/2x/icon/tree_solid_collapse_2.png b/dist/images/2x/icon/tree_solid_collapse_2.png index b0be8e5c0..0796aaf5a 100644 Binary files a/dist/images/2x/icon/tree_solid_collapse_2.png and b/dist/images/2x/icon/tree_solid_collapse_2.png differ diff --git a/dist/images/2x/icon/tree_solid_collapse_3.png b/dist/images/2x/icon/tree_solid_collapse_3.png index b592b6a14..125a28dc7 100644 Binary files a/dist/images/2x/icon/tree_solid_collapse_3.png and b/dist/images/2x/icon/tree_solid_collapse_3.png differ diff --git a/dist/images/2x/icon/tree_solid_collapse_4.png b/dist/images/2x/icon/tree_solid_collapse_4.png index f9b67b9cd..643f0123b 100644 Binary files a/dist/images/2x/icon/tree_solid_collapse_4.png and b/dist/images/2x/icon/tree_solid_collapse_4.png differ diff --git a/dist/images/2x/icon/tree_solid_collapse_5.png b/dist/images/2x/icon/tree_solid_collapse_5.png index 55e059d6f..0cb0d5d73 100644 Binary files a/dist/images/2x/icon/tree_solid_collapse_5.png and b/dist/images/2x/icon/tree_solid_collapse_5.png differ diff --git a/dist/images/2x/icon/tree_solid_expand_1.png b/dist/images/2x/icon/tree_solid_expand_1.png index 751eac8f4..d607d042c 100644 Binary files a/dist/images/2x/icon/tree_solid_expand_1.png and b/dist/images/2x/icon/tree_solid_expand_1.png differ diff --git a/dist/images/2x/icon/tree_solid_expand_2.png b/dist/images/2x/icon/tree_solid_expand_2.png index 2a94194d1..bde83809a 100644 Binary files a/dist/images/2x/icon/tree_solid_expand_2.png and b/dist/images/2x/icon/tree_solid_expand_2.png differ diff --git a/dist/images/2x/icon/tree_solid_expand_3.png b/dist/images/2x/icon/tree_solid_expand_3.png index 2300dad28..e16463b19 100644 Binary files a/dist/images/2x/icon/tree_solid_expand_3.png and b/dist/images/2x/icon/tree_solid_expand_3.png differ diff --git a/dist/images/2x/icon/tree_solid_expand_4.png b/dist/images/2x/icon/tree_solid_expand_4.png index 7eb88716c..9738216c4 100644 Binary files a/dist/images/2x/icon/tree_solid_expand_4.png and b/dist/images/2x/icon/tree_solid_expand_4.png differ diff --git a/dist/images/2x/icon/tree_solid_expand_5.png b/dist/images/2x/icon/tree_solid_expand_5.png index 7ae297dd6..74bd1198e 100644 Binary files a/dist/images/2x/icon/tree_solid_expand_5.png and b/dist/images/2x/icon/tree_solid_expand_5.png differ diff --git a/dist/images/2x/icon/tree_solid_vertical_line_2.png b/dist/images/2x/icon/tree_solid_vertical_line_2.png index 429f958ac..56c00c080 100644 Binary files a/dist/images/2x/icon/tree_solid_vertical_line_2.png and b/dist/images/2x/icon/tree_solid_vertical_line_2.png differ diff --git a/dist/images/2x/icon/tree_solid_vertical_line_3.png b/dist/images/2x/icon/tree_solid_vertical_line_3.png index a44f13db0..43aab5d89 100644 Binary files a/dist/images/2x/icon/tree_solid_vertical_line_3.png and b/dist/images/2x/icon/tree_solid_vertical_line_3.png differ diff --git a/dist/images/2x/icon/tree_solid_vertical_line_4.png b/dist/images/2x/icon/tree_solid_vertical_line_4.png index 7de47eeed..4b40cc497 100644 Binary files a/dist/images/2x/icon/tree_solid_vertical_line_4.png and b/dist/images/2x/icon/tree_solid_vertical_line_4.png differ diff --git a/dist/images/2x/icon/tree_vertical_line_1.png b/dist/images/2x/icon/tree_vertical_line_1.png index ea9e933a0..c6b8e799c 100644 Binary files a/dist/images/2x/icon/tree_vertical_line_1.png and b/dist/images/2x/icon/tree_vertical_line_1.png differ diff --git a/examples/worker_new.html b/examples/worker_new.html new file mode 100644 index 000000000..53e83c4ee --- /dev/null +++ b/examples/worker_new.html @@ -0,0 +1,15 @@ + + + + + + + Document + + + + +
+ + + \ No newline at end of file diff --git a/examples/worker_new/index.js b/examples/worker_new/index.js new file mode 100644 index 000000000..b95fbbad6 --- /dev/null +++ b/examples/worker_new/index.js @@ -0,0 +1,105 @@ +document.cookie = "test=demo"; + +// worker获取主线程资源 +var CookieAction = BI.inherit(BI.Workers.WorkerBaseAction, { + addActionHandler: function() { + this.controller.addActionHandler("Cookie", this.getCookie.bind(this)); + }, + + getCookie: function() { + return document.cookie; + } +}); + +// 调用worker计算 +var FibonacciAction = BI.inherit(BI.Workers.WorkerBaseAction, { + addActionHandler: function() {}, + + getResult: function(times) { + return this.controller.requestPromise("Fibonacci", { times: times }) + .then(function(v) { + console.log(v); + }); + } +}); + +// 主线程与worker多次交互 +const HeartBeatCheckAction = BI.inherit(BI.Workers.WorkerBaseAction, { + addActionHandler: function() { + this.controller.addActionHandler("HeartBeatChecked", this.recieveHeartBeatChecked.bind(this)); + }, + + recieveHeartBeatChecked: function (payload) { + console.log("heartbeat: " + payload.time); + }, + + startHeatBeatCheck: function() { + return this.controller.request("HeartBeatCheckStart"); + }, + + stopHeatBeatCheck: function() { + return this.controller.request("HeartBeatCheckStop"); + } +}); + +var MainThreadWorker = BI.inherit(BI.Workers.MainThreadWorker, { + initActions: function() { + this.cookieAction = this.createAction(CookieAction); + + this.fibonacciAction = this.createAction(FibonacciAction); + + this.heartBeatCheckAction = this.createAction(HeartBeatCheckAction); + }, + + calculateFibonacci: function(times) { + this.fibonacciAction.getResult(times); + }, + + startHeatBeatCheck: function() { + this.heartBeatCheckAction.startHeatBeatCheck(); + }, + + stopHeatBeatCheck: function() { + this.heartBeatCheckAction.stopHeatBeatCheck(); + } +}); + +var mainThreadWorker = BI.Workers.createWorker(MainThreadWorker, { + workerUrl: "./worker_new/worker.js", + workerName: "demo" +}); + +BI.createWidget({ + type: "bi.vertical", + element: "#wrapper", + vgap: 10, + hgap: 10, + items: [ + { + type: "bi.button", + text: "点击计算斐波那契数列第40项", + width: 200, + handler: function() { + console.log("click"); + + mainThreadWorker.calculateFibonacci(40); + } + }, + { + type: "bi.button", + text: "开始心跳", + width: 200, + handler: function() { + mainThreadWorker.startHeatBeatCheck(); + } + }, + { + type: "bi.button", + text: "停止心跳", + width: 200, + handler: function() { + mainThreadWorker.stopHeatBeatCheck(); + } + } + ] +}); diff --git a/examples/worker_new/worker.js b/examples/worker_new/worker.js new file mode 100644 index 000000000..96e88e542 --- /dev/null +++ b/examples/worker_new/worker.js @@ -0,0 +1,80 @@ +self.importScripts("https://fanruan.design/fineui/fineui_without_jquery_polyfill.js"); + +var CookieAction = BI.inherit(BI.Workers.WorkerBaseAction, { + addActionHandler: function() {}, + + getCookie: function() { + return this.controller.requestPromise("Cookie"); + } +}); + +function fibonacci(n) { + if (n === 1 || n === 2) { + return 1; + } + + return fibonacci(n - 2) + fibonacci(n - 1); +} + +var FibonacciAction = BI.inherit(BI.Workers.WorkerBaseAction, { + addActionHandler: function() { + this.controller.addActionHandler("Fibonacci", this.getResult.bind(this)); + }, + + getResult: function(payload) { + return fibonacci(payload.times); + } +}); + +const HeartBeatCheckAction = BI.inherit(BI.Workers.WorkerBaseAction, { + addActionHandler: function() { + this.controller.addActionHandler("HeartBeatCheckStart", this.startHeatBeatCheck.bind(this)); + this.controller.addActionHandler("HeartBeatCheckStop", this.stopHeatBeatCheck.bind(this)); + }, + + startHeatBeatCheck: function() { + var self = this; + + if (!this.timer) { + console.log("heart beat check started"); + + this.timer = setInterval(function() { + // 模拟请求 + setTimeout(function() { + self.controller.request("HeartBeatChecked", { + time: new Date() + }); + }, 50); + }, 5 * 1000); + } else { + console.log("heart beat has already started!"); + } + }, + + stopHeatBeatCheck: function() { + console.log("heart beat check stopped"); + + clearInterval(this.timer); + } +}); + +var MainThreadWorker = BI.inherit(BI.Workers.WorkerThreadWorker, { + initActions: function() { + this.cookieAction = this.createAction(CookieAction); + + this.fibonacciAction = this.createAction(FibonacciAction); + + this.heartBeatCheckAction = this.createAction(HeartBeatCheckAction); + }, + + fetchCookie: function() { + return this.cookieAction.getCookie() + .then(function (v) { + console.log(v); + }); + } +}); + +var workerThreadWorker = BI.Workers.createWorker(MainThreadWorker); + +workerThreadWorker.fetchCookie(); diff --git a/examples/替换loading动画.html b/examples/替换loading动画.html new file mode 100644 index 000000000..9ae3707d4 --- /dev/null +++ b/examples/替换loading动画.html @@ -0,0 +1,104 @@ + + + + + + + + Document + + + + + + +
+ + + + \ No newline at end of file diff --git a/package.json b/package.json index 8a4760cc4..fe7f49173 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,17 @@ { "name": "fineui", - "version": "2.0.20220623215302", + "version": "2.0.20220727111551", "description": "fineui", "main": "dist/fineui_without_conflict.min.js", "types": "dist/lib/index.d.ts", + "bin": { + "fui-cli": "./bin/cli/cli.js" + }, "devDependencies": { "@babel/core": "^7.17.4", "@babel/polyfill": "7.6.0", "@fui/babel-preset-fineui": "^2.0.0", - "@fui/eslint-plugin": "1.0.11", + "@fui/eslint-plugin": "1.0.15", "@types/node": "15.6.1", "autoprefixer": "9.6.1", "babel-loader": "8.0.6", @@ -47,7 +50,7 @@ "source-map-loader": "0.2.4", "style-loader": "0.23.1", "terser-webpack-plugin": "4.2.3", - "typescript": "3.5.2", + "typescript": "3.9.2", "webpack": "4.35.2", "webpack-cli": "3.3.5", "webpack-dev-server": "3.7.2", diff --git a/plugins/webpack-fui-worker-plugin/constants.js b/plugins/webpack-fui-worker-plugin/constants.js new file mode 100644 index 000000000..7b80ac3dc --- /dev/null +++ b/plugins/webpack-fui-worker-plugin/constants.js @@ -0,0 +1,9 @@ +const WorkerPluginName = 'FuiWorkerPlugin'; +const WorkerLoaderName = 'FuiWorkerWorkerLoader'; +const FileNamePrefix = 'worker-'; + +module.exports = { + WorkerPluginName, + WorkerLoaderName, + FileNamePrefix, +}; diff --git a/plugins/webpack-fui-worker-plugin/index.js b/plugins/webpack-fui-worker-plugin/index.js new file mode 100644 index 000000000..19f660299 --- /dev/null +++ b/plugins/webpack-fui-worker-plugin/index.js @@ -0,0 +1,45 @@ +/* + * worker-plugin + */ + +const path = require('path'); +const webpack = require('webpack'); +const { WorkerPluginName } = require('./constants'); + +class FuiWorkerPlugin { + constructor(options = {}) { + this.options = options; + } + + apply(compiler) { + // 为主线程构建添加 __WORKER__ 环境变量, 构建中区分不同线程源码, 实现代码拆减 + compiler.hooks.afterPlugins.tap(WorkerPluginName, compiler => { + new webpack.DefinePlugin({ + // __WORKER__ 表示当前所在线程是否是 worker 线程 + // 主线程构建中为 false + __WORKER__: false, + }).apply(compiler); + }); + + // 添加自定义的worker entry-loader + compiler.hooks.afterResolvers.tap(WorkerPluginName, compiler => { + /** + * https://webpack.js.org/configuration/resolve/#resolveloader + * 使用 resolveloader 添加自定义的 worker loader + */ + if (!compiler.options.resolveLoader) { + compiler.options.resolveLoader = { + alias: {}, + }; + } + if (!compiler.options.resolveLoader.alias) { + compiler.options.resolveLoader.alias = {}; + } + + // 动态添加 worker 的 worker-loader, 命名为 "fui-worker" + compiler.options.resolveLoader.alias['fui-worker'] = path.resolve(__dirname, './worker-loader.js'); + }); + } +} + +module.exports = FuiWorkerPlugin; diff --git a/plugins/webpack-fui-worker-plugin/worker-loader.js b/plugins/webpack-fui-worker-plugin/worker-loader.js new file mode 100644 index 000000000..9affe81f4 --- /dev/null +++ b/plugins/webpack-fui-worker-plugin/worker-loader.js @@ -0,0 +1,109 @@ +/* + * fui-worker worker-loader + */ + +const webpack = require('webpack'); +const loaderUtils = require('loader-utils'); +const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin'); +const { WorkerLoaderName, FileNamePrefix } = require('./constants'); + +// 正常 loader 处理逻辑 +function loader() { + const callback = this.async(); + this.cacheable(false); + + // 过滤掉当前的 worker-loader, 保留 worker 侧构建需要的其他 loader(babel-loader/ts-loader 等) + const otherLoaders = this.loaders.filter((loader, index) => { + if (index === this.loaderIndex) { + return false; + } + + return true; + }); + /** + * 拼接构建需要的 loader 字符串, 用于指定 childCompiler 的构建 loader + * 比如: /path/to/babel-loader/lib/index.js!/path/to/ts-loader/index.js! + */ + const loaderPath = otherLoaders.reduce((pre, loader) => `${pre}${loader.path}!`, ''); + + /** + * worker 独立构建的 entry + * 构建 loader + worker 源码入口文件路径 + * + * https://webpack.js.org/concepts/loaders/#inline + * `!!` 实现在 childCompiler 中忽略其他所有 loader, 只保留主构建的 loader + * 不然 worker 入口在 childCompiler 中会继续由 worker-loader 处理, 造成死循环 + */ + const workerEntry = `!!${loaderPath}${this.resourcePath}`; + + // 把资源纳入构建流程的依赖, 实现 dev 模式下的 watch + this.addDependency(workerEntry); + + // 生成的 service 独立 bundle 名称 + const entryFileName = `${FileNamePrefix}index`; + + // 获取传递给 loader 的 options + loaderUtils.getOptions(this) || {}; + + // 创建 childCompiler, 用于实现 worker 构建为独立 js 资源 + const childCompiler = this._compilation.createChildCompiler(WorkerLoaderName, { + globalObject: 'this', + }); + childCompiler.context = this._compiler.context; + + // 指定独立构建的 entry 和生成 js 资源名称 + new SingleEntryPlugin(this.context, workerEntry, entryFileName).apply(childCompiler); + + // 设置 worker 侧的环境变量 + new webpack.DefinePlugin({ + __WORKER__: true, + }).apply(childCompiler); + + // 添加 window 全局对象, 映射为 worker 线程全局对象 self + // 如果在 worker 源码中添加, 可能没有前置到所有引用模块前 + new webpack.BannerPlugin({ + banner: 'self.window = self;', + raw: true, + entryOnly: true, + }).apply(childCompiler); + + const subCache = `subcache ${__dirname} ${workerEntry}`; + childCompiler.hooks.compilation.tap(WorkerLoaderName, compilation => { + if (compilation.cache) { + if (!compilation.cache[subCache]) compilation.cache[subCache] = {}; + compilation.cache = compilation.cache[subCache]; + } + }); + + childCompiler.runAsChild((error, entries, compilation) => { + if (!error && compilation.errors && compilation.errors.length) { + // eslint-disable-next-line no-param-reassign + error = compilation.errors[0]; + } + + // compatible with Array (v4) and Set (v5) prototypes + const entry = entries && entries[0] && entries[0].files.values().next().value; + if (!error && !entry) { + // eslint-disable-next-line no-param-reassign + error = Error(`${WorkerLoaderName}, no entry for ${workerEntry}`); + } + + if (error) { + return callback(error); + } + + return callback( + null, + // 插入代码的转译和压缩由主构建配置的 babel/ts loader 处理, 不需要 worker-worker 来处理 + // 添加 @ts-nocheck 避免 ts-check 报错 + `// @ts-nocheck + const servicePath = __webpack_public_path__ + ${JSON.stringify(entry)}; + export const workerUrl = servicePath; + ` + ); + }); + + return; +} + +module.exports = loader; diff --git a/src/base/1.pane.js b/src/base/1.pane.js index 8df568ead..0a4c93a96 100644 --- a/src/base/1.pane.js +++ b/src/base/1.pane.js @@ -40,30 +40,9 @@ BI.Pane = BI.inherit(BI.Widget, { loading: function () { var self = this, o = this.options; - var isIE = BI.isIE(); - var loadingAnimation = BI.createWidget({ - type: "bi.horizontal", - cls: "bi-loading-widget" + (isIE ? " wave-loading hack" : ""), - height: this._getSize(60), - width: this._getSize(60), - hgap: this._getSize(10), - vgap: 2.5, - items: isIE ? [] : [{ - type: "bi.layout", - cls: "animate-rect rect1", - height: this._getSize(50), - width: this._getSize(5) - }, { - type: "bi.layout", - cls: "animate-rect rect2", - height: this._getSize(50), - width: this._getSize(5) - }, { - type: "bi.layout", - cls: "animate-rect rect3", - height: this._getSize(50), - width: this._getSize(5) - }] + var loadingAnimation = BI.Providers.getProvider("bi.provider.system").getLoading({ + loadingSize: o.loadingSize, + context: this }); // pane在同步方式下由items决定tipText的显示与否 // loading的异步情况下由loaded后对面板的populate的时机决定 diff --git a/src/base/combination/group.button.js b/src/base/combination/group.button.js index a9ba5c23c..ed4f9d680 100644 --- a/src/base/combination/group.button.js +++ b/src/base/combination/group.button.js @@ -197,7 +197,7 @@ BI.ButtonGroup = BI.inherit(BI.Widget, { items = this._packageItems(items, this._packageBtns(this.buttons)); } - this.layouts = BI.createWidget(BI.extend({element: this}, this._packageLayout(items))); + this.layouts = BI.createWidget(BI.extend({ element: this }, this._packageLayout(items))); }, setNotSelectedValue: function (v) { @@ -233,6 +233,23 @@ BI.ButtonGroup = BI.inherit(BI.Widget, { }); }, + setValueMap: function (map) { + map = map || {}; + BI.each(this.buttons, function (i, item) { + if (BI.isNotNull(map[item.getValue()])) { + item.setSelected && item.setSelected(true); + } else { + item.setSelected && item.setSelected(false); + } + }); + }, + + setAllSelected: function (v) { + BI.each(this.getAllButtons(), function (i, btn) { + (btn.setSelected || btn.setAllSelected).apply(btn, [v]); + }); + }, + getNotSelectedValue: function () { var v = []; BI.each(this.buttons, function (i, item) { @@ -314,6 +331,16 @@ BI.ButtonGroup = BI.inherit(BI.Widget, { return node; }, + /** + * 滚动到指定的节点 + */ + scrollToValue: function (value, scrollIntoViewOptions) { + var node = this.getNodeByValue(value); + if (node) { + node.element[0].scrollIntoView(scrollIntoViewOptions); + } + }, + empty: function () { BI.ButtonGroup.superclass.empty.apply(this, arguments); this.options.items = []; diff --git a/src/base/combination/group.virtual.js b/src/base/combination/group.virtual.js index e816ab108..d42c7a5b7 100644 --- a/src/base/combination/group.virtual.js +++ b/src/base/combination/group.virtual.js @@ -96,6 +96,20 @@ BI.VirtualGroup = BI.inherit(BI.Widget, { return v; }, + getNodeByValue: function (value) { + return this.buttonMap[value]; + }, + + /** + * 滚动到指定的节点 + */ + scrollToValue: function (value, scrollIntoViewOptions) { + var node = this.getNodeByValue(value); + if (node) { + node.element[0].scrollIntoView(scrollIntoViewOptions); + } + }, + getValue: function () { var v = []; BI.each(this.buttonMap, function (i, item) { @@ -113,7 +127,7 @@ BI.VirtualGroup = BI.inherit(BI.Widget, { this.options.items = items; items = this._packageBtns(items); if (!this.layouts) { - this.layouts = BI.createWidget(BI.extend({element: this}, this._packageLayout(items))); + this.layouts = BI.createWidget(BI.extend({ element: this }, this._packageLayout(items))); } else { this.layouts.populate(items, { context: this diff --git a/src/base/combination/tab.js b/src/base/combination/tab.js index 893513721..90a788a17 100644 --- a/src/base/combination/tab.js +++ b/src/base/combination/tab.js @@ -86,15 +86,18 @@ BI.Tab = BI.inherit(BI.Widget, { created: function () { var self = this, o = this.options; - if (o.showIndex !== false) { - if (BI.isFunction(o.showIndex)) { - var v = this.__watch(o.showIndex, function (context, newValue) { - self.setSelect(newValue); - }); - this.setSelect(v); - } else { - this.setSelect(o.showIndex); - } + + var showIndex; + if (BI.isFunction(o.showIndex)) { + showIndex = this.__watch(o.showIndex, function (context, newValue) { + self.setSelect(newValue); + }); + } else { + showIndex = o.showIndex; + } + + if (showIndex !== false) { + this.setSelect(showIndex); } }, @@ -135,7 +138,7 @@ BI.Tab = BI.inherit(BI.Widget, { getTab: function (v) { this._assertCard(v); -return this.layout.getCardByName(v); + return this.layout.getCardByName(v); }, setValue: function (v) { diff --git a/src/base/foundation/message.js b/src/base/foundation/message.js index 156297c9d..82ab48ee5 100644 --- a/src/base/foundation/message.js +++ b/src/base/foundation/message.js @@ -22,7 +22,7 @@ BI.Msg = function () { // BI.Msg.prompt(title, message, value, callback, min_width); }, toast: function (message, options, context) { - BI.isString(options) && (options = { level: options }) + BI.isString(options) && (options = { level: options }); options = options || {}; context = context || BI.Widget._renderEngine.createElement("body"); var level = options.level || "common"; @@ -41,7 +41,7 @@ BI.Msg = function () { BI.remove(toastStack, toast.element); var _height = BI.SIZE_CONSANTS.TOAST_TOP; BI.each(toastStack, function (i, element) { - element.css({"top": _height}); + element.css({ "top": _height }); _height += element.outerHeight() + 10; }); callback(); @@ -62,16 +62,16 @@ BI.Msg = function () { }] }); toastStack.push(toast.element); - toast.element.css({"margin-left": -1 * toast.element.outerWidth() / 2}); + toast.element.css({ "margin-left": -1 * toast.element.outerWidth() / 2 }); toast.element.removeClass("bi-message-leave").addClass("bi-message-enter"); autoClose && BI.delay(function () { toast.element.removeClass("bi-message-enter").addClass("bi-message-leave"); - toast.destroy(); + toast.destroy?.(); }, 5000); return function () { toast.element.removeClass("bi-message-enter").addClass("bi-message-leave"); - toast.destroy(); + toast.destroy?.(); }; }, _show: function (hasCancel, title, message, callback) { diff --git a/src/base/layer/layer.drawer.js b/src/base/layer/layer.drawer.js index e62c272dc..2bf2b2942 100644 --- a/src/base/layer/layer.drawer.js +++ b/src/base/layer/layer.drawer.js @@ -154,22 +154,22 @@ BI.Drawer = BI.inherit(BI.Widget, { switch (o.placement) { case "right": self.element.css({ - transform: "translateX(-" + size.width + "px)" + left: "calc(100% - " + size.width + "px)" }); break; case "left": self.element.css({ - transform: "translateX(" + size.width + "px)" + right: "calc(100% - " + size.width + "px)" }); break; case "top": self.element.css({ - transform: "translateY(" + size.height + "px)" + bottom: "calc(100% - " + size.height + "px)" }); break; case "bottom": self.element.css({ - transform: "translateY(-" + size.height + "px)" + top: "calc(100% - " + size.height + "px)" }); break; } @@ -180,17 +180,26 @@ BI.Drawer = BI.inherit(BI.Widget, { hide: function (callback) { var self = this, o = this.options; requestAnimationFrame(function () { + var size = self._getSuitableSize(); switch (o.placement) { case "right": + self.element.css({ + left: "100%" + }); + break; case "left": self.element.css({ - transform: "translateX(0px)" + right: "100%" }); break; case "top": + self.element.css({ + bottom: "100%" + }); + break; case "bottom": self.element.css({ - transform: "translateY(0px)" + top: "100%" }); break; } @@ -213,7 +222,7 @@ BI.Drawer = BI.inherit(BI.Widget, { }, setZindex: function (zindex) { - this.element.css({"z-index": zindex}); + this.element.css({ "z-index": zindex }); }, destroyed: function () { diff --git a/src/base/list/listview.js b/src/base/list/listview.js index 052abc139..65f777a99 100644 --- a/src/base/list/listview.js +++ b/src/base/list/listview.js @@ -1,5 +1,5 @@ /** - * 表示当前对象 + * 边滚动边加载的列表控件 * * Created by GUY on 2017/5/23. * @class BI.ListView @@ -33,8 +33,8 @@ BI.ListView = BI.inherit(BI.Widget, { items: [BI.extend({ type: "bi.vertical", scrolly: false, - ref: function () { - self.container = this; + ref: function (_ref) { + self.container = _ref; } }, o.el)], element: this @@ -69,8 +69,8 @@ BI.ListView = BI.inherit(BI.Widget, { var self = this, o = this.options; var height = this.element.height(); var minContentHeight = o.scrollTop + height + o.overscanHeight; - var index = (this.cache[this.renderedIndex] && (this.cache[this.renderedIndex].index + o.blockSize)) || 0, - cnt = this.renderedIndex + 1; + var index = (this.cache[this.renderedIndex] && (this.cache[this.renderedIndex].index + o.blockSize)) || 0; + var cnt = this.renderedIndex + 1; var lastHeight; var getElementHeight = function () { return self.container.element.height(); @@ -112,6 +112,12 @@ BI.ListView = BI.inherit(BI.Widget, { this.cache = {}; }, + scrollTo: function (scrollTop) { + this.options.scrollTop = scrollTop; + this._calculateBlocksToRender(); + this.element.scrollTop(scrollTop); + }, + populate: function (items) { if (items && this.options.items !== items) { this.restore(); diff --git a/src/base/list/virtualgrouplist.js b/src/base/list/virtualgrouplist.js index 0eae74875..cb8d1b533 100644 --- a/src/base/list/virtualgrouplist.js +++ b/src/base/list/virtualgrouplist.js @@ -62,7 +62,7 @@ BI.VirtualGroupList = BI.inherit(BI.Widget, { }) : o.items; this._populate(); this.ticking = false; - this.element.scroll(function() { + this.element.scroll(function () { o.scrollTop = self.element.scrollTop(); if (!self.ticking) { requestAnimationFrame(function () { @@ -123,7 +123,7 @@ BI.VirtualGroupList = BI.inherit(BI.Widget, { } this.bottomBlank.setHeight(this.tree.sumTo(this.renderedIndex) - this.tree.sumTo(Math.min(end, this.renderedIndex))); this.container.populate(items.map(function (item, i) { - return o.itemFormatter(item, (start < 0 ? 0 : start) * o.blockSize + i) + return o.itemFormatter(item, (start < 0 ? 0 : start) * o.blockSize + i); })); } else { for (var i = (start < 0 ? 0 : start); i <= end; i++) { @@ -134,7 +134,7 @@ BI.VirtualGroupList = BI.inherit(BI.Widget, { } this.container.element.height(o.rowHeight * o.items.length - topHeight); this.container.populate(items.map(function (item, i) { - return o.itemFormatter(item, (start < 0 ? 0 : start) * o.blockSize + i) + return o.itemFormatter(item, (start < 0 ? 0 : start) * o.blockSize + i); })); } }, @@ -162,6 +162,13 @@ BI.VirtualGroupList = BI.inherit(BI.Widget, { this.bottomBlank.setHeight(0); }, + // 暂时只支持固定行高的场景 + scrollTo: function (scrollTop) { + this.options.scrollTop = scrollTop; + this._calculateBlocksToRender(); + this.element.scrollTop(scrollTop); + }, + restore: function () { this.options.scrollTop = 0; this._restore(); diff --git a/src/base/list/virtuallist.js b/src/base/list/virtuallist.js index b7f2700c7..d5881b30b 100644 --- a/src/base/list/virtuallist.js +++ b/src/base/list/virtuallist.js @@ -46,7 +46,6 @@ BI.VirtualList = BI.inherit(BI.Widget, { self.bottomBlank = this; } }], - element: this }; }, @@ -78,7 +77,7 @@ BI.VirtualList = BI.inherit(BI.Widget, { while ((lastHeight = getElementHeight()) < minContentHeight && index < o.items.length) { var items = o.items.slice(index, index + o.blockSize); this.container.addItems(items.map(function (item, i) { - return o.itemFormatter(item, index + i) + return o.itemFormatter(item, index + i); }), this); var addedHeight = getElementHeight() - lastHeight; this.tree.set(cnt, addedHeight); @@ -177,6 +176,12 @@ BI.VirtualList = BI.inherit(BI.Widget, { this.container.attr("items", []); }, + scrollTo: function (scrollTop) { + this.options.scrollTop = scrollTop; + this._calculateBlocksToRender(); + this.element.scrollTop(scrollTop); + }, + restore: function () { this.renderedIndex = -1; this._clearChildren(); diff --git a/src/base/single/0.single.js b/src/base/single/0.single.js index bb926e9a2..47dc6718f 100644 --- a/src/base/single/0.single.js +++ b/src/base/single/0.single.js @@ -9,6 +9,9 @@ * @extends BI.Widget * @abstract */ + +var delayingTooltips; + BI.Single = BI.inherit(BI.Widget, { _defaultConfig: function () { var conf = BI.Single.superclass._defaultConfig.apply(this, arguments); @@ -17,17 +20,29 @@ BI.Single = BI.inherit(BI.Widget, { title: null, warningTitle: null, tipType: null, // success或warning - belowMouse: false // title是否跟随鼠标 + belowMouse: false, // title是否跟随鼠标 + enableHover: false }); }, _showToolTip: function (e, opt) { opt || (opt = {}); - var self = this, o = this.options; - var type = this.getTipType() || (this.isEnabled() ? "success" : "warning"); - var title = type === "success" ? this.getTitle() : (this.getWarningTitle() || this.getTitle()); - if (BI.isKey(title)) { - BI.Tooltips.show(e, this.getName(), title, type, this, opt); + var o = this.options; + var tooltipOpt = {}; + var title = this.getTitle(); + if (BI.isPlainObject(title)) { + tooltipOpt = title; + } else { + tooltipOpt.level = this.getTipType() || "success"; + // 由于以前的用法,存在大量disabled:true搭配warningTitle的情况,所以这里做一个兼容,disabled:true的情况下,依然优先显示warningTitle,避免只设置了warningTitle而没有设置title的情况 + if (BI.isNull(o.tipType) && !this.isEnabled()) { + tooltipOpt.text = (this.getWarningTitle() || title); + } else { + tooltipOpt.text = tooltipOpt.level === "success" ? title : (this.getWarningTitle() || title); + } + } + if (BI.isKey(tooltipOpt.text)) { + BI.Tooltips.show(e, this.getName(), tooltipOpt, this, opt); if (o.action) { BI.Actions.runAction(o.action, "hover", o, this); } @@ -51,7 +66,7 @@ BI.Single = BI.inherit(BI.Widget, { self.setValue(newValue); }) : o.value; BI.Single.superclass._init.apply(this, arguments); - if (BI.isKey(o.title) || BI.isKey(o.warningTitle) + if (o.enableHover || BI.isKey(o.title) || BI.isKey(o.warningTitle) || BI.isFunction(o.title) || BI.isFunction(o.warningTitle)) { this.enableHover({ belowMouse: o.belowMouse, @@ -78,14 +93,16 @@ BI.Single = BI.inherit(BI.Widget, { this.element.on("mouseenter.title" + this.getName(), function (e) { self._e = e; if (self.getTipType() === "warning" || (BI.isKey(self.getWarningTitle()) && !self.isEnabled())) { + delayingTooltips = self.getName(); self.showTimeout = BI.delay(function () { - if (BI.isNotNull(self.showTimeout)) { + if (BI.isNotNull(self.showTimeout) && delayingTooltips === self.getName()) { self._showToolTip(self._e || e, opt); } }, 200); } else if (self.getTipType() === "success" || self.isEnabled()) { + delayingTooltips = self.getName(); self.showTimeout = BI.delay(function () { - if (BI.isNotNull(self.showTimeout)) { + if (BI.isNotNull(self.showTimeout) && delayingTooltips === self.getName()) { self._showToolTip(self._e || e, opt); } }, 500); diff --git a/src/base/single/button/buttons/button.js b/src/base/single/button/buttons/button.js index d7f875b8f..420b5f7aa 100644 --- a/src/base/single/button/buttons/button.js +++ b/src/base/single/button/buttons/button.js @@ -24,18 +24,20 @@ if (isVertical(props.iconPosition)) { // 图标高度和文字高度默认相等 adaptiveHeight += (props.textHeight || 16) * 2; - adaptiveHeight += props.iconGap || 4; + adaptiveHeight += props.iconGap || 0; var tGap = props.tgap || props.vgap || 2; var bGap = props.bgap || props.vgap || 2; adaptiveHeight += (tGap + bGap); } + var clearMinWidth = props.block === true || props.clear === true || props.plain; + return BI.extend(conf, { baseCls: (conf.baseCls || "") + " bi-button" + ((BI.isIE() && BI.isIE9Below()) ? " hack" : ""), attributes: { tabIndex: 1 }, - minWidth: (props.block === true || props.clear === true) ? 0 : 80, + minWidth: clearMinWidth ? 0 : 80, height: isVertical(props.iconPosition) ? adaptiveHeight : 24, shadow: props.clear !== true, isShadowShowingOnSelected: true, @@ -58,13 +60,13 @@ bgap: 0, lgap: 0, rgap: 0, - iconGap: 4, + iconGap: 0, iconPosition: "left" }); }, render: function () { - var o = this.options; + var o = this.options, self = this; // 由于button默认情况下有个边框,所以要主动算行高 var lineHeight, textHeight = o.textHeight; @@ -110,12 +112,12 @@ tgap: o.iconPosition === "top" ? o.iconGap : 0, bgap: o.iconPosition === "bottom" ? o.iconGap : 0 }; - var items = [this.icon, BI.extend({el: this.text}, gapContainer)]; + var items = [this.icon, BI.extend({ el: this.text }, gapContainer)]; if (isVertical(o.iconPosition)) { layoutType = "bi.vertical"; } if (o.iconPosition === "right" || o.iconPosition === "bottom") { - items = [BI.extend({el: this.text}, gapContainer), this.icon]; + items = [BI.extend({ el: this.text }, gapContainer), this.icon]; } BI.createWidget({ type: "bi.center_adapt", @@ -145,29 +147,20 @@ rgap: o.rgap, element: this, text: o.text, - value: o.value + value: o.value, + title: null, }); } - if (o.block === true) { - this.element.addClass("block"); - } - if (o.clear === true) { - this.element.addClass("clear"); - } - if (o.ghost === true) { - this.element.addClass("ghost"); - } - if (o.plain === true) { - this.element.addClass("plain"); - } - if (o.loading === true) { - this.element.addClass("loading"); - } - if (o.light === true) { - this.element.addClass("light"); - } + var classArr = ["block", "clear", "ghost", "plain", "loading", "light"]; + // 如果 options 对应的属性为 true 则给元素添加 class + BI.each(classArr, function (_, clz) { + if (BI.get(o, clz) === true) { + self.element.addClass(clz); + } + }); + if (o.minWidth > 0) { - this.element.css({"min-width": o.minWidth / BI.pixRatio + BI.pixUnit}); + this.element.css({ "min-width": o.minWidth / BI.pixRatio + BI.pixUnit }); } }, diff --git a/src/base/single/editor/editor.multifile.js b/src/base/single/editor/editor.multifile.js index 7aec220f0..ae0d5e0a9 100644 --- a/src/base/single/editor/editor.multifile.js +++ b/src/base/single/editor/editor.multifile.js @@ -71,6 +71,10 @@ BI.MultifileEditor = BI.inherit(BI.Widget, { this.file.reset(); }, + setUrl: function (v) { + this.file.setUrl(v); + }, + setMaxFileLength: function (v) { this.file.setMaxFileLength(v); }, diff --git a/src/base/single/input/file.js b/src/base/single/input/file.js index b86963f41..ff211e6f1 100644 --- a/src/base/single/input/file.js +++ b/src/base/single/input/file.js @@ -655,6 +655,13 @@ }); }, + setUrl: function(v) { + this.options.url = v; + if (this.wrap) { + this.wrap.url = v; + } + }, + setMaxFileLength: function (v) { this.options.maxLength = v; if (this.wrap) { diff --git a/src/base/single/label/abstract.label.js b/src/base/single/label/abstract.label.js index 76fb27fbd..46e5e7969 100644 --- a/src/base/single/label/abstract.label.js +++ b/src/base/single/label/abstract.label.js @@ -18,10 +18,27 @@ tgap: 0, bgap: 0, highLight: false, - handler: null + handler: null, + enableHover: props.title !== null, }); }, + getTitle: function () { + var title = this.options.title; + var text = this.options.text; + if (BI.isFunction(title)) { + return title(); + } + if (BI.isNotNull(title)) { + return title; + } + + if (BI.isFunction(text)) { + return text(); + } + return text; + }, + _createJson: function () { var o = this.options; return { @@ -374,6 +391,7 @@ setValue: function (v) { BI.AbstractLabel.superclass.setValue.apply(this, arguments); if (!this.isReadOnly()) { + this.options.text = v; this.text.setValue(v); } } diff --git a/src/base/single/tip/tip.toast.js b/src/base/single/tip/tip.toast.js index a9a7aeb7e..0440ee23d 100644 --- a/src/base/single/tip/tip.toast.js +++ b/src/base/single/tip/tip.toast.js @@ -11,7 +11,6 @@ BI.Toast = BI.inherit(BI.Tip, { minWidth: 100, closableMaxWidth: 410, maxWidth: 400, - hgap: 8 }, _defaultConfig: function () { @@ -21,7 +20,10 @@ BI.Toast = BI.inherit(BI.Tip, { level: "success", // success或warning autoClose: true, closable: null, - vgap: 7, + textHeight: 20, + vgap: 10, + innerHgap: 4, + hgap: 8, }); }, @@ -46,7 +48,7 @@ BI.Toast = BI.inherit(BI.Tip, { mouseleave: fn, mousemove: fn }); - var cls = "close-font"; + var cls; switch (o.level) { case "success": cls = "toast-success-font"; @@ -72,19 +74,18 @@ BI.Toast = BI.inherit(BI.Tip, { var items = [{ type: "bi.icon_label", cls: cls + " toast-icon", - width: 36 + height: o.textHeight, }, { el: BI.isPlainObject(o.text) ? o.text : { type: "bi.label", whiteSpace: "normal", text: o.text, - textHeight: 16, + textHeight: o.textHeight, textAlign: "left" }, - rgap: hasCloseIcon() ? 0 : this._const.hgap }]; - var columnSize = [36, "fill"]; + var columnSize = ["", "fill"]; if (hasCloseIcon()) { items.push({ @@ -93,23 +94,20 @@ BI.Toast = BI.inherit(BI.Tip, { handler: function () { self.destroy(); }, - width: 36 + height: o.textHeight, }); - columnSize.push(36); + columnSize.push(""); } - this.text = BI.createWidget({ + return { type: "bi.horizontal", horizontalAlign: BI.HorizontalAlign.Stretch, - element: this, items: items, + hgap: o.hgap, vgap: o.vgap, + innerHgap: o.innerHgap, columnSize: columnSize - }); - }, - - setText: function (text) { - this.text.setText(text); + }; }, beforeDestroy: function () { diff --git a/src/base/single/tip/tip.tooltip.js b/src/base/single/tip/tip.tooltip.js index ca206a849..5499f05ac 100644 --- a/src/base/single/tip/tip.tooltip.js +++ b/src/base/single/tip/tip.tooltip.js @@ -7,8 +7,8 @@ */ BI.Tooltip = BI.inherit(BI.Tip, { _const: { - hgap: 5, - vgap: 3 + hgap: 8, + vgap: 4 }, _defaultConfig: function () { @@ -17,7 +17,8 @@ BI.Tooltip = BI.inherit(BI.Tip, { text: "", level: "success", // success或warning stopEvent: false, - stopPropagation: false + stopPropagation: false, + textAlign: "left", }); }, @@ -44,10 +45,11 @@ BI.Tooltip = BI.inherit(BI.Tip, { type: "bi.vertical", element: this, hgap: this._const.hgap, + innerVgap: this._const.vgap, items: BI.map(texts, function (i, text) { return { type: "bi.label", - textAlign: "left", + textAlign: o.textAlign, whiteSpace: "normal", text: text, textHeight: 18 @@ -58,11 +60,12 @@ BI.Tooltip = BI.inherit(BI.Tip, { this.text = BI.createWidget({ type: "bi.label", element: this, - textAlign: "left", + textAlign: o.textAlign, whiteSpace: "normal", text: o.text, textHeight: 18, - hgap: this._const.hgap + hgap: this._const.hgap, + vgap: this._const.vgap, }); } }, diff --git a/src/case/button/item.multiselect.js b/src/case/button/item.multiselect.js index 0d44b6da8..b90fafac5 100644 --- a/src/case/button/item.multiselect.js +++ b/src/case/button/item.multiselect.js @@ -20,11 +20,6 @@ BI.MultiSelectItem = BI.inherit(BI.BasicButton, { this.checkbox = BI.createWidget({ type: "bi.checkbox" }); - this.checkbox.on(BI.Controller.EVENT_CHANGE, function (type) { - if (type === BI.Events.CLICK) { - self.setSelected(self.isSelected()); - } - }); return { type: "bi.vertical_adapt", columnSize: [o.iconWrapperWidth || o.height, "fill"], @@ -74,7 +69,6 @@ BI.MultiSelectItem = BI.inherit(BI.BasicButton, { doClick: function () { BI.MultiSelectItem.superclass.doClick.apply(this, arguments); - this.checkbox.setSelected(this.isSelected()); if (this.isValid()) { this.fireEvent(BI.MultiSelectItem.EVENT_CHANGE, this.getValue(), this); } diff --git a/src/case/button/item.singleselect.radio.js b/src/case/button/item.singleselect.radio.js index a7c5fe04b..74ebe49c9 100644 --- a/src/case/button/item.singleselect.radio.js +++ b/src/case/button/item.singleselect.radio.js @@ -28,16 +28,6 @@ BI.SingleSelectRadioItem = BI.inherit(BI.BasicButton, { ref: function (_ref) { self.radio = _ref; }, - listeners: [ - { - eventName: BI.Controller.EVENT_CHANGE, - action: function (type) { - if (type === BI.Events.CLICK) { - self.setSelected(self.isSelected()); - } - } - } - ], }] }, { el: { @@ -82,7 +72,6 @@ BI.SingleSelectRadioItem = BI.inherit(BI.BasicButton, { doClick: function () { BI.SingleSelectRadioItem.superclass.doClick.apply(this, arguments); - this.radio.setSelected(this.isSelected()); if (this.isValid()) { this.fireEvent(BI.SingleSelectRadioItem.EVENT_CHANGE, this.isSelected(), this); } diff --git a/src/case/combo/bubblecombo/popup.bubble.js b/src/case/combo/bubblecombo/popup.bubble.js index 7bc1f500c..6826a99f5 100644 --- a/src/case/combo/bubblecombo/popup.bubble.js +++ b/src/case/combo/bubblecombo/popup.bubble.js @@ -36,7 +36,9 @@ BI.BubblePopupBarView = BI.inherit(BI.BubblePopupView, { }, { text: BI.i18nText(BI.i18nText("BI-Basic_OK")), value: true - }] + }], + innerVgap: 16, + innerHgap: 16, }); }, @@ -48,8 +50,7 @@ BI.BubblePopupBarView = BI.inherit(BI.BubblePopupView, { if (BI.isWidget(buttonOpt)) { items.push({ el: buttonOpt, - lgap: i === 0 ? 15 : 10, - rgap: i === o.buttons.length - 1 ? 15 : 0 + lgap: 12, }); } else { items.push({ @@ -60,14 +61,14 @@ BI.BubblePopupBarView = BI.inherit(BI.BubblePopupView, { self.fireEvent(BI.BubblePopupBarView.EVENT_CLICK_TOOLBAR_BUTTON, v); } }, buttonOpt), - lgap: i === 0 ? 15 : 10, - rgap: i === o.buttons.length - 1 ? 15 : 0 + lgap: 12, }); } }); return BI.createWidget({ type: "bi.right_vertical_adapt", - height: 44, + innerVgap: o.innerVgap, + innerHgap: o.innerHgap, items: items }); }, @@ -79,20 +80,17 @@ BI.BubblePopupBarView = BI.inherit(BI.BubblePopupView, { _createView: function () { var o = this.options; - var button = BI.createWidget({ - type: "bi.button_group", + var view = BI.createWidget({ + type: "bi.vertical", items: [this._createContent()], - layouts: [{ - type: "bi.vertical", - cls: "bar-popup-container", - hgap: BI.SIZE_CONSANTS.H_GAP_SIZE, - tgap: BI.SIZE_CONSANTS.V_GAP_SIZE - }] + cls: "bar-popup-container", + hgap: o.innerHgap, + tgap: o.innerVgap, }); - button.element.css("min-height", o.minHeight - 44); + view.element.css("min-height", o.minHeight); - return button; + return view; } }); BI.BubblePopupBarView.EVENT_CLICK_TOOLBAR_BUTTON = "EVENT_CLICK_TOOLBAR_BUTTON"; diff --git a/src/case/combo/searchtextvaluecombo/combo.searchtextvalue.js b/src/case/combo/searchtextvaluecombo/combo.searchtextvalue.js index 8de574c76..09608dea9 100644 --- a/src/case/combo/searchtextvaluecombo/combo.searchtextvalue.js +++ b/src/case/combo/searchtextvaluecombo/combo.searchtextvalue.js @@ -33,7 +33,6 @@ BI.SearchTextValueCombo = BI.inherit(BI.Widget, { adjustLength: 2, height: height, width: width, - toggle: false, ref: function () { self.combo = this; }, diff --git a/src/case/combo/searchtextvaluecombo/trigger.searchtextvalue.js b/src/case/combo/searchtextvaluecombo/trigger.searchtextvalue.js index c61093b35..7f6f7925d 100644 --- a/src/case/combo/searchtextvaluecombo/trigger.searchtextvalue.js +++ b/src/case/combo/searchtextvaluecombo/trigger.searchtextvalue.js @@ -24,10 +24,6 @@ BI.SearchTextValueTrigger = BI.inherit(BI.Trigger, { }, width: o.height, height: o.height, - stopPropagation: true, - handler: function () { - self.fireEvent(BI.Controller.EVENT_CHANGE, BI.Events.TOGGLE); - } }; var stateText = this._digest(o.value, o.items) || o.text; diff --git a/src/case/layer/pane.list.js b/src/case/layer/pane.list.js index efeb8c8fb..26be09624 100644 --- a/src/case/layer/pane.list.js +++ b/src/case/layer/pane.list.js @@ -47,11 +47,15 @@ BI.ListPane = BI.inherit(BI.Pane, { } o.itemsCreator(op, function () { calback.apply(self, arguments); - op.times === 1 && BI.nextTick(function () { - self.loaded(); - // callback可能在loading之前执行, check保证显示正确 - self.check(); - }); + o.items = BI.concat(o.items, BI.get(arguments, [0], [])); + if (op.times === 1) { + o.items = BI.get(arguments, [0], []); + BI.nextTick(function () { + self.loaded(); + // callback可能在loading之前执行, check保证显示正确 + self.check(); + }); + } }); }, hasNext: o.hasNext, @@ -113,7 +117,7 @@ BI.ListPane = BI.inherit(BI.Pane, { populate: function (items) { var self = this, o = this.options; if (arguments.length === 0 && (BI.isFunction(this.button_group.attr("itemsCreator")))) {// 接管loader的populate方法 - this.button_group.attr("itemsCreator").apply(this, [{times: 1}, function () { + this.button_group.attr("itemsCreator").apply(this, [{ times: 1 }, function () { if (arguments.length === 0) { throw new Error("参数不能为空"); } @@ -150,6 +154,16 @@ BI.ListPane = BI.inherit(BI.Pane, { this.button_group.setValue.apply(this.button_group, arguments); }, + setAllSelected: function (v) { + if (this.button_group.setAllSelected) { + this.button_group.setAllSelected(v); + } else { + BI.each(this.getAllButtons(), function (i, btn) { + (btn.setSelected || btn.setAllSelected).apply(btn, [v]); + }); + } + }, + getValue: function () { return this.button_group.getValue.apply(this.button_group, arguments); }, @@ -183,4 +197,4 @@ BI.ListPane = BI.inherit(BI.Pane, { } }); BI.ListPane.EVENT_CHANGE = "EVENT_CHANGE"; -BI.shortcut("bi.list_pane", BI.ListPane); \ No newline at end of file +BI.shortcut("bi.list_pane", BI.ListPane); diff --git a/src/case/list/list.select.js b/src/case/list/list.select.js index bab4a15af..a1c77107c 100644 --- a/src/case/list/list.select.js +++ b/src/case/list/list.select.js @@ -113,9 +113,13 @@ BI.SelectList = BI.inherit(BI.Widget, { }, setAllSelected: function (v) { - BI.each(this.getAllButtons(), function (i, btn) { - (btn.setSelected || btn.setAllSelected).apply(btn, [v]); - }); + if (this.list.setAllSelected) { + this.list.setAllSelected(v); + } else { + BI.each(this.getAllButtons(), function (i, btn) { + (btn.setSelected || btn.setAllSelected).apply(btn, [v]); + }); + } this.allSelected = !!v; this.toolbar.setSelected(v); this.toolbar.setHalfSelected(false); diff --git a/src/case/ztree/jquery.ztree.core-3.5.js b/src/case/ztree/jquery.ztree.core-3.5.js index 27115904a..875f33e67 100644 --- a/src/case/ztree/jquery.ztree.core-3.5.js +++ b/src/case/ztree/jquery.ztree.core-3.5.js @@ -2017,4 +2017,4 @@ var zt = $.fn.zTree, $$ = tools.$, consts = zt.consts; -})(jQuery); +})(BI.jQuery); diff --git a/src/component/allvaluechooser/abstract.allvaluechooser.js b/src/component/allvaluechooser/abstract.allvaluechooser.js index 90cd4afd0..c13d8a30f 100644 --- a/src/component/allvaluechooser/abstract.allvaluechooser.js +++ b/src/component/allvaluechooser/abstract.allvaluechooser.js @@ -40,6 +40,18 @@ BI.AbstractAllValueChooser = BI.inherit(BI.Widget, { return text; }, + _getItemsByTimes: function (items, times) { + var res = []; + for (var i = (times - 1) * this._const.perPage; items[i] && i < times * this._const.perPage; i++) { + res.push(items[i]); + } + return res; + }, + + _hasNextByTimes: function (items, times) { + return times * this._const.perPage < items.length; + }, + _itemsCreator: function (options, callback) { var self = this, o = this.options; if (!o.cache || !this.items) { @@ -50,13 +62,14 @@ BI.AbstractAllValueChooser = BI.inherit(BI.Widget, { } else { call(this.items); } - function call (items) { + + function call(items) { var keywords = (options.keywords || []).slice(); if (options.keyword) { keywords.push(options.keyword); } var resultItems = items; - if(BI.isNotEmptyArray(keywords)) { + if (BI.isNotEmptyArray(keywords)) { resultItems = []; BI.each(keywords, function (i, kw) { var search = BI.Func.getSearchResult(items, kw); @@ -77,12 +90,12 @@ BI.AbstractAllValueChooser = BI.inherit(BI.Widget, { return; } if (options.type === BI.MultiSelectCombo.REQ_GET_DATA_LENGTH) { - callback({count: resultItems.length}); + callback({ count: resultItems.length }); return; } callback({ - items: resultItems, - hasNext: false + items: self._getItemsByTimes(resultItems, options.times), + hasNext: self._hasNextByTimes(resultItems, options.times) }); } }, @@ -101,4 +114,4 @@ BI.AbstractAllValueChooser = BI.inherit(BI.Widget, { } return value; }, -}); \ No newline at end of file +}); diff --git a/src/component/allvaluechooser/pane.allvaluechooser.js b/src/component/allvaluechooser/pane.allvaluechooser.js index 93ad36cba..6188f14ee 100644 --- a/src/component/allvaluechooser/pane.allvaluechooser.js +++ b/src/component/allvaluechooser/pane.allvaluechooser.js @@ -50,10 +50,11 @@ BI.AllValueChooserPane = BI.inherit(BI.AbstractAllValueChooser, { getValue: function () { var val = this.list.getValue() || {}; - if (val.type === BI.Selection.All) { - return val.assist; + if (val.type === BI.Selection.Multi) { + return val.value || []; } - return val.value || []; + + return BI.difference(BI.map(this.items, "value"), val.value || []); }, populate: function (items) { diff --git a/src/core/2.base.js b/src/core/2.base.js index 4ab530629..7071eb3e1 100644 --- a/src/core/2.base.js +++ b/src/core/2.base.js @@ -524,6 +524,10 @@ isWindow: function (obj) { return obj != null && obj == obj.window; + }, + + isPromise: function(obj) { + return !!obj && (BI.isObject(obj) || BI.isFunction(obj)) && BI.isFunction(obj.then); } }); diff --git a/src/core/3.ob.js b/src/core/3.ob.js index 30b6ce536..9cf8fd3f9 100644 --- a/src/core/3.ob.js +++ b/src/core/3.ob.js @@ -1,5 +1,5 @@ !(function () { - function extend () { + function extend() { var target = arguments[0] || {}, length = arguments.length, i = 1, options, name, src, copy; for (; i < length; i++) { // Only deal with non-null/undefined values @@ -90,11 +90,11 @@ //释放当前对象 _purgeRef: function () { if (this.options.__ref) { - this.options.__ref.call(null); + this.options.__ref.call(null, null); this.options.__ref = null; } if (this.options.ref) { - this.options.ref.call(null); + this.options.ref.call(null, null); this.options.ref = null; } }, diff --git a/src/core/4.widget.js b/src/core/4.widget.js index 4ca395207..d24c01566 100644 --- a/src/core/4.widget.js +++ b/src/core/4.widget.js @@ -738,6 +738,8 @@ callLifeHook(this, "destroyed"); this.destroyed = null; this._isDestroyed = true; + // this._purgeRef(); // 清除ref的时机还是要仔细考虑一下 + }, _unMount: function () { diff --git a/src/core/5.inject.js b/src/core/5.inject.js index 3e606f1a7..9e0ae8503 100644 --- a/src/core/5.inject.js +++ b/src/core/5.inject.js @@ -32,7 +32,7 @@ } moduleInjection[xtype] = cls; } - + return function () { return BI.Modules.getModule(xtype); }; @@ -45,7 +45,7 @@ } else { constantInjection[xtype] = cls; } - + return function () { return BI.Constants.getConstant(xtype); }; @@ -58,7 +58,7 @@ } else { modelInjection[xtype] = cls; } - + return function (config) { return BI.Models.getModel(xtype, config); }; @@ -71,7 +71,7 @@ } else { storeInjection[xtype] = cls; } - + return function (config) { return BI.Stores.getStore(xtype, config); }; @@ -81,10 +81,10 @@ BI.service = BI.service || function (xtype, cls) { if (serviceInjection[xtype] != null) { _global.console && console.error("service: [" + xtype + "] 已经注册过了"); - } else { - serviceInjection[xtype] = cls; } - + + serviceInjection[xtype] = cls; + return function (config) { return BI.Services.getService(xtype, config); }; @@ -97,7 +97,7 @@ } else { providerInjection[xtype] = cls; } - + return function (config) { return BI.Providers.getProvider(xtype, config); }; @@ -402,8 +402,6 @@ BI.shortcut = BI.component = BI.shortcut || function (xtype, cls) { if (kv[xtype] != null) { _global.console && console.error("组件: [" + xtype + "] 已经注册过了"); - - return; } if (cls) { cls["xtype"] = xtype; diff --git a/src/core/conflict.js b/src/core/conflict.js index e478c567d..dba9b8b6f 100644 --- a/src/core/conflict.js +++ b/src/core/conflict.js @@ -1,7 +1,7 @@ -if (!window.$ && !window.jQuery) { - window.jQuery = window.$ = BI.jQuery; +if (!_global.$ && !_global.jQuery) { + _global.jQuery = _global.$ = BI.jQuery; } -if (!window._) { - window._ = BI._; +if (!_global._) { + _global._ = BI._; } diff --git a/src/core/controller/controller.bubbles.js b/src/core/controller/controller.bubbles.js index a705ef4a6..fa5cefb5f 100644 --- a/src/core/controller/controller.bubbles.js +++ b/src/core/controller/controller.bubbles.js @@ -30,7 +30,7 @@ BI.BubblesController = BI.inherit(BI.Controller, { if (!this.storeBubbles[name]) { this.storeBubbles[name] = BI.createWidget({ - type: "bi.label", + type: "bi.text", cls: "bi-bubble" + " bubble-" + level, text: text, hgap: 5, diff --git a/src/core/controller/controller.tooltips.js b/src/core/controller/controller.tooltips.js index 5e4db152b..14dbba9e4 100644 --- a/src/core/controller/controller.tooltips.js +++ b/src/core/controller/controller.tooltips.js @@ -12,17 +12,25 @@ BI.TooltipsController = BI.inherit(BI.Controller, { this.showingTips = {};// 存储正在显示的tooltip }, - _createTooltip: function (text, level) { + /** + * + * @param opt + * @param opt.text {String} 文本 + * @param opt.level {String} 级别, success或warning + * @param opt.textAlign {String} 文本对齐方式, left, center, right + * @returns {*} + * @private + */ + _createTooltip: function (opt) { return BI.createWidget({ type: "bi.tooltip", - text: text, - level: level, + ...opt, stopEvent: true }); }, // opt: {container: '', belowMouse: false} - show: function (e, name, text, level, context, opt) { + show: function (e, name, tooltipOpt, context, opt) { opt || (opt = {}); var self = this; BI.each(this.showingTips, function (i, tip) { @@ -30,7 +38,7 @@ BI.TooltipsController = BI.inherit(BI.Controller, { }); this.showingTips = {}; if (!this.has(name)) { - this.create(name, text, level, opt.container || "body"); + this.create(name, tooltipOpt, document.fullscreenElement ? context : (opt.container || "body")); } if (!opt.belowMouse) { var offset = context.element.offset(); @@ -41,7 +49,6 @@ BI.TooltipsController = BI.inherit(BI.Controller, { var top = offset.top + bounds.height + 5; } var tooltip = this.get(name); - tooltip.setText(text); tooltip.element.css({ left: "0px", top: "0px" @@ -84,9 +91,9 @@ BI.TooltipsController = BI.inherit(BI.Controller, { return this; }, - create: function (name, text, level, context) { + create: function (name, tooltipOpt, context) { if (!this.has(name)) { - var tooltip = this._createTooltip(text, level); + var tooltip = this._createTooltip(tooltipOpt); this.add(name, tooltip); BI.createWidget({ type: "bi.absolute", diff --git a/src/core/element/element.js b/src/core/element/element.js new file mode 100644 index 000000000..7bf1c9d4a --- /dev/null +++ b/src/core/element/element.js @@ -0,0 +1,74 @@ +import { registFunction } from './plugins'; + +export function Element(widget, attribs) { + this.l = this.r = this.t = this.b = 0; // 边框 + this.marginLeft = this.marginRight = this.marginTop = this.marginBottom = 0; //间距 + this.position = {}; + this.classMap = {}; + this.classList = []; + this.children = []; + this.attribs = attribs || {}; + this.styles = {}; + // 兼容处理 + this['0'] = this; + this.style = {}; + if (!widget) { + this.nodeName = 'body'; + this.position.x = 0; + this.position.y = 0; + this.attribs.id = 'body'; + } else if (BI.isWidget(widget)) { + this.widget = widget; + this.nodeName = widget.options.tagName; + this.textBaseLine = widget.options.textBaseLine; + } else if (BI.isString(widget)) { + this.nodeName = widget; + } +} + +initElement(Element); +registFunction(Element); + +function initElement(element) { + element.prototype = { + appendChild(child) { + child.parent = this; + if (this.children.push(child) !== 1) { + var sibling = this.children[this.children.length - 2]; + sibling.next = child; + child.prev = sibling; + child.next = null; + } + }, + append(child) { + child.parent = this; + if (this.children.push(child) !== 1) { + var sibling = this.children[this.children.length - 2]; + sibling.next = child; + child.prev = sibling; + child.next = null; + } + }, + getParent() { + return this.parent; + }, + getSiblings() { + var parent = this.getParent(); + return parent ? parent.getChildren() : [this]; + }, + getChildren() { + return this.children; + }, + + getBounds() { + return {}; + }, + + width() { + + }, + height() { + + } + }; +} diff --git a/src/core/element/index.js b/src/core/element/index.js new file mode 100644 index 000000000..bbc4eb926 --- /dev/null +++ b/src/core/element/index.js @@ -0,0 +1,31 @@ +import { Element } from './element'; + +BI.Element = Element; +BI.Element.renderEngine = { + createElement: (widget) => { + // eslint-disable-next-line no-undef + if (BI.isWidget(widget)) { + var o = widget.options; + if (o.element instanceof Element) { + return o.element; + } + if (typeof o.element === 'string' && o.element !== 'body') { + o.root = false; + return new Element(widget); + } + + if (o.root === true) { + return new Element(); + } + } + // eslint-disable-next-line no-undef + if (BI.isString(widget)) { + return new Element(widget); + } + return new Element(widget); + }, + + createFragment() { + return new Element(); + } +} diff --git a/src/core/element/plugins/attr.js b/src/core/element/plugins/attr.js new file mode 100644 index 000000000..3ae918f29 --- /dev/null +++ b/src/core/element/plugins/attr.js @@ -0,0 +1,22 @@ +export const registAttrFun = (Element) => { + Element.registerFunction('attr', function (key, value) { + var self = this; + if (BI.isObject(key)) { + BI.each(key, (k, v) => { + self.attr(k, v); + }); + return this; + } + if (BI.isNull(value)) { + return this.attribs[key]; + } + this.attribs[key] = value; + return this; + }); + Element.registerFunction('hasAttrib', function (key) { + return this.attribs[key] != null; + }); + Element.registerFunction('removeAttr', function (key) { + delete this.attribs[key]; + }); +}; diff --git a/src/core/element/plugins/class.js b/src/core/element/plugins/class.js new file mode 100644 index 000000000..ce46e6864 --- /dev/null +++ b/src/core/element/plugins/class.js @@ -0,0 +1,23 @@ +export const registClassFun = (Element) => { + Element.registerFunction('addClass', function (classList) { + var self = this; + BI.each(classList.split(' '), (i, cls) => { + if (cls && !self.classMap[cls]) { + self.classList.push(cls); + } + cls && (self.classMap[cls] = true); + }); + return this; + }); + + Element.registerFunction('removeClass', function (classList) { + var self = this; + BI.each(classList.split(' '), (i, cls) => { + if (cls && self.classMap[cls]) { + delete self.classMap[cls]; + self.classList.splice(self.classList.indexOf(cls), 1); + } + }); + return this; + }); +}; diff --git a/src/core/element/plugins/css.js b/src/core/element/plugins/css.js new file mode 100644 index 000000000..e1826a155 --- /dev/null +++ b/src/core/element/plugins/css.js @@ -0,0 +1,22 @@ +export const registCssFun = (Element) => { + Element.registerFunction('css', function (key, value) { + var self = this; + if (BI.isObject(key)) { + BI.each(key, (k, v) => { + self.css(k, v); + }); + return this; + } + key = BI.trim(BI.camelize(key)); + return css(this, key, value); + }); +}; + +const css = (elem, key, value) => { + key = BI.trim(BI.camelize(key)); + if (BI.isNull(value)) { + return elem.styles[key]; + } + elem.styles[key] = value; + return elem; +}; diff --git a/src/core/element/plugins/data.js b/src/core/element/plugins/data.js new file mode 100644 index 000000000..e141c96c3 --- /dev/null +++ b/src/core/element/plugins/data.js @@ -0,0 +1,12 @@ +export const registDataFun = (Element) => { + Element.registerFunction('data', function (key, value) { + if (!this._data) { + this._data = {}; + } + if (BI.isNull(value)) { + return this._data[key]; + } + this._data[key] = value; + return this; + }); +}; diff --git a/src/core/element/plugins/empty.js b/src/core/element/plugins/empty.js new file mode 100644 index 000000000..dbe5e0c1d --- /dev/null +++ b/src/core/element/plugins/empty.js @@ -0,0 +1,9 @@ +export const registEmptyFun = (Element) => { + Element.registerFunction('empty', function (text) { + this.children = []; + return this; + }); + Element.registerFunction('destroy', function (text) { + return this; + }); +}; diff --git a/src/core/element/plugins/event.js b/src/core/element/plugins/event.js new file mode 100644 index 000000000..38004ed88 --- /dev/null +++ b/src/core/element/plugins/event.js @@ -0,0 +1,32 @@ +var returnThis = function () { + return this; +}; +export const registEventFun = (Element) => { + [ + 'mousedown', + 'mouseup', + 'mousewheel', + 'keydown', + 'keyup', + 'focus', + 'focusin', + 'focusout', + 'click', + 'on', + 'off', + 'bind', + 'unbind', + 'trigger', + 'hover', + 'scroll', + 'scrollLeft', + 'scrollTop', + 'resize', + 'show', + 'hide', + 'dblclick', + 'blur', + ].forEach((event) => { + Element.registerFunction(event, returnThis); + }); +}; diff --git a/src/core/element/plugins/html.js b/src/core/element/plugins/html.js new file mode 100644 index 000000000..b115f9388 --- /dev/null +++ b/src/core/element/plugins/html.js @@ -0,0 +1,15 @@ +export const registHtmlFun = (Element) => { + Element.registerFunction('html', function (text) { + if (text && text.charAt(0) === '<') { + BI.createWidget({ + type: 'bi.html', + element: this.widget, + html: text, + }); + this.originalHtml = text; + } else { + this.text = BI.htmlDecode(text); + } + return this; + }); +}; diff --git a/src/core/element/plugins/index.js b/src/core/element/plugins/index.js new file mode 100644 index 000000000..c32aab4dc --- /dev/null +++ b/src/core/element/plugins/index.js @@ -0,0 +1,31 @@ +import { registAttrFun } from './attr'; +import { registClassFun } from './class'; +import { registCssFun } from './css'; +import { registDataFun } from './data'; +import { registEmptyFun } from './empty'; +import { registEventFun } from './event'; +import { registHtmlFun } from './html'; +import { registKeywordMarkFun } from './keywordMark'; +import { registRenderToHtmlFun } from './renderToHtml'; +import { registRenderToStringFun } from './renderToString'; +import { registTextFun } from './text'; +import { registValFun } from './val'; + +export const registFunction = (Element) => { + var functionMap = {}; + Element.registerFunction = (key, fn) => { + Element.prototype[key] = functionMap[key] = fn; + }; + registAttrFun(Element); + registClassFun(Element); + registCssFun(Element); + registDataFun(Element); + registEmptyFun(Element); + registEventFun(Element); + registHtmlFun(Element); + registKeywordMarkFun(Element); + registRenderToStringFun(Element); + registRenderToHtmlFun(Element); + registTextFun(Element); + registValFun(Element); +}; diff --git a/src/core/element/plugins/keywordMark.js b/src/core/element/plugins/keywordMark.js new file mode 100644 index 000000000..a7eab83dd --- /dev/null +++ b/src/core/element/plugins/keywordMark.js @@ -0,0 +1,6 @@ +export const registKeywordMarkFun = (Element) => { + Element.registerFunction('__textKeywordMarked__', function (text) { + this[0].textContent = text; + return this; + }); +}; diff --git a/src/core/element/plugins/renderToHtml.js b/src/core/element/plugins/renderToHtml.js new file mode 100644 index 000000000..525afe062 --- /dev/null +++ b/src/core/element/plugins/renderToHtml.js @@ -0,0 +1,65 @@ +var skipArray = []; +var pxStyle = ['font-size', 'width', 'height']; +var _renderToHtml = function (root) { + var str = ''; + if (BI.isNull(root.originalHtml)) { + if (root.tag !== 'body') { + str += `<${root.tag}`; + if (root.classList.length > 0) { + str += ' class="'; + BI.each(root.classList, (i, cls) => { + str += ` ${cls}`; + }); + str += '"'; + } + str += ' style="'; + BI.each(root.originalStyles, (key, stl) => { + if ( + skipArray.contains(key) || + (key == 'height' && root.classList.contains('bi-design-components-data-data-table-cell')) + ) { + return; + } + key = BI.hyphenate(key); + if (key === 'font-family') { + stl = stl.replace(/\"/g, ''); + } + if (pxStyle.contains(key) && BI.isNumeric(stl)) { + stl += 'px'; + } + if (BI.isKey(stl)) { + str += ` ${key}:${stl};`; + } + }); + str += '"'; + BI.each(root.attribs, (key, attr) => { + if (BI.isKey(attr)) { + str += ` ${key}=${attr}`; + } + }); + if (root.textContent) { + str += ` title=${root.textContent}`; + } + str += '>'; + } + // 特殊处理,spread_table的行列元素是不取配置里的高度的,使用stretch拉伸的(leaves取了高度),但是功能代码里给单元格默认高度了,导致拉伸不了 + // 而spread_grid_table的行列元素是取配置里的高度的,拉不拉伸都一样 + BI.each(root.children, (i, child) => { + str += _renderToHtml(child); + }); + } else { + str += root.originalHtml; + } + if (root.tag !== 'body') { + if (root.textContent) { + str += root.textContent; + } + str += ``; + } + return str; +}; +export const registRenderToHtmlFun = (Element) => { + Element.registerFunction('renderToHtml', function () { + return _renderToHtml(this); + }); +}; diff --git a/src/core/element/plugins/renderToString.js b/src/core/element/plugins/renderToString.js new file mode 100644 index 000000000..f0073f258 --- /dev/null +++ b/src/core/element/plugins/renderToString.js @@ -0,0 +1,50 @@ +var skipArray = ['width', 'height']; +var _renderToString = function (root) { + var str = ''; + if (root.nodeName !== 'body') { + str += `<${root.nodeName}`; + if (root.classList.length > 0) { + str += ' class="'; + BI.each(root.classList, (i, cls) => { + str += ` ${cls}`; + }); + str += '"'; + } + str += ' style="'; + BI.each(root.styles, (key, stl) => { + if (skipArray.includes(key)) { + return; + } + key = BI.hyphenate(key); + str += ` ${key}:${stl};`; + }); + str += ` width:${root.width}px;`; + str += ` height:${root.height}px;`; + str += ' position: fixed;'; + str += ` left: ${root.position.x}px;`; + str += ` top: ${root.position.y}px;`; + str += '"'; + BI.each(root.attribs, (key, attr) => { + str += ` ${key}:${attr}`; + }); + str += '>'; + } + BI.each(root.children, (i, child) => { + str += _renderToString(child); + }); + // if (root.htmlContent) { + // str += root.htmlContent; + // } + if (root.nodeName !== 'body') { + if (root.text) { + str += root.text; + } + str += ``; + } + return str; +}; +export const registRenderToStringFun = (Element) => { + Element.registerFunction('renderToString', function () { + return _renderToString(this); + }); +}; diff --git a/src/core/element/plugins/text.js b/src/core/element/plugins/text.js new file mode 100644 index 000000000..555195251 --- /dev/null +++ b/src/core/element/plugins/text.js @@ -0,0 +1,10 @@ +export const registTextFun = (Element) => { + Element.registerFunction('setText', function (text) { + this.text = text; + return this; + }); + Element.registerFunction('setValue', function (text) { + this.text = text; + return this; + }); +}; diff --git a/src/core/element/plugins/val.js b/src/core/element/plugins/val.js new file mode 100644 index 000000000..f4b868918 --- /dev/null +++ b/src/core/element/plugins/val.js @@ -0,0 +1,9 @@ +export const registValFun = (Element) => { + Element.registerFunction('val', function (value) { + if (BI.isNotNull(value)) { + this.text = `${value}`; + return this; + } + return this.text; + }); +}; diff --git a/src/core/platform/web/config.js b/src/core/platform/web/config.js index eeda344b0..9c4ba840c 100644 --- a/src/core/platform/web/config.js +++ b/src/core/platform/web/config.js @@ -210,12 +210,12 @@ BI.prepares.push(function () { }); BI.Plugin.configWidget("bi.horizontal_sticky", function (ob) { if (!isSupportSticky) { - return BI.extend({}, ob, {type: "bi.horizontal_fill"}); + return BI.extend({ scrollx: true }, ob, {type: "bi.horizontal_fill"}); } }); BI.Plugin.configWidget("bi.vertical_sticky", function (ob) { if (!isSupportSticky) { - return BI.extend({}, ob, {type: "bi.vertical_fill"}); + return BI.extend({ scrolly: true }, ob, {type: "bi.vertical_fill"}); } }); diff --git a/src/core/system.js b/src/core/system.js index 652a9c256..545057e49 100644 --- a/src/core/system.js +++ b/src/core/system.js @@ -19,6 +19,40 @@ TOAST_TOP: 10, H_GAP_SIZE: "M", V_GAP_SIZE: "S" + }, + loadingCreator: function(config) { + var loadingSize = (config ? config.loadingSize : "small") || "small"; + + var isIE = BI.isIE(); + + function getSize(v) { + return Math.ceil(v / (loadingSize === "small" ? 2 : 1)); + } + + return { + type: "bi.horizontal", + cls: "bi-loading-widget" + (isIE ? " wave-loading hack" : ""), + height: getSize(60), + width: getSize(60), + hgap: getSize(10), + vgap: 2.5, + items: isIE ? [] : [{ + type: "bi.layout", + cls: "animate-rect rect1", + height: getSize(50), + width: getSize(5) + }, { + type: "bi.layout", + cls: "animate-rect rect2", + height: getSize(50), + width: getSize(5) + }, { + type: "bi.layout", + cls: "animate-rect rect3", + height: getSize(50), + width: getSize(5) + }] + }; } }; @@ -29,12 +63,11 @@ "L": 24 }; - var provider = function () { - + function provider () { this.SYSTEM = system; this.setSize = function (opt) { - BI.deepExtend(system, {size: opt}); + BI.deepExtend(system, { size: opt }); }; this.setResponsiveMode = function (mode) { @@ -60,6 +93,10 @@ BI.extend(system.dependencies, moduleConfig); }; + this.setLoadingCreator = function(creator) { + system.loadingCreator = creator; + }; + this.$get = function () { return BI.inherit(BI.OB, { @@ -88,13 +125,17 @@ getDependencies: function () { return system.dependencies; + }, + + getLoading: function(config) { + return system.loadingCreator(config); } }); }; - }; + } BI.provider("bi.provider.system", provider); -})(); +}()); BI.prepares.push(function () { BI.SIZE_CONSANTS = BI.Providers.getProvider("bi.provider.system").getSize(); diff --git a/src/less/base/single/button/button.less b/src/less/base/single/button/button.less index 6eae05320..b458ad89a 100644 --- a/src/less/base/single/button/button.less +++ b/src/less/base/single/button/button.less @@ -401,6 +401,10 @@ body .bi-button, #body .bi-button { } .bi-basic-button { + + // 按钮水波纹需要根据按钮根结点定位 + position: relative; + &.button-common, &.button-success, &.button-warning, &.button-error { &:after { content: ""; diff --git a/src/less/base/tree/ztree.less b/src/less/base/tree/ztree.less index bbd057b16..c4a8b6097 100644 --- a/src/less/base/tree/ztree.less +++ b/src/less/base/tree/ztree.less @@ -17,7 +17,7 @@ line-height: 14px; text-align: left; white-space: pre; - outline: 0 + outline: 0; } .ztree li ul { @@ -73,7 +73,7 @@ padding: 0 3px 0 0; margin: 0; cursor: pointer; - height: 23px; + height: 24px; background-color: transparent; text-decoration: none; vertical-align: top; @@ -94,7 +94,7 @@ } .ztree.solid li a { - height: 31px; + height: 32px; } .ztree li a.curSelectedNode { @@ -148,6 +148,21 @@ line-height: 32px; } +.ztree li span.icon { + display: inline-block; + vertical-align: top; + text-align: center; + width: 24px; + height: 24px; + line-height: 24px; +} + +.ztree li span.icon { + width: 32px; + height: 32px; + line-height: 32px; +} + .ztree li span.button { line-height: 0; margin: 0; diff --git a/src/less/base/view/drawer.less b/src/less/base/view/drawer.less index 76ab23d18..940b9b7c9 100644 --- a/src/less/base/view/drawer.less +++ b/src/less/base/view/drawer.less @@ -2,5 +2,5 @@ .bi-drawer { .box-shadows(-6px 0 16px -8px #00000014, -9px 0 28px #0000000d, -12px 0 48px 16px #00000008); - .transitions(transform .3s cubic-bezier(.23, 1, .32, 1), box-shadow .3s cubic-bezier(.23, 1, .32, 1)); + .transitions(inset .3s cubic-bezier(.23, 1, .32, 1), box-shadow .3s cubic-bezier(.23, 1, .32, 1)); } diff --git a/src/less/lib/constant.less b/src/less/lib/constant.less index b3a514511..17421baf0 100644 --- a/src/less/lib/constant.less +++ b/src/less/lib/constant.less @@ -246,4 +246,4 @@ @scroll-thumb-color-theme-dark: @color-black; //box-shadow效果 -@box-shadow-toast: 0 2px 6px 0 rgba(0, 0, 0, 0.2); +@box-shadow-toast: 0 6px 20px -2px rgba(9, 30, 64, 0.16); diff --git a/src/less/resource/app.less b/src/less/resource/app.less index 4f61322d1..e09b2bd15 100644 --- a/src/less/resource/app.less +++ b/src/less/resource/app.less @@ -14,7 +14,6 @@ body { top: 0; left: 0; background-repeat: repeat; - .user-select-disable(); color: @color-bi-text-normal; font: normal 12px "Helvetica Neue", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Heiti, "黑体", sans-serif; -webkit-font-smoothing: antialiased; @@ -29,6 +28,7 @@ body { div, textarea { scrollbar-color: fade(@scroll-color, 10%) fade(@scroll-color, 5%); scrollbar-width: thin; + &::-webkit-scrollbar { -webkit-appearance: none; width: 10px; @@ -37,10 +37,10 @@ div, textarea { &::-webkit-scrollbar-track { .border-radius(5px); - .background-color(@scroll-color, 5%); + .background-color(@scroll-color, 0%); &:hover { - .background-color(@scroll-color, 5%); + .background-color(@scroll-color, 0%); } } @@ -48,10 +48,11 @@ div, textarea { border: 2px solid transparent; background-clip: padding-box; .border-radius(10px); - .background-color(@scroll-color, 10%); + .background-color(@scroll-color, 7%); &:hover { - .background-color(@scroll-color, 30%); + .background-color(@scroll-color, 19%); + border: 1px solid transparent; } } @@ -63,23 +64,24 @@ div, textarea { .bi-theme-dark { div, textarea { scrollbar-color: fade(@scroll-color-theme-dark, 10%) fade(@scroll-color-theme-dark, 5%); + &::-webkit-scrollbar { -webkit-appearance: none; } &::-webkit-scrollbar-track { - .background-color(@scroll-color-theme-dark, 5%); + .background-color(@scroll-color-theme-dark, 0%); &:hover { - .background-color(@scroll-color-theme-dark, 5%); + .background-color(@scroll-color-theme-dark, 0%); } } &::-webkit-scrollbar-thumb { - .background-color(@scroll-color-theme-dark, 10%); + .background-color(@scroll-color-theme-dark, 7%); &:hover { - .background-color(@scroll-color-theme-dark, 30%); + .background-color(@scroll-color-theme-dark, 19%); } } } diff --git a/src/less/typographic.less b/src/less/typographic.less index 27aeba270..7c61d36e5 100644 --- a/src/less/typographic.less +++ b/src/less/typographic.less @@ -1,29 +1,30 @@ -.x-overflow-auto(){ +.x-overflow-auto() { overflow-y: hidden; overflow-x: auto; } -.y-overflow-auto(){ +.y-overflow-auto() { overflow-x: hidden; overflow-y: auto; } -.overflow-auto(){ +.overflow-auto() { overflow: auto; overflow-x: auto; overflow-y: auto; } -.overflow-hidden(){ +.overflow-hidden() { overflow: hidden; overflow-x: hidden; overflow-y: hidden; } -.overflow-dot(){ - text-overflow:ellipsis; + +.overflow-dot() { + text-overflow: ellipsis; overflow-x: hidden; overflow-y: hidden; - white-space:nowrap; + white-space: nowrap; } .user-select(@select) { @@ -35,19 +36,10 @@ user-select: @select; } -.user-select-disable(){ - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - -o-user-select: none; - user-select: none; -} -.user-select-enable(){ - -webkit-user-select: text; - -khtml-user-select: text; - -moz-user-select: text; - -ms-user-select: text; - -o-user-select: text; - user-select: text; -} \ No newline at end of file +.user-select-disable() { + .user-select(none); +} + +.user-select-enable() { + .user-select(text); +} diff --git a/src/less/widget/multitree/multi.tree.combo.less b/src/less/widget/multitree/multi.tree.combo.less index 7e193b7d3..62b29c2a3 100644 --- a/src/less/widget/multitree/multi.tree.combo.less +++ b/src/less/widget/multitree/multi.tree.combo.less @@ -1,9 +1,11 @@ @import "../../index.less"; + @val: transform .3s ease; -.bi-multi-tree-combo{ - & .multi-select-trigger-icon-button{ +.bi-multi-tree-combo, .bi-multi-tree-insert-combo, .bi-multi-tree-list-combo { + & .multi-select-trigger-icon-button { font-size: @font-size-16; } + // 此combo的trigger_button是absolute上去的,与bi-combo在同一层级,独立写一下 & .bi-combo.bi-combo-popup + .bi-trigger-icon-button { & .x-icon { @@ -11,6 +13,7 @@ .transition(@val); } } + & .bi-combo + .bi-trigger-icon-button { & .x-icon { .rotate(0deg); diff --git a/src/widget/dynamicdate/dynamicdate.combo.js b/src/widget/dynamicdate/dynamicdate.combo.js index 75c3739af..7ba55181e 100644 --- a/src/widget/dynamicdate/dynamicdate.combo.js +++ b/src/widget/dynamicdate/dynamicdate.combo.js @@ -120,6 +120,7 @@ BI.DynamicDateCombo = BI.inherit(BI.Single, { }, { eventName: BI.DynamicDateTrigger.EVENT_VALID, action: function () { + self.storeValue = self.trigger.getValue(); self.combo.element.removeClass("error"); self.fireEvent(BI.DynamicDateCombo.EVENT_VALID); } diff --git a/src/widget/multiselect/loader.js b/src/widget/multiselect/loader.js index f6b6bd3de..47f34a729 100644 --- a/src/widget/multiselect/loader.js +++ b/src/widget/multiselect/loader.js @@ -87,6 +87,8 @@ BI.MultiSelectInnerLoader = BI.inherit(BI.Widget, { } }); + var renderEngine = BI.Widget._renderEngine; + BI.Widget.registerRenderEngine(BI.Element.renderEngine); this.cachGroup = BI.createWidget(o.el, { type: "bi.button_group", root: true, @@ -98,6 +100,7 @@ BI.MultiSelectInnerLoader = BI.inherit(BI.Widget, { }], value: o.value }); + BI.Widget.registerRenderEngine(renderEngine); if (o.next !== false) { this.next = BI.createWidget(BI.extend({ @@ -145,7 +148,10 @@ BI.MultiSelectInnerLoader = BI.inherit(BI.Widget, { this.next.setEnd(); } } + var renderEngine = BI.Widget._renderEngine; + BI.Widget.registerRenderEngine(BI.Element.renderEngine); this.cachGroup.addItems.apply(this.cachGroup, arguments); + BI.Widget.registerRenderEngine(renderEngine); this.button_group.addItems.apply(this.button_group, arguments); }, @@ -181,7 +187,10 @@ BI.MultiSelectInnerLoader = BI.inherit(BI.Widget, { if (items.length > 100) { this.cachItems = items.slice(100); } + var renderEngine = BI.Widget._renderEngine; + BI.Widget.registerRenderEngine(BI.Element.renderEngine); this.cachGroup.populate.call(this.cachGroup, items, keyword); + BI.Widget.registerRenderEngine(renderEngine); this.button_group.populate.call(this.button_group, items.slice(0, 100), keyword); } }, @@ -195,9 +204,15 @@ BI.MultiSelectInnerLoader = BI.inherit(BI.Widget, { return this.cachGroup.getNotSelectedValue(); }, - setValue: function () { - this.cachGroup.setValue.apply(this.cachGroup, arguments); - this.button_group.setValue.apply(this.button_group, arguments); + setAllSelected: function (v) { + this.button_group.setAllSelected(v); + this.cachGroup.setAllSelected(v); + }, + + setValue: function (value) { + var map = BI.makeObject(BI.isArray(value) ? value : [value]); + this.cachGroup.setValueMap.call(this.cachGroup, map); + this.button_group.setValueMap.call(this.button_group, map); }, getValue: function () { diff --git a/src/widget/multiselect/multiselect.combo.js b/src/widget/multiselect/multiselect.combo.js index 2e71071b8..23d5385b8 100644 --- a/src/widget/multiselect/multiselect.combo.js +++ b/src/widget/multiselect/multiselect.combo.js @@ -173,7 +173,7 @@ BI.MultiSelectCombo = BI.inherit(BI.Single, { }, value: o.value, hideChecker: function (e) { - return self.numberCounter.element.find(e.target).length === 0; + return triggerBtn.element.find(e.target).length === 0 && self.numberCounter.element.find(e.target).length === 0; }, }); @@ -198,6 +198,21 @@ BI.MultiSelectCombo = BI.inherit(BI.Single, { } }); + var triggerBtn = BI.createWidget({ + type: "bi.trigger_icon_button", + width: o.height, + height: o.height, + cls: "multi-select-trigger-icon-button", + }); + triggerBtn.on(BI.TriggerIconButton.EVENT_CHANGE, function () { + self.numberCounter.hideView(); + if (self.combo.isViewVisible()) { + self.combo.hideView(); + } else { + self.combo.showView(); + } + }); + this.numberCounter = BI.createWidget({ type: "bi.multi_select_check_selected_switcher", masker: { @@ -248,6 +263,11 @@ BI.MultiSelectCombo = BI.inherit(BI.Single, { right: 0, top: 0, bottom: 0, + }, { + el: triggerBtn, + right: 0, + top: 0, + bottom: 0, }, { el: { type: "bi.vertical_adapt", diff --git a/src/widget/multiselect/multiselect.loader.js b/src/widget/multiselect/multiselect.loader.js index d344c09ae..3dc7229bb 100644 --- a/src/widget/multiselect/multiselect.loader.js +++ b/src/widget/multiselect/multiselect.loader.js @@ -17,6 +17,7 @@ BI.MultiSelectLoader = BI.inherit(BI.Widget, { }, valueFormatter: BI.emptyFn, itemsCreator: BI.emptyFn, + itemFormatter: BI.emptyFn, onLoaded: BI.emptyFn, itemHeight: BI.SIZE_CONSANTS.LIST_ITEM_HEIGHT, }); @@ -62,7 +63,8 @@ BI.MultiSelectLoader = BI.inherit(BI.Widget, { text: txt, value: v, title: txt, - selected: self.storeValue.type === BI.Selection.Multi + selected: self.storeValue.type === BI.Selection.Multi, + ...opts.itemFormatter(v), }; }); if (BI.isKey(self._startValue) && !BI.contains(self.storeValue.value, self._startValue)) { @@ -71,7 +73,8 @@ BI.MultiSelectLoader = BI.inherit(BI.Widget, { text: txt, value: startValue, title: txt, - selected: true + selected: true, + ...opts.itemFormatter(startValue), }); } firstItems = self._createItems(json); @@ -107,12 +110,13 @@ BI.MultiSelectLoader = BI.inherit(BI.Widget, { }, _createItems: function (items) { + var allSelected = this.isAllSelected(); return BI.createItems(items, { type: "bi.multi_select_item", logic: this.options.logic, cls: "bi-list-item-active", height: this.options.itemHeight || BI.SIZE_CONSANTS.LIST_ITEM_HEIGHT, - selected: this.isAllSelected(), + selected: allSelected, iconWrapperWidth: 36 }); }, diff --git a/src/widget/multiselect/multiselect.trigger.js b/src/widget/multiselect/multiselect.trigger.js index 4f75b38cb..7140741ff 100644 --- a/src/widget/multiselect/multiselect.trigger.js +++ b/src/widget/multiselect/multiselect.trigger.js @@ -78,22 +78,10 @@ BI.MultiSelectTrigger = BI.inherit(BI.Trigger, { items: [ { el: this.searcher, - width: "fill" - }, { - el: this.wrapNumberCounter, - width: 0 - }, { - el: { - type: "bi.trigger_icon_button", - height: o.height, - stopPropagation: true, - cls: "multi-select-trigger-icon-button", - handler: function () { - self.fireEvent(BI.Controller.EVENT_CHANGE, BI.Events.TOGGLE); - } - }, - width: 24 - }] + width: "fill", + rgap: 24 + } + ] }); !o.allowEdit && BI.createWidget({ @@ -123,7 +111,7 @@ BI.MultiSelectTrigger = BI.inherit(BI.Trigger, { * 重新调整numberCounter的空白占位符 */ refreshPlaceHolderWidth: function (width) { - this.wrapper.attr("items")[1].width = width; + this.wrapper.attr("items")[0].rgap = 24 + width; this.wrapper.resize(); }, diff --git a/src/widget/multiselect/search/multiselect.search.insert.pane.js b/src/widget/multiselect/search/multiselect.search.insert.pane.js index 5af3ec36e..e72b64661 100644 --- a/src/widget/multiselect/search/multiselect.search.insert.pane.js +++ b/src/widget/multiselect/search/multiselect.search.insert.pane.js @@ -39,6 +39,7 @@ BI.MultiSelectSearchInsertPane = BI.inherit(BI.Widget, { type: "bi.multi_select_search_loader", keywordGetter: o.keywordGetter, valueFormatter: o.valueFormatter, + itemFormatter: o.itemFormatter, itemsCreator: function (op, callback) { o.itemsCreator.apply(self, [op, function (res) { callback(res); @@ -95,4 +96,4 @@ BI.MultiSelectSearchInsertPane = BI.inherit(BI.Widget, { BI.MultiSelectSearchInsertPane.EVENT_CHANGE = "EVENT_CHANGE"; -BI.shortcut("bi.multi_select_search_insert_pane", BI.MultiSelectSearchInsertPane); \ No newline at end of file +BI.shortcut("bi.multi_select_search_insert_pane", BI.MultiSelectSearchInsertPane); diff --git a/src/widget/multiselect/search/multiselect.search.loader.js b/src/widget/multiselect/search/multiselect.search.loader.js index dc2cf5d06..07d4cfbea 100644 --- a/src/widget/multiselect/search/multiselect.search.loader.js +++ b/src/widget/multiselect/search/multiselect.search.loader.js @@ -12,6 +12,7 @@ BI.MultiSelectSearchLoader = BI.inherit(BI.Widget, { itemsCreator: BI.emptyFn, keywordGetter: BI.emptyFn, valueFormatter: BI.emptyFn, + itemFormatter: BI.emptyFn, itemHeight: 24 }); }, @@ -90,13 +91,14 @@ BI.MultiSelectSearchLoader = BI.inherit(BI.Widget, { }, _createItems: function (items) { + var allSelected = this.isAllSelected(); return BI.createItems(items, { type: "bi.multi_select_item", logic: { dynamic: false }, height: this.options.itemHeight || BI.SIZE_CONSANTS.LIST_ITEM_HEIGHT, - selected: this.isAllSelected(), + selected: allSelected, cls: "bi-list-item-active", iconWrapperWidth: 36 }); @@ -113,7 +115,7 @@ BI.MultiSelectSearchLoader = BI.inherit(BI.Widget, { var newValues = BI.map(values, function (i, v) { return { text: o.valueFormatter(v) || v, - value: v + value: v, }; }); if (BI.isKey(keyword)) { @@ -125,7 +127,8 @@ BI.MultiSelectSearchLoader = BI.inherit(BI.Widget, { text: v.text, title: v.text, value: v.value, - selected: src.type === BI.Selection.All + selected: src.type === BI.Selection.All, + ...o.itemFormatter(v.value), }; }); }, @@ -162,4 +165,4 @@ BI.MultiSelectSearchLoader = BI.inherit(BI.Widget, { }); BI.MultiSelectSearchLoader.EVENT_CHANGE = "EVENT_CHANGE"; -BI.shortcut("bi.multi_select_search_loader", BI.MultiSelectSearchLoader); \ No newline at end of file +BI.shortcut("bi.multi_select_search_loader", BI.MultiSelectSearchLoader); diff --git a/src/widget/multiselect/search/multiselect.search.pane.js b/src/widget/multiselect/search/multiselect.search.pane.js index c81b40257..98c733348 100644 --- a/src/widget/multiselect/search/multiselect.search.pane.js +++ b/src/widget/multiselect/search/multiselect.search.pane.js @@ -31,6 +31,7 @@ BI.MultiSelectSearchPane = BI.inherit(BI.Widget, { type: "bi.multi_select_search_loader", keywordGetter: o.keywordGetter, valueFormatter: o.valueFormatter, + itemFormatter: o.itemFormatter, itemsCreator: function (op, callback) { o.itemsCreator.apply(self, [op, function (res) { callback(res); @@ -82,4 +83,4 @@ BI.MultiSelectSearchPane = BI.inherit(BI.Widget, { BI.MultiSelectSearchPane.EVENT_CHANGE = "EVENT_CHANGE"; -BI.shortcut("bi.multi_select_search_pane", BI.MultiSelectSearchPane); \ No newline at end of file +BI.shortcut("bi.multi_select_search_pane", BI.MultiSelectSearchPane); diff --git a/src/widget/multiselectlist/multiselectlist.insert.js b/src/widget/multiselectlist/multiselectlist.insert.js index 082669692..277ee20a9 100644 --- a/src/widget/multiselectlist/multiselectlist.insert.js +++ b/src/widget/multiselectlist/multiselectlist.insert.js @@ -28,6 +28,7 @@ BI.MultiSelectInsertList = BI.inherit(BI.Single, { itemsCreator: o.itemsCreator, itemHeight: o.itemHeight, valueFormatter: o.valueFormatter, + itemFormatter: o.itemFormatter, logic: { dynamic: false }, @@ -45,6 +46,7 @@ BI.MultiSelectInsertList = BI.inherit(BI.Single, { type: "bi.multi_select_search_insert_pane", cls: "bi-border-left bi-border-right bi-border-bottom", valueFormatter: o.valueFormatter, + itemFormatter: o.itemFormatter, keywordGetter: function () { return self.trigger.getKeyword(); }, @@ -240,7 +242,7 @@ BI.MultiSelectInsertList = BI.inherit(BI.Single, { digest(); - function digest () { + function digest() { BI.each(keywords, function (i, val) { self.storeValue.type === BI.Selection.Multi ? BI.pushDistinct(self.storeValue.value, val) : BI.remove(self.storeValue.value, val); }); diff --git a/src/widget/multiselectlist/multiselectlist.js b/src/widget/multiselectlist/multiselectlist.js index c0a3505ff..eccc18581 100644 --- a/src/widget/multiselectlist/multiselectlist.js +++ b/src/widget/multiselectlist/multiselectlist.js @@ -215,7 +215,7 @@ BI.MultiSelectList = BI.inherit(BI.Widget, { digest(values); }); - function digest (items) { + function digest(items) { var selectedMap = self._makeMap(items); BI.each(keywords, function (i, val) { if (BI.isNotNull(selectedMap[val])) { @@ -235,7 +235,7 @@ BI.MultiSelectList = BI.inherit(BI.Widget, { text: o.valueFormatter(v) || v, value: v }; - }), this.trigger.getKey()); + }), this.trigger.getKeyword()); var change = false; var map = this._makeMap(this.storeValue.value); BI.each(BI.concat(result.match, result.find), function (i, obj) { @@ -251,7 +251,7 @@ BI.MultiSelectList = BI.inherit(BI.Widget, { } o.itemsCreator({ type: BI.MultiSelectList.REQ_GET_ALL_DATA, - keywords: [this.trigger.getKey()], + keywords: [this.trigger.getKeyword()], selectedValues: BI.filter(this.storeValue.value, function (_i, v) { return !BI.contains(res.value, v); }), @@ -288,7 +288,7 @@ BI.MultiSelectList = BI.inherit(BI.Widget, { callback(); } - function adjust () { + function adjust() { if (self.storeValue.type === BI.Selection.All && self.storeValue.value.length >= self._count) { self.storeValue = { type: BI.Selection.Multi, @@ -365,4 +365,4 @@ BI.extend(BI.MultiSelectList, { }); BI.MultiSelectList.EVENT_CHANGE = "EVENT_CHANGE"; -BI.shortcut("bi.multi_select_list", BI.MultiSelectList); \ No newline at end of file +BI.shortcut("bi.multi_select_list", BI.MultiSelectList); diff --git a/src/widget/multitree/multi.tree.combo.js b/src/widget/multitree/multi.tree.combo.js index ec94374d8..4e42df154 100644 --- a/src/widget/multitree/multi.tree.combo.js +++ b/src/widget/multitree/multi.tree.combo.js @@ -22,7 +22,7 @@ BI.MultiTreeCombo = BI.inherit(BI.Single, { var isInit = false; var want2showCounter = false; - this.storeValue = { value: o.value || {} }; + this.storeValue = {value: o.value || {}}; this.trigger = BI.createWidget({ type: "bi.multi_select_trigger", @@ -44,7 +44,7 @@ BI.MultiTreeCombo = BI.inherit(BI.Single, { type: "bi.multi_tree_searcher", itemsCreator: o.itemsCreator }, - value: { value: o.value || {} } + value: {value: o.value || {}} }); this.combo = BI.createWidget({ @@ -105,9 +105,10 @@ BI.MultiTreeCombo = BI.inherit(BI.Single, { maxWidth: o.isNeedAdjustWidth ? "auto" : 500, }, isNeedAdjustWidth: o.isNeedAdjustWidth, - value: { value: o.value || {} }, + value: {value: o.value || {}}, hideChecker: function (e) { - return self.numberCounter.element.find(e.target).length === 0; + return triggerBtn.element.find(e.target).length === 0 && + self.numberCounter.element.find(e.target).length === 0; } }); @@ -130,12 +131,12 @@ BI.MultiTreeCombo = BI.inherit(BI.Single, { }); this.trigger.on(BI.MultiSelectTrigger.EVENT_START, function () { - self.storeValue = { value: self.combo.getValue() }; + self.storeValue = {value: self.combo.getValue()}; this.setValue(self.storeValue); self.numberCounter.setValue(self.storeValue); }); this.trigger.on(BI.MultiSelectTrigger.EVENT_STOP, function () { - self.storeValue = { value: this.getValue() }; + self.storeValue = {value: this.getValue()}; self.combo.setValue(self.storeValue); self.numberCounter.setValue(self.storeValue); BI.nextTick(function () { @@ -150,11 +151,11 @@ BI.MultiTreeCombo = BI.inherit(BI.Single, { self.fireEvent(BI.MultiTreeCombo.EVENT_SEARCHING); }); - function showCounter() { + function showCounter () { if (isSearching()) { - self.storeValue = { value: self.trigger.getValue() }; + self.storeValue = {value: self.trigger.getValue()}; } else if (isPopupView()) { - self.storeValue = { value: self.combo.getValue() }; + self.storeValue = {value: self.combo.getValue()}; } self.trigger.setValue(self.storeValue); self.numberCounter.setValue(self.storeValue); @@ -173,7 +174,7 @@ BI.MultiTreeCombo = BI.inherit(BI.Single, { var checked = this.getSearcher().hasChecked(); var val = { type: BI.Selection.Multi, - value: checked ? { 1: 1 } : {} + value: checked ? {1: 1} : {} }; this.getSearcher().setState(checked ? BI.Selection.Multi : BI.Selection.None); self.numberCounter.setButtonChecked(val); @@ -185,7 +186,7 @@ BI.MultiTreeCombo = BI.inherit(BI.Single, { return; } if (change === true) { - self.storeValue = { value: self.combo.getValue() }; + self.storeValue = {value: self.combo.getValue()}; change = false; } self.combo.setValue(self.storeValue); @@ -200,9 +201,9 @@ BI.MultiTreeCombo = BI.inherit(BI.Single, { } else { if (isPopupView()) { self._stopEditing(); - self.storeValue = { value: self.combo.getValue() }; + self.storeValue = {value: self.combo.getValue()}; if (clear === true) { - self.storeValue = { value: {} }; + self.storeValue = {value: {}}; } self.fireEvent(BI.MultiTreeCombo.EVENT_CONFIRM); } @@ -211,6 +212,21 @@ BI.MultiTreeCombo = BI.inherit(BI.Single, { change = false; }); + var triggerBtn = BI.createWidget({ + type: "bi.trigger_icon_button", + width: o.height, + height: o.height, + cls: "multi-select-trigger-icon-button" + }); + triggerBtn.on(BI.TriggerIconButton.EVENT_CHANGE, function () { + self.numberCounter.hideView(); + if (self.combo.isViewVisible()) { + self.combo.hideView(); + } else { + self.combo.showView(); + } + }); + this.numberCounter = BI.createWidget({ type: "bi.multi_select_check_selected_switcher", el: { @@ -229,7 +245,7 @@ BI.MultiTreeCombo = BI.inherit(BI.Single, { }, itemsCreator: o.itemsCreator, valueFormatter: o.valueFormatter, - value: { value: o.value || {} } + value: {value: o.value || {}} }); this.numberCounter.on(BI.MultiSelectCheckSelectedSwitcher.EVENT_TRIGGER_CHANGE, function () { if (!self.combo.isViewVisible()) { @@ -273,6 +289,11 @@ BI.MultiTreeCombo = BI.inherit(BI.Single, { right: 0, top: 0, bottom: 0 + }, { + el: triggerBtn, + right: 0, + top: 0, + bottom: 0 }, { el: { type: "bi.vertical_adapt", @@ -285,7 +306,7 @@ BI.MultiTreeCombo = BI.inherit(BI.Single, { }); }, - _stopEditing: function () { + _stopEditing: function() { this.trigger.stopEditing(); this.numberCounter.hideView(); }, diff --git a/src/widget/multitree/multi.tree.insert.combo.js b/src/widget/multitree/multi.tree.insert.combo.js index 9823b37a7..9694b3556 100644 --- a/src/widget/multitree/multi.tree.insert.combo.js +++ b/src/widget/multitree/multi.tree.insert.combo.js @@ -23,7 +23,7 @@ BI.MultiTreeInsertCombo = BI.inherit(BI.Single, { var isInit = false; var want2showCounter = false; - this.storeValue = { value: o.value || {} }; + this.storeValue = {value: o.value || {}}; this.trigger = BI.createWidget({ type: "bi.multi_select_trigger", @@ -59,7 +59,7 @@ BI.MultiTreeInsertCombo = BI.inherit(BI.Single, { }] } }, - value: { value: o.value || {} } + value: {value: o.value || {}} }); @@ -121,9 +121,10 @@ BI.MultiTreeInsertCombo = BI.inherit(BI.Single, { maxWidth: o.isNeedAdjustWidth ? "auto" : 500, }, isNeedAdjustWidth: o.isNeedAdjustWidth, - value: { value: o.value || {} }, + value: {value: o.value || {}}, hideChecker: function (e) { - return self.numberCounter.element.find(e.target).length === 0; + return triggerBtn.element.find(e.target).length === 0 && + self.numberCounter.element.find(e.target).length === 0; } }); @@ -146,12 +147,12 @@ BI.MultiTreeInsertCombo = BI.inherit(BI.Single, { }); this.trigger.on(BI.MultiSelectTrigger.EVENT_START, function () { - self.storeValue = { value: self.combo.getValue() }; + self.storeValue = {value: self.combo.getValue()}; this.setValue(self.storeValue); self.numberCounter.setValue(self.storeValue); }); this.trigger.on(BI.MultiSelectTrigger.EVENT_STOP, function () { - self.storeValue = { value: this.getValue() }; + self.storeValue = {value: this.getValue()}; self.combo.setValue(self.storeValue); self.numberCounter.setValue(self.storeValue); BI.nextTick(function () { @@ -166,11 +167,11 @@ BI.MultiTreeInsertCombo = BI.inherit(BI.Single, { self.fireEvent(BI.MultiTreeInsertCombo.EVENT_SEARCHING); }); - function showCounter() { + function showCounter () { if (isSearching()) { - self.storeValue = { value: self.trigger.getValue() }; + self.storeValue = {value: self.trigger.getValue()}; } else if (isPopupView()) { - self.storeValue = { value: self.combo.getValue() }; + self.storeValue = {value: self.combo.getValue()}; } self.trigger.setValue(self.storeValue); self.numberCounter.setValue(self.storeValue); @@ -184,7 +185,7 @@ BI.MultiTreeInsertCombo = BI.inherit(BI.Single, { var checked = this.getSearcher().hasChecked(); var val = { type: BI.Selection.Multi, - value: checked ? { 1: 1 } : {} + value: checked ? {1: 1} : {} }; this.getSearcher().setState(checked ? BI.Selection.Multi : BI.Selection.None); self.numberCounter.setButtonChecked(val); @@ -196,7 +197,7 @@ BI.MultiTreeInsertCombo = BI.inherit(BI.Single, { return; } if (change === true) { - self.storeValue = { value: self.combo.getValue() }; + self.storeValue = {value: self.combo.getValue()}; change = false; } self.combo.setValue(self.storeValue); @@ -211,9 +212,9 @@ BI.MultiTreeInsertCombo = BI.inherit(BI.Single, { } else { if (isPopupView()) { self._stopEditing(); - self.storeValue = { value: self.combo.getValue() }; + self.storeValue = {value: self.combo.getValue()}; if (clear === true) { - self.storeValue = { value: {} }; + self.storeValue = {value: {}}; } self.fireEvent(BI.MultiTreeInsertCombo.EVENT_CONFIRM); } @@ -222,6 +223,21 @@ BI.MultiTreeInsertCombo = BI.inherit(BI.Single, { change = false; }); + var triggerBtn = BI.createWidget({ + type: "bi.trigger_icon_button", + width: o.height, + height: o.height, + cls: "multi-select-trigger-icon-button" + }); + triggerBtn.on(BI.TriggerIconButton.EVENT_CHANGE, function () { + self.numberCounter.hideView(); + if (self.combo.isViewVisible()) { + self.combo.hideView(); + } else { + self.combo.showView(); + } + }); + this.numberCounter = BI.createWidget({ type: "bi.multi_select_check_selected_switcher", el: { @@ -284,6 +300,11 @@ BI.MultiTreeInsertCombo = BI.inherit(BI.Single, { right: 0, top: 0, bottom: 0 + }, { + el: triggerBtn, + right: 0, + top: 0, + bottom: 0 }, { el: { type: "bi.vertical_adapt", @@ -301,7 +322,7 @@ BI.MultiTreeInsertCombo = BI.inherit(BI.Single, { this.numberCounter.setButtonChecked(this.storeValue); }, - _stopEditing: function () { + _stopEditing: function() { this.trigger.stopEditing(); this.numberCounter.hideView(); }, diff --git a/src/widget/multitree/multi.tree.list.combo.js b/src/widget/multitree/multi.tree.list.combo.js index 2a378b1ae..ff5a2864a 100644 --- a/src/widget/multitree/multi.tree.list.combo.js +++ b/src/widget/multitree/multi.tree.list.combo.js @@ -23,7 +23,7 @@ BI.MultiTreeListCombo = BI.inherit(BI.Single, { var isInit = false; var want2showCounter = false; - this.storeValue = { value: o.value || [] }; + this.storeValue = {value: o.value || []}; this.trigger = BI.createWidget({ type: "bi.multi_select_trigger", @@ -74,7 +74,7 @@ BI.MultiTreeListCombo = BI.inherit(BI.Single, { itemsCreator: o.itemsCreator } }, - value: { value: o.value || {} } + value: {value: o.value || {}} }); @@ -139,9 +139,10 @@ BI.MultiTreeListCombo = BI.inherit(BI.Single, { maxWidth: o.isNeedAdjustWidth ? "auto" : 500, }, isNeedAdjustWidth: o.isNeedAdjustWidth, - value: { value: o.value || {} }, + value: {value: o.value || {}}, hideChecker: function (e) { - return self.numberCounter.element.find(e.target).length === 0; + return triggerBtn.element.find(e.target).length === 0 && + self.numberCounter.element.find(e.target).length === 0; } }); @@ -164,12 +165,12 @@ BI.MultiTreeListCombo = BI.inherit(BI.Single, { }); this.trigger.on(BI.MultiSelectTrigger.EVENT_START, function () { - self.storeValue = { value: self.combo.getValue() }; + self.storeValue = {value: self.combo.getValue()}; this.setValue(self.storeValue); self.numberCounter.setValue(self.storeValue); }); this.trigger.on(BI.MultiSelectTrigger.EVENT_STOP, function () { - self.storeValue = { value: this.getValue() }; + self.storeValue = {value: this.getValue()}; self.combo.setValue(self.storeValue); self.numberCounter.setValue(self.storeValue); BI.nextTick(function () { @@ -184,11 +185,11 @@ BI.MultiTreeListCombo = BI.inherit(BI.Single, { self.fireEvent(BI.MultiTreeListCombo.EVENT_SEARCHING); }); - function showCounter() { + function showCounter () { if (isSearching()) { - self.storeValue = { value: self.trigger.getValue() }; + self.storeValue = {value: self.trigger.getValue()}; } else if (isPopupView()) { - self.storeValue = { value: self.combo.getValue() }; + self.storeValue = {value: self.combo.getValue()}; } self.trigger.setValue(self.storeValue); self.numberCounter.setValue(self.storeValue); @@ -202,7 +203,7 @@ BI.MultiTreeListCombo = BI.inherit(BI.Single, { var checked = this.getSearcher().hasChecked(); var val = { type: BI.Selection.Multi, - value: checked ? { 1: 1 } : {} + value: checked ? {1: 1} : {} }; this.getSearcher().setState(checked ? BI.Selection.Multi : BI.Selection.None); self.numberCounter.setButtonChecked(val); @@ -214,7 +215,7 @@ BI.MultiTreeListCombo = BI.inherit(BI.Single, { return; } if (change === true) { - self.storeValue = { value: self.combo.getValue() }; + self.storeValue = {value: self.combo.getValue()}; change = false; } self.combo.setValue(self.storeValue); @@ -229,9 +230,9 @@ BI.MultiTreeListCombo = BI.inherit(BI.Single, { } else { if (isPopupView()) { self._stopEditing(); - self.storeValue = { value: self.combo.getValue() }; + self.storeValue = {value: self.combo.getValue()}; if (clear === true) { - self.storeValue = { value: [] }; + self.storeValue = {value: []}; } self.fireEvent(BI.MultiTreeListCombo.EVENT_CONFIRM); } @@ -240,6 +241,21 @@ BI.MultiTreeListCombo = BI.inherit(BI.Single, { change = false; }); + var triggerBtn = BI.createWidget({ + type: "bi.trigger_icon_button", + width: o.height, + height: o.height, + cls: "multi-select-trigger-icon-button" + }); + triggerBtn.on(BI.TriggerIconButton.EVENT_CHANGE, function () { + self.numberCounter.hideView(); + if (self.combo.isViewVisible()) { + self.combo.hideView(); + } else { + self.combo.showView(); + } + }); + this.numberCounter = BI.createWidget({ type: "bi.multi_select_check_selected_switcher", el: { @@ -302,6 +318,11 @@ BI.MultiTreeListCombo = BI.inherit(BI.Single, { right: 0, top: 0, bottom: 0 + }, { + el: triggerBtn, + right: 0, + top: 0, + bottom: 0 }, { el: { type: "bi.vertical_adapt", @@ -319,7 +340,7 @@ BI.MultiTreeListCombo = BI.inherit(BI.Single, { this.numberCounter.setButtonChecked(this.storeValue); }, - _stopEditing: function () { + _stopEditing: function() { this.trigger.stopEditing(); this.numberCounter.hideView(); }, diff --git a/tsconfig.json b/tsconfig.json index 8a4138e71..5630b275a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -28,5 +28,7 @@ "types/*.d.ts", "src/*.js", "src/**/*.js", + "examples/*.js", + "examples/**/*.js", ] } \ No newline at end of file diff --git a/types/globals.d.ts b/types/globals.d.ts index f35336db4..0b5805aa0 100644 --- a/types/globals.d.ts +++ b/types/globals.d.ts @@ -11,3 +11,5 @@ declare const Fix: Obj; declare interface String { replaceAll(regx: string, callback: (str: string) => void): string; } + +declare const _global: typeof window; diff --git a/typescript/base/combination/group.button.ts b/typescript/base/combination/group.button.ts index eca1a224b..020ef0275 100644 --- a/typescript/base/combination/group.button.ts +++ b/typescript/base/combination/group.button.ts @@ -40,4 +40,6 @@ export declare class ButtonGroup extends Widget { getNodeByValue(value: any): any; getValue(): T[]; + + scrollToValue(value: any, scrollIntoViewOptions?: boolean | Obj): void; } diff --git a/typescript/base/combination/group.virtual.ts b/typescript/base/combination/group.virtual.ts index c3004e75f..d8e8c1b24 100644 --- a/typescript/base/combination/group.virtual.ts +++ b/typescript/base/combination/group.virtual.ts @@ -3,7 +3,7 @@ import { Widget } from '../../core/widget'; export declare class VirtualGroup extends Widget { static xtype: string; static EVENT_CHANGE: string; - + addItems(items: T[]): void; prependItems(items: T[]): void; @@ -13,4 +13,6 @@ export declare class VirtualGroup extends Widget { getValue(): T[]; populate(items?: any, ...args: any[]): void -} \ No newline at end of file + + scrollToValue(value: any, scrollIntoViewOptions?: boolean | Obj): void; +} diff --git a/typescript/base/list/listview.ts b/typescript/base/list/listview.ts index 459623383..b0007d5a1 100644 --- a/typescript/base/list/listview.ts +++ b/typescript/base/list/listview.ts @@ -1,4 +1,4 @@ -import { Widget } from '../../core/widget'; +import { Widget } from "../../core/widget"; export declare class ListView extends Widget { static xtype: string; @@ -6,4 +6,6 @@ export declare class ListView extends Widget { restore(): void; populate(items: T[]): void; + + scrollTo(scrollTop: number): void; } diff --git a/typescript/base/list/virtualgrouplist.ts b/typescript/base/list/virtualgrouplist.ts new file mode 100644 index 000000000..51b27423d --- /dev/null +++ b/typescript/base/list/virtualgrouplist.ts @@ -0,0 +1,11 @@ +import { Widget } from "../../core/widget"; + +export declare class VirtualGroupList extends Widget { + static xtype: string; + + restore(): void; + + populate(items: T[]): void; + + scrollTo(scrollTop: number): void; +} diff --git a/typescript/base/list/virtuallist.ts b/typescript/base/list/virtuallist.ts new file mode 100644 index 000000000..f23c00487 --- /dev/null +++ b/typescript/base/list/virtuallist.ts @@ -0,0 +1,11 @@ +import { Widget } from "../../core/widget"; + +export declare class VirtualList extends Widget { + static xtype: string; + + restore(): void; + + populate(items: T[]): void; + + scrollTo(scrollTop: number): void; +} diff --git a/typescript/core/base.ts b/typescript/core/base.ts index a30828489..b5a3ce55c 100644 --- a/typescript/core/base.ts +++ b/typescript/core/base.ts @@ -1,7 +1,7 @@ import { _Widget } from "./widget"; export interface _base { - assert: (v: any, is: Function) => Boolean + assert: (v: any, is: Function) => Boolean; warn: (message: any) => Boolean; @@ -15,62 +15,62 @@ export interface _base { packageItems: (items: any[], layouts: any[]) => any[]; - formatEL: (obj: T) => {el: T} | T + formatEL: (obj: T) => { el: T } | T; - stripEL: (obj: {el: T} | T) => T; + stripEL: (obj: { el: T } | T) => T; trans2Element: (widgets: any[]) => any[]; // 集合相关方法 - where: (collection: any[]|object|string, source: object) => any[]; + where: (collection: any[] | object | string, source: object) => any[]; - findWhere: (collection: any[]|object|string, callback?: Function|object|string, thisArg?: any) => object|undefined; + findWhere: (collection: any[] | object | string, callback?: Function | object | string, thisArg?: any) => object | undefined; - invoke: (collection: any[]|object|string, methodName: Function|string, arg?: any) => any[]; + invoke: (collection: any[] | object | string, methodName: Function | string, arg?: any) => any[]; - pluck: (collection: any[]|object|string, property: string) => any[]; + pluck: (collection: any[] | object | string, property: string) => any[]; - shuffle: (collection: any[]|object|string) => any[]; + shuffle: (collection: any[] | object | string) => any[]; - sample: (collection: any[]|object|string, n?: number) => any[]; + sample: (collection: any[] | object | string, n?: number) => any[]; - toArray: (collection: any[]|object|string) => any[]; + toArray: (collection: any[] | object | string) => any[]; size: (collection: any) => number; - each: (collection: T[]|object|string, callback?: ((index: number, value: T) => void)|object|string, thisArg?: any) => any; + each: (collection: T[] | object | string, callback?: ((index: number, value: T) => void) | object | string, thisArg?: any) => any; - map: (collection: T[]|object|string|null|undefined, callback?: ((index: number, value: T) => U)|object|string, thisArg?: any) => U[]; + map: (collection: T[] | object | string | null | undefined, callback?: ((index: number, value: T) => U) | object | string, thisArg?: any) => U[]; - reduce: (collection: T[]|object|string, callback?: ((total: U extends T ? U : T, currentValue: T, currentIndex: number) => U extends T ? U : T)|object|string, initialValue?: U|T) => U extends T ? U : T; + reduce: (collection: T[] | object | string, callback?: ((total: U extends T ? U : T, currentValue: T, currentIndex: number) => U extends T ? U : T) | object | string, initialValue?: U | T) => U extends T ? U : T; - reduceRight: (collection: T[]|object|string, callback?: ((total: U extends T ? U : T, currentValue: T, currentIndex: number) => U extends T ? U : T)|object|string, initialValue?: U|T) => U extends T ? U : T; + reduceRight: (collection: T[] | object | string, callback?: ((total: U extends T ? U : T, currentValue: T, currentIndex: number) => U extends T ? U : T) | object | string, initialValue?: U | T) => U extends T ? U : T; - find: (collection: T[]|object|string, callback?: ((index: number, value: T) => boolean)|object|string, thisArg?: any) => T | undefined; + find: (collection: T[] | object | string, callback?: ((index: number, value: T) => boolean) | object | string, thisArg?: any) => T | undefined; - filter: (collection: T[]|object|string, callback?: ((index: number, value: T) => boolean)|object|string, thisArg?: any) => T[]; + filter: (collection: T[] | object | string, callback?: ((index: number, value: T) => boolean) | object | string, thisArg?: any) => T[]; - reject: (collection: T[]|object|string, callback?: ((index: number, value: T) => boolean)|object|string, thisArg?: any) => T[]; + reject: (collection: T[] | object | string, callback?: ((index: number, value: T) => boolean) | object | string, thisArg?: any) => T[]; - every: (collection: T[]|object|string, callback?: ((index: number, value: T) => boolean)|object|string, thisArg?: any) => boolean; + every: (collection: T[] | object | string, callback?: ((index: number, value: T) => boolean) | object | string, thisArg?: any) => boolean; - all: (collection: T[]|object|string, callback?: ((index: number, value: T) => boolean)|object|string, thisArg?: any) => boolean; + all: (collection: T[] | object | string, callback?: ((index: number, value: T) => boolean) | object | string, thisArg?: any) => boolean; - some: (collection: T[]|object|string, callback?: ((index: number, value: T) => boolean)|object|string, thisArg?: any) => boolean; + some: (collection: T[] | object | string, callback?: ((index: number, value: T) => boolean) | object | string, thisArg?: any) => boolean; - any: (collection: T[]|object|string, callback?: ((index: number, value: T) => boolean)|object|string, thisArg?: any) => boolean; + any: (collection: T[] | object | string, callback?: ((index: number, value: T) => boolean) | object | string, thisArg?: any) => boolean; max: (collection: T[]) => T; min: (collection: T[]) => T; - sortBy: (collection: any[]|object|string, callback?: ((index: number, value: T) => number)|object|string, thisArg?: any) => any[]; + sortBy: (collection: any[] | object | string, callback?: ((index: number, value: T) => number) | object | string, thisArg?: any) => any[]; - groupBy: (collection: any[]|object|string, callback?: ((index: number, value: T) => any)|object|string, thisArg?: any) => object; + groupBy: (collection: any[] | object | string, callback?: ((index: number, value: T) => any) | object | string, thisArg?: any) => object; - indexBy: (collection: any[]|object|string, callback?: ((index: number, value: T) => any)|object|string, thisArg?: any) => object; + indexBy: (collection: any[] | object | string, callback?: ((index: number, value: T) => any) | object | string, thisArg?: any) => object; - countBy: (collection: any[]|object|string, callback?: ((index: number, value: T) => any)|object|string, thisArg?: any) => object; + countBy: (collection: any[] | object | string, callback?: ((index: number, value: T) => any) | object | string, thisArg?: any) => object; count: (from: number, to: number, predicate: Function) => number; @@ -99,7 +99,7 @@ export interface _base { remove: (obj: any, predicate: any, context?: any) => void; - removeAt: (obj: any, index: number|number[]) => void; + removeAt: (obj: any, index: number | number[]) => void; string2Array: (str: string) => string[]; @@ -110,17 +110,17 @@ export interface _base { int2Abc: (num: number) => string; // 数组相关的方法 - first: (array: T[], callback?: Function|object|number|string, thisArg?: any) => T; + first: (array: T[], callback?: Function | object | number | string, thisArg?: any) => T; - initial: (array: T[], callback?: Function|object|number|string, thisArg?: any) => T[]; + initial: (array: T[], callback?: Function | object | number | string, thisArg?: any) => T[]; - last: (array: T[], callback?: Function|object|number|string, thisArg?: any) => T; + last: (array: T[], callback?: Function | object | number | string, thisArg?: any) => T; - rest: (array: T[], callback?: Function|object|number|string, thisArg?: any) => T[]; + rest: (array: T[], callback?: Function | object | number | string, thisArg?: any) => T[]; compact: (array: any[]) => any[]; - flatten: (array: any[], isShallow?: boolean, callback?: Function|object|string, thisArg?: any) => any[]; + flatten: (array: any[], isShallow?: boolean, callback?: Function | object | string, thisArg?: any) => any[]; without: (array: any[], value?: any) => any[]; @@ -140,7 +140,7 @@ export interface _base { lastIndexOf: (array: any[], value: any, fromIndex?: number) => number; - sortedIndex: (array: any[], value: any, callback?: Function|object|string, thisArg?: any) => number; + sortedIndex: (array: any[], value: any, callback?: Function | object | string, thisArg?: any) => number; range: (start: number, end: number, step: number) => number[]; @@ -148,9 +148,9 @@ export interface _base { takeRight: (array: T[], n: number) => T[]; - findIndex: (array: any[], value: any, callback?: Function|object|string, thisArg?: any) => number; + findIndex: (array: any[], value: any, callback?: Function | object | string, thisArg?: any) => number; - findLastIndex: (array: any[], value: any, callback?: Function|object|string, thisArg?: any) => number; + findLastIndex: (array: any[], value: any, callback?: Function | object | string, thisArg?: any) => number; makeArray: (length: number, value?: T) => number[] | T[]; @@ -181,7 +181,7 @@ export interface _base { clone: (object: T) => T; - property: (path: any[]|string) => Function; + property: (path: any[] | string) => Function; propertyOf: (object: object) => Function; @@ -189,7 +189,7 @@ export interface _base { isMatch: (object: object, source: object, customizer?: Function, thisArg?: any) => boolean; - isEmpty: (value: any[]|object|string|null|undefined|number) => boolean; + isEmpty: (value: any[] | object | string | null | undefined | number) => boolean; isElement: (value: any) => boolean; @@ -225,11 +225,11 @@ export interface _base { cloneDeep: (value: T) => T; - findKey: (object: object, predicate?: Function|object|string, thisArg?: any) => any; + findKey: (object: object, predicate?: Function | object | string, thisArg?: any) => any; - pick: (object: object, predicate?: Function|string|string[], thisArg?: any) => object; + pick: (object: object, predicate?: Function | string | string[], thisArg?: any) => object; - omit: (object: object, predicate?: Function|string|string[], thisArg?: any) => object; + omit: (object: object, predicate?: Function | string | string[], thisArg?: any) => object; tap: (value: any, interceptor: Function, thisArg?: any) => any; @@ -237,21 +237,21 @@ export interface _base { init: () => void; - has: (obj: object, keys: string|string[]) => boolean; + has: (obj: object, keys: string | string[]) => boolean; freeze: (value: T) => T; isKey: (key: any) => key is (number | string); - isCapitalEqual: (a: string|null|undefined, b: string|null|undefined) => boolean; + isCapitalEqual: (a: string | null | undefined, b: string | null | undefined) => boolean; - isWidthOrHeight: (w: number|string) => boolean; + isWidthOrHeight: (w: number | string) => boolean; isNotNull: (obj: T) => obj is NonNullable; isNull: (obj: any) => obj is (undefined | null); - isEmptyArray: (arr: T[] | U) => arr is T[] & {length: 0}; + isEmptyArray: (arr: T[] | U) => arr is T[] & { length: 0 }; isNotEmptyArray: (arr: T[] | U) => arr is [T, ...T[]]; @@ -263,7 +263,7 @@ export interface _base { deepClone: (obj: T) => T; - deepExtend: merge['deepExtend']; + deepExtend: merge["deepExtend"]; isDeepMatch: (object: any, attrs: any) => boolean; @@ -307,7 +307,7 @@ export interface _base { wrap: (value: any, wrapper: Function) => Function; - nextTick: (func: Function) => Promise; + nextTick: (func?: Function) => Promise; random: (min?: number, max?: number, floating?: boolean) => number; @@ -317,21 +317,21 @@ export interface _base { parseFloat: (string: string) => number; - isNaturalNumber: (value: string|number) => boolean; + isNaturalNumber: (value: string | number) => boolean; - isPositiveInteger: (value: string|number) => boolean; + isPositiveInteger: (value: string | number) => boolean; - isNegativeInteger: (value: string|number) => boolean; + isNegativeInteger: (value: string | number) => boolean; - isInteger: (value: string|number) => boolean; + isInteger: (value: string | number) => boolean; - isNumeric: (value: string|number) => boolean; + isNumeric: (value: string | number) => boolean; - isFloat: (value: string|number) => boolean; + isFloat: (value: string | number) => boolean; - isOdd: (value: string|number) => boolean; + isOdd: (value: string | number) => boolean; - isEven: (value: string|number) => boolean; + isEven: (value: string | number) => boolean; sum: (array: any[], iteratee?: Function, context?: any) => number; @@ -367,7 +367,7 @@ export interface _base { isLeapYear: (year: number) => boolean; - checkDateVoid: (YY: string | number, MM: string | number, DD: string | number, minDate: string, maxDate: string) => (number|string)[]; + checkDateVoid: (YY: string | number, MM: string | number, DD: string | number, minDate: string, maxDate: string) => (number | string)[]; checkDateLegal: (str: string) => boolean; @@ -376,6 +376,12 @@ export interface _base { getDate: (...args: (number | string)[]) => Date; getTime: (...args: any[]) => number; + + /** + * 判断一个对象是不是promise + * @param obj 对象 + */ + isPromise: (obj: any) => obj is Promise; } type merge = { diff --git a/typescript/core/widget.ts b/typescript/core/widget.ts index 0a1b63246..4c1fc9d2f 100644 --- a/typescript/core/widget.ts +++ b/typescript/core/widget.ts @@ -42,8 +42,8 @@ export interface _Widget extends _OB { element: { width(): number; height(): number; - width(width: number | string): _Widget['element']; - height(height: number | string): _Widget['element']; + width(width: number | string): _Widget["element"]; + height(height: number | string): _Widget["element"]; [key: string]: any; }; @@ -110,7 +110,7 @@ export interface _Widget extends _OB { /** * 更新前 */ - shouldUpdate?(): void; + shouldUpdate?(...args: any[]): void; /** * 更新 @@ -449,8 +449,8 @@ export declare class Widget extends OB { element: { width(): number; height(): number; - width(width: number | string): Widget['element']; - height(height: number | string): Widget['element']; + width(width: number | string): Widget["element"]; + height(height: number | string): Widget["element"]; [key: string]: any; }; @@ -783,6 +783,11 @@ export declare class Widget extends OB { */ _unMount(): void; + /** + * watch响应式数据 + */ + __watch any>(getter: T, handler: Function, options?: Obj): ReturnType + /** * hang元素 */ diff --git a/typescript/core/worker/action/worker.action.ts b/typescript/core/worker/action/worker.action.ts new file mode 100644 index 000000000..8f6920003 --- /dev/null +++ b/typescript/core/worker/action/worker.action.ts @@ -0,0 +1,28 @@ +import type { WorkerBaseController } from "../controller/worker.controller"; + +/** + * 事务的基类 + */ +export class WorkerBaseAction { + /** + * 通信控制器 + */ + protected controller: WorkerBaseController; + + /** + * 线程上的 action 集合, 用于调用其他命名空间下的事务 + */ + protected threadAction: any; + + public constructor(controller: WorkerBaseController, threadAction: any) { + this.controller = controller; + this.threadAction = threadAction; + + this.addActionHandler(); + } + + /** + * 添加事务的处理器 + */ + protected addActionHandler() {} +} diff --git a/typescript/core/worker/controller/worker.controller.ts b/typescript/core/worker/controller/worker.controller.ts new file mode 100644 index 000000000..123bba98a --- /dev/null +++ b/typescript/core/worker/controller/worker.controller.ts @@ -0,0 +1,131 @@ +import type { IWorkerController, IWorkerMessage } from '../worker.core'; +import { WorkerChannel } from '../worker.channel'; + +/** + * 通信控制器 + * + * @class WorkerBaseController + */ +export class WorkerBaseController implements IWorkerController { + /** + * 原生 worker, 在子类中实例化 + */ + protected worker: Worker; + + /** + * 通信 Channel, 在子类中实例化 + */ + protected channel: WorkerChannel; + + /** + * 事务处理器 Map + */ + protected actionHandlerMap: { + [propsName: string]: (payload: any) => any; + }; + + public constructor() { + this.actionHandlerMap = {}; + } + + /** + * 发送事务,不等待结果 + * + * @param actionType 事务类型 + * @param payload 负载 + */ + public request(actionType: string, payload: any): void { + if (this.channel) { + return this.channel.request(actionType, payload); + } + + console.error('No channel.'); + + return; + } + + /** + * 发送 Promise 形式的事务, 在 then 中获取响应 + * + * @param actionType 事务类型 + * @param payload 负载 + * @param [timeout] 响应的超时; Worker 通道是可靠的, 超时后只上报, 不阻止当前请求 + */ + public requestPromise(actionType: string, payload: any = '', timeout?: number): Promise { + // 有 Channel 实例才能进行通信, 此时还没有实例化是浏览器不支持创建 worker + if (this.channel) { + return this.channel.requestPromise(actionType, payload, timeout); + } + + // 兼容上层调用的 .then().catch() + return Promise.reject(new Error('No channel.')); + } + + /** + * 添加事务处理器, 不允许重复添加 + * + * @param actionType 事务类型 + * @param handler 事务处理器 + */ + public addActionHandler(actionType: string, handler: (payload: any) => any): void { + if (this.hasActionHandler(actionType)) { + throw new Error(`Add action \`${actionType}\` handler repeat`); + } + this.actionHandlerMap[actionType] = handler; + } + + /** + * 事务处理器, 提供给通信 Channel 调用 + * + * @param message 会话消息 + * @returns + */ + public actionHandler(message: IWorkerMessage): Promise { + const { actionType, payload } = message; + + if (this.hasActionHandler(actionType)) { + // 执行指定的事务处理器, 并返回 Promise 封装的事务结果 + try { + const actionResult = this.actionHandlerMap[actionType](payload); + + // 对于 Promise 形式的结果, 需要进行 Promise 错误捕获 + if (BI.isPromise(actionResult)) { + return actionResult.catch(error => Promise.reject(error)); + } + + // 对数据结果, 包装为 Promise + return Promise.resolve(actionResult); + } catch (error) { + // 继续抛出给外层 + return Promise.reject(error); + } + } else { + throw new Error(`Not Found Session Handler \`${actionType}\`.`); + } + } + + /** + * 添加 worker onmessage 事件的回调 + * + * @param {(event: any) => void} onmessage 回调函数 + * @returns {() => void} 移除监听函数 + */ + public addOnmessageListener(onmessage: (event: any) => void): () => void { + this.worker.addEventListener('message', onmessage); + + // 返回移除监听函数 + return () => { + this.worker.removeEventListener('message', onmessage); + }; + } + + /** + * 判断是否有指定事务的处理器 + * + * @param actionType 事务类型 + * @returns {boolean} + */ + protected hasActionHandler(actionType: string): boolean { + return !!this.actionHandlerMap[actionType]; + } +} diff --git a/typescript/core/worker/controller/worker.main_thread.controller.ts b/typescript/core/worker/controller/worker.main_thread.controller.ts new file mode 100644 index 000000000..bd7dcfc96 --- /dev/null +++ b/typescript/core/worker/controller/worker.main_thread.controller.ts @@ -0,0 +1,85 @@ +import { WorkerChannel } from "../worker.channel"; +import type { IWorkerOptions } from "../worker.core"; +import { WorkerBaseController } from "./worker.controller"; + +export class WorkerMainThreadController extends WorkerBaseController { + /** + * 浏览器是否实现了 HTML 规范的 Worker Class + */ + public static hasWorkerClass = !!_global.Worker; + + /** + * 是否支持 new Worker, 默认为 Wroker Class 是否实现 + */ + + public canNewWorker: boolean = WorkerMainThreadController.hasWorkerClass; + + /** + * 主线程 new Worker 起始时刻 + */ + public timeBeforeNewWorker: number; + + /** + * 主线程 new Worker 完毕时刻 + */ + public timeAfterNewWorker: number; + + public constructor(options: IWorkerOptions) { + super(); + + if (!this.canNewWorker) { + // 都没有 Worker Class, 没法继续了 + return; + } + + this.newWorker(options); + } + + /** + * 销毁 Worker 线程实例 + */ + public terminate(): void { + this.worker?.terminate(); + } + + protected reportActionHandlerError(actionType: string, error: any): void { + console.error(`Worker aciton ${actionType}:`, error); + + // 主线程的报错, 在 window.onerror 中可以拿到报错堆栈, 直接抛出即可 + throw new Error(error); + } + + /** + * 创建 Worker 线程实例 + */ + private newWorker(options: IWorkerOptions) { + this.timeBeforeNewWorker = Date.now(); + + try { + // 主线程通过 new Worker() 获取 Worker 实例 + this.worker = new Worker(options.workerUrl, { + name: options.workerName, + }); + + /** + * 监控和上报 worker 中的报错 + * window.onerror 中也能监控到 worker.onerror( Worker 运行报错) + */ + this.worker.onerror = (error): void => { + console.error('Worker onerror:', error); + }; + + this.timeAfterNewWorker = Date.now(); + + // 实例化 Channel + this.channel = new WorkerChannel(this.worker, { + actionHandler: this.actionHandler.bind(this), + }); + } catch (error) { + console.error('Init worker fail:', error); + + // 创建 worker 失败, 标识改为不支持 + this.canNewWorker = false; + } + } +} diff --git a/typescript/core/worker/controller/worker.worker_thread.controller.ts b/typescript/core/worker/controller/worker.worker_thread.controller.ts new file mode 100644 index 000000000..2f4bac6cf --- /dev/null +++ b/typescript/core/worker/controller/worker.worker_thread.controller.ts @@ -0,0 +1,21 @@ +import { WorkerChannel } from "../worker.channel"; +import { WorkerBaseController } from "./worker.controller"; + +export class WorkerThreadController extends WorkerBaseController { + public constructor() { + super(); + + // Worker 线程中的全局环境 self 就是 Worker 实例 + this.worker = self as any; + this.channel = new WorkerChannel(this.worker, { + actionHandler: this.actionHandler.bind(this), + }); + } + + protected reportActionHandlerError(actionType: string, error: any): void { + console.error(`Worker aciton ${actionType}:`, error); + + // 正常抛出 + throw new Error(error); + } +} diff --git a/typescript/core/worker/worker.channel.ts b/typescript/core/worker/worker.channel.ts new file mode 100644 index 000000000..a39124a14 --- /dev/null +++ b/typescript/core/worker/worker.channel.ts @@ -0,0 +1,200 @@ +import { IWorkerController, WorkerMessageType, IWorkerMessage } from './worker.core'; + +const COMMUNICATION_TIMEOUT = 30000; + +/** + * 通信通道 + */ +export class WorkerChannel { + /** + * Web Worker 实例 + */ + private worker: Worker; + + /** + * 上层通信控制器 + */ + private controller: IWorkerController; + + /** + * 会话响应器 Map + */ + private sessionHandlerMap: { + [propsName: string]: Function; + }; + + public constructor(worker: Worker, controller: IWorkerController) { + this.worker = worker; + this.controller = controller; + + this.sessionHandlerMap = {}; + + // 绑定 worker onmessage 事件的回调 + this.worker.addEventListener('message', this.onmessage.bind(this)); + } + + /** + * 发送响应 + * + * @param sessionId 会话 Id + * @param payload 负载 + */ + public response(sessionId: string, actionType: string, payload: any): void { + this.postMessage({ + messageType: WorkerMessageType.REPLY, + actionType, + payload, + sessionId, + }); + } + + /** + * 发送请求, 不等待响应 + * + * @param actionType 事务类型 + * @param payload 负载 + */ + public request(actionType: string, payload: any): void { + const sessionId = this.generateSessionId(); + this.postMessage({ + messageType: WorkerMessageType.REQUEST, + actionType, + payload, + sessionId, + }); + + // 不等待结果, 还会收到响应, 添加个空的会话响应器 + this.addSessionHandler(sessionId, () => {}); + } + + /** + * 发送请求, 并等待响应 + * + * @param actionType 事务类型 + * @param payload 负载 + * @param timeout 响应超时 + * @returns {Promise} 等待响应的 Promise + */ + public requestPromise(actionType: string, payload: any, timeout = COMMUNICATION_TIMEOUT): Promise { + const sessionId = this.generateSessionId(); + const message = { + messageType: WorkerMessageType.REQUEST, + actionType, + payload, + sessionId, + }; + + // 请求封装为一个 Promise, 等待会话响应器进行 resolve + const PromiseFunction = (resolve: Function, reject: Function): any => { + // 启动请求超时计时器 + const timeoutHandler = setTimeout(() => { + clearTimeout(timeoutHandler); + + reject(); + }, timeout); + + const sessionHandler: Function = (message: IWorkerMessage) => { + // 会话回调函数, 开始处理响应 + this.deleteSessionHandler(message.sessionId); + clearTimeout(timeoutHandler); + + resolve(message.payload); + }; + + this.addSessionHandler(sessionId, sessionHandler); + + // 开始发送请求 + this.postMessage(message); + }; + + return new Promise(PromiseFunction); + } + + /** + * 收到会话消息的处理函数 + * + * 发现是请求, 调用通信控制器的事务处理器进行处理, 获取事务结果并响应; + * 发现是响应,调用会话响应器 + * @param event worker 通信事件 + */ + private onmessage(event: { data: IWorkerMessage }): void { + const { data: message } = event; + const { messageType, sessionId, actionType } = message; + + // 接收到请求 + if (messageType === WorkerMessageType.REQUEST) { + // 处理请求 + this.controller.actionHandler(message) + .then(actionResult => { + // 响应请求 + this.response(sessionId, actionType, actionResult); + }); + } + + // 接收到响应 + if (messageType === WorkerMessageType.REPLY) { + // 处理响应 + if (this.hasSessionHandler(sessionId)) { + this.sessionHandlerMap[sessionId](message); + } else { + throw new Error(`Session \`${sessionId}\` handler no exist`); + } + } + } + + /** + * 封装的 worker 原生 postMessage 接口 + * 支持 structured clone 和 transfer 2种通信模式 + * + * @param message 会话消息 + */ + private postMessage(message: IWorkerMessage): void { + this.worker.postMessage(message); + } + + /** + * 添加会话响应器 + * + * @param sessionId 会话 Id + * @param handler 会话响应器 + */ + private addSessionHandler(sessionId: string, handler: Function): void { + if (!this.hasSessionHandler(sessionId)) { + this.sessionHandlerMap[sessionId] = handler; + } else { + throw new Error(`SessionId \`${sessionId}\` already exist!`); + } + } + + /** + * 移除会话响应器 + * + * @param sessionId + */ + private deleteSessionHandler(sessionId: string): void { + if (this.hasSessionHandler(sessionId)) { + delete this.sessionHandlerMap[sessionId]; + } + } + + /** + * 生成每次独立会话的 Id + * + * @returns 会话 Id + */ + private generateSessionId(): string { + const sessionId = `w_${BI.UUID()}`; + + return sessionId; + } + + /** + * 判断是否有指定会话的处理器 + * + * @param sessionId 会话 Id + * @returns {boolean} 判断结果 + */ + private hasSessionHandler(sessionId: string): boolean { + return !!this.sessionHandlerMap[sessionId]; + } +} diff --git a/typescript/core/worker/worker.core.ts b/typescript/core/worker/worker.core.ts new file mode 100644 index 000000000..0c6db6186 --- /dev/null +++ b/typescript/core/worker/worker.core.ts @@ -0,0 +1,48 @@ +/** + * 会话消息类型枚举 + */ +export const WorkerMessageType = { + REQUEST: 'REQUEST', + REPLY: 'REPLY', +}; + +/** + * 会话消息 + */ +export interface IWorkerMessage { + messageType: string; + actionType: string; + sessionId: string; + + /** + * 数据交换参数 + */ + payload: any; +} + +/** + * 通信控制器需要实现的 interface + */ +export interface IWorkerController { + + /** + * 事务处理器 + */ + actionHandler: (message: IWorkerMessage) => Promise; +} + +/** + * Worker创建配置 + */ +export interface IWorkerOptions { + + /** + * worker 资源 url + */ + workerUrl: string; + + /** + * worker 实例名称 + */ + workerName: string; +} diff --git a/typescript/core/worker/worker.main_thread.ts b/typescript/core/worker/worker.main_thread.ts new file mode 100644 index 000000000..c87bc9833 --- /dev/null +++ b/typescript/core/worker/worker.main_thread.ts @@ -0,0 +1,51 @@ +import type { WorkerBaseAction } from "./action/worker.action"; +import { WorkerMainThreadController } from "./controller/worker.main_thread.controller"; +import { IWorkerOptions } from "./worker.core"; + +/** + * 主线程Worker + */ +export class MainThreadWorker { + /** + * Worker 名称 + */ + public name: string; + + /** + * 主线程通信控制器 + */ + public controller: WorkerMainThreadController; + + /** + * 是否已经终止掉 Worker + */ + protected isTerminated = false; + + public constructor(options: IWorkerOptions) { + this.name = options.workerName; + this.controller = new WorkerMainThreadController(options); + } + + /** + * 初始化业务actions + */ + public initActions() {} + + /** + * 销毁 worker 实例 + * 子实例需要销毁action + */ + public terminate(): void { + this.controller.terminate(); + // 设置终止标志位 + this.isTerminated = true; + } + + /** + * 实例化action + * @param Action action类 + */ + protected createAction(Action: T): InstanceType { + return (new Action(this.controller, this)) as InstanceType; + } +} diff --git a/typescript/core/worker/worker.worker_thread.ts b/typescript/core/worker/worker.worker_thread.ts new file mode 100644 index 000000000..53b621403 --- /dev/null +++ b/typescript/core/worker/worker.worker_thread.ts @@ -0,0 +1,29 @@ +import type { WorkerBaseAction } from "./action/worker.action"; +import { WorkerThreadController } from "./controller/worker.worker_thread.controller"; + +/** + * worker线程实例 + */ +export class WorkerThreadWorker { + /** + * Worker 线程通信控制器 + */ + protected controller: WorkerThreadController; + + public constructor() { + this.controller = new WorkerThreadController(); + } + + /** + * 初始化业务actions + */ + public initActions() {} + + /** + * 实例化action + * @param Action action类 + */ + protected createAction(Action: T): InstanceType { + return (new Action(this.controller, this)) as InstanceType; + } +} diff --git a/typescript/core/worker/workers.ts b/typescript/core/worker/workers.ts new file mode 100644 index 000000000..5ab99dddc --- /dev/null +++ b/typescript/core/worker/workers.ts @@ -0,0 +1,30 @@ +import { WorkerChannel } from "./worker.channel"; +import { WorkerBaseController } from "./controller/worker.controller"; +import { IWorkerOptions, WorkerMessageType } from "./worker.core"; +import { WorkerMainThreadController } from "./controller/worker.main_thread.controller"; +import { WorkerThreadController } from "./controller/worker.worker_thread.controller"; +import { WorkerBaseAction } from "./action/worker.action"; +import { MainThreadWorker } from "./worker.main_thread"; +import { WorkerThreadWorker } from "./worker.worker_thread"; + +function createWorker(ThreadWorker: T, options: IWorkerOptions): InstanceType +function createWorker(ThreadWorker: T): InstanceType +function createWorker(ThreadWorker: T, options?: IWorkerOptions): InstanceType { + const threadWorker = new ThreadWorker(options as any) as InstanceType; + + threadWorker.initActions(); + + return threadWorker; +} + +export const Workers = { + WorkerChannel, + WorkerBaseController, + WorkerMainThreadController, + WorkerThreadController, + WorkerBaseAction, + MainThreadWorker, + WorkerThreadWorker, + WorkerMessageType, + createWorker, +}; diff --git a/typescript/index.ts b/typescript/index.ts index fa9a08d19..3aaca1ead 100644 --- a/typescript/index.ts +++ b/typescript/index.ts @@ -20,12 +20,12 @@ import { IconChangeButton } from "./case/button/icon/icon.change"; import { MultiSelectItem } from "./case/button/item.multiselect"; import { BubbleCombo } from "./case/combo/bubblecombo/combo.bubble"; import { TextValueCombo } from "./case/combo/combo.textvalue"; -import { SmallTextValueCombo } from './case/combo/combo.textvaluesmall'; +import { SmallTextValueCombo } from "./case/combo/combo.textvaluesmall"; import { SearchTextValueCombo } from "./case/combo/searchtextvaluecombo/combo.searchtextvalue"; import { SignEditor } from "./case/editor/editor.sign"; -import { StateEditor } from './case/editor/editor.state'; +import { StateEditor } from "./case/editor/editor.state"; import { AllValueMultiTextValueCombo } from "./component/allvaluemultitextvaluecombo/allvalue.multitextvalue.combo"; -import { Form } from './component/form/form'; +import { Form } from "./component/form/form"; import { AbstractTreeValueChooser } from "./component/treevaluechooser/abstract.treevaluechooser"; import { AbstractListTreeValueChooser } from "./component/treevaluechooser/abstract.treevaluechooser.list"; import { Action, ActionFactory } from "./core/action/action"; @@ -53,7 +53,10 @@ import { DownListCombo } from "./widget/downlist/combo.downlist"; import { DownListPopup } from "./widget/downlist/popup.downlist"; import { Icon } from "./base/single/icon/icon"; import { LeftVerticalAdaptLayout } from "./core/wrapper/layout/adapt/adapt.leftvertical"; -import { LeftRightVerticalAdaptLayout, RightVerticalAdaptLayout } from "./core/wrapper/layout/adapt/adapt.leftrightvertical"; +import { + LeftRightVerticalAdaptLayout, + RightVerticalAdaptLayout, +} from "./core/wrapper/layout/adapt/adapt.leftrightvertical"; import { IconTextIconItem } from "./base/single/button/listitem/icontexticonitem"; import { HorizontalAutoLayout } from "./core/wrapper/layout/adapt/auto.horizontal"; import { InlineVerticalAdaptLayout } from "./core/wrapper/layout/adapt/inline.vertical"; @@ -82,14 +85,14 @@ import { MultiSelectCombo } from "./widget/multiselect/multiselect.combo"; import { SearchEditor } from "./widget/editor/editor.search"; import { MultiLayerSingleLevelTree } from "./widget/multilayersingletree/multilayersingletree.leveltree"; import { SimpleColorChooser } from "./case/colorchooser/colorchooser.simple"; -import { ColorChooser } from './case/colorchooser/colorchooser'; +import { ColorChooser } from "./case/colorchooser/colorchooser"; import { A } from "./base/a/a"; import { Html } from "./base/single/html/html"; import { Switcher } from "./base/combination/switcher"; -import { Expander } from './base/combination/expander'; +import { Expander } from "./base/combination/expander"; import { Loader } from "./base/combination/loader"; import { ListPane } from "./case/layer/pane.list"; -import { MultiPopupView } from './case/layer/layer.multipopup'; +import { MultiPopupView } from "./case/layer/layer.multipopup"; import { MultiSelectBar } from "./case/toolbar/toolbar.multiselect"; import { SelectList } from "./case/list/list.select"; import { AbstractAllValueChooser } from "./component/allvaluechooser/abstract.allvaluechooser"; @@ -130,7 +133,7 @@ import { TextValueDownListCombo } from "./widget/textvaluedownlistcombo/combo.te import { Switch } from "./case/button/switch"; import { HorizontalLayout } from "./core/wrapper/layout/layout.horizontal"; import { ShelterEditor } from "./case/editor/editor.shelter"; -import { TextTrigger } from './case/trigger/trigger.text'; +import { TextTrigger } from "./case/trigger/trigger.text"; import { SelectTextTrigger } from "./case/trigger/trigger.text.select"; import { DateInterval } from "./widget/timeinterval/dateinterval"; import { DynamicDatePane } from "./widget/datepane/datepane"; @@ -151,43 +154,46 @@ import { Segment } from "./case/segment/segment"; import { LinearSegment } from "./case/linersegment/linear.segment"; import { Img } from "./base/single/img/img"; import { EditorIconCheckCombo } from "./case/combo/editoriconcheckcombo/combo.editiconcheck"; -import { IconTextValueCombo } from './case/combo/icontextvaluecombo/combo.icontextvalue'; -import { ListView } from './base/list/listview'; -import { FloatCenterLayout } from './core/wrapper/layout/middle/middle.float.center'; -import { _msg } from './base/foundation/message'; -import { _web } from './core/platform/web'; -import { DynamicYearMonthPopup } from './widget/yearmonth/popup.yearmonth'; -import { _utils } from './core/utils'; +import { IconTextValueCombo } from "./case/combo/icontextvaluecombo/combo.icontextvalue"; +import { ListView } from "./base/list/listview"; +import { VirtualList } from "./base/list/virtuallist"; +import { VirtualGroupList } from "./base/list/virtualgrouplist"; +import { FloatCenterLayout } from "./core/wrapper/layout/middle/middle.float.center"; +import { _msg } from "./base/foundation/message"; +import { _web } from "./core/platform/web"; +import { DynamicYearMonthPopup } from "./widget/yearmonth/popup.yearmonth"; +import { _utils } from "./core/utils"; import { Controller } from "./core/controller/controller"; import { LayerController } from "./core/controller/controller.layer"; import { DateCalendarPopup } from "./widget/date/calendar/popup.calendar.date"; import { Tree, Node } from "./core/utils/tree"; import { TextNode } from "./base/single/button/node/textnode"; import { TextValueCheckComboPopup } from "./case/combo/textvaluecheckcombo/popup.textvaluecheck"; -import { ImageButton } from './base/single/button/buttons/button.image'; +import { ImageButton } from "./base/single/button/buttons/button.image"; import { History, Router } from "./router/router"; -import { DateTimeCombo } from './widget/datetime/datetime.combo'; +import { DateTimeCombo } from "./widget/datetime/datetime.combo"; import { FloatHorizontalLayout } from "./core/wrapper/layout/adapt/float.horizontal"; import { AdaptiveLayout } from "./core/wrapper/layout/layout.adaptive"; -import { HexColorChooserPopup } from './case/colorchooser/colorchooser.popup.hex'; -import { BlankIconTextItem } from './base/single/button/listitem/blankicontextitem'; +import { HexColorChooserPopup } from "./case/colorchooser/colorchooser.popup.hex"; +import { BlankIconTextItem } from "./base/single/button/listitem/blankicontextitem"; import { Broadcasts, Layers } from "./base/base"; import { BroadcastController } from "./core/controller/controller.broadcast"; import { Pager } from "./base/pager/pager"; -import { TimeInterval } from './widget/timeinterval/timeinterval'; -import { DynamicDateTimePane } from './widget/datetimepane/datetimepane'; -import { SingleSelectInsertList } from './widget/singleselect/singleselectlist.insert'; -import { MultiSelectTree } from './widget/multiselecttree/multiselecttree'; +import { TimeInterval } from "./widget/timeinterval/timeinterval"; +import { DynamicDateTimePane } from "./widget/datetimepane/datetimepane"; +import { SingleSelectInsertList } from "./widget/singleselect/singleselectlist.insert"; +import { MultiSelectTree } from "./widget/multiselecttree/multiselecttree"; import { HtmlLabel } from "./base/single/label/html.label"; -import { TreeValueChooserPane } from './component/treevaluechooser/pane.treevaluechooser'; -import { TdLayout } from './core/wrapper/layout/layout.td'; -import { MultiLayerSelectLevelTree } from './widget/multilayerselecttree/multilayerselecttree.leveltree'; -import { SelectTreeExpander } from './widget/selecttree/selecttree.expander'; +import { TreeValueChooserPane } from "./component/treevaluechooser/pane.treevaluechooser"; +import { TdLayout } from "./core/wrapper/layout/layout.td"; +import { MultiLayerSelectLevelTree } from "./widget/multilayerselecttree/multilayerselecttree.leveltree"; +import { SelectTreeExpander } from "./widget/selecttree/selecttree.expander"; import { DownListGroupItem } from "./widget/downlist/item.downlistgroup"; import { VerticalStickyLayout } from "./core/wrapper/layout/sticky/sticky.vertical"; import { HorizontalStickyLayout } from "./core/wrapper/layout/sticky/sticky.horizontal"; import { TableLayout } from "./core/wrapper/layout/layout.table"; -import './shims-tsx'; +import "./shims-tsx"; +import { Workers } from "./core/worker/workers"; export interface BI extends _func, _i18n, _base, _inject, _var, _web, _utils { @@ -355,6 +361,8 @@ export interface BI extends _func, _i18n, _base, _inject, _var, _web, _utils { EditorIconCheckCombo: typeof EditorIconCheckCombo; IconTextValueCombo: typeof IconTextValueCombo; ListView: typeof ListView; + VirtualList: typeof VirtualList; + VirtualGroupList: typeof VirtualGroupList; FloatCenterLayout: typeof FloatCenterLayout; Msg: _msg; DynamicYearMonthPopup: typeof DynamicYearMonthPopup; @@ -382,10 +390,12 @@ export interface BI extends _func, _i18n, _base, _inject, _var, _web, _utils { VerticalStickyLayout: typeof VerticalStickyLayout; HorizontalStickyLayout: typeof HorizontalStickyLayout; TableLayout: typeof TableLayout; + Workers: typeof Workers; } export default { Decorators: decorator, + Workers, }; export { OB, @@ -547,6 +557,8 @@ export { EditorIconCheckCombo, IconTextValueCombo, ListView, + VirtualList, + VirtualGroupList, FloatCenterLayout, DynamicYearMonthPopup, DateCalendarPopup,