diff --git a/client/VERSION b/client/VERSION index 314c3d71..ab679818 100644 --- a/client/VERSION +++ b/client/VERSION @@ -1 +1 @@ -1.1.5 \ No newline at end of file +1.1.6 \ No newline at end of file diff --git a/client/config/test/jest.config.js b/client/config/test/jest.config.js index 201cf3f3..1f1196b7 100644 --- a/client/config/test/jest.config.js +++ b/client/config/test/jest.config.js @@ -6,7 +6,9 @@ const globals = {}; buildVars.forEach(({ name, defaultValue }) => { globals[name] = process.env[name] || defaultValue; }); -const isEE = process.env.REACT_APP_EDITION === "enterprise"; +const edition = process.env.REACT_APP_EDITION; +const isEEGlobal = edition === "enterprise-global"; +const isEE = edition === "enterprise" || isEEGlobal; const dirname = currentDirName(import.meta.url); export default { diff --git a/client/package.json b/client/package.json index f9ffb39b..36415a57 100644 --- a/client/package.json +++ b/client/package.json @@ -12,6 +12,7 @@ "scripts": { "start": "yarn workspace openblocks start", "start:ee": "REACT_APP_EDITION=enterprise yarn workspace openblocks start", + "start:ee-global": "REACT_APP_EDITION=enterprise-global yarn workspace openblocks start", "build": "yarn node ./scripts/build.js", "test": "jest", "prepare": "yarn workspace openblocks prepare", @@ -71,6 +72,7 @@ "react-virtualized@^9.22.3": "patch:react-virtualized@npm%3A9.22.3#./.yarn/patches/react-virtualized-npm-9.22.3-0fff3cbf64.patch" }, "dependencies": { + "antd-mobile": "^5.28.0", "chalk": "4", "number-precision": "^1.6.0", "react-player": "^2.11.0", diff --git a/client/packages/openblocks-core/lib/index.cjs b/client/packages/openblocks-core/lib/index.cjs index ec946769..a25796e5 100644 --- a/client/packages/openblocks-core/lib/index.cjs +++ b/client/packages/openblocks-core/lib/index.cjs @@ -106,49 +106,40 @@ function __spreadArray(to, from, pack) { return to.concat(ar || Array.prototype.slice.call(from)); } -function getCache(obj, fnName) { +function isEqualArgs(args, cacheArgs, equals) { + if (!cacheArgs) { + return false; + } + if (args.length === 0 && cacheArgs.length === 0) { + return true; + } + return (args.length === cacheArgs.length && + cacheArgs.every(function (arg, index) { var _a, _b; return (_b = (_a = equals === null || equals === void 0 ? void 0 : equals[index]) === null || _a === void 0 ? void 0 : _a.call(equals, arg, args[index])) !== null && _b !== void 0 ? _b : arg === args[index]; })); +} +function getCacheResult(thisObj, fnName, args, equals) { var _a; - return (_a = obj === null || obj === void 0 ? void 0 : obj.__cache) === null || _a === void 0 ? void 0 : _a[fnName]; + var cache = (_a = thisObj === null || thisObj === void 0 ? void 0 : thisObj.__cache) === null || _a === void 0 ? void 0 : _a[fnName]; + if (cache && isEqualArgs(args, cache.args, equals)) { + return cache.result; + } } -function createCache(obj, fnName, args) { - if (!obj.__cache) { - obj.__cache = {}; +function cache(fn, args, thisObj, fnName, equals) { + var result = getCacheResult(thisObj, fnName, args, equals); + if (result) { + return result.value; } - obj.__cache[fnName] = { + var cache = { id: Symbol("id"), args: args, - isInProgress: true, time: Date.now(), }; - return getCache(obj, fnName); -} -function genCache(fn, args, thisObj, fnName) { - var cache = createCache(thisObj, fnName, args); - var value = fn.apply(thisObj, args); - cache.isInProgress = false; - cache.value = value; -} -function read(thisObj, fnName) { - var cache = getCache(thisObj, fnName); - return cache && cache.value; -} -function hitCache(args, thisObj, fnName, equals) { - var cache = getCache(thisObj, fnName); - if (!cache || !cache.args) - return false; - if (args.length === 0 && cache.args.length === 0) - return true; - return cache.args.every(function (arg, index) { var _a, _b; return (_b = (_a = equals === null || equals === void 0 ? void 0 : equals[index]) === null || _a === void 0 ? void 0 : _a.call(equals, arg, args[index])) !== null && _b !== void 0 ? _b : arg === args[index]; }); -} -function isCyclic(thisObj, fnName) { - var cache = getCache(thisObj, fnName); - return cache && cache.isInProgress; -} -function cache(fn, args, thisObj, fnName, equals) { - if (!hitCache(args, thisObj, fnName, equals) && !isCyclic(thisObj, fnName)) { - genCache(fn, args, thisObj, fnName); + if (!thisObj.__cache) { + thisObj.__cache = {}; } - return read(thisObj, fnName); + thisObj.__cache[fnName] = cache; + var value = fn.apply(thisObj, args); + cache.result = { value: value }; + return value; } function memoized(equals) { return function (target, fnName, descriptor) { @@ -1049,23 +1040,24 @@ var loglevel = {exports: {}}; var log = loglevel.exports; -// global variables black list, forbidden to use -var blacklist = new Set([ +// global variables black list, forbidden to use in for jsQuery/jsAction +var functionBlacklist = new Set([ "top", "parent", "document", "location", "chrome", - "setTimeout", "fetch", - "setInterval", - "clearInterval", - "setImmediate", "XMLHttpRequest", "importScripts", "Navigator", "MutationObserver", ]); +var expressionBlacklist = new Set(__spreadArray(__spreadArray([], Array.from(functionBlacklist.values()), true), [ + "setTimeout", + "setInterval", + "setImmediate", +], false)); var globalVarNames = new Set(["window", "globalThis", "self", "global"]); function createBlackHole() { return new Proxy(function () { @@ -1087,12 +1079,14 @@ function createBlackHole() { }, }); } -function createMockWindow() { - var win = new Proxy({}, { +function createMockWindow(base, blacklist) { + if (blacklist === void 0) { blacklist = expressionBlacklist; } + var win = new Proxy(Object.assign({}, base), { has: function () { return true; }, set: function (target, p, newValue) { + console.info("set:", p, newValue); return Reflect.set(target, p, newValue); }, get: function (target, p) { @@ -1102,19 +1096,11 @@ function createMockWindow() { if (globalVarNames.has(p)) { return win; } - if (typeof p === "string" && blacklist.has(p)) { + if (typeof p === "string" && (blacklist === null || blacklist === void 0 ? void 0 : blacklist.has(p))) { log.log("[Sandbox] access ".concat(String(p), " on mock window, return mock object")); return createBlackHole(); } - var ret = Reflect.get(window, p); - if (typeof ret === "function" && !ret.prototype) { - return ret.bind(window); - } - // get DOM element by id, serializing may cause error - if (isDomElement(ret)) { - return undefined; - } - return ret; + return getPropertyFromNativeWindow(p); }, }); return win; @@ -1126,12 +1112,26 @@ function clearMockWindow() { function isDomElement(obj) { return obj instanceof Element || obj instanceof HTMLCollection; } +function getPropertyFromNativeWindow(prop) { + var ret = Reflect.get(window, prop); + if (typeof ret === "function" && !ret.prototype) { + return ret.bind(window); + } + // get DOM element by id, serializing may cause error + if (isDomElement(ret)) { + return undefined; + } + return ret; +} function proxySandbox(context, methods, options) { - var _a = (options || {}).disableLimit, disableLimit = _a === void 0 ? false : _a; + var _a = options || {}, _b = _a.disableLimit, disableLimit = _b === void 0 ? false : _b, _c = _a.scope, scope = _c === void 0 ? "expression" : _c; var isProtectedVar = function (key) { return key in context || key in (methods || {}) || globalVarNames.has(key); }; var cache = {}; + if (scope === "function") { + mockWindow = createMockWindow(mockWindow, functionBlacklist); + } return new Proxy(mockWindow, { has: function (target, p) { // proxy all variables @@ -1163,7 +1163,7 @@ function proxySandbox(context, methods, options) { return value; } if (disableLimit) { - return Reflect.get(window, p); + return getPropertyFromNativeWindow(p); } return Reflect.get(target, p, receiver); }, @@ -1427,6 +1427,7 @@ var DefaultParser = /** @class */ (function () { function evalJson(unevaledValue, context) { return new RelaxedJsonParser(unevaledValue, context).parse(); } +// this will also be used in node-service var RelaxedJsonParser = /** @class */ (function (_super) { __extends(RelaxedJsonParser, _super); function RelaxedJsonParser(unevaledValue, context) { @@ -1503,11 +1504,12 @@ var RelaxedJsonParser = /** @class */ (function (_super) { }(DefaultParser)); function evalFunction(unevaledValue, context, methods, isAsync) { try { - return new ValueAndMsg(function (args, runInHost) { + return new ValueAndMsg(function (args, runInHost, scope) { if (runInHost === void 0) { runInHost = false; } + if (scope === void 0) { scope = "function"; } return evalFunc(unevaledValue.startsWith("return") ? unevaledValue + "\n" - : "return ".concat(isAsync ? "async " : "", "function(){'use strict'; ").concat(unevaledValue, "\n}()"), args ? __assign(__assign({}, context), args) : context, methods, { disableLimit: runInHost }, isAsync); + : "return ".concat(isAsync ? "async " : "", "function(){'use strict'; ").concat(unevaledValue, "\n}()"), args ? __assign(__assign({}, context), args) : context, methods, { disableLimit: runInHost, scope: scope }, isAsync); }); } catch (err) { @@ -3209,8 +3211,8 @@ function updateNodesV2Action(value) { value: value, }; } -function wrapActionExtraInfo(action, extraCompInfos) { - return __assign(__assign({}, action), { extraInfo: { compInfos: extraCompInfos } }); +function wrapActionExtraInfo(action, extraInfos) { + return __assign(__assign({}, action), { extraInfo: __assign(__assign({}, action.extraInfo), extraInfos) }); } function deferAction(action) { return __assign(__assign({}, action), { priority: "defer" }); @@ -7537,6 +7539,7 @@ exports.FetchCheckNode = FetchCheckNode; exports.FunctionNode = FunctionNode; exports.MultiBaseComp = MultiBaseComp; exports.RecordNode = RecordNode; +exports.RelaxedJsonParser = RelaxedJsonParser; exports.SimpleAbstractComp = SimpleAbstractComp; exports.SimpleComp = SimpleComp; exports.SimpleNode = SimpleNode; @@ -7558,6 +7561,7 @@ exports.evalFunc = evalFunc; exports.evalFunctionResult = evalFunctionResult; exports.evalNodeOrMinor = evalNodeOrMinor; exports.evalPerfUtil = evalPerfUtil; +exports.evalScript = evalScript; exports.evalStyle = evalStyle; exports.executeQueryAction = executeQueryAction; exports.fromRecord = fromRecord; diff --git a/client/packages/openblocks-core/lib/index.d.ts b/client/packages/openblocks-core/lib/index.d.ts index e04fb951..a9f1a039 100644 --- a/client/packages/openblocks-core/lib/index.d.ts +++ b/client/packages/openblocks-core/lib/index.d.ts @@ -1,6 +1,6 @@ /// -import * as react from "react"; -import { ReactNode } from "react"; +import * as react from 'react'; +import { ReactNode } from 'react'; declare type EvalMethods = Record>; declare type CodeType = undefined | "JSON" | "Function"; @@ -8,87 +8,85 @@ declare type CodeFunction = (args?: Record, runInHost?: boolean declare type NodeToValue = NodeT extends Node ? ValueType : never; declare type FetchInfo = { - /** - * whether any of dependencies' node has executing query - */ - isFetching: boolean; - /** - * whether all dependencies' query have be executed once - */ - ready: boolean; + /** + * whether any of dependencies' node has executing query + */ + isFetching: boolean; + /** + * whether all dependencies' query have be executed once + */ + ready: boolean; }; /** * keyof without optional key */ declare type NonOptionalKeys = { - [k in keyof T]-?: undefined extends T[k] ? never : k; + [k in keyof T]-?: undefined extends T[k] ? never : k; }[keyof T]; /** * T extends {[key: string]: Node | undefined} */ declare type RecordOptionalNodeToValue = { - [K in NonOptionalKeys]: NodeToValue; + [K in NonOptionalKeys]: NodeToValue; }; interface FetchInfoOptions { - ignoreManualDepReadyStatus?: boolean; + ignoreManualDepReadyStatus?: boolean; } /** * the base structure for evaluate */ interface Node { - readonly type: string; - /** - * calculate evaluate result - * @param exposingNodes other dependent Nodes - */ - evaluate(exposingNodes?: Record>, methods?: EvalMethods): T; - /** - * whether the current or its dependencies have cyclic dependencies - * this function only can be used after evaluate() has been called - */ - hasCycle(): boolean; - /** - * only available after evaluate - */ - dependNames(): string[]; - dependValues(): Record; - /** - * filter the real dependencies, for boosting the evaluation - * @warn - * the results include direct dependencies and dependencies of dependencies. - * since input node's dependencies don't belong to module in the module feature, the node name may duplicate. - * - * FIXME: this should be a protected function. - */ - filterNodes(exposingNodes: Record>): Map, Set>; - fetchInfo(exposingNodes: Record>, options?: FetchInfoOptions): FetchInfo; + readonly type: string; + /** + * calculate evaluate result + * @param exposingNodes other dependent Nodes + */ + evaluate(exposingNodes?: Record>, methods?: EvalMethods): T; + /** + * whether the current or its dependencies have cyclic dependencies + * this function only can be used after evaluate() has been called + */ + hasCycle(): boolean; + /** + * only available after evaluate + */ + dependNames(): string[]; + dependValues(): Record; + /** + * filter the real dependencies, for boosting the evaluation + * @warn + * the results include direct dependencies and dependencies of dependencies. + * since input node's dependencies don't belong to module in the module feature, the node name may duplicate. + * + * FIXME: this should be a protected function. + */ + filterNodes(exposingNodes: Record>): Map, Set>; + fetchInfo(exposingNodes: Record>, options?: FetchInfoOptions): FetchInfo; } declare abstract class AbstractNode implements Node { - readonly type: string; - evalCache: EvalCache; - constructor(); - evaluate(exposingNodes?: Record>, methods?: EvalMethods): T; - hasCycle(): boolean; - abstract getChildren(): Node[]; - dependNames(): string[]; - abstract dependValues(): Record; - isHitEvalCache(exposingNodes?: Record>): boolean; - abstract filterNodes( - exposingNodes: Record> - ): Map, Set>; - /** - * evaluate without cache - */ - abstract justEval(exposingNodes: Record>, methods?: EvalMethods): T; - abstract fetchInfo(exposingNodes: Record>): FetchInfo; + readonly type: string; + evalCache: EvalCache; + constructor(); + evaluate(exposingNodes?: Record>, methods?: EvalMethods): T; + hasCycle(): boolean; + abstract getChildren(): Node[]; + dependNames(): string[]; + abstract dependValues(): Record; + isHitEvalCache(exposingNodes?: Record>): boolean; + abstract filterNodes(exposingNodes: Record>): Map, Set>; + /** + * evaluate without cache + */ + abstract justEval(exposingNodes: Record>, methods?: EvalMethods): T; + abstract fetchInfo(exposingNodes: Record>): FetchInfo; } interface EvalCache { - dependingNodeMap?: Map, Set>; - value?: T; - inEval?: boolean; - cyclic?: boolean; - inIsFetching?: boolean; - inFilterNodes?: boolean; + dependingNodeMap?: Map, Set>; + value?: T; + inEval?: boolean; + cyclic?: boolean; + inIsFetching?: boolean; + inFilterNodes?: boolean; } /** * check whether 2 dependingNodeMaps are equal @@ -99,24 +97,21 @@ interface EvalCache { * @param dependingNodeMap2 second dependingNodeMap * @returns whether equals */ -declare function dependingNodeMapEquals( - dependingNodeMap1: Map, Set> | undefined, - dependingNodeMap2: Map, Set> -): boolean; +declare function dependingNodeMapEquals(dependingNodeMap1: Map, Set> | undefined, dependingNodeMap2: Map, Set>): boolean; interface CachedValue { - value: T; - isCached: boolean; + value: T; + isCached: boolean; } declare class CachedNode extends AbstractNode> { - type: string; - child: AbstractNode; - constructor(child: AbstractNode); - filterNodes(exposingNodes: Record>): Map, Set>; - justEval(exposingNodes: Record>, methods?: EvalMethods): CachedValue; - getChildren(): Node[]; - dependValues(): Record; - fetchInfo(exposingNodes: Record>): FetchInfo; + type: string; + child: AbstractNode; + constructor(child: AbstractNode); + filterNodes(exposingNodes: Record>): Map, Set>; + justEval(exposingNodes: Record>, methods?: EvalMethods): CachedValue; + getChildren(): Node[]; + dependValues(): Record; + fetchInfo(exposingNodes: Record>): FetchInfo; } /** * return a new node with two input nodes. @@ -137,40 +132,37 @@ declare function evalNodeOrMinor(mainNode: AbstractNode, minorNode: Node extends AbstractNode { - readonly child: Node; - readonly func: (params: T) => OutputType; - readonly type = "function"; - constructor(child: Node, func: (params: T) => OutputType); - filterNodes(exposingNodes: Record>): Map, Set>; - justEval(exposingNodes: Record>, methods?: EvalMethods): OutputType; - getChildren(): Node[]; - dependValues(): Record; - fetchInfo(exposingNodes: Record>, options?: FetchInfoOptions): FetchInfo; -} -declare function withFunction( - child: Node, - func: (params: T) => OutputType -): FunctionNode; + readonly child: Node; + readonly func: (params: T) => OutputType; + readonly type = "function"; + constructor(child: Node, func: (params: T) => OutputType); + filterNodes(exposingNodes: Record>): Map, Set>; + justEval(exposingNodes: Record>, methods?: EvalMethods): OutputType; + getChildren(): Node[]; + dependValues(): Record; + fetchInfo(exposingNodes: Record>, options?: FetchInfoOptions): FetchInfo; +} +declare function withFunction(child: Node, func: (params: T) => OutputType): FunctionNode; declare type ValueExtra = { - segments?: { - value: string; - success: boolean; - }[]; + segments?: { + value: string; + success: boolean; + }[]; }; declare class ValueAndMsg { - value: T; - msg?: string; - extra?: ValueExtra; - midValue?: any; - constructor(value: T, msg?: string, extra?: ValueExtra, midValue?: any); - hasError(): boolean; - getMsg(displayValueFn?: (value: T) => string): string; + value: T; + msg?: string; + extra?: ValueExtra; + midValue?: any; + constructor(value: T, msg?: string, extra?: ValueExtra, midValue?: any); + hasError(): boolean; + getMsg(displayValueFn?: (value: T) => string): string; } interface CodeNodeOptions { - codeType?: CodeType; - evalWithMethods?: boolean; + codeType?: CodeType; + evalWithMethods?: boolean; } /** * user input node @@ -182,73 +174,60 @@ interface CodeNodeOptions { * FIXME(libin): distinguish Json CodeNodeļ¼Œsince wrapContext may cause problems. */ declare class CodeNode extends AbstractNode> { - readonly unevaledValue: string; - readonly options?: CodeNodeOptions | undefined; - readonly type = "input"; - private readonly codeType?; - private readonly evalWithMethods; - private directDepends; - constructor(unevaledValue: string, options?: CodeNodeOptions | undefined); - private convertedValue; - filterNodes(exposingNodes: Record>): Map, Set>; - private filterDirectDepends; - justEval( - exposingNodes: Record>, - methods?: EvalMethods - ): ValueAndMsg; - getChildren(): Node[]; - dependValues(): Record; - fetchInfo(exposingNodes: Record>, options?: FetchInfoOptions): FetchInfo; + readonly unevaledValue: string; + readonly options?: CodeNodeOptions | undefined; + readonly type = "input"; + private readonly codeType?; + private readonly evalWithMethods; + private directDepends; + constructor(unevaledValue: string, options?: CodeNodeOptions | undefined); + private convertedValue; + filterNodes(exposingNodes: Record>): Map, Set>; + private filterDirectDepends; + justEval(exposingNodes: Record>, methods?: EvalMethods): ValueAndMsg; + getChildren(): Node[]; + dependValues(): Record; + fetchInfo(exposingNodes: Record>, options?: FetchInfoOptions): FetchInfo; } /** * generate node for unevaledValue */ -declare function fromUnevaledValue( - unevaledValue: string -): FunctionNode, unknown>; +declare function fromUnevaledValue(unevaledValue: string): FunctionNode, unknown>; /** * evaluate to get FetchInfo or fetching status */ declare class FetchCheckNode extends AbstractNode { - readonly child: Node; - readonly options?: FetchInfoOptions | undefined; - readonly type = "fetchCheck"; - constructor(child: Node, options?: FetchInfoOptions | undefined); - filterNodes(exposingNodes: Record>): Map, Set>; - justEval(exposingNodes: Record>): FetchInfo; - getChildren(): Node[]; - dependValues(): Record; - fetchInfo(exposingNodes: Record>): FetchInfo; + readonly child: Node; + readonly options?: FetchInfoOptions | undefined; + readonly type = "fetchCheck"; + constructor(child: Node, options?: FetchInfoOptions | undefined); + filterNodes(exposingNodes: Record>): Map, Set>; + justEval(exposingNodes: Record>): FetchInfo; + getChildren(): Node[]; + dependValues(): Record; + fetchInfo(exposingNodes: Record>): FetchInfo; } declare function isFetching(node: Node): Node; declare type RecordNodeToValue = { - [K in keyof T]: NodeToValue; + [K in keyof T]: NodeToValue; }; /** * the evaluated value is the record constructed by the children nodes */ -declare class RecordNode>> extends AbstractNode< - RecordNodeToValue -> { - readonly children: T; - readonly type = "record"; - constructor(children: T); - filterNodes(exposingNodes: Record>): Map, Set>; - justEval( - exposingNodes: Record>, - methods?: EvalMethods - ): RecordNodeToValue; - getChildren(): Node[]; - dependValues(): Record; - fetchInfo( - exposingNodes: Record>, - options?: FetchInfoOptions - ): { - isFetching: boolean; - ready: boolean; - }; +declare class RecordNode>> extends AbstractNode> { + readonly children: T; + readonly type = "record"; + constructor(children: T); + filterNodes(exposingNodes: Record>): Map, Set>; + justEval(exposingNodes: Record>, methods?: EvalMethods): RecordNodeToValue; + getChildren(): Node[]; + dependValues(): Record; + fetchInfo(exposingNodes: Record>, options?: FetchInfoOptions): { + isFetching: boolean; + ready: boolean; + }; } declare function fromRecord>>(record: T): RecordNode; @@ -256,17 +235,17 @@ declare function fromRecord>>(record: T): * directly provide data */ declare class SimpleNode extends AbstractNode { - readonly value: T; - readonly type = "simple"; - constructor(value: T); - filterNodes(exposingNodes: Record>): Map, Set>; - justEval(exposingNodes: Record>): T; - getChildren(): Node[]; - dependValues(): Record; - fetchInfo(exposingNodes: Record>): { - isFetching: boolean; - ready: boolean; - }; + readonly value: T; + readonly type = "simple"; + constructor(value: T); + filterNodes(exposingNodes: Record>): Map, Set>; + justEval(exposingNodes: Record>): T; + getChildren(): Node[]; + dependValues(): Record; + fetchInfo(exposingNodes: Record>): { + isFetching: boolean; + ready: boolean; + }; } /** * provide simple value, don't need to eval @@ -275,23 +254,18 @@ declare function fromValue(value: T): SimpleNode; declare function fromValueWithCache(value: T): SimpleNode; declare class WrapNode extends AbstractNode { - readonly delegate: Node; - readonly moduleExposingNodes: Record>; - readonly moduleExposingMethods?: EvalMethods | undefined; - readonly inputNodes?: Record> | undefined; - readonly type = "wrap"; - constructor( - delegate: Node, - moduleExposingNodes: Record>, - moduleExposingMethods?: EvalMethods | undefined, - inputNodes?: Record> | undefined - ); - private wrap; - filterNodes(exposingNodes: Record>): Map, Set>; - justEval(exposingNodes: Record>, methods: EvalMethods): T; - fetchInfo(exposingNodes: Record>): FetchInfo; - getChildren(): Node[]; - dependValues(): Record; + readonly delegate: Node; + readonly moduleExposingNodes: Record>; + readonly moduleExposingMethods?: EvalMethods | undefined; + readonly inputNodes?: Record> | undefined; + readonly type = "wrap"; + constructor(delegate: Node, moduleExposingNodes: Record>, moduleExposingMethods?: EvalMethods | undefined, inputNodes?: Record> | undefined); + private wrap; + filterNodes(exposingNodes: Record>): Map, Set>; + justEval(exposingNodes: Record>, methods: EvalMethods): T; + fetchInfo(exposingNodes: Record>): FetchInfo; + getChildren(): Node[]; + dependValues(): Record; } declare type WrapContextFn = (params?: Record) => T; @@ -301,43 +275,40 @@ declare function wrapContext(node: Node): Node>; * build a new node by setting new dependent nodes in child node */ declare class WrapContextNodeV2 extends AbstractNode { - readonly child: Node; - readonly paramNodes: Record>; - readonly type = "wrapContextV2"; - constructor(child: Node, paramNodes: Record>); - filterNodes(exposingNodes: Record>): Map, Set>; - justEval(exposingNodes: Record>, methods?: EvalMethods): T; - getChildren(): Node[]; - dependValues(): Record; - fetchInfo(exposingNodes: Record>): FetchInfo; - private wrap; + readonly child: Node; + readonly paramNodes: Record>; + readonly type = "wrapContextV2"; + constructor(child: Node, paramNodes: Record>); + filterNodes(exposingNodes: Record>): Map, Set>; + justEval(exposingNodes: Record>, methods?: EvalMethods): T; + getChildren(): Node[]; + dependValues(): Record; + fetchInfo(exposingNodes: Record>): FetchInfo; + private wrap; } -declare function transformWrapper( - transformFn: (value: unknown) => T, - defaultValue?: T -): (valueAndMsg: ValueAndMsg) => ValueAndMsg; +declare function transformWrapper(transformFn: (value: unknown) => T, defaultValue?: T): (valueAndMsg: ValueAndMsg) => ValueAndMsg; interface PerfInfo { - obj: any; - name: string; - childrenPerfInfo: PerfInfo[]; - costMs: number; - depth: number; - info: Record; + obj: any; + name: string; + childrenPerfInfo: PerfInfo[]; + costMs: number; + depth: number; + info: Record; } declare type Log = (key: string, log: any) => void; declare class RecursivePerfUtil { - root: symbol; - record: PerfInfo; - stack: number[]; - constructor(); - private initRecord; - private getRecordByStack; - log(info: Record, key: string, log: any): void; - perf(obj: any, name: string, fn: (log: Log) => T): T; - clear: () => void; - print: (stack: number[], cost_ms_print_thr?: number) => void; + root: symbol; + record: PerfInfo; + stack: number[]; + constructor(); + private initRecord; + private getRecordByStack; + log(info: Record, key: string, log: any): void; + perf(obj: any, name: string, fn: (log: Log) => T): T; + clear: () => void; + print: (stack: number[], cost_ms_print_thr?: number) => void; } declare const evalPerfUtil: RecursivePerfUtil; @@ -347,43 +318,50 @@ declare function isDynamicSegment(segment: string): boolean; declare function getDynamicStringSegments(input: string): string[]; declare function clearMockWindow(): void; +declare type SandboxScope = "function" | "expression"; interface SandBoxOption { - /** - * disable all limit, like running in host - */ - disableLimit?: boolean; -} -declare function evalFunc( - functionBody: string, - context: any, - methods?: EvalMethods, - options?: SandBoxOption, - isAsync?: boolean -): any; + /** + * disable all limit, like running in host + */ + disableLimit?: boolean; + /** + * the scope this sandbox works in, which will use different blacklist + */ + scope?: SandboxScope; +} +declare function evalScript(script: string, context: any, methods?: EvalMethods): any; +declare function evalFunc(functionBody: string, context: any, methods?: EvalMethods, options?: SandBoxOption, isAsync?: boolean): any; declare function evalStyle(id: string, css: string[]): void; declare function clearStyleEval(id?: string): void; -declare function evalFunctionResult( - unevaledValue: string, - context: Record, - methods?: EvalMethods -): Promise>; +declare class DefaultParser { + readonly context: Record; + protected readonly segments: string[]; + private readonly valueAndMsgs; + constructor(unevaledValue: string, context: Record); + parse(): ValueAndMsg; + parseObject(): unknown; + evalDynamicSegment(segment: string): unknown; +} +declare class RelaxedJsonParser extends DefaultParser { + constructor(unevaledValue: string, context: Record); + parseObject(): any; + parseRelaxedJson(): any; + evalIndexedObject(obj: any): any; + evalIndexedStringToObject(indexedString: string): unknown; + evalIndexedStringToString(indexedString: string): string; + evalIndexedSnippet(snippet: string): unknown; +} +declare function evalFunctionResult(unevaledValue: string, context: Record, methods?: EvalMethods): Promise>; -declare function nodeIsRecord( - node: Node -): node is RecordNode>>; +declare function nodeIsRecord(node: Node): node is RecordNode>>; -declare function changeDependName( - unevaledValue: string, - oldName: string, - name: string, - isFunction?: boolean -): string; +declare function changeDependName(unevaledValue: string, oldName: string, name: string, isFunction?: boolean): string; declare type JSONValue = string | number | boolean | JSONObject | JSONArray | null; interface JSONObject { - [x: string]: JSONValue | undefined; + [x: string]: JSONValue | undefined; } declare type JSONArray = Array; @@ -391,208 +369,167 @@ declare type OptionalNodeType = Node | undefined; declare type DispatchType = (action: CompAction) => void; /** */ -interface Comp< - ViewReturn = any, - DataType extends JSONValue = JSONValue, - NodeType extends OptionalNodeType = OptionalNodeType -> { - dispatch: DispatchType; - getView(): ViewReturn; - getPropertyView(): ReactNode; - reduce(action: CompAction): this; - node(): NodeType; - toJsonValue(): DataType; - /** - * change current comp's dispatch function. - * used when the comp is moved across the tree structure. - */ - changeDispatch(dispatch: DispatchType): this; -} -declare abstract class AbstractComp< - ViewReturn = any, - DataType extends JSONValue = JSONValue, - NodeType extends OptionalNodeType = OptionalNodeType -> implements Comp -{ - dispatch: DispatchType; - constructor(params: CompParams); - abstract getView(): ViewReturn; - abstract getPropertyView(): ReactNode; - abstract toJsonValue(): DataType; - abstract reduce(_action: CompAction): this; - abstract nodeWithoutCache(): NodeType; - changeDispatch(dispatch: DispatchType): this; - /** - * trigger changeValueAction, type safe - */ - dispatchChangeValueAction(value: DataType): void; - /** - * don't override the function, override nodeWithout function instead - * FIXME: node reference mustn't be changed if this object is changed - */ - node(): NodeType; +interface Comp { + dispatch: DispatchType; + getView(): ViewReturn; + getPropertyView(): ReactNode; + reduce(action: CompAction): this; + node(): NodeType; + toJsonValue(): DataType; + /** + * change current comp's dispatch function. + * used when the comp is moved across the tree structure. + */ + changeDispatch(dispatch: DispatchType): this; +} +declare abstract class AbstractComp implements Comp { + dispatch: DispatchType; + constructor(params: CompParams); + abstract getView(): ViewReturn; + abstract getPropertyView(): ReactNode; + abstract toJsonValue(): DataType; + abstract reduce(_action: CompAction): this; + abstract nodeWithoutCache(): NodeType; + changeDispatch(dispatch: DispatchType): this; + /** + * trigger changeValueAction, type safe + */ + dispatchChangeValueAction(value: DataType): void; + /** + * don't override the function, override nodeWithout function instead + * FIXME: node reference mustn't be changed if this object is changed + */ + node(): NodeType; } declare type OptionalComp = Comp | undefined; -declare type CompConstructor< - ViewReturn = any, - DataType extends JSONValue = any, - NodeType extends OptionalNodeType = OptionalNodeType -> = new (params: CompParams) => Comp; +declare type CompConstructor = new (params: CompParams) => Comp; /** * extract constructor's generic type */ -declare type ConstructorToView = T extends CompConstructor - ? ViewReturn - : never; +declare type ConstructorToView = T extends CompConstructor ? ViewReturn : never; declare type ConstructorToComp = T extends new (params: CompParams) => infer X ? X : never; -declare type ConstructorToDataType = T extends new (params: CompParams) => any - ? DataType - : never; -declare type ConstructorToNodeType = ConstructorToComp extends Comp - ? NodeType - : never; +declare type ConstructorToDataType = T extends new (params: CompParams) => any ? DataType : never; +declare type ConstructorToNodeType = ConstructorToComp extends Comp ? NodeType : never; declare type RecordConstructorToComp = { - [K in keyof T]: ConstructorToComp; + [K in keyof T]: ConstructorToComp; }; declare type RecordConstructorToView = { - [K in keyof T]: ConstructorToView; + [K in keyof T]: ConstructorToView; }; interface CompParams { - dispatch?: (action: CompAction) => void; - value?: DataType; + dispatch?: (action: CompAction) => void; + value?: DataType; } declare enum CompActionTypes { - CHANGE_VALUE = "CHANGE_VALUE", - RENAME = "RENAME", - MULTI_CHANGE = "MULTI_CHANGE", - ADD_CHILD = "ADD_CHILD", - DELETE_COMP = "DELETE_COMP", - REPLACE_COMP = "REPLACE_COMP", - ONLY_EVAL = "NEED_EVAL", - ASYNC = "ASYNC", - ASYNC_END = "ASYNC_END", - UPDATE_NODES_V2 = "UPDATE_NODES_V2", - EXECUTE_QUERY = "EXECUTE_QUERY", - TRIGGER_MODULE_EVENT = "TRIGGER_MODULE_EVENT", - /** - * this action can pass data to the comp by name - */ - ROUTE_BY_NAME = "ROUTE_BY_NAME", - /** - * execute action with context. for example, buttons in table's column should has currentRow as context - * FIXME: this is a broadcast message, better to be improved by a heritage mechanism. - */ - UPDATE_ACTION_CONTEXT = "UPDATE_ACTION_CONTEXT", - /** - * comp-specific action can be placed not globally. - * use CUSTOM uniformly. - */ - CUSTOM = "CUSTOM", - /** - * broadcast other actions in comp tree structure. - * used for encapsulate MultiBaseComp - */ - BROADCAST = "BROADCAST", -} -declare type ExtraActionType = - | "layout" - | "delete" - | "add" - | "modify" - | "rename" - | "recover" - | "upgrade"; + CHANGE_VALUE = "CHANGE_VALUE", + RENAME = "RENAME", + MULTI_CHANGE = "MULTI_CHANGE", + ADD_CHILD = "ADD_CHILD", + DELETE_COMP = "DELETE_COMP", + REPLACE_COMP = "REPLACE_COMP", + ONLY_EVAL = "NEED_EVAL", + ASYNC = "ASYNC", + ASYNC_END = "ASYNC_END", + UPDATE_NODES_V2 = "UPDATE_NODES_V2", + EXECUTE_QUERY = "EXECUTE_QUERY", + TRIGGER_MODULE_EVENT = "TRIGGER_MODULE_EVENT", + /** + * this action can pass data to the comp by name + */ + ROUTE_BY_NAME = "ROUTE_BY_NAME", + /** + * execute action with context. for example, buttons in table's column should has currentRow as context + * FIXME: this is a broadcast message, better to be improved by a heritage mechanism. + */ + UPDATE_ACTION_CONTEXT = "UPDATE_ACTION_CONTEXT", + /** + * comp-specific action can be placed not globally. + * use CUSTOM uniformly. + */ + CUSTOM = "CUSTOM", + /** + * broadcast other actions in comp tree structure. + * used for encapsulate MultiBaseComp + */ + BROADCAST = "BROADCAST" +} +declare type ExtraActionType = "layout" | "delete" | "add" | "modify" | "rename" | "recover" | "upgrade"; declare type ActionExtraInfo = { - compInfos: { - compName: string; - compType: string; - type: ExtraActionType; - }[]; + compInfos?: { + compName: string; + compType: string; + type: ExtraActionType; + }[]; }; declare type ActionPriority = "sync" | "defer"; interface ActionCommon { - path: Array; - skipHistory?: boolean; - extraInfo?: ActionExtraInfo; - priority?: ActionPriority; + path: Array; + skipHistory?: boolean; + extraInfo?: ActionExtraInfo; + priority?: ActionPriority; } interface CustomAction extends ActionCommon { - type: CompActionTypes.CUSTOM; - value: DataType; + type: CompActionTypes.CUSTOM; + value: DataType; } interface ChangeValueAction extends ActionCommon { - type: CompActionTypes.CHANGE_VALUE; - value: DataType; + type: CompActionTypes.CHANGE_VALUE; + value: DataType; } interface ReplaceCompAction extends ActionCommon { - type: CompActionTypes.REPLACE_COMP; - compFactory: CompConstructor; + type: CompActionTypes.REPLACE_COMP; + compFactory: CompConstructor; } interface RenameAction extends ActionCommon { - type: CompActionTypes.RENAME; - oldName: string; - name: string; + type: CompActionTypes.RENAME; + oldName: string; + name: string; } interface BroadcastAction extends ActionCommon { - type: CompActionTypes.BROADCAST; - action: Action; + type: CompActionTypes.BROADCAST; + action: Action; } interface MultiChangeAction extends ActionCommon { - type: CompActionTypes.MULTI_CHANGE; - changes: Record; + type: CompActionTypes.MULTI_CHANGE; + changes: Record; } interface AddChildAction extends ActionCommon { - type: CompActionTypes.ADD_CHILD; - key: string; - value: JSONValue; + type: CompActionTypes.ADD_CHILD; + key: string; + value: JSONValue; } interface SimpleCompAction extends ActionCommon { - type: CompActionTypes.DELETE_COMP | CompActionTypes.ONLY_EVAL; + type: CompActionTypes.DELETE_COMP | CompActionTypes.ONLY_EVAL; } interface ExecuteQueryAction extends ActionCommon { - type: CompActionTypes.EXECUTE_QUERY; - queryName?: string; - args?: Record; - afterExecFunc?: () => void; + type: CompActionTypes.EXECUTE_QUERY; + queryName?: string; + args?: Record; + afterExecFunc?: () => void; } interface TriggerModuleEventAction extends ActionCommon { - type: CompActionTypes.TRIGGER_MODULE_EVENT; - name: string; + type: CompActionTypes.TRIGGER_MODULE_EVENT; + name: string; } interface RouteByNameAction extends ActionCommon { - type: CompActionTypes.ROUTE_BY_NAME; - name: string; - action: CompAction; + type: CompActionTypes.ROUTE_BY_NAME; + name: string; + action: CompAction; } interface UpdateNodesV2Action extends ActionCommon { - type: CompActionTypes.UPDATE_NODES_V2; - value: any; + type: CompActionTypes.UPDATE_NODES_V2; + value: any; } declare type ActionContextType = Record; interface UpdateActionContextAction extends ActionCommon { - type: CompActionTypes.UPDATE_ACTION_CONTEXT; - context: ActionContextType; -} -declare type CompAction = - | CustomAction - | ChangeValueAction - | BroadcastAction - | RenameAction - | ReplaceCompAction - | AddChildAction - | MultiChangeAction - | SimpleCompAction - | ExecuteQueryAction - | UpdateActionContextAction - | RouteByNameAction - | TriggerModuleEventAction - | UpdateNodesV2Action; + type: CompActionTypes.UPDATE_ACTION_CONTEXT; + context: ActionContextType; +} +declare type CompAction = CustomAction | ChangeValueAction | BroadcastAction | RenameAction | ReplaceCompAction | AddChildAction | MultiChangeAction | SimpleCompAction | ExecuteQueryAction | UpdateActionContextAction | RouteByNameAction | TriggerModuleEventAction | UpdateNodesV2Action; declare function customAction(value: DataType): CustomAction; -declare function updateActionContextAction( - context: ActionContextType -): BroadcastAction; +declare function updateActionContextAction(context: ActionContextType): BroadcastAction; /** * check if it's current custom action. * keep type safe via generics, users should keep type the same as T, otherwise may cause bug. @@ -605,18 +542,15 @@ declare function isCustomAction(action: CompAction, type: string): action is * RootComp will change the path correctly when queryName is passed. */ declare function executeQueryAction(props: { - args?: Record; - afterExecFunc?: () => void; + args?: Record; + afterExecFunc?: () => void; }): ExecuteQueryAction; declare function triggerModuleEventAction(name: string): TriggerModuleEventAction; /** * better to use comp.dispatchChangeValueAction to keep type safe */ declare function changeValueAction(value: JSONValue): ChangeValueAction; -declare function isBroadcastAction( - action: CompAction, - type: T["type"] -): action is BroadcastAction; +declare function isBroadcastAction(action: CompAction, type: T["type"]): action is BroadcastAction; declare function renameAction(oldName: string, name: string): BroadcastAction; declare function routeByNameAction(name: string, action: CompAction): RouteByNameAction; declare function addChildAction(key: string, value: JSONValue): AddChildAction; @@ -629,17 +563,13 @@ declare function isChildAction(action: CompAction): boolean; declare function unwrapChildAction(action: CompAction): [string, CompAction]; declare function changeChildAction(childName: string, value: JSONValue): CompAction; declare function updateNodesV2Action(value: any): UpdateNodesV2Action; -declare function wrapActionExtraInfo( - action: T, - extraCompInfos: ActionExtraInfo["compInfos"] -): T; +declare function wrapActionExtraInfo(action: T, extraInfos: ActionExtraInfo): T; declare function deferAction(action: T): T; /** * MultiBaseCompConstructor with abstract function implemented */ -declare type MultiCompConstructor = new (params: CompParams) => MultiBaseComp & - Comp; +declare type MultiCompConstructor = new (params: CompParams) => MultiBaseComp & Comp; /** * wrap a dispatch as a child dispatch * @@ -649,8 +579,8 @@ declare type MultiCompConstructor = new (params: CompParams) => MultiBaseCo */ declare function wrapDispatch(dispatch: DispatchType | undefined, childName: string): DispatchType; declare type ExtraNodeType = { - node: Record>; - updateNodeFields: (value: any) => Record; + node: Record>; + updateNodeFields: (value: any) => Record; }; /** * the core class of multi function @@ -658,231 +588,86 @@ declare type ExtraNodeType = { * @remarks * functions can be cached if needed. **/ -declare abstract class MultiBaseComp< - ChildrenType extends Record> = Record>, - DataType extends JSONValue = JSONValue, - NodeType extends OptionalNodeType = OptionalNodeType -> extends AbstractComp { - readonly children: ChildrenType; - constructor(params: CompParams); - abstract parseChildrenFromValue(params: CompParams): ChildrenType; - reduce(action: CompAction): this; - protected reduceOrUndefined(action: CompAction): this | undefined; - setChild(childName: keyof ChildrenType, newChild: Comp): this; - protected setChildren( - children: Record, - params?: { - keepCacheKeys?: string[]; - } - ): this; - /** - * extended interface. - * - * @return node for additional node, updateNodeFields for handling UPDATE_NODE event - * FIXME: make type safe - */ - protected extraNode(): ExtraNodeType | undefined; - protected childrenNode(): { - [key: string]: Node; - }; - nodeWithoutCache(): NodeType; - changeDispatch(dispatch: DispatchType): this; - protected ignoreChildDefaultValue(): boolean; - readonly IGNORABLE_DEFAULT_VALUE: {}; - toJsonValue(): DataType; - autoHeight(): boolean; +declare abstract class MultiBaseComp> = Record>, DataType extends JSONValue = JSONValue, NodeType extends OptionalNodeType = OptionalNodeType> extends AbstractComp { + readonly children: ChildrenType; + constructor(params: CompParams); + abstract parseChildrenFromValue(params: CompParams): ChildrenType; + reduce(action: CompAction): this; + protected reduceOrUndefined(action: CompAction): this | undefined; + setChild(childName: keyof ChildrenType, newChild: Comp): this; + protected setChildren(children: Record, params?: { + keepCacheKeys?: string[]; + }): this; + /** + * extended interface. + * + * @return node for additional node, updateNodeFields for handling UPDATE_NODE event + * FIXME: make type safe + */ + protected extraNode(): ExtraNodeType | undefined; + protected childrenNode(): { + [key: string]: Node; + }; + nodeWithoutCache(): NodeType; + changeDispatch(dispatch: DispatchType): this; + protected ignoreChildDefaultValue(): boolean; + readonly IGNORABLE_DEFAULT_VALUE: {}; + toJsonValue(): DataType; + autoHeight(): boolean; } declare function mergeExtra(e1: ExtraNodeType | undefined, e2: ExtraNodeType): ExtraNodeType; /** * maintainer a JSONValue, nothing else */ -declare abstract class SimpleAbstractComp extends AbstractComp< - any, - ViewReturn, - Node -> { - value: ViewReturn; - constructor(params: CompParams); - protected abstract getDefaultValue(): ViewReturn; - /** - * may override this to implement compatibility - */ - protected oldValueToNew(value?: ViewReturn): ViewReturn | undefined; - reduce(action: CompAction): this; - nodeWithoutCache(): SimpleNode; - exposingNode(): Node; - toJsonValue(): ViewReturn; -} -declare abstract class SimpleComp< - ViewReturn extends JSONValue -> extends SimpleAbstractComp { - getView(): ViewReturn; +declare abstract class SimpleAbstractComp extends AbstractComp> { + value: ViewReturn; + constructor(params: CompParams); + protected abstract getDefaultValue(): ViewReturn; + /** + * may override this to implement compatibility + */ + protected oldValueToNew(value?: ViewReturn): ViewReturn | undefined; + reduce(action: CompAction): this; + nodeWithoutCache(): SimpleNode; + exposingNode(): Node; + toJsonValue(): ViewReturn; +} +declare abstract class SimpleComp extends SimpleAbstractComp { + getView(): ViewReturn; } interface LocaleInfo { - locale: string; - language: string; - region?: string; + locale: string; + language: string; + region?: string; } declare const i18n: { - locale: string; - language: string; - region?: string | undefined; - locales: string[]; + locale: string; + language: string; + region?: string | undefined; + locales: string[]; }; declare function getValueByLocale(defaultValue: T, func: (info: LocaleInfo) => T | undefined): T; declare type AddDot = T extends "" ? "" : `.${T}`; declare type ValidKey = Exclude; -declare type NestedKey = ( - T extends object - ? { - [K in ValidKey]: `${K}${AddDot>}`; - }[ValidKey] - : "" -) extends infer D - ? Extract - : never; +declare type NestedKey = (T extends object ? { + [K in ValidKey]: `${K}${AddDot>}`; +}[ValidKey] : "") extends infer D ? Extract : never; declare type AddPrefix = { - [K in keyof T as K extends string ? `${P}${K}` : never]: T[K]; + [K in keyof T as K extends string ? `${P}${K}` : never]: T[K]; }; declare const globalMessages: AddPrefix<{}, "@">; declare type GlobalMessageKey = NestedKey; declare type VariableValue = string | number | boolean | Date | React.ReactNode; declare class Translator { - private readonly messages; - readonly language: string; - constructor(fileData: object, filterLocales?: string, locales?: string[]); - trans( - key: NestedKey | GlobalMessageKey, - variables?: Record - ): string; - transToNode( - key: NestedKey | GlobalMessageKey, - variables?: Record - ): - | string - | {} - | react.ReactElement> - | Iterable - | react.ReactPortal - | ( - | string - | {} - | react.ReactElement> - | Iterable - | react.ReactPortal - )[]; - private getMessage; + private readonly messages; + readonly language: string; + constructor(fileData: object, filterLocales?: string, locales?: string[]); + trans(key: NestedKey | GlobalMessageKey, variables?: Record): string; + transToNode(key: NestedKey | GlobalMessageKey, variables?: Record): string | {} | react.ReactElement> | Iterable | react.ReactPortal | (string | {} | react.ReactElement> | Iterable | react.ReactPortal)[]; + private getMessage; } declare function getI18nObjects(fileData: object, filterLocales?: string): I18nObjects; -export { - AbstractComp, - AbstractNode, - ActionContextType, - ActionExtraInfo, - ActionPriority, - AddChildAction, - BroadcastAction, - CachedNode, - ChangeValueAction, - CodeFunction, - CodeNode, - CodeNodeOptions, - CodeType, - Comp, - CompAction, - CompActionTypes, - CompConstructor, - CompParams, - ConstructorToComp, - ConstructorToDataType, - ConstructorToNodeType, - ConstructorToView, - CustomAction, - DispatchType, - EvalMethods, - ExecuteQueryAction, - ExtraActionType, - ExtraNodeType, - FetchCheckNode, - FetchInfo, - FetchInfoOptions, - FunctionNode, - MultiBaseComp, - MultiChangeAction, - MultiCompConstructor, - Node, - NodeToValue, - OptionalComp, - OptionalNodeType, - RecordConstructorToComp, - RecordConstructorToView, - RecordNode, - RecordNodeToValue, - RecordOptionalNodeToValue, - RenameAction, - ReplaceCompAction, - RouteByNameAction, - SimpleAbstractComp, - SimpleComp, - SimpleCompAction, - SimpleNode, - Translator, - TriggerModuleEventAction, - UpdateActionContextAction, - UpdateNodesV2Action, - ValueAndMsg, - WrapContextFn, - WrapContextNodeV2, - WrapNode, - addChildAction, - changeChildAction, - changeDependName, - changeValueAction, - clearMockWindow, - clearStyleEval, - customAction, - deferAction, - deleteCompAction, - dependingNodeMapEquals, - evalFunc, - evalFunctionResult, - evalNodeOrMinor, - evalPerfUtil, - evalStyle, - executeQueryAction, - fromRecord, - fromUnevaledValue, - fromValue, - fromValueWithCache, - getDynamicStringSegments, - getI18nObjects, - getValueByLocale, - i18n, - isBroadcastAction, - isChildAction, - isCustomAction, - isDynamicSegment, - isFetching, - isMyCustomAction, - mergeExtra, - multiChangeAction, - nodeIsRecord, - onlyEvalAction, - relaxedJSONToJSON, - renameAction, - replaceCompAction, - routeByNameAction, - transformWrapper, - triggerModuleEventAction, - unwrapChildAction, - updateActionContextAction, - updateNodesV2Action, - withFunction, - wrapActionExtraInfo, - wrapChildAction, - wrapContext, - wrapDispatch, -}; +export { AbstractComp, AbstractNode, ActionContextType, ActionExtraInfo, ActionPriority, AddChildAction, BroadcastAction, CachedNode, ChangeValueAction, CodeFunction, CodeNode, CodeNodeOptions, CodeType, Comp, CompAction, CompActionTypes, CompConstructor, CompParams, ConstructorToComp, ConstructorToDataType, ConstructorToNodeType, ConstructorToView, CustomAction, DispatchType, EvalMethods, ExecuteQueryAction, ExtraActionType, ExtraNodeType, FetchCheckNode, FetchInfo, FetchInfoOptions, FunctionNode, MultiBaseComp, MultiChangeAction, MultiCompConstructor, Node, NodeToValue, OptionalComp, OptionalNodeType, RecordConstructorToComp, RecordConstructorToView, RecordNode, RecordNodeToValue, RecordOptionalNodeToValue, RelaxedJsonParser, RenameAction, ReplaceCompAction, RouteByNameAction, SimpleAbstractComp, SimpleComp, SimpleCompAction, SimpleNode, Translator, TriggerModuleEventAction, UpdateActionContextAction, UpdateNodesV2Action, ValueAndMsg, WrapContextFn, WrapContextNodeV2, WrapNode, addChildAction, changeChildAction, changeDependName, changeValueAction, clearMockWindow, clearStyleEval, customAction, deferAction, deleteCompAction, dependingNodeMapEquals, evalFunc, evalFunctionResult, evalNodeOrMinor, evalPerfUtil, evalScript, evalStyle, executeQueryAction, fromRecord, fromUnevaledValue, fromValue, fromValueWithCache, getDynamicStringSegments, getI18nObjects, getValueByLocale, i18n, isBroadcastAction, isChildAction, isCustomAction, isDynamicSegment, isFetching, isMyCustomAction, mergeExtra, multiChangeAction, nodeIsRecord, onlyEvalAction, relaxedJSONToJSON, renameAction, replaceCompAction, routeByNameAction, transformWrapper, triggerModuleEventAction, unwrapChildAction, updateActionContextAction, updateNodesV2Action, withFunction, wrapActionExtraInfo, wrapChildAction, wrapContext, wrapDispatch }; diff --git a/client/packages/openblocks-core/lib/index.js b/client/packages/openblocks-core/lib/index.js index a4e95872..df951f93 100644 --- a/client/packages/openblocks-core/lib/index.js +++ b/client/packages/openblocks-core/lib/index.js @@ -98,49 +98,40 @@ function __spreadArray(to, from, pack) { return to.concat(ar || Array.prototype.slice.call(from)); } -function getCache(obj, fnName) { +function isEqualArgs(args, cacheArgs, equals) { + if (!cacheArgs) { + return false; + } + if (args.length === 0 && cacheArgs.length === 0) { + return true; + } + return (args.length === cacheArgs.length && + cacheArgs.every(function (arg, index) { var _a, _b; return (_b = (_a = equals === null || equals === void 0 ? void 0 : equals[index]) === null || _a === void 0 ? void 0 : _a.call(equals, arg, args[index])) !== null && _b !== void 0 ? _b : arg === args[index]; })); +} +function getCacheResult(thisObj, fnName, args, equals) { var _a; - return (_a = obj === null || obj === void 0 ? void 0 : obj.__cache) === null || _a === void 0 ? void 0 : _a[fnName]; + var cache = (_a = thisObj === null || thisObj === void 0 ? void 0 : thisObj.__cache) === null || _a === void 0 ? void 0 : _a[fnName]; + if (cache && isEqualArgs(args, cache.args, equals)) { + return cache.result; + } } -function createCache(obj, fnName, args) { - if (!obj.__cache) { - obj.__cache = {}; +function cache(fn, args, thisObj, fnName, equals) { + var result = getCacheResult(thisObj, fnName, args, equals); + if (result) { + return result.value; } - obj.__cache[fnName] = { + var cache = { id: Symbol("id"), args: args, - isInProgress: true, time: Date.now(), }; - return getCache(obj, fnName); -} -function genCache(fn, args, thisObj, fnName) { - var cache = createCache(thisObj, fnName, args); - var value = fn.apply(thisObj, args); - cache.isInProgress = false; - cache.value = value; -} -function read(thisObj, fnName) { - var cache = getCache(thisObj, fnName); - return cache && cache.value; -} -function hitCache(args, thisObj, fnName, equals) { - var cache = getCache(thisObj, fnName); - if (!cache || !cache.args) - return false; - if (args.length === 0 && cache.args.length === 0) - return true; - return cache.args.every(function (arg, index) { var _a, _b; return (_b = (_a = equals === null || equals === void 0 ? void 0 : equals[index]) === null || _a === void 0 ? void 0 : _a.call(equals, arg, args[index])) !== null && _b !== void 0 ? _b : arg === args[index]; }); -} -function isCyclic(thisObj, fnName) { - var cache = getCache(thisObj, fnName); - return cache && cache.isInProgress; -} -function cache(fn, args, thisObj, fnName, equals) { - if (!hitCache(args, thisObj, fnName, equals) && !isCyclic(thisObj, fnName)) { - genCache(fn, args, thisObj, fnName); + if (!thisObj.__cache) { + thisObj.__cache = {}; } - return read(thisObj, fnName); + thisObj.__cache[fnName] = cache; + var value = fn.apply(thisObj, args); + cache.result = { value: value }; + return value; } function memoized(equals) { return function (target, fnName, descriptor) { @@ -1041,23 +1032,24 @@ var loglevel = {exports: {}}; var log = loglevel.exports; -// global variables black list, forbidden to use -var blacklist = new Set([ +// global variables black list, forbidden to use in for jsQuery/jsAction +var functionBlacklist = new Set([ "top", "parent", "document", "location", "chrome", - "setTimeout", "fetch", - "setInterval", - "clearInterval", - "setImmediate", "XMLHttpRequest", "importScripts", "Navigator", "MutationObserver", ]); +var expressionBlacklist = new Set(__spreadArray(__spreadArray([], Array.from(functionBlacklist.values()), true), [ + "setTimeout", + "setInterval", + "setImmediate", +], false)); var globalVarNames = new Set(["window", "globalThis", "self", "global"]); function createBlackHole() { return new Proxy(function () { @@ -1079,12 +1071,14 @@ function createBlackHole() { }, }); } -function createMockWindow() { - var win = new Proxy({}, { +function createMockWindow(base, blacklist) { + if (blacklist === void 0) { blacklist = expressionBlacklist; } + var win = new Proxy(Object.assign({}, base), { has: function () { return true; }, set: function (target, p, newValue) { + console.info("set:", p, newValue); return Reflect.set(target, p, newValue); }, get: function (target, p) { @@ -1094,19 +1088,11 @@ function createMockWindow() { if (globalVarNames.has(p)) { return win; } - if (typeof p === "string" && blacklist.has(p)) { + if (typeof p === "string" && (blacklist === null || blacklist === void 0 ? void 0 : blacklist.has(p))) { log.log("[Sandbox] access ".concat(String(p), " on mock window, return mock object")); return createBlackHole(); } - var ret = Reflect.get(window, p); - if (typeof ret === "function" && !ret.prototype) { - return ret.bind(window); - } - // get DOM element by id, serializing may cause error - if (isDomElement(ret)) { - return undefined; - } - return ret; + return getPropertyFromNativeWindow(p); }, }); return win; @@ -1118,12 +1104,26 @@ function clearMockWindow() { function isDomElement(obj) { return obj instanceof Element || obj instanceof HTMLCollection; } +function getPropertyFromNativeWindow(prop) { + var ret = Reflect.get(window, prop); + if (typeof ret === "function" && !ret.prototype) { + return ret.bind(window); + } + // get DOM element by id, serializing may cause error + if (isDomElement(ret)) { + return undefined; + } + return ret; +} function proxySandbox(context, methods, options) { - var _a = (options || {}).disableLimit, disableLimit = _a === void 0 ? false : _a; + var _a = options || {}, _b = _a.disableLimit, disableLimit = _b === void 0 ? false : _b, _c = _a.scope, scope = _c === void 0 ? "expression" : _c; var isProtectedVar = function (key) { return key in context || key in (methods || {}) || globalVarNames.has(key); }; var cache = {}; + if (scope === "function") { + mockWindow = createMockWindow(mockWindow, functionBlacklist); + } return new Proxy(mockWindow, { has: function (target, p) { // proxy all variables @@ -1155,7 +1155,7 @@ function proxySandbox(context, methods, options) { return value; } if (disableLimit) { - return Reflect.get(window, p); + return getPropertyFromNativeWindow(p); } return Reflect.get(target, p, receiver); }, @@ -1419,6 +1419,7 @@ var DefaultParser = /** @class */ (function () { function evalJson(unevaledValue, context) { return new RelaxedJsonParser(unevaledValue, context).parse(); } +// this will also be used in node-service var RelaxedJsonParser = /** @class */ (function (_super) { __extends(RelaxedJsonParser, _super); function RelaxedJsonParser(unevaledValue, context) { @@ -1495,11 +1496,12 @@ var RelaxedJsonParser = /** @class */ (function (_super) { }(DefaultParser)); function evalFunction(unevaledValue, context, methods, isAsync) { try { - return new ValueAndMsg(function (args, runInHost) { + return new ValueAndMsg(function (args, runInHost, scope) { if (runInHost === void 0) { runInHost = false; } + if (scope === void 0) { scope = "function"; } return evalFunc(unevaledValue.startsWith("return") ? unevaledValue + "\n" - : "return ".concat(isAsync ? "async " : "", "function(){'use strict'; ").concat(unevaledValue, "\n}()"), args ? __assign(__assign({}, context), args) : context, methods, { disableLimit: runInHost }, isAsync); + : "return ".concat(isAsync ? "async " : "", "function(){'use strict'; ").concat(unevaledValue, "\n}()"), args ? __assign(__assign({}, context), args) : context, methods, { disableLimit: runInHost, scope: scope }, isAsync); }); } catch (err) { @@ -3201,8 +3203,8 @@ function updateNodesV2Action(value) { value: value, }; } -function wrapActionExtraInfo(action, extraCompInfos) { - return __assign(__assign({}, action), { extraInfo: { compInfos: extraCompInfos } }); +function wrapActionExtraInfo(action, extraInfos) { + return __assign(__assign({}, action), { extraInfo: __assign(__assign({}, action.extraInfo), extraInfos) }); } function deferAction(action) { return __assign(__assign({}, action), { priority: "defer" }); @@ -7521,4 +7523,4 @@ function getI18nObjects(fileData, filterLocales) { return getDataByLocale(fileData, "Obj", filterLocales).data; } -export { AbstractComp, AbstractNode, CachedNode, CodeNode, CompActionTypes, FetchCheckNode, FunctionNode, MultiBaseComp, RecordNode, SimpleAbstractComp, SimpleComp, SimpleNode, Translator, ValueAndMsg, WrapContextNodeV2, WrapNode, addChildAction, changeChildAction, changeDependName, changeValueAction, clearMockWindow, clearStyleEval, customAction, deferAction, deleteCompAction, dependingNodeMapEquals, evalFunc, evalFunctionResult, evalNodeOrMinor, evalPerfUtil, evalStyle, executeQueryAction, fromRecord, fromUnevaledValue, fromValue, fromValueWithCache, getDynamicStringSegments, getI18nObjects, getValueByLocale, i18n, isBroadcastAction, isChildAction, isCustomAction, isDynamicSegment, isFetching, isMyCustomAction, mergeExtra, multiChangeAction, nodeIsRecord, onlyEvalAction, relaxedJSONToJSON, renameAction, replaceCompAction, routeByNameAction, transformWrapper, triggerModuleEventAction, unwrapChildAction, updateActionContextAction, updateNodesV2Action, withFunction, wrapActionExtraInfo, wrapChildAction, wrapContext, wrapDispatch }; +export { AbstractComp, AbstractNode, CachedNode, CodeNode, CompActionTypes, FetchCheckNode, FunctionNode, MultiBaseComp, RecordNode, RelaxedJsonParser, SimpleAbstractComp, SimpleComp, SimpleNode, Translator, ValueAndMsg, WrapContextNodeV2, WrapNode, addChildAction, changeChildAction, changeDependName, changeValueAction, clearMockWindow, clearStyleEval, customAction, deferAction, deleteCompAction, dependingNodeMapEquals, evalFunc, evalFunctionResult, evalNodeOrMinor, evalPerfUtil, evalScript, evalStyle, executeQueryAction, fromRecord, fromUnevaledValue, fromValue, fromValueWithCache, getDynamicStringSegments, getI18nObjects, getValueByLocale, i18n, isBroadcastAction, isChildAction, isCustomAction, isDynamicSegment, isFetching, isMyCustomAction, mergeExtra, multiChangeAction, nodeIsRecord, onlyEvalAction, relaxedJSONToJSON, renameAction, replaceCompAction, routeByNameAction, transformWrapper, triggerModuleEventAction, unwrapChildAction, updateActionContextAction, updateNodesV2Action, withFunction, wrapActionExtraInfo, wrapChildAction, wrapContext, wrapDispatch }; diff --git a/client/packages/openblocks-core/package.json b/client/packages/openblocks-core/package.json index f50376bd..a55a33c2 100644 --- a/client/packages/openblocks-core/package.json +++ b/client/packages/openblocks-core/package.json @@ -1,6 +1,6 @@ { "name": "openblocks-core", - "version": "0.0.5", + "version": "0.0.7", "type": "module", "scripts": { "start": "rollup -c rollup.config.js --watch", diff --git a/client/packages/openblocks-core/src/actions/actionTypes.ts b/client/packages/openblocks-core/src/actions/actionTypes.ts index 999f7619..f4d5b327 100644 --- a/client/packages/openblocks-core/src/actions/actionTypes.ts +++ b/client/packages/openblocks-core/src/actions/actionTypes.ts @@ -52,7 +52,7 @@ export type ExtraActionType = | "recover" | "upgrade"; export type ActionExtraInfo = { - compInfos: { + compInfos?: { compName: string; compType: string; type: ExtraActionType; diff --git a/client/packages/openblocks-core/src/actions/actions.tsx b/client/packages/openblocks-core/src/actions/actions.tsx index e2a45985..c16b1aa0 100644 --- a/client/packages/openblocks-core/src/actions/actions.tsx +++ b/client/packages/openblocks-core/src/actions/actions.tsx @@ -1,25 +1,25 @@ -import { JSONValue } from "util/jsonTypes"; +import { CompConstructor } from "baseComps/comp"; import _ from "lodash"; +import { JSONValue } from "util/jsonTypes"; import { - CompAction, ActionContextType, ActionExtraInfo, AddChildAction, BroadcastAction, ChangeValueAction, + CompAction, CompActionTypes, CustomAction, ExecuteQueryAction, MultiChangeAction, RenameAction, + ReplaceCompAction, RouteByNameAction, SimpleCompAction, + TriggerModuleEventAction, UpdateActionContextAction, UpdateNodesV2Action, - TriggerModuleEventAction, - ReplaceCompAction, } from "./actionTypes"; -import { CompConstructor } from "baseComps/comp"; export function customAction(value: DataType): CustomAction { return { @@ -189,9 +189,9 @@ export function updateNodesV2Action(value: any): UpdateNodesV2Action { export function wrapActionExtraInfo( action: T, - extraCompInfos: ActionExtraInfo["compInfos"] + extraInfos: ActionExtraInfo ): T { - return { ...action, extraInfo: { compInfos: extraCompInfos } }; + return { ...action, extraInfo: { ...action.extraInfo, ...extraInfos } }; } export function deferAction(action: T): T { diff --git a/client/packages/openblocks-core/src/eval/index.tsx b/client/packages/openblocks-core/src/eval/index.tsx index fcf3750d..21dfec91 100644 --- a/client/packages/openblocks-core/src/eval/index.tsx +++ b/client/packages/openblocks-core/src/eval/index.tsx @@ -15,9 +15,9 @@ export type { EvalMethods, CodeType, CodeFunction } from "./types/evalTypes"; export { ValueAndMsg } from "./types/valueAndMsg"; export { relaxedJSONToJSON } from "./utils/relaxedJson"; export { getDynamicStringSegments, isDynamicSegment } from "./utils/segmentUtils"; -export { clearMockWindow, evalFunc } from "./utils/evalScript"; +export { clearMockWindow, evalFunc, evalScript } from "./utils/evalScript"; export { clearStyleEval, evalStyle } from "./utils/evalStyle"; -export { evalFunctionResult } from "./utils/string2Fn"; +export { evalFunctionResult, RelaxedJsonParser } from "./utils/string2Fn"; export { nodeIsRecord } from "./utils/nodeUtils"; export { changeDependName } from "./utils/evaluate"; export { FetchCheckNode } from "./fetchCheckNode"; diff --git a/client/packages/openblocks-core/src/eval/utils/evalScript.test.tsx b/client/packages/openblocks-core/src/eval/utils/evalScript.test.tsx index 3f81231b..4705bc1d 100644 --- a/client/packages/openblocks-core/src/eval/utils/evalScript.test.tsx +++ b/client/packages/openblocks-core/src/eval/utils/evalScript.test.tsx @@ -6,8 +6,14 @@ test("evalFunc", () => { expect(() => evalFunc("setTimeout(() => {});", {}, undefined, { disableLimit: true }) ).not.toThrow(); - expect(evalFunc("return window.setTimeout", {}, {})).not.toBe(window.setTimeout); - expect(evalFunc("return window.setTimeout", {}, {}, { disableLimit: true })).toBe( + expect(evalFunc("console.info(window.fetch);return window.fetch;", {}, {})).not.toBe( + window.fetch + ); + expect(evalFunc("return window.fetch", {}, {}, { disableLimit: true })).toBe(window.fetch); + expect(evalFunc("return window.seTimeout", {}, {}, { scope: "expression" })).not.toBe( + window.setTimeout + ); + expect(evalFunc("return window.setTimeout", {}, {}, { scope: "function" })).toBe( window.setTimeout ); }); @@ -112,10 +118,6 @@ describe("evalScript", () => { }); it("setPrototypeOf", () => { - // expect(() => evalScript("Object.setPrototypeOf(this, {})", {})).toThrow(); - // expect(() => evalScript("Object.setPrototypeOf(window, {})", {})).toThrow(); - expect(() => evalScript("Object.setPrototypeOf(setTimeout, {})", {})).toThrow(); - let context = { input1: { value: { test: 7 } } }; expect(() => evalScript("Object.setPrototypeOf(input1, {})", context)).toThrow(); expect(() => evalScript("Object.setPrototypeOf(input1.value, {})", context)).toThrow(); @@ -127,6 +129,9 @@ describe("evalScript", () => { expect(evalScript("window.crypto", {})).not.toBeUndefined(); expect(evalScript("this.crypto", {})).not.toBeUndefined(); expect(evalScript("crypto", {})).not.toBeUndefined(); + + evalFunc("window.a = 1;", {}); + expect(evalScript("window.a", {})).toBe(1); }); it("black hole is everything", () => { diff --git a/client/packages/openblocks-core/src/eval/utils/evalScript.tsx b/client/packages/openblocks-core/src/eval/utils/evalScript.tsx index 9edbc70e..b2348308 100644 --- a/client/packages/openblocks-core/src/eval/utils/evalScript.tsx +++ b/client/packages/openblocks-core/src/eval/utils/evalScript.tsx @@ -1,24 +1,27 @@ import { EvalMethods } from "../types/evalTypes"; import log from "loglevel"; -// global variables black list, forbidden to use -const blacklist = new Set([ +// global variables black list, forbidden to use in for jsQuery/jsAction +const functionBlacklist = new Set([ "top", "parent", "document", "location", "chrome", - "setTimeout", "fetch", - "setInterval", - "clearInterval", - "setImmediate", "XMLHttpRequest", "importScripts", "Navigator", "MutationObserver", ]); +const expressionBlacklist = new Set([ + ...Array.from(functionBlacklist.values()), + "setTimeout", + "setInterval", + "setImmediate", +]); + const globalVarNames = new Set(["window", "globalThis", "self", "global"]); export function createBlackHole(): any { @@ -45,39 +48,29 @@ export function createBlackHole(): any { ); } -function createMockWindow() { - const win: any = new Proxy( - {}, - { - has() { - return true; - }, - set(target, p, newValue) { - return Reflect.set(target, p, newValue); - }, - get(target, p) { - if (p in target) { - return Reflect.get(target, p); - } - if (globalVarNames.has(p)) { - return win; - } - if (typeof p === "string" && blacklist.has(p)) { - log.log(`[Sandbox] access ${String(p)} on mock window, return mock object`); - return createBlackHole(); - } - const ret = Reflect.get(window, p); - if (typeof ret === "function" && !ret.prototype) { - return ret.bind(window); - } - // get DOM element by id, serializing may cause error - if (isDomElement(ret)) { - return undefined; - } - return ret; - }, - } - ); +function createMockWindow(base?: object, blacklist: Set = expressionBlacklist) { + const win: any = new Proxy(Object.assign({}, base), { + has() { + return true; + }, + set(target, p, newValue) { + console.info("set:", p, newValue); + return Reflect.set(target, p, newValue); + }, + get(target, p) { + if (p in target) { + return Reflect.get(target, p); + } + if (globalVarNames.has(p)) { + return win; + } + if (typeof p === "string" && blacklist?.has(p)) { + log.log(`[Sandbox] access ${String(p)} on mock window, return mock object`); + return createBlackHole(); + } + return getPropertyFromNativeWindow(p); + }, + }); return win; } @@ -87,23 +80,45 @@ export function clearMockWindow() { mockWindow = createMockWindow(); } +export type SandboxScope = "function" | "expression"; + interface SandBoxOption { /** * disable all limit, like running in host */ disableLimit?: boolean; + + /** + * the scope this sandbox works in, which will use different blacklist + */ + scope?: SandboxScope; } function isDomElement(obj: any): boolean { return obj instanceof Element || obj instanceof HTMLCollection; } +function getPropertyFromNativeWindow(prop: PropertyKey) { + const ret = Reflect.get(window, prop); + if (typeof ret === "function" && !ret.prototype) { + return ret.bind(window); + } + // get DOM element by id, serializing may cause error + if (isDomElement(ret)) { + return undefined; + } + return ret; +} + function proxySandbox(context: any, methods?: EvalMethods, options?: SandBoxOption) { - const { disableLimit = false } = options || {}; + const { disableLimit = false, scope = "expression" } = options || {}; const isProtectedVar = (key: PropertyKey) => { return key in context || key in (methods || {}) || globalVarNames.has(key); }; const cache = {}; + if (scope === "function") { + mockWindow = createMockWindow(mockWindow, functionBlacklist); + } return new Proxy(mockWindow, { has(target, p) { // proxy all variables @@ -139,7 +154,7 @@ function proxySandbox(context: any, methods?: EvalMethods, options?: SandBoxOpti } if (disableLimit) { - return Reflect.get(window, p); + return getPropertyFromNativeWindow(p); } return Reflect.get(target, p, receiver); diff --git a/client/packages/openblocks-core/src/eval/utils/string2Fn.tsx b/client/packages/openblocks-core/src/eval/utils/string2Fn.tsx index 0cf9a6a0..8428de73 100644 --- a/client/packages/openblocks-core/src/eval/utils/string2Fn.tsx +++ b/client/packages/openblocks-core/src/eval/utils/string2Fn.tsx @@ -1,7 +1,7 @@ import { ValueAndMsg } from "../types/valueAndMsg"; import _ from "lodash"; import { getErrorMessage } from "./nodeUtils"; -import { evalFunc, evalScript } from "./evalScript"; +import { evalFunc, evalScript, SandboxScope } from "./evalScript"; import { getDynamicStringSegments, isDynamicSegment } from "./segmentUtils"; import { CodeFunction, CodeType, EvalMethods } from "../types/evalTypes"; import { relaxedJSONToJSON } from "./relaxedJson"; @@ -70,7 +70,8 @@ export function evalJson(unevaledValue: string, context: Record return new RelaxedJsonParser(unevaledValue, context).parse(); } -class RelaxedJsonParser extends DefaultParser { +// this will also be used in node-service +export class RelaxedJsonParser extends DefaultParser { constructor(unevaledValue: string, context: Record) { super(unevaledValue, context); this.evalIndexedObject = this.evalIndexedObject.bind(this); @@ -161,16 +162,21 @@ export function evalFunction( isAsync?: boolean ): ValueAndMsg { try { - return new ValueAndMsg((args?: Record, runInHost: boolean = false) => - evalFunc( - unevaledValue.startsWith("return") - ? unevaledValue + "\n" - : `return ${isAsync ? "async " : ""}function(){'use strict'; ${unevaledValue}\n}()`, - args ? { ...context, ...args } : context, - methods, - { disableLimit: runInHost }, - isAsync - ) + return new ValueAndMsg( + ( + args?: Record, + runInHost: boolean = false, + scope: SandboxScope = "function" + ) => + evalFunc( + unevaledValue.startsWith("return") + ? unevaledValue + "\n" + : `return ${isAsync ? "async " : ""}function(){'use strict'; ${unevaledValue}\n}()`, + args ? { ...context, ...args } : context, + methods, + { disableLimit: runInHost, scope }, + isAsync + ) ); } catch (err) { return new ValueAndMsg(() => {}, getErrorMessage(err)); diff --git a/client/packages/openblocks-core/src/util/memoize/memoize.test.tsx b/client/packages/openblocks-core/src/util/memoize/memoize.test.tsx index 7725e65b..a19de00b 100644 --- a/client/packages/openblocks-core/src/util/memoize/memoize.test.tsx +++ b/client/packages/openblocks-core/src/util/memoize/memoize.test.tsx @@ -1,13 +1,18 @@ import { shallowEqual } from "util/objectUtils"; import { memoized } from "."; -describe("memorize", () => { - it("memorize_with_shallowEqual", () => { +describe("memoized", () => { + it("shallowEqual", () => { class Test { + data: Record = {}; @memoized([shallowEqual]) test(record: Record>) { - console.info("cache: ", (this as any).__cache.test, " record: ", record); - return Math.random(); + const key = Object.values(record) + .flatMap((t) => Object.values(t)) + .join(""); + const num = this.data[key] ?? 0; + this.data[key] = num + 1; + return key + ":" + num; } } const test = new Test(); @@ -17,9 +22,26 @@ describe("memorize", () => { const rec1 = { a: sub1, b: sub2 }; const rec2 = { a: sub1, b: sub2 }; const rec3 = { c: sub3 }; - test.test(rec3); - test.test(rec1); - (test as any).__cache.test.value = 123; - expect(test.test(rec2)).toEqual(123); + expect(test.test(rec1)).toEqual("12:0"); + expect(test.test(rec2)).toEqual("12:0"); + expect(test.test(rec3)).toEqual("3:0"); + expect(test.test(rec2)).toEqual("12:1"); + }); + it("cyclic", () => { + class Test { + cyclic: boolean = false; + @memoized() + test(): any { + if (this.cyclic) { + return 5; + } + this.cyclic = true; + const ret = this.test(); + this.cyclic = false; + return ret + ":3"; + } + } + const test = new Test(); + expect(test.test()).toEqual("5:3"); }); }); diff --git a/client/packages/openblocks-core/src/util/memoize/memoized.tsx b/client/packages/openblocks-core/src/util/memoize/memoized.tsx index 48f0687a..4ad54831 100644 --- a/client/packages/openblocks-core/src/util/memoize/memoized.tsx +++ b/client/packages/openblocks-core/src/util/memoize/memoized.tsx @@ -1,50 +1,39 @@ -function getCache(obj: any, fnName: string) { - return obj?.__cache?.[fnName]; -} +type Cache = { + id: Symbol; + args: any[]; + time: number; + result?: { value: any }; +}; -function createCache(obj: any, fnName: string, args: any[]) { - if (!obj.__cache) { - obj.__cache = {}; +function isEqualArgs( + args: any[], + cacheArgs?: any[], + equals?: Array<(i1: any, i2: any) => boolean> +) { + if (!cacheArgs) { + return false; } - obj.__cache[fnName] = { - id: Symbol("id"), - args: args, - isInProgress: true, - time: Date.now(), - }; - return getCache(obj, fnName); -} - -function genCache(fn: (...args: any[]) => any, args: any[], thisObj: any, fnName: string) { - const cache = createCache(thisObj, fnName, args); - - const value = fn.apply(thisObj, args); - cache.isInProgress = false; - cache.value = value; -} - -function read(thisObj: any, fnName: string) { - const cache = getCache(thisObj, fnName); - return cache && cache.value; + if (args.length === 0 && cacheArgs.length === 0) { + return true; + } + return ( + args.length === cacheArgs.length && + cacheArgs.every( + (arg: any, index: number) => equals?.[index]?.(arg, args[index]) ?? arg === args[index] + ) + ); } -function hitCache( - args: any[], +function getCacheResult( thisObj: any, fnName: string, + args: any[], equals?: Array<(i1: any, i2: any) => boolean> ) { - const cache = getCache(thisObj, fnName); - if (!cache || !cache.args) return false; - if (args.length === 0 && cache.args.length === 0) return true; - return cache.args.every( - (arg: any, index: number) => equals?.[index]?.(arg, args[index]) ?? arg === args[index] - ); -} - -function isCyclic(thisObj: any, fnName: string) { - const cache = getCache(thisObj, fnName); - return cache && cache.isInProgress; + const cache: Cache | undefined = thisObj?.__cache?.[fnName]; + if (cache && isEqualArgs(args, cache.args, equals)) { + return cache.result; + } } function cache( @@ -54,10 +43,22 @@ function cache( fnName: string, equals?: Array<(i1: any, i2: any) => boolean> ) { - if (!hitCache(args, thisObj, fnName, equals) && !isCyclic(thisObj, fnName)) { - genCache(fn, args, thisObj, fnName); + const result = getCacheResult(thisObj, fnName, args, equals); + if (result) { + return result.value; } - return read(thisObj, fnName); + const cache: Cache = { + id: Symbol("id"), + args: args, + time: Date.now(), + }; + if (!thisObj.__cache) { + thisObj.__cache = {}; + } + thisObj.__cache[fnName] = cache; + const value = fn.apply(thisObj, args); + cache.result = { value }; + return value; } export function memoized(equals?: Array<(i1: any, i2: any) => boolean>) { diff --git a/client/packages/openblocks-design/src/components/CustomModal.tsx b/client/packages/openblocks-design/src/components/CustomModal.tsx index 6c98175c..887a8a48 100644 --- a/client/packages/openblocks-design/src/components/CustomModal.tsx +++ b/client/packages/openblocks-design/src/components/CustomModal.tsx @@ -177,7 +177,7 @@ function ModalFooter(props: { if (result && !!result.then) { return result.then(model && model.destroy).finally(() => setConfirmLoading(false)); } - setConfirmLoading(false) + setConfirmLoading(false); model && model.destroy(); }} autoFocus={autoFocusButton === "ok"} @@ -225,7 +225,7 @@ function CustomModalRender(props: CustomModalProps & ModalFuncProps) {
{props.children}
- {props.footer ? ( + {props.footer === null || props.footer ? ( props.footer ) : ( diff --git a/client/packages/openblocks-design/src/components/Dropdown.tsx b/client/packages/openblocks-design/src/components/Dropdown.tsx index d35c0e31..2cfa3256 100644 --- a/client/packages/openblocks-design/src/components/Dropdown.tsx +++ b/client/packages/openblocks-design/src/components/Dropdown.tsx @@ -6,6 +6,7 @@ import { ReactNode } from "react"; import styled from "styled-components"; import { CustomSelect } from "./customSelect"; import { EllipsisTextCss } from "./Label"; +import { TacoMarkDown } from "./markdown"; import { Tooltip, ToolTipLabel } from "./toolTip"; type ControlPlacement = "bottom" | "right" | "modal"; @@ -252,12 +253,30 @@ interface DropdownOptionLabelWithDescProps { description: string; } +const DropdownOptionDesc = styled.div` + font-size: 12px; + max-width: 600px; + white-space: normal; + .markdown-body { + font-size: 12px; + background-color: transparent; + p { + margin-bottom: 4px; + color: ${GreyTextColor}; + } + } +`; + export function DropdownOptionLabelWithDesc(props: DropdownOptionLabelWithDescProps) { const { label, description } = props; return (
{label}
- {description &&
{description}
} + {description && ( + + {description} + + )}
); } diff --git a/client/packages/openblocks-design/src/components/MarkdownTooltip.tsx b/client/packages/openblocks-design/src/components/MarkdownTooltip.tsx new file mode 100644 index 00000000..647bc99a --- /dev/null +++ b/client/packages/openblocks-design/src/components/MarkdownTooltip.tsx @@ -0,0 +1,25 @@ +import styled from "styled-components"; +import { TacoMarkDown } from "./markdown"; + +interface MarkdownTooltipProps { + children: string; +} + +const Wrapper = styled.div` + .markdown-body { + font-size: 12px; + background-color: transparent; + color: white; + p { + margin-bottom: 4px; + } + } +`; + +export default function MarkdownTooltip(props: MarkdownTooltipProps) { + return ( + + {props.children} + + ); +} diff --git a/client/packages/openblocks-design/src/components/audio.tsx b/client/packages/openblocks-design/src/components/audio.tsx index cae988fb..cb39aabb 100644 --- a/client/packages/openblocks-design/src/components/audio.tsx +++ b/client/packages/openblocks-design/src/components/audio.tsx @@ -1,5 +1,5 @@ import { CSSProperties } from "react"; -import { RefObject } from "react"; +import { Ref } from "react"; import ReactPlayer from "react-player"; import styled from "styled-components"; @@ -14,7 +14,7 @@ export function TacoAudio(props: { onEnded?: () => void; className?: string; style?: CSSProperties; - audioRef?: RefObject; + audioRef?: Ref; autoPlay?: boolean; loop?: boolean; }) { diff --git a/client/packages/openblocks-design/src/components/button.tsx b/client/packages/openblocks-design/src/components/button.tsx index 00148e37..08594256 100644 --- a/client/packages/openblocks-design/src/components/button.tsx +++ b/client/packages/openblocks-design/src/components/button.tsx @@ -153,6 +153,7 @@ const StyledAntdButton = styled(Button)<{ $buttonType: TacoButtonType }>` display: none; } } + & > svg { width: 12px; height: 12px; @@ -175,10 +176,11 @@ const TacoButton = forwardRef( return ( {props.loading ? ( - + buttonType === "delete" ? ( + + ) : ( + + ) ) : ( props.children )} diff --git a/client/packages/openblocks-design/src/components/control.tsx b/client/packages/openblocks-design/src/components/control.tsx index 923b7b89..08d03f34 100644 --- a/client/packages/openblocks-design/src/components/control.tsx +++ b/client/packages/openblocks-design/src/components/control.tsx @@ -98,6 +98,8 @@ const ChildrenWrapper = styled.div<{ layout: ControlLayout }>` const LastNode = styled.div` margin-left: 8px; + display: inline-flex; + align-items: center; .ant-select-selection-item { width: 40px; diff --git a/client/packages/openblocks-design/src/components/eventHandler.tsx b/client/packages/openblocks-design/src/components/eventHandler.tsx index 44fa8335..f7209ebe 100644 --- a/client/packages/openblocks-design/src/components/eventHandler.tsx +++ b/client/packages/openblocks-design/src/components/eventHandler.tsx @@ -51,6 +51,7 @@ const EventContent = styled.div` const EventTitle = styled.div` ${labelCss}; + line-height: normal; flex: 0 0 30%; margin-right: 8px; overflow: hidden; diff --git a/client/packages/openblocks-design/src/components/form.tsx b/client/packages/openblocks-design/src/components/form.tsx index 5a2162cb..147409ca 100644 --- a/client/packages/openblocks-design/src/components/form.tsx +++ b/client/packages/openblocks-design/src/components/form.tsx @@ -1,4 +1,4 @@ -import { Form, Input, InputProps, Radio, Select, InputNumber, InputNumberProps } from "antd"; +import { Form, Input, InputNumber, InputNumberProps, InputProps, Radio, Select } from "antd"; import { ReactNode } from "react"; import { CheckBox } from "./checkBox"; import { CustomSelect } from "./customSelect"; @@ -72,6 +72,7 @@ const FormCheckbox = styled(CheckBox)` const StartIcon = styled(Star)` margin-right: 4px; + flex-shrink: 0; `; const LabelDiv = styled.div<{ width?: number }>` display: flex; @@ -129,7 +130,7 @@ export const FormSection = styled.div<{ size?: FormSize }>` export interface FormItemProps extends AntdFormItemProps { disabled?: boolean; - label?: string; + label?: ReactNode; required?: boolean; placeholder?: string; help?: ReactNode; diff --git a/client/packages/openblocks-design/src/components/keyValueList.tsx b/client/packages/openblocks-design/src/components/keyValueList.tsx index 0f3edd16..576fe616 100644 --- a/client/packages/openblocks-design/src/components/keyValueList.tsx +++ b/client/packages/openblocks-design/src/components/keyValueList.tsx @@ -65,6 +65,11 @@ const AddBtn = styled(TacoButton)` &:hover ${AddIcon} g { stroke: #315efb; } + + > svg { + height: 8px; + width: 8px; + } `; export const KeyValueList = (props: { diff --git a/client/packages/openblocks-design/src/components/markdown.tsx b/client/packages/openblocks-design/src/components/markdown.tsx index efbc461f..0ef71f93 100644 --- a/client/packages/openblocks-design/src/components/markdown.tsx +++ b/client/packages/openblocks-design/src/components/markdown.tsx @@ -3,6 +3,7 @@ import ReactMarkdown from "react-markdown"; import rehypeRaw from "rehype-raw"; import rehypeSanitize, { defaultSchema } from "rehype-sanitize"; import remarkGfm from "remark-gfm"; +import { ReactMarkdownOptions } from "react-markdown/lib/react-markdown"; export const markdownCompCss = css` .markdown-body { @@ -30,10 +31,27 @@ export const markdownCompCss = css` } `; -export const TacoMarkDown = (props: { children: string }) => { +interface TacoMarkDownProps extends ReactMarkdownOptions { + children: string; +} + +const components = { + a: (props: any) => { + const { node, children, ...otherProps } = props; + return ( + + {children} + + ); + }, +}; + +export const TacoMarkDown = (props: TacoMarkDownProps) => { + const { children, ...otherProps } = props; return ( { ], ]} className="markdown-body" + {...otherProps} > - {props.children} + {children} ); }; diff --git a/client/packages/openblocks-design/src/components/toolTip.tsx b/client/packages/openblocks-design/src/components/toolTip.tsx index 9d280093..e47eacf8 100644 --- a/client/packages/openblocks-design/src/components/toolTip.tsx +++ b/client/packages/openblocks-design/src/components/toolTip.tsx @@ -147,14 +147,11 @@ const TooltipTitleWrapper = styled.span` `; export const UnderlineCss = css` - span { - background-image: linear-gradient(to right, #8b8fa3 50%, #fff 0%); - background-size: 4px 1px; - background-position: 5px bottom; - background-repeat: repeat-x; - padding-bottom: 2.5px !important; - line-height: 18px; - } + background-image: linear-gradient(to right, #8b8fa3 50%, #fff 0%); + background-size: 4px 1px; + background-position: 5px bottom; + background-repeat: repeat-x; + padding-bottom: 2.5px !important; `; function Tooltip(props: TooltipProps) { @@ -162,15 +159,14 @@ function Tooltip(props: TooltipProps) { } const Label = styled.div<{ border?: boolean }>` - ${(props) => { - if (props.border) { - return UnderlineCss; - } - }}; + span { + ${(props) => props.border && UnderlineCss} + line-height: ${(props) => props.border && "18px"}; + } ${labelCss}; - line-height: normal; margin: 0; width: fit-content; + line-height: 1; `; function ToolTipLabel( diff --git a/client/packages/openblocks-design/src/icons/icon-danger.svg b/client/packages/openblocks-design/src/icons/icon-danger.svg new file mode 100644 index 00000000..7c9fd7f6 --- /dev/null +++ b/client/packages/openblocks-design/src/icons/icon-danger.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/client/packages/openblocks-design/src/icons/icon-left-signature.svg b/client/packages/openblocks-design/src/icons/icon-left-signature.svg new file mode 100644 index 00000000..df4ef213 --- /dev/null +++ b/client/packages/openblocks-design/src/icons/icon-left-signature.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/client/packages/openblocks-design/src/icons/icon-manual.svg b/client/packages/openblocks-design/src/icons/icon-manual.svg new file mode 100644 index 00000000..1c474485 --- /dev/null +++ b/client/packages/openblocks-design/src/icons/icon-manual.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/packages/openblocks-design/src/icons/icon-query-data-responder.svg b/client/packages/openblocks-design/src/icons/icon-query-data-responder.svg new file mode 100644 index 00000000..515f8724 --- /dev/null +++ b/client/packages/openblocks-design/src/icons/icon-query-data-responder.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/client/packages/openblocks-design/src/icons/icon-query-snowflake.svg b/client/packages/openblocks-design/src/icons/icon-query-snowflake.svg new file mode 100644 index 00000000..6e378ca6 --- /dev/null +++ b/client/packages/openblocks-design/src/icons/icon-query-snowflake.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/client/packages/openblocks-design/src/icons/icon-signature.svg b/client/packages/openblocks-design/src/icons/icon-signature.svg new file mode 100644 index 00000000..d858bfd2 --- /dev/null +++ b/client/packages/openblocks-design/src/icons/icon-signature.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/packages/openblocks-design/src/icons/icon-sync-manual.svg b/client/packages/openblocks-design/src/icons/icon-sync-manual.svg new file mode 100644 index 00000000..5ad83150 --- /dev/null +++ b/client/packages/openblocks-design/src/icons/icon-sync-manual.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/client/packages/openblocks-design/src/icons/icon-undo.svg b/client/packages/openblocks-design/src/icons/icon-undo.svg new file mode 100644 index 00000000..e7296453 --- /dev/null +++ b/client/packages/openblocks-design/src/icons/icon-undo.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/client/packages/openblocks-design/src/icons/icon-warn.svg b/client/packages/openblocks-design/src/icons/icon-warn.svg new file mode 100644 index 00000000..628f5011 --- /dev/null +++ b/client/packages/openblocks-design/src/icons/icon-warn.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/client/packages/openblocks-design/src/icons/index.ts b/client/packages/openblocks-design/src/icons/index.ts index ad9de61c..ee6fcf48 100644 --- a/client/packages/openblocks-design/src/icons/index.ts +++ b/client/packages/openblocks-design/src/icons/index.ts @@ -185,6 +185,7 @@ export { ReactComponent as HomeListIcon } from "./icon-application-list.svg"; export { ReactComponent as HomeCardIcon } from "./icon-application-card.svg"; export { ReactComponent as CarouselCompIcon } from "./icon-carousel.svg"; export { ReactComponent as MysqlIcon } from "./icon-query-MySQL.svg"; +export { ReactComponent as DataResponderIcon } from "./icon-query-data-responder.svg"; export { ReactComponent as MongoIcon } from "./icon-query-MongoDB.svg"; export { ReactComponent as RestApiIcon } from "./icon-query-API.svg"; export { ReactComponent as DeleteApiIcon } from "./icon-query-delete.svg"; @@ -215,6 +216,7 @@ export { ReactComponent as CollapsibleContainerCompIcon } from "./icon-collapsib export { ReactComponent as ToggleButtonCompIcon } from "./icon-toggle-button.svg"; export { ReactComponent as GoogleSheetsIcon } from "./icon-query-GoogleSheets.svg"; export { ReactComponent as GraphqlIcon } from "./icon-query-Graphql.svg"; +export { ReactComponent as SnowflakeIcon } from "./icon-query-snowflake.svg"; export { ReactComponent as imageEditorIcon } from "./icon-insert-imageEditor.svg"; export { ReactComponent as HomeSettingsIcon } from "./icon-home-settings.svg"; export { ReactComponent as HomeSettingsActiveIcon } from "./icon-home-settings-active.svg"; @@ -259,3 +261,10 @@ export { ReactComponent as LeftOpen } from "./icon-left-comp-open.svg"; export { ReactComponent as LeftClose } from "./icon-left-comp-close.svg"; export { ReactComponent as ScannerIcon } from "./icon-scanner.svg"; export { ReactComponent as MaterialUploadIcon } from "./icon-material-upload.svg"; +export { ReactComponent as LeftSignature } from "./icon-left-signature.svg"; +export { ReactComponent as UndoIcon } from "./icon-undo.svg"; +export { ReactComponent as SignatureIcon } from "./icon-signature.svg"; +export { ReactComponent as ManualIcon } from "./icon-manual.svg"; +export { ReactComponent as WarnIcon } from "./icon-warn.svg"; +export { ReactComponent as SyncManualIcon } from "./icon-sync-manual.svg"; +export { ReactComponent as DangerIcon } from "icons/icon-danger.svg"; diff --git a/client/packages/openblocks-dev-utils/external.js b/client/packages/openblocks-dev-utils/external.js index f209de25..201394c3 100644 --- a/client/packages/openblocks-dev-utils/external.js +++ b/client/packages/openblocks-dev-utils/external.js @@ -59,7 +59,7 @@ export const getAllLibGlobalVarNames = () => { return ret; }; -export const libsImportCode = () => { +export const libsImportCode = (exclude = []) => { const importLines = []; const assignLines = []; libs.forEach((i) => { @@ -73,6 +73,10 @@ export const libsImportCode = () => { from = i.from ?? name; } + if (exclude.includes(name)) { + return; + } + const varName = getLibGlobalVarName(name); if (merge) { importLines.push(`import * as ${varName}_named_exports from '${from}';`); diff --git a/client/packages/openblocks-dev-utils/globalDepPlguin.js b/client/packages/openblocks-dev-utils/globalDepPlguin.js index e6fee474..72dd6d82 100644 --- a/client/packages/openblocks-dev-utils/globalDepPlguin.js +++ b/client/packages/openblocks-dev-utils/globalDepPlguin.js @@ -1,6 +1,6 @@ import { libsImportCode } from "./external.js"; -export function globalDepPlugin() { +export function globalDepPlugin(exclude = []) { const virtualModuleId = "virtual:globals"; return { name: "openblocks-global-plugin", @@ -11,7 +11,7 @@ export function globalDepPlugin() { }, load(id) { if (id === virtualModuleId) { - return libsImportCode(); + return libsImportCode(exclude); } }, }; diff --git a/client/packages/openblocks-sdk/dataSource.d.ts b/client/packages/openblocks-sdk/dataSource.d.ts index 5b23f7bf..c1b12269 100644 --- a/client/packages/openblocks-sdk/dataSource.d.ts +++ b/client/packages/openblocks-sdk/dataSource.d.ts @@ -6,7 +6,13 @@ type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( type CommonParamType = "textInput" | "numberInput" | "select"; type DataSourceParamType = CommonParamType | "password" | "checkbox" | "groupTitle"; -type ActionParamType = CommonParamType | "booleanInput" | "switch" | "file" | "jsonInput"; +type ActionParamType = + | CommonParamType + | "booleanInput" + | "switch" + | "file" + | "jsonInput" + | "sqlInput"; type ParamTypeToValueType = T extends | "textInput" @@ -57,6 +63,7 @@ interface ActionBooleanInputParamConfig extends ActionCommonParamConfig<"boolean interface ActionSwitchParamConfig extends ActionCommonParamConfig<"switch"> {} interface ActionFileParamConfig extends ActionCommonParamConfig<"file"> {} interface ActionJSONParamConfig extends ActionCommonParamConfig<"jsonInput"> {} +interface ActionSQLParamConfig extends ActionCommonParamConfig<"sqlInput"> {} interface ActionSelectParamConfig extends ActionCommonParamConfig<"select"> { options: readonly ParamOption[]; } @@ -130,6 +137,7 @@ type StringParamConfig = | DataSourcePasswordInputParamConfig | ActionTextInputParamConfig | ActionSelectParamConfig + | ActionSQLParamConfig | ActionFileParamConfig; type NumberParamConfig = DataSourceNumberInputParamConfig | ActionNumberInputParamConfig; @@ -176,6 +184,7 @@ export type ActionParamConfig = KeyedParamConfig< | ActionSwitchParamConfig | ActionFileParamConfig | ActionJSONParamConfig + | ActionSQLParamConfig >; export type ArrayParamConfig = readonly KeyedParamConfig< diff --git a/client/packages/openblocks-sdk/package.json b/client/packages/openblocks-sdk/package.json index 85674785..8f59727e 100644 --- a/client/packages/openblocks-sdk/package.json +++ b/client/packages/openblocks-sdk/package.json @@ -1,6 +1,6 @@ { "name": "openblocks-sdk", - "version": "0.0.34", + "version": "0.0.36", "type": "module", "files": [ "src", diff --git a/client/packages/openblocks-sdk/src/index.ts b/client/packages/openblocks-sdk/src/index.ts index 0a35cbf8..30e0aa2e 100644 --- a/client/packages/openblocks-sdk/src/index.ts +++ b/client/packages/openblocks-sdk/src/index.ts @@ -1,3 +1,6 @@ // @ts-nocheck import "../../openblocks/src/index.less"; +import * as sdk from "openblocks"; export * from "openblocks"; + +window.$openblocks_sdk = sdk; diff --git a/client/packages/openblocks-sdk/vite.config.mts b/client/packages/openblocks-sdk/vite.config.mts index 129bfecf..07875a6f 100644 --- a/client/packages/openblocks-sdk/vite.config.mts +++ b/client/packages/openblocks-sdk/vite.config.mts @@ -5,6 +5,7 @@ import svgrPlugin from "vite-plugin-svgr"; import path from "path"; import { ensureLastSlash } from "openblocks-dev-utils/util"; import { buildVars } from "openblocks-dev-utils/buildVars"; +import { globalDepPlugin } from "openblocks-dev-utils/globalDepPlguin"; const define = {}; buildVars.forEach(({ name, defaultValue }) => { @@ -72,6 +73,7 @@ export const viteConfig: UserConfig = { }, }, plugins: [ + globalDepPlugin(["openblocks-sdk"]), react({ babel: { parserOpts: { diff --git a/client/packages/openblocks/package.json b/client/packages/openblocks/package.json index 29a6c9c5..92840ddc 100644 --- a/client/packages/openblocks/package.json +++ b/client/packages/openblocks/package.json @@ -31,6 +31,7 @@ "@types/node": "^16.7.13", "@types/react": "^17.0.20", "@types/react-dom": "^17.0.9", + "@types/react-signature-canvas": "^1.0.2", "@types/react-test-renderer": "^18.0.0", "@types/react-virtualized": "^9.21.21", "ali-oss": "^6.17.1", @@ -75,6 +76,7 @@ "react-resize-detector": "^7.0.0", "react-router": "^5.2.1", "react-router-dom": "^5.3.0", + "react-signature-canvas": "^1.0.6", "react-test-renderer": "^18.1.0", "react-use": "^17.3.2", "really-relaxed-json": "^0.3.1", diff --git a/client/packages/openblocks/src/api/apiUtils.ts b/client/packages/openblocks/src/api/apiUtils.ts index a29bfc1c..73b33987 100644 --- a/client/packages/openblocks/src/api/apiUtils.ts +++ b/client/packages/openblocks/src/api/apiUtils.ts @@ -7,7 +7,7 @@ import { ERROR_500, SERVER_API_TIMEOUT_ERROR, } from "constants/messages"; -import { AUTH_BIND_URL, AUTH_LOGIN_URL, OAUTH_REDIRECT } from "constants/routesURL"; +import { AUTH_BIND_URL, OAUTH_REDIRECT } from "constants/routesURL"; import log from "loglevel"; import history from "util/history"; import { message } from "antd"; @@ -93,7 +93,7 @@ export const apiFailureResponseInterceptor = (error: any) => { } if (axios.isCancel(error)) { - return; + return Promise.reject(error); } if ( diff --git a/client/packages/openblocks/src/api/datasourceApi.ts b/client/packages/openblocks/src/api/datasourceApi.ts index 9077139d..9b927493 100644 --- a/client/packages/openblocks/src/api/datasourceApi.ts +++ b/client/packages/openblocks/src/api/datasourceApi.ts @@ -1,11 +1,11 @@ import { AxiosPromise } from "axios"; import Api from "./api"; import { GenericApiResponse } from "./apiResponses"; -import { DEFAULT_TEST_DATA_SOURCE_TIMEOUT_MS } from "../constants/apiConstants"; +import { DEFAULT_TEST_DATA_SOURCE_TIMEOUT_MS } from "constants/apiConstants"; import { Datasource as CEDatasource } from "../constants/datasourceConstants"; import { DatasourceType } from "@openblocks-ee/constants/queryConstants"; -import { JSONArray } from "../util/jsonTypes"; -import { AuthType, HttpOAuthGrantType } from "../pages/datasource/form/httpDatasourceForm"; +import { JSONArray } from "util/jsonTypes"; +import { AuthType, HttpOAuthGrantType } from "pages/datasource/form/httpDatasourceForm"; import { Datasource } from "@openblocks-ee/constants/datasourceConstants"; import { DataSourcePluginMeta } from "openblocks-sdk/dataSource"; @@ -33,6 +33,11 @@ export interface OracleConfig extends SQLConfig { usingSid: boolean; } +export interface SnowflakeConfig extends Omit { + schema: string; + extParams: Record; +} + export interface EsConfig { connectionString: string; username: string; @@ -113,7 +118,8 @@ export type DatasourceConfigType = | OAuthBasicConfig | EsConfig | OracleConfig - | GoogleSheetsConfig; + | GoogleSheetsConfig + | SnowflakeConfig; export interface DatasourceInfo { datasource: Datasource; diff --git a/client/packages/openblocks/src/api/queryApi.ts b/client/packages/openblocks/src/api/queryApi.ts index e43299d8..0a97dc3f 100644 --- a/client/packages/openblocks/src/api/queryApi.ts +++ b/client/packages/openblocks/src/api/queryApi.ts @@ -1,8 +1,8 @@ import Api, { HttpMethod } from "./api"; import { DEFAULT_EXECUTE_ACTION_TIMEOUT_MS } from "constants/apiConstants"; import { Property } from "types/entities/common"; -import { AxiosPromise } from "axios"; -import { JSONValue } from "../util/jsonTypes"; +import axios, { AxiosPromise, CancelTokenSource } from "axios"; +import { JSONValue } from "util/jsonTypes"; export type PaginationField = "PREV" | "NEXT"; @@ -19,6 +19,7 @@ export interface QueryExecuteRequest extends APIRequest { params?: Property[]; paginationField?: PaginationField; viewMode: boolean; + cancelPrevious?: boolean; // default true // for query library libraryQueryId?: string; @@ -47,12 +48,30 @@ export type QueryExecuteResponse = { export class QueryApi extends Api { static url = "v1/query"; + static queryExecuteCancelTokenSource: Record = {}; + static executeQuery( request: QueryExecuteRequest, timeout?: number ): AxiosPromise { + const queryId = (request.queryId ?? request.libraryQueryId)!; + const { cancelPrevious = true } = request; + + if (cancelPrevious) { + if (QueryApi.queryExecuteCancelTokenSource[queryId]) { + QueryApi.queryExecuteCancelTokenSource[queryId].cancel("cancel"); + } + QueryApi.queryExecuteCancelTokenSource[queryId] = axios.CancelToken.source(); + } else { + if (!QueryApi.queryExecuteCancelTokenSource[queryId]) { + // associate with same token if query can not cancel previous one + QueryApi.queryExecuteCancelTokenSource[queryId] = axios.CancelToken.source(); + } + } + return Api.post(QueryApi.url + "/execute", request, undefined, { timeout: timeout ? timeout + QUERY_TIMEOUT_BUFFER_MS : DEFAULT_EXECUTE_ACTION_TIMEOUT_MS, + cancelToken: QueryApi.queryExecuteCancelTokenSource[queryId].token, }); } } diff --git a/client/packages/openblocks/src/api/userApi.ts b/client/packages/openblocks/src/api/userApi.ts index 1cafaf3e..5be87ddb 100644 --- a/client/packages/openblocks/src/api/userApi.ts +++ b/client/packages/openblocks/src/api/userApi.ts @@ -13,7 +13,8 @@ interface FormLoginRequest extends CommonLoginParam { loginId: string; password: string; register: boolean; - source: string; + source?: string; + authId?: string; } export interface GetUserResponse extends ApiResponse { @@ -44,7 +45,7 @@ class UserApi extends Api { return Api.post(UserApi.formLoginURL, reqBody, queryParam); } - static bindEmail(request: { email: string }): AxiosPromise { + static bindEmail(request: { email: string, authId?: string }): AxiosPromise { return Api.post(UserApi.emailBindURL, undefined, request); } diff --git a/client/packages/openblocks/src/assets/images/index.tsx b/client/packages/openblocks/src/assets/images/index.tsx index 7a780dad..4ef1ae33 100644 --- a/client/packages/openblocks/src/assets/images/index.tsx +++ b/client/packages/openblocks/src/assets/images/index.tsx @@ -1,12 +1,12 @@ //window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches -import { ReactComponent as CELogo } from "./logo.svg"; -import { ReactComponent as CELogoWithName } from "./logo-with-name.svg"; +import { ReactComponent as LogoIcon } from "./logo.svg"; +import { ReactComponent as LogoWithNameIcon } from "./logo-with-name.svg"; export { default as favicon } from "./favicon.ico"; export const Logo = (props: { branding?: boolean }) => { - return ; + return ; }; export const LogoWithName = (props: { branding?: boolean }) => { - return ; + return ; }; diff --git a/client/packages/openblocks/src/base/codeEditor/clickCompName.tsx b/client/packages/openblocks/src/base/codeEditor/clickCompName.tsx index 863ee1c8..2a2f8e61 100644 --- a/client/packages/openblocks/src/base/codeEditor/clickCompName.tsx +++ b/client/packages/openblocks/src/base/codeEditor/clickCompName.tsx @@ -28,7 +28,7 @@ export function getJsCompNameRanges(js: string, exposingData?: Record { + js.replace(/[a-zA-Z_$][\w$]*/g, (s) => { const start = js.indexOf(s, position); if (start >= 0) { if (exposingData.hasOwnProperty(s) && (start === 0 || js[start - 1] !== ".")) { diff --git a/client/packages/openblocks/src/base/codeEditor/codeEditorTypes.tsx b/client/packages/openblocks/src/base/codeEditor/codeEditorTypes.tsx index 936c26ab..11f96fb4 100644 --- a/client/packages/openblocks/src/base/codeEditor/codeEditorTypes.tsx +++ b/client/packages/openblocks/src/base/codeEditor/codeEditorTypes.tsx @@ -34,6 +34,7 @@ export interface CodeEditorProps extends CodeEditorControlParams { // extension exposingData?: Record; + boostExposingData?: Record; enableClickCompName?: boolean; onChange?: (state: EditorState) => void; diff --git a/client/packages/openblocks/src/base/codeEditor/completion/exposingCompletionSource.tsx b/client/packages/openblocks/src/base/codeEditor/completion/exposingCompletionSource.tsx index 3e6d65e0..d4ac026a 100644 --- a/client/packages/openblocks/src/base/codeEditor/completion/exposingCompletionSource.tsx +++ b/client/packages/openblocks/src/base/codeEditor/completion/exposingCompletionSource.tsx @@ -1,7 +1,8 @@ import { AutocompleteDataType } from "base/codeEditor/completion/ternServer"; import _ from "lodash"; +import { evalScript } from "openblocks-core"; import { checkCursorInBinding } from "../codeEditorUtils"; -import { Completion, CompletionContext, CompletionResult } from "../codeMirror"; +import { Completion, CompletionContext, CompletionResult, EditorView } from "../codeMirror"; import { CompletionSource } from "./completion"; const PRIORITY_PROPS = ["value", "selectedRow", "data", "text"]; @@ -9,6 +10,7 @@ const PRIORITY_FUNCTIONS = ["setValue", "setData"]; export class ExposingCompletionSource extends CompletionSource { data?: Record; + boostExposingData?: Record; completionSource( context: CompletionContext ): CompletionResult | Promise | null { @@ -16,7 +18,9 @@ export class ExposingCompletionSource extends CompletionSource { if (this.data === undefined || !checkCursorInBinding(context, this.isFunction)) { return null; } - const matchPath = context.matchBefore(/(\w+(\[\s*\d+\s*\])*\.)*\w*/); + const matchPath = context.matchBefore( + /(?:[A-Za-z_$][\w$]*(?:\[\s*(?:\d+|(["'])(?:[^\1\\]|\\.)*?\1)\s*\])*\.)*(?:[A-Za-z_$][\w$]*)?/ + ); if (!matchPath) { return null; } @@ -34,11 +38,32 @@ export class ExposingCompletionSource extends CompletionSource { const keys = Object.keys(currentData).filter((key) => key.startsWith(prefix)); const options = keys.map((key) => { const dataType = getDataType(currentData[key]); + const isBoost = offset === 0 && this.boostExposingData?.hasOwnProperty(key); const result: Completion = { type: _.lowerCase(dataType), label: key, detail: _.capitalize(dataType), - boost: PRIORITY_PROPS.includes(key) ? 3 : PRIORITY_FUNCTIONS.includes(key) ? 2 : 1, + boost: isBoost + ? 20 + : PRIORITY_PROPS.includes(key) + ? 3 + : PRIORITY_FUNCTIONS.includes(key) + ? 2 + : 1, + apply: + offset === 0 + ? undefined + : (view: EditorView, c: Completion, from: number, to: number) => { + view.dispatch({ + changes: { + from: from - 1, + to: to, + insert: key.match(/^[A-Za-z_$][\w$]*$/) + ? `.${key}` + : `['${key.replace(/[\\']/g, (c) => "\\" + c)}']`, + }, + }); + }, }; return result; }); @@ -55,30 +80,18 @@ export class ExposingCompletionSource extends CompletionSource { } export function getDataInfo(data: Record, path: string) { - let currentData: any = data; - let offset: number = 0; - for (let i = 0; i < path.length; ++i) { - switch (path[i]) { - case ".": - case "[": - case "]": - if (offset < i) { - currentData = currentData[path.slice(offset, i).trim()]; - if (!currentData || typeof currentData !== "object") { - return; - } - } - offset = i + 1; - if (path[i] === "." && Array.isArray(currentData)) { - return; - } - if (path[i] === "[" && !Array.isArray(currentData)) { - return; - } - break; + const pos = path.lastIndexOf("."); + if (pos < 0) { + return [data, 0, path]; + } + try { + const value = evalScript(path.slice(0, pos), data); + if (typeof value === "object" && value && !Array.isArray(value)) { + return [value, pos + 1, path.slice(pos + 1)]; } + } catch (e) { + return; } - return [currentData, offset, path.slice(offset)]; } function getDataType(data: unknown): string { diff --git a/client/packages/openblocks/src/base/codeEditor/extensions.tsx b/client/packages/openblocks/src/base/codeEditor/extensions.tsx index 22e7a813..014c74c2 100644 --- a/client/packages/openblocks/src/base/codeEditor/extensions.tsx +++ b/client/packages/openblocks/src/base/codeEditor/extensions.tsx @@ -318,7 +318,7 @@ export function useChangeExtension( } export function useCompletionSources(props: CodeEditorProps) { - const { language, codeType, exposingData, enableMetaCompletion } = props; + const { language, codeType, exposingData, boostExposingData, enableMetaCompletion } = props; const context = useContext(QueryContext); // FIXME: temporarily handle, expect to delete after the backend supports eval // auto-completion for comp exposing const exposingSource = useMemo(() => new ExposingCompletionSource(), []); @@ -332,7 +332,8 @@ export function useCompletionSources(props: CodeEditorProps) { useEffect(() => { exposingSource.data = exposingData; - }, [exposingSource, exposingData]); + exposingSource.boostExposingData = boostExposingData; + }, [exposingSource, exposingData, boostExposingData]); const sqlMetaData = useContext(MetaDataContext); useEffect(() => { diff --git a/client/packages/openblocks/src/components/DataSourceIcon.tsx b/client/packages/openblocks/src/components/DataSourceIcon.tsx index f00d279f..4aea4fcb 100644 --- a/client/packages/openblocks/src/components/DataSourceIcon.tsx +++ b/client/packages/openblocks/src/components/DataSourceIcon.tsx @@ -1,7 +1,8 @@ -import { BottomResType, getBottomResIcon } from "@openblocks-ee/util/bottomResUtils"; +import { getBottomResIcon } from "@openblocks-ee/util/bottomResUtils"; import { HttpMethod } from "api/api"; import { useSelector } from "react-redux"; import { getDataSource } from "redux/selectors/datasourceSelectors"; +import { BottomResType } from "util/bottomResUtils"; export default function DataSourceIcon(props: { dataSourceType: BottomResType; @@ -11,5 +12,10 @@ export default function DataSourceIcon(props: { const { dataSourceType, size, httpMethod } = props; const datasourceList = useSelector(getDataSource); const datasource = datasourceList.find((i) => i.datasource.type === dataSourceType); - return getBottomResIcon(dataSourceType, size, datasource?.datasource.pluginDefinition?.icon, httpMethod); + return getBottomResIcon( + dataSourceType, + size, + datasource?.datasource.pluginDefinition?.icon, + httpMethod + ); } diff --git a/client/packages/openblocks/src/components/KeyValueItemList.tsx b/client/packages/openblocks/src/components/KeyValueItemList.tsx index e03baa37..71fb6dd5 100644 --- a/client/packages/openblocks/src/components/KeyValueItemList.tsx +++ b/client/packages/openblocks/src/components/KeyValueItemList.tsx @@ -25,6 +25,7 @@ const ListWrapper = styled.div` .list-header { display: flex; font-size: 13px; + line-height: 1; margin-bottom: 8px; align-items: center; justify-content: space-between; diff --git a/client/packages/openblocks/src/components/PageSkeleton.tsx b/client/packages/openblocks/src/components/PageSkeleton.tsx index efac89db..aa728c54 100644 --- a/client/packages/openblocks/src/components/PageSkeleton.tsx +++ b/client/packages/openblocks/src/components/PageSkeleton.tsx @@ -6,10 +6,7 @@ import { Logo, LogoWithName } from "@openblocks-ee/assets/images"; import styled from "styled-components"; import { useSelector } from "react-redux"; import { getBrandingConfig, getSystemConfigFetching } from "../redux/selectors/configSelectors"; -import { getUser, isFetchUserFinished } from "../redux/selectors/usersSelectors"; -import { matchPath } from "react-router"; -import { AppPathParams } from "../constants/applicationConstants"; -import { APP_EDITOR_URL } from "../constants/routesURL"; +import { isFetchUserFinished } from "../redux/selectors/usersSelectors"; import { CSSProperties } from "react"; interface IProps { diff --git a/client/packages/openblocks/src/components/ResCreatePanel.tsx b/client/packages/openblocks/src/components/ResCreatePanel.tsx index b96c4756..b82a34f3 100644 --- a/client/packages/openblocks/src/components/ResCreatePanel.tsx +++ b/client/packages/openblocks/src/components/ResCreatePanel.tsx @@ -102,6 +102,7 @@ const ResButton = (props: { | Partial | BottomResTypeEnum.TempState | BottomResTypeEnum.Transformer + | BottomResTypeEnum.DateResponder | Datasource; }) => { let label = ""; @@ -117,6 +118,9 @@ const ResButton = (props: { if (props.identifier === BottomResTypeEnum.TempState) { label = trans("query.tempState"); handleClick = () => props.onSelect(BottomResTypeEnum.TempState); + } else if (props.identifier === BottomResTypeEnum.DateResponder) { + label = trans("query.dataResponder"); + handleClick = () => props.onSelect(BottomResTypeEnum.DateResponder); } else if (props.identifier === BottomResTypeEnum.Transformer) { label = trans("query.transformer"); handleClick = () => props.onSelect(BottomResTypeEnum.Transformer); @@ -248,6 +252,11 @@ export function ResCreatePanel(props: ResCreateModalProps) { identifier={BottomResTypeEnum.Transformer} onSelect={onSelect} /> + diff --git a/client/packages/openblocks/src/components/Table.tsx b/client/packages/openblocks/src/components/Table.tsx index 8bb091ed..82b5abb5 100644 --- a/client/packages/openblocks/src/components/Table.tsx +++ b/client/packages/openblocks/src/components/Table.tsx @@ -23,6 +23,10 @@ export const Table = styled(AntdTable)` padding-bottom: 9px; border: none; //border-bottom: 1px solid #8b8fa3; // divider line for the table header + padding: 3px 12px; + height: 36px; + font-size: 14px; + font-weight: 400; } .ant-table-thead > tr > th::before { @@ -105,7 +109,7 @@ export const Table = styled(AntdTable)` .ant-table-ping-right:not(.ant-table-has-fix-right) .ant-table-container::after { box-shadow: none; } - .ant-table-thead > tr > th, .ant-table-tbody > tr > td { + .ant-table-tbody > tr > td { padding: 16px 12px; } `; diff --git a/client/packages/openblocks/src/components/layout/SubSideBar.tsx b/client/packages/openblocks/src/components/layout/SubSideBar.tsx index de1cdab3..c8af9a5e 100644 --- a/client/packages/openblocks/src/components/layout/SubSideBar.tsx +++ b/client/packages/openblocks/src/components/layout/SubSideBar.tsx @@ -9,16 +9,17 @@ const Wrapper = styled.div` border-right: 1px solid #f0f0f0; box-sizing: border-box; border-radius: 2px; - padding: 32px 16px 0 16px; + padding: 26px 16px 0 16px; h2 { font-weight: 500; font-size: 20px; color: #222222; - margin: 0 0 20px 12px; + margin: 0 0 20px 20px; } .ant-menu-inline .ant-menu-item { margin: 4px 0; + padding: 10px 20px !important; } .ant-menu-item:hover, .ant-menu-item-selected { border-radius: 4px; diff --git a/client/packages/openblocks/src/components/table/EditableCell.tsx b/client/packages/openblocks/src/components/table/EditableCell.tsx index 35838c81..03ea2925 100644 --- a/client/packages/openblocks/src/components/table/EditableCell.tsx +++ b/client/packages/openblocks/src/components/table/EditableCell.tsx @@ -46,7 +46,10 @@ export const SizeWrapper = styled.div<{ $size?: string }>` props.$size && `padding: ${ props.$size === "small" ? "8.5px 8px" : props.$size === "large" ? "16.5px 16px" : "12.5px 8px" - }`} + }; + line-height: 21px; + height: ${props.$size === "small" ? "39px" : props.$size === "large" ? "55px" : "47px"}; + `} `; const BorderDiv = styled.div` diff --git a/client/packages/openblocks/src/comps/comps/appSettingsComp.tsx b/client/packages/openblocks/src/comps/comps/appSettingsComp.tsx index a480847d..48a8c209 100644 --- a/client/packages/openblocks/src/comps/comps/appSettingsComp.tsx +++ b/client/packages/openblocks/src/comps/comps/appSettingsComp.tsx @@ -13,10 +13,10 @@ import { GreyTextColor } from "constants/style"; import { Divider } from "antd"; import { THEME_SETTING } from "constants/routesURL"; import { CustomShortcutsComp } from "./customShortcutsComp"; +import { DEFAULT_THEMEID } from "comps/utils/themeUtil"; const TITLE = trans("appSetting.title"); const USER_DEFINE = "__USER_DEFINE"; -const DEFAULT_THEMEID = "default"; const ItemSpan = styled.span` display: inline-flex; diff --git a/client/packages/openblocks/src/comps/comps/buttonComp/buttonComp.tsx b/client/packages/openblocks/src/comps/comps/buttonComp/buttonComp.tsx index 77780c3e..c446cfa9 100644 --- a/client/packages/openblocks/src/comps/comps/buttonComp/buttonComp.tsx +++ b/client/packages/openblocks/src/comps/comps/buttonComp/buttonComp.tsx @@ -17,6 +17,8 @@ import { CommonNameConfig, NameConfig, withExposingConfigs } from "../../generat import { IForm } from "../formComp/formDataConstants"; import { SimpleNameComp } from "../simpleNameComp"; import { Button100, ButtonCompWrapper, ButtonStyleControl } from "./buttonCompConstants"; +import { RefControl } from "comps/controls/refControl"; +import { refMethods } from "comps/generators/withMethodExposing"; const FormLabel = styled(CommonBlueLabel)` font-size: 13px; @@ -119,12 +121,14 @@ const ButtonTmpComp = (function () { prefixIcon: IconControl, suffixIcon: IconControl, style: ButtonStyleControl, + viewRef: RefControl, }; return new UICompBuilder(childrenMap, (props) => ( {(editorState) => ( {children.style.getPropertyView()} )) + .setExposeMethodConfigs(refMethods(["focus", "blur", "click"])) .build(); })(); diff --git a/client/packages/openblocks/src/comps/comps/buttonComp/linkComp.tsx b/client/packages/openblocks/src/comps/comps/buttonComp/linkComp.tsx index 5a872ff4..f9c5f32e 100644 --- a/client/packages/openblocks/src/comps/comps/buttonComp/linkComp.tsx +++ b/client/packages/openblocks/src/comps/comps/buttonComp/linkComp.tsx @@ -18,6 +18,8 @@ import { import { trans } from "i18n"; import { IconControl } from "comps/controls/iconControl"; import { hasIcon } from "comps/utils"; +import { RefControl } from "comps/controls/refControl"; +import { refMethods } from "comps/generators/withMethodExposing"; const Link = styled(Button)<{ $style: LinkStyleType }>` ${(props) => `color: ${props.$style.text};`} @@ -59,6 +61,7 @@ const LinkTmpComp = (function () { style: migrateOldData(styleControl(LinkStyle), fixOldData), prefixIcon: IconControl, suffixIcon: IconControl, + viewRef: RefControl, }; return new UICompBuilder(childrenMap, (props) => { // chrome86 bug: button children should not contain only empty span @@ -66,6 +69,7 @@ const LinkTmpComp = (function () { return ( ); }) + .setExposeMethodConfigs(refMethods(["focus", "blur", "click"])) .build(); })(); diff --git a/client/packages/openblocks/src/comps/comps/buttonComp/scannerComp.tsx b/client/packages/openblocks/src/comps/comps/buttonComp/scannerComp.tsx index be2c5858..58aa9b8f 100644 --- a/client/packages/openblocks/src/comps/comps/buttonComp/scannerComp.tsx +++ b/client/packages/openblocks/src/comps/comps/buttonComp/scannerComp.tsx @@ -15,6 +15,8 @@ import React, { Suspense, useEffect, useRef, useState } from "react"; import { arrayStringExposingStateControl } from "comps/controls/codeStateControl"; import { BoolControl } from "comps/controls/boolControl"; import { ItemType } from "antd/lib/menu/hooks/useItems"; +import { RefControl } from "comps/controls/refControl"; +import { refMethods } from "comps/generators/withMethodExposing"; const Error = styled.div` color: #f5222d; @@ -26,19 +28,29 @@ const Error = styled.div` `; const Wrapper = styled.div` - video { - height: 250px; + video, + .ant-skeleton { + height: 400px; + max-height: 70vh; position: relative; object-fit: cover; background-color: #000; } + .ant-skeleton { + h3, + li { + background-color: transparent; + } + } `; const CustomModalStyled = styled(CustomModal)` + top: 10vh; .react-draggable { max-width: 100%; + width: 500px; } -` +`; const BarcodeScannerComponent = React.lazy(() => import("react-qr-barcode-scanner")); @@ -52,6 +64,7 @@ const ScannerTmpComp = (function () { onEvent: ScannerEventHandlerControl, disabled: BoolCodeControl, style: styleControl(ButtonStyle), + viewRef: RefControl, }; return new UICompBuilder(childrenMap, (props) => { const [showModal, setShowModal] = useState(false); @@ -61,13 +74,13 @@ const ScannerTmpComp = (function () { }); const [modeList, setModeList] = useState([]); const [dropdownShow, setDropdownShow] = useState(false); - const [success, setSuccess] = useState(false) + const [success, setSuccess] = useState(false); - useEffect(() =>{ + useEffect(() => { if (!showModal && success) { props.onEvent("success"); } - }, [success, showModal]) + }, [success, showModal]); const continuousValue = useRef([]); @@ -75,7 +88,9 @@ const ScannerTmpComp = (function () { if (!!result) { if (props.continuous) { continuousValue.current = [...continuousValue.current, result.text]; - const val = props.uniqueData ? [...new Set(continuousValue.current)] : continuousValue.current; + const val = props.uniqueData + ? [...new Set(continuousValue.current)] + : continuousValue.current; props.data.onChange(val); props.onEvent("success"); } else { @@ -110,6 +125,7 @@ const ScannerTmpComp = (function () { return ( { @@ -134,51 +150,52 @@ const ScannerTmpComp = (function () { > {!!errMessage ? ( {errMessage} - ) : (showModal && ( - - }> - - -
{ - setDropdownShow(false); - }} - > - setDropdownShow(value)} - overlay={ - - setVideoConstraints({ ...videoConstraints, deviceId: value.key }) - } - /> - } + ) : ( + showModal && ( + + }> + + +
{ + setDropdownShow(false); + }} > - - -
-
- ))} + + +
+
+ ) + )}
); @@ -205,6 +222,7 @@ const ScannerTmpComp = (function () { ); }) + .setExposeMethodConfigs(refMethods(["focus", "blur", "click"])) .build(); })(); diff --git a/client/packages/openblocks/src/comps/comps/buttonComp/toggleButtonComp.tsx b/client/packages/openblocks/src/comps/comps/buttonComp/toggleButtonComp.tsx index 7be6a747..27da645d 100644 --- a/client/packages/openblocks/src/comps/comps/buttonComp/toggleButtonComp.tsx +++ b/client/packages/openblocks/src/comps/comps/buttonComp/toggleButtonComp.tsx @@ -18,6 +18,8 @@ import { booleanExposingStateControl } from "comps/controls/codeStateControl"; import { ToggleButtonStyle } from "comps/controls/styleControlConstants"; import { styleControl } from "comps/controls/styleControl"; import { BoolControl } from "comps/controls/boolControl"; +import { RefControl } from "comps/controls/refControl"; +import { refMethods } from "comps/generators/withMethodExposing"; const IconWrapper = styled.div` display: flex; @@ -53,6 +55,7 @@ const ToggleTmpComp = (function () { alignment: AlignWithStretchControl, style: styleControl(ToggleButtonStyle), showBorder: withDefault(BoolControl, true), + viewRef: RefControl, }; return new UICompBuilder(childrenMap, (props) => { const text = props.showText @@ -65,6 +68,7 @@ const ToggleTmpComp = (function () { showBorder={props.showBorder} > )) + .setExposeMethodConfigs(refMethods(["focus", "blur", "click"])) .build(); })(); diff --git a/client/packages/openblocks/src/comps/comps/containerComp/containerView.tsx b/client/packages/openblocks/src/comps/comps/containerComp/containerView.tsx index 73e97921..31828eba 100644 --- a/client/packages/openblocks/src/comps/comps/containerComp/containerView.tsx +++ b/client/packages/openblocks/src/comps/comps/containerComp/containerView.tsx @@ -1,21 +1,10 @@ -import { - CompAction, - ActionExtraInfo, - changeChildAction, - changeValueAction, - deferAction, - deleteCompAction, - multiChangeAction, - wrapActionExtraInfo, - wrapChildAction, -} from "openblocks-core"; -import { Comp, DispatchType, RecordConstructorToView, wrapDispatch } from "openblocks-core"; import { EditorContext, EditorState } from "comps/editorState"; import { sameTypeMap, stateComp, valueComp } from "comps/generators"; import { addMapChildAction, addMapCompChildAction } from "comps/generators/sameTypeMap"; import { hookCompCategory, HookCompType } from "comps/hooks/hookCompTypes"; import { UICompLayoutInfo, uiCompRegistry, UICompType } from "comps/uiCompRegistry"; import { genRandomKey } from "comps/utils/idGenerator"; +import { parseCompType } from "comps/utils/remote"; import { DEFAULT_POSITION_PARAMS, draggingUtils, @@ -35,10 +24,26 @@ import { DEFAULT_ROW_HEIGHT, } from "layout/calculateUtils"; import _ from "lodash"; +import { + ActionExtraInfo, + changeChildAction, + changeValueAction, + Comp, + CompAction, + deferAction, + deleteCompAction, + DispatchType, + multiChangeAction, + RecordConstructorToView, + wrapActionExtraInfo, + wrapChildAction, + wrapDispatch, +} from "openblocks-core"; import React, { DragEvent, HTMLAttributes, MouseEvent, + ReactElement, ReactNode, RefObject, useCallback, @@ -48,14 +53,12 @@ import React, { useRef, useState, } from "react"; -import { ReactElement } from "react"; import { useResizeDetector } from "react-resize-detector"; import styled from "styled-components"; +import { checkIsMobile } from "util/commonUtils"; import { ExternalEditorContext } from "util/context/ExternalEditorContext"; import { selectCompModifierKeyPressed } from "util/keyUtils"; import { defaultLayout, GridItemComp, GridItemDataType } from "../gridItemComp"; -import { checkIsMobile } from "util/commonUtils"; -import { parseCompType } from "comps/utils/remote"; const childrenMap = { layout: valueComp({}), @@ -161,7 +164,7 @@ const onLayoutChange = ( onLayoutChange?.(newLayout); // Purely changing the layout does not need to eval // log.debug("layout: onLayoutChange. currentLayout: ", currentLayout, " newLayout: ", newLayout); - dispatch(wrapActionExtraInfo(changeChildAction("layout", newLayout), compInfos)); + dispatch(wrapActionExtraInfo(changeChildAction("layout", newLayout), { compInfos })); }; const onFlyDrop = (layout: Layout, items: Layout, dispatch: DispatchType) => { @@ -182,10 +185,7 @@ const onFlyDrop = (layout: Layout, items: Layout, dispatch: DispatchType) => { // 2. Add a new Comp for (const [key, item] of Object.entries(items)) { if (item.comp) { - const newItem = item.comp.changeDispatch( - wrapDispatch(wrapDispatch(dispatch, "items"), key) - ); - dispatch(deferAction(wrapChildAction("items", addMapCompChildAction(key, newItem)))); + dispatch(deferAction(wrapChildAction("items", addMapCompChildAction(key, item.comp)))); } } dispatch(deferAction(changeChildAction("layout", layout))); @@ -211,7 +211,7 @@ const onDrop = ( editorState .getHooksComp() .pushAction({ name: compName, compType: compType as HookCompType }), - [{ compName: compName, compType: compType, type: "add" }] + { compInfos: [{ compName: compName, compType: compType, type: "add" }] } ) ); editorState.setSelectedCompNames(new Set([compName]), "addComp"); @@ -239,7 +239,7 @@ const onDrop = ( }), items: addMapChildAction(key, widgetValue), }), - [{ compName: compName, compType: compType, type: "add" }] + { compInfos: [{ compName: compName, compType: compType, type: "add" }] } ) ); editorState.setSelectedCompNames(new Set([compName]), "addComp"); diff --git a/client/packages/openblocks/src/comps/comps/dataChangeResponderComp.tsx b/client/packages/openblocks/src/comps/comps/dataChangeResponderComp.tsx new file mode 100644 index 00000000..fce6884f --- /dev/null +++ b/client/packages/openblocks/src/comps/comps/dataChangeResponderComp.tsx @@ -0,0 +1,138 @@ +import { getBottomResIcon } from "@openblocks-ee/util/bottomResUtils"; +import { JSONValueControl } from "comps/controls/codeControl"; +import { EventConfigType, eventHandlerControl } from "comps/controls/eventHandlerControl"; +import { MultiCompBuilder, valueComp } from "comps/generators"; +import { bottomResListComp } from "comps/generators/bottomResList"; +import { NameConfig, withExposingConfigs } from "comps/generators/withExposing"; +import { trans } from "i18n"; +import _ from "lodash"; +import { CompAction, CompActionTypes } from "openblocks-core"; +import { + DocLink, + QueryConfigLabel, + QueryConfigWrapper, + QueryPropertyViewWrapper, + QuerySectionWrapper, +} from "openblocks-design"; +import { BottomTabs } from "pages/editor/bottom/BottomTabs"; +import { ReactNode } from "react"; +import { BottomResComp, BottomResCompResult, BottomResTypeEnum } from "types/bottomRes"; +import { setFieldsNoTypeCheck } from "util/objectUtils"; +import { QueryTutorials } from "util/tutorialUtils"; +import { SimpleNameComp } from "./simpleNameComp"; + +const DataResponderCompType = "dataResponder"; + +const dataChangeEvent: EventConfigType = { + label: "onDataChange", + value: "dataChange", + description: "", +}; + +const DataResponderItemCompBase = new MultiCompBuilder( + { + name: SimpleNameComp, + order: valueComp(0), + onEvent: eventHandlerControl([dataChangeEvent], "query"), + data: JSONValueControl, + }, + () => null +) + .setPropertyViewFn((children) => { + return ( + + + {children.data.propertyView({ + label: trans("dataResponder.data"), + tooltip: trans("dataResponder.dataTooltip"), + placement: "bottom", + extraChildren: QueryTutorials.dataResponder && ( + + {trans("dataResponder.docLink")} + + ), + })} + + + + + + {trans("eventHandler.eventHandlers")} + + {children.onEvent.getPropertyView()} + + + + ), + }, + ]} + tabTitle={children.name.getView()} + status="" + /> + ); + }) + .build(); + +class DataChangeResponderAsBottomRes extends DataResponderItemCompBase implements BottomResComp { + result(): BottomResCompResult | null { + return null; + } + type(): BottomResTypeEnum { + return BottomResTypeEnum.DateResponder; + } + name(): string { + return this.children.name.getView(); + } + icon(): ReactNode { + return getBottomResIcon(BottomResTypeEnum.DateResponder); + } + order(): number { + return this.children.order.getView(); + } + reduce(action: CompAction) { + if (action.type === CompActionTypes.UPDATE_NODES_V2) { + const depends = this.children.data.node().dependValues(); + const dsl = this.children.data.toJsonValue(); + const lastDependsKey = "__data_responder_data_last_depends"; + const lastDslKey = "__data_responder_last_node"; + const target = this as any; + const preDepends = target[lastDependsKey]; + const preDsl = target[lastDslKey]; + const dependsChanged = !_.isEqual(preDepends, depends); + const dslNotChanged = _.isEqual(preDsl, dsl); + + if (dependsChanged && dslNotChanged) { + const onEvent = this.children.onEvent.getView(); + setTimeout(() => { + onEvent("dataChange"); + }); + } + + const next = super.reduce(action); + return setFieldsNoTypeCheck(next, { + [lastDependsKey]: depends, + [lastDslKey]: dsl, + }); + } + return super.reduce(action); + } +} + +export const DataChangeResponderItemComp = withExposingConfigs(DataChangeResponderAsBottomRes, [ + new NameConfig("data", trans("dataResponder.dataDesc")), +]); + +export const DataChangeResponderListComp = bottomResListComp( + DataChangeResponderItemComp, + DataResponderCompType, + { + data: JSON.stringify({}), + } +); diff --git a/client/packages/openblocks/src/comps/comps/dateComp.tsx b/client/packages/openblocks/src/comps/comps/dateComp.tsx index 063e7510..55e4b61a 100644 --- a/client/packages/openblocks/src/comps/comps/dateComp.tsx +++ b/client/packages/openblocks/src/comps/comps/dateComp.tsx @@ -24,7 +24,7 @@ import { formDataChildren, FormDataPropertyView } from "./formComp/formDataConst import { styleControl } from "comps/controls/styleControl"; import { DateTimeStyle, DateTimeStyleType } from "comps/controls/styleControlConstants"; import styled, { css } from "styled-components"; -import { withMethodExposing } from "../generators/withMethodExposing"; +import { refMethods, withMethodExposing } from "../generators/withMethodExposing"; import { disabledPropertyView, formatPropertyView, @@ -51,6 +51,8 @@ import { useContext } from "react"; import { EditorContext } from "comps/editorState"; import { IconControl } from "comps/controls/iconControl"; import { hasIcon } from "comps/utils"; +import { RefControl } from "comps/controls/refControl"; +import { CommonPickerMethods } from "antd/lib/date-picker/generatePicker/interface"; const EventOptions = [changeEvent, focusEvent, blurEvent] as const; @@ -260,7 +262,7 @@ const RangePickerStyled = styled(DatePicker.RangePicker)<{ $style: DateTimeStyle ${(props) => props.$style && getStyle(props.$style)} `; -export const datePickerControl = (function () { +const datePickerControl = (function () { const dateTypeOptions = [ { label: trans("date.date"), value: "date" }, { label: trans("date.week"), value: "week" }, @@ -274,6 +276,7 @@ export const datePickerControl = (function () { // dateType: dropdownControl(dateTypeOptions, "date"), todo: temp remove ...commonChildren, ...formDataChildren, + viewRef: RefControl, }; return new UICompBuilder(childrenMap, (props) => { @@ -282,6 +285,7 @@ export const datePickerControl = (function () { const children = ( <> disabledDate(current, props.minDate, props.maxDate)} disabledTime={() => disabledTime(props.minTime, props.maxTime)} $style={props.style} @@ -360,14 +364,16 @@ export const datePickerControl = (function () {
{children.style.getPropertyView()}
)) + .setExposeMethodConfigs(refMethods(["focus", "blur"])) .build(); })(); -export const dateRangeControl = (function () { +const dateRangeControl = (function () { const childrenMap = { start: stringExposingStateControl("start"), end: stringExposingStateControl("end"), ...commonChildren, + viewRef: RefControl, }; return new UICompBuilder(childrenMap, (props) => { @@ -375,6 +381,7 @@ export const dateRangeControl = (function () { const children = ( <> disabledDate(current, props.minDate, props.maxDate)} disabledTime={() => disabledTime(props.minTime, props.maxTime)} - onChange={(time) => { - const start = time && time[0] ? moment(time[0]) : null; - const end = time && time[1] ? moment(time[1]) : null; + onCalendarChange={(time) => { + const start = time?.[0]; + const end = time?.[1]; props.start.onChange( start && start.isValid() ? start.format(props.showTime ? DATE_TIME_FORMAT : DATE_FORMAT) @@ -599,6 +606,7 @@ export let DateRangeComp = withExposingConfigs(dateRangeControl, [ ]); DateRangeComp = withMethodExposing(DateRangeComp, [ + ...refMethods(["focus", "blur"]), { method: { name: "clearAll", diff --git a/client/packages/openblocks/src/comps/comps/fileComp.test.csv b/client/packages/openblocks/src/comps/comps/fileComp/fileComp.test.csv similarity index 100% rename from client/packages/openblocks/src/comps/comps/fileComp.test.csv rename to client/packages/openblocks/src/comps/comps/fileComp/fileComp.test.csv diff --git a/client/packages/openblocks/src/comps/comps/fileComp.test.json b/client/packages/openblocks/src/comps/comps/fileComp/fileComp.test.json similarity index 100% rename from client/packages/openblocks/src/comps/comps/fileComp.test.json rename to client/packages/openblocks/src/comps/comps/fileComp/fileComp.test.json diff --git a/client/packages/openblocks/src/comps/comps/fileComp.test.png b/client/packages/openblocks/src/comps/comps/fileComp/fileComp.test.png similarity index 100% rename from client/packages/openblocks/src/comps/comps/fileComp.test.png rename to client/packages/openblocks/src/comps/comps/fileComp/fileComp.test.png diff --git a/client/packages/openblocks/src/comps/comps/fileComp.test.tsx b/client/packages/openblocks/src/comps/comps/fileComp/fileComp.test.tsx similarity index 88% rename from client/packages/openblocks/src/comps/comps/fileComp.test.tsx rename to client/packages/openblocks/src/comps/comps/fileComp/fileComp.test.tsx index 3c0d39ed..6f030106 100644 --- a/client/packages/openblocks/src/comps/comps/fileComp.test.tsx +++ b/client/packages/openblocks/src/comps/comps/fileComp/fileComp.test.tsx @@ -100,11 +100,11 @@ function getFile(path: string) { test("test resolveParsedValue", async () => { const files = await Promise.all([ - getFile("packages/openblocks/src/comps/comps/fileComp.test.csv"), - getFile("packages/openblocks/src/comps/comps/fileComp.test.json"), - getFile("packages/openblocks/src/comps/comps/fileComp.test.png"), - getFile("packages/openblocks/src/comps/comps/fileComp.test.txt"), - getFile("packages/openblocks/src/comps/comps/fileComp.test.xlsx"), + getFile("packages/openblocks/src/comps/comps/fileComp/fileComp.test.csv"), + getFile("packages/openblocks/src/comps/comps/fileComp/fileComp.test.json"), + getFile("packages/openblocks/src/comps/comps/fileComp/fileComp.test.png"), + getFile("packages/openblocks/src/comps/comps/fileComp/fileComp.test.txt"), + getFile("packages/openblocks/src/comps/comps/fileComp/fileComp.test.xlsx"), ]); const parsedValue = await resolveParsedValue(files as any); expect(parsedValue[0]).toMatchObject(expectParseValue); diff --git a/client/packages/openblocks/src/comps/comps/fileComp.test.xlsx b/client/packages/openblocks/src/comps/comps/fileComp/fileComp.test.xlsx similarity index 100% rename from client/packages/openblocks/src/comps/comps/fileComp.test.xlsx rename to client/packages/openblocks/src/comps/comps/fileComp/fileComp.test.xlsx diff --git a/client/packages/openblocks/src/comps/comps/fileComp.tsx b/client/packages/openblocks/src/comps/comps/fileComp/fileComp.tsx similarity index 95% rename from client/packages/openblocks/src/comps/comps/fileComp.tsx rename to client/packages/openblocks/src/comps/comps/fileComp/fileComp.tsx index 074d3c91..ffa3828f 100644 --- a/client/packages/openblocks/src/comps/comps/fileComp.tsx +++ b/client/packages/openblocks/src/comps/comps/fileComp/fileComp.tsx @@ -6,8 +6,7 @@ import _ from "lodash"; import { UploadRequestOption } from "rc-upload/lib/interface"; import { useEffect, useState } from "react"; import styled, { css } from "styled-components"; -import { JSONObject, JSONValue } from "../../util/jsonTypes"; -import { darkenColor, Section, sectionNames } from "openblocks-design"; +import { JSONObject, JSONValue } from "../../../util/jsonTypes"; import { changeValueAction, CompAction, @@ -21,13 +20,13 @@ import { codeControl, NumberControl, StringControl, -} from "../controls/codeControl"; -import { BoolControl, BoolPureControl } from "../controls/boolControl"; -import { dropdownControl } from "../controls/dropdownControl"; -import { changeEvent, eventHandlerControl } from "../controls/eventHandlerControl"; -import { stateComp, UICompBuilder, withDefault } from "../generators"; -import { CommonNameConfig, NameConfig, withExposingConfigs } from "../generators/withExposing"; -import { formDataChildren, FormDataPropertyView } from "./formComp/formDataConstants"; +} from "../../controls/codeControl"; +import { BoolControl, BoolPureControl } from "../../controls/boolControl"; +import { dropdownControl } from "../../controls/dropdownControl"; +import { changeEvent, eventHandlerControl } from "../../controls/eventHandlerControl"; +import { stateComp, UICompBuilder, withDefault } from "../../generators"; +import { CommonNameConfig, NameConfig, withExposingConfigs } from "../../generators/withExposing"; +import { formDataChildren, FormDataPropertyView } from "../formComp/formDataConstants"; import { withMethodExposing } from "comps/generators/withMethodExposing"; import { styleControl } from "comps/controls/styleControl"; import { FileStyle, FileStyleType } from "comps/controls/styleControlConstants"; @@ -37,6 +36,8 @@ import { getComponentDocUrl } from "comps/utils/compDocUtil"; import mime from "mime"; import { IconControl } from "comps/controls/iconControl"; import { hasIcon } from "comps/utils"; +import { darkenColor } from "components/colorSelect/colorUtils"; +import { Section, sectionNames } from "components/Section"; const FileSizeControl = codeControl((value) => { if (typeof value === "number") { diff --git a/client/packages/openblocks/src/comps/comps/gridItemComp.tsx b/client/packages/openblocks/src/comps/comps/gridItemComp.tsx index eff0dabd..f9f1bd4f 100644 --- a/client/packages/openblocks/src/comps/comps/gridItemComp.tsx +++ b/client/packages/openblocks/src/comps/comps/gridItemComp.tsx @@ -14,25 +14,15 @@ import { UICompType, } from "comps/uiCompRegistry"; import { ExposingInfo } from "comps/utils/exposingTypes"; -import { getReduceContext, ListViewContext } from "comps/utils/reduceContext"; +import { getReduceContext, WithParamsContext } from "comps/utils/reduceContext"; import { parseCompType } from "comps/utils/remote"; -import _ from "lodash"; import { Comp, CompAction, ConstructorToDataType } from "openblocks-core"; import React, { Profiler, useContext, useMemo } from "react"; import { profilerCallback } from "util/cacheUtils"; -import { setFieldsNoTypeCheck } from "util/objectUtils"; +import { setFieldsNoTypeCheck, shallowEqual } from "util/objectUtils"; import { remoteComp } from "./remoteComp/remoteComp"; import { SimpleNameComp } from "./simpleNameComp"; -const ITEM_CHAR = "ijklmn"; - -function getListExposingHints(listViewDepth: number): Record { - return _(_.range(0, Math.min(listViewDepth, ITEM_CHAR.length))) - .map((i) => [ITEM_CHAR[i], 0] as const) - .fromPairs() - .value(); -} - export function defaultLayout(compType: UICompType): UICompLayoutInfo { return uiCompRegistry[compType]?.layoutInfo ?? { w: 5, h: 5 }; } @@ -76,24 +66,19 @@ function CachedView(props: { comp: Comp; name: string }) { ); } -function CachedPropertyView(props: { comp: Comp; name: string; listViewContext: ListViewContext }) { +function CachedPropertyView(props: { + comp: Comp; + name: string; + withParamsContext: WithParamsContext; +}) { const prevHints = useContext(CompExposingContext); - const prevCurrentItems = useMemo( - () => (typeof prevHints?.currentItems === "object" ? prevHints?.currentItems : {}), - [prevHints?.currentItems] - ); - const { listViewContext } = props; - const currentItem = useMemo( - () => ({ ...prevCurrentItems, ...listViewContext.currentItem }), - [listViewContext.currentItem, prevCurrentItems] - ); + const { withParamsContext } = props; const hints = useMemo( () => ({ ...prevHints, - ...getListExposingHints(listViewContext.listViewDepth), - ...(_.isEmpty(currentItem) ? {} : { currentItem }), + ...withParamsContext.params, }), - [prevHints, listViewContext.listViewDepth, currentItem] + [prevHints, withParamsContext.params] ); return useMemo(() => { @@ -111,7 +96,7 @@ function CachedPropertyView(props: { comp: Comp; name: string; listViewContext: } export class GridItemComp extends TmpComp { - private readonly listViewContext: ListViewContext = { listViewDepth: 0, currentItem: {} }; + private readonly withParamsContext: WithParamsContext = { params: {} }; override getView() { return ; } @@ -119,7 +104,9 @@ export class GridItemComp extends TmpComp { override getPropertyView() { const name = this.children.name.getView(); const comp = this.children.comp; - return ; + return ( + + ); } autoHeight(): boolean { @@ -133,9 +120,10 @@ export class GridItemComp extends TmpComp { override reduce(action: CompAction): this { let comp = super.reduce(action); - const listViewContext = getReduceContext().listViewContext; - if (listViewContext !== this.listViewContext) - comp = setFieldsNoTypeCheck(comp, { listViewContext }, { keepCacheKeys: ["node"] }); + const withParamsContext = getReduceContext().withParamsContext; + if (!shallowEqual(withParamsContext.params, this.withParamsContext.params)) { + comp = setFieldsNoTypeCheck(comp, { withParamsContext }, { keepCacheKeys: ["node"] }); + } return comp; } } diff --git a/client/packages/openblocks/src/comps/comps/jsonComp/jsonExplorerComp.tsx b/client/packages/openblocks/src/comps/comps/jsonComp/jsonExplorerComp.tsx index ce104d69..c93cd594 100644 --- a/client/packages/openblocks/src/comps/comps/jsonComp/jsonExplorerComp.tsx +++ b/client/packages/openblocks/src/comps/comps/jsonComp/jsonExplorerComp.tsx @@ -5,7 +5,6 @@ import ReactJson, { ThemeKeys } from "react-json-view"; import { defaultData } from "./jsonConstants"; import styled from "styled-components"; import { BoolControl } from "comps/controls/boolControl"; -import { jsonObjectExposingStateControl } from "comps/controls/codeStateControl"; import { dropdownControl } from "comps/controls/dropdownControl"; import { ArrayOrJSONObjectControl, NumberControl } from "comps/controls/codeControl"; import { hiddenPropertyView } from "comps/utils/propertyUtils"; diff --git a/client/packages/openblocks/src/comps/comps/listViewComp/contextContainerComp.tsx b/client/packages/openblocks/src/comps/comps/listViewComp/contextContainerComp.tsx new file mode 100644 index 00000000..be4d26e6 --- /dev/null +++ b/client/packages/openblocks/src/comps/comps/listViewComp/contextContainerComp.tsx @@ -0,0 +1,77 @@ +import { withParamsForMapWithDefault } from "comps/generators"; +import { CHILD_KEY } from "comps/generators/withMultiContext"; +import { reduceInContext } from "comps/utils/reduceContext"; +import _ from "lodash"; +import { CompAction, CompActionTypes, customAction, isMyCustomAction } from "openblocks-core"; +import { JSONObject } from "util/jsonTypes"; +import { SimpleContainerComp } from "../containerBase/simpleContainerComp"; +import { getCurrentItemParams } from "./listViewUtils"; + +type Context = { + itemIndexName: string; + itemDataName: string; + itemCount: number; + data: Array; +}; + +type ResetContextAction = { + type: "resetContext"; + context: Context; +}; + +const ContextContainerTmpComp = withParamsForMapWithDefault(SimpleContainerComp, {}); + +export class ContextContainerComp extends ContextContainerTmpComp { + override reduce(action: CompAction): this { + let comp = this; + if (isMyCustomAction(action, "resetContext")) { + const { itemIndexName, itemDataName, itemCount, data } = action.value.context; + const paramsValueMap = _(_.range(0, itemCount)) + .toPairs() + .fromPairs() + .mapValues((idx) => ({ + [itemIndexName]: idx, + [itemDataName]: getCurrentItemParams(data, idx), + })) + .value(); + comp = this.clear().batchSet(paramsValueMap); + } else { + comp = super.reduce(action); + } + const thisCompMap = this.getView(); + const newCompMap = comp.getView(); + const size = _.size(newCompMap); + if ( + action.type !== CompActionTypes.UPDATE_NODES_V2 && + action.path[1] === "0" && + thisCompMap[0] !== newCompMap[0] + ) { + // broadcast + _.range(1, size).forEach((idx) => { + const newAction = { + ...action, + path: [action.path[0], String(idx), ...action.path.slice(2)], + }; + comp = reduceInContext({ disableUpdateState: true }, () => comp.reduce(newAction)); + }); + // try set the original comp as 0-th comp + const tryOriginalComp = reduceInContext({ disableUpdateState: true }, () => + thisCompMap[0].reduce({ ...action, path: action.path.slice(2) }) + ); + if (tryOriginalComp !== thisCompMap[0]) { + comp = comp.setChild( + CHILD_KEY, + tryOriginalComp.changeDispatch(comp.getOriginalComp().dispatch) + ); + } + } + return comp; + } + + static resetContextAction(context: Context) { + return customAction({ + type: "resetContext", + context, + }); + } +} diff --git a/client/packages/openblocks/src/comps/comps/listViewComp/gridComp.tsx b/client/packages/openblocks/src/comps/comps/listViewComp/gridComp.tsx index ef8f3397..00c3a502 100644 --- a/client/packages/openblocks/src/comps/comps/listViewComp/gridComp.tsx +++ b/client/packages/openblocks/src/comps/comps/listViewComp/gridComp.tsx @@ -47,7 +47,10 @@ export function defaultGridData(compName: string, nameGenerator: NameGenerator) name: "click", handler: { compType: "goToURL", - comp: { url: "{{currentItem.url}}" }, + comp: { + url: "{{currentItem.url}}", + inNewTab: true, + }, condition: "", slowdown: "debounce", delay: "", diff --git a/client/packages/openblocks/src/comps/comps/listViewComp/listView.tsx b/client/packages/openblocks/src/comps/comps/listViewComp/listView.tsx index c89d3aff..879bf8dc 100644 --- a/client/packages/openblocks/src/comps/comps/listViewComp/listView.tsx +++ b/client/packages/openblocks/src/comps/comps/listViewComp/listView.tsx @@ -13,7 +13,7 @@ import { InnerGrid, } from "../containerComp/containerView"; import { ListViewImplComp } from "./listViewComp"; -import { getCurrentItemParams, getData } from "./listViewUtils"; +import { getData } from "./listViewUtils"; const Wrapper = styled.div` overflow: auto; @@ -50,16 +50,13 @@ export function ListView(props: Props) { const editorState = useContext(EditorContext); const isDragging = editorState.isDragging; const [listHeight, setListHeight] = useDelayState(0, isDragging); - const { data, itemCount } = useMemo( - () => getData(children.noOfRows.getView()), - [children.noOfRows] - ); const dynamicHeight = useMemo(() => children.dynamicHeight.getView(), [children.dynamicHeight]); const heightUnitOfRow = useMemo( () => children.heightUnitOfRow.getView(), [children.heightUnitOfRow] ); - const containerViewFn = useMemo(() => children.container.getView(), [children.container]); + const containerComps = useMemo(() => children.container.getView(), [children.container]); + const itemCount = useMemo(() => _.size(containerComps), [containerComps]); const autoHeight = useMemo(() => children.autoHeight.getView(), [children.autoHeight]); const noOfColumns = useMemo( () => Math.max(1, children.noOfColumns.getView()), @@ -68,7 +65,6 @@ export function ListView(props: Props) { const style = children.style.getView(); const commonLayout = children.container.getOriginalComp().getComp().children.layout.getView(); - const commonLayoutDispatch = children.container.getOriginalComp().getComp().dispatch; const isOneItem = itemCount > 0 && (_.isEmpty(commonLayout) || editorState.isDragging); const noOfRows = isOneItem ? 1 : Math.floor((itemCount + noOfColumns - 1) / noOfColumns); const rowHeight = isOneItem ? "100%" : dynamicHeight ? "auto" : heightUnitOfRow * 44 + "px"; @@ -88,28 +84,25 @@ export function ListView(props: Props) { > {_.range(0, noOfColumns).map((colIdx) => { - const i = rowIdx * noOfColumns + colIdx; - if (i >= itemCount || (isOneItem && i > 0)) { - return
; + const itemIdx = rowIdx * noOfColumns + colIdx; + if (itemIdx >= itemCount || (isOneItem && itemIdx > 0)) { + return
; } - const containerProps = containerViewFn( - { i, currentItem: getCurrentItemParams(data, i) }, - String(i) - ); + const containerProps = containerComps[itemIdx].getView(); return ( <>) .setPropertyViewFn(() => <>) @@ -34,7 +55,10 @@ const ListViewTmpComp = new UICompBuilder(childrenMap, () => <>) export class ListViewImplComp extends ListViewTmpComp implements IContainer { private getOriginalContainer() { - return this.children.container.getOriginalComp().getComp(); + const containers = this.children.container.getView(); + return containers[0] + ? containers[0].getComp() + : this.children.container.getOriginalComp().getComp(); } realSimpleContainer(key?: string): SimpleContainerComp | undefined { return this.getOriginalContainer().realSimpleContainer(key); @@ -53,48 +77,30 @@ export class ListViewImplComp extends ListViewTmpComp implements IContainer { } override reduce(action: CompAction): this { // console.info("listView reduce. action: ", action); - const listViewContext = getReduceContext().listViewContext; - const { data: thisData } = getData(this.children.noOfRows.getView()); - let comp = reduceInContext( - { - inEventContext: true, - listViewContext: { - listViewDepth: listViewContext.listViewDepth + 1, - currentItem: { ...listViewContext.currentItem, ...getCurrentItemParams(thisData) }, - }, - }, - () => super.reduce(action) - ); - - if (isCustomAction(action, "moduleReady")) { - // mirror the 0-th item to the origin - const thisMapComps = this.children.container.children[MAP_KEY].getView(); - const compMapComps = comp.children.container.children[MAP_KEY].getView(); - const topItemChanged = thisMapComps[0] !== compMapComps[0]; - if (topItemChanged) { - comp = comp.setChild( - "container", - comp.children.container.reduce(ContextContainerComp.mirrorToOriginAction("0")) - ); - } - } + let comp = reduceInContext({ inEventContext: true }, () => super.reduce(action)); if (action.type === CompActionTypes.UPDATE_NODES_V2) { - const { data: compData } = getData(comp.children.noOfRows.getView()); - const paramsChanged = !_.isEqual( - getCurrentItemParams(thisData), - getCurrentItemParams(compData) + const dataChanged = !_.isEqual( + this.children.noOfRows.getView(), + comp.children.noOfRows.getView() ); - if (paramsChanged) { - // keep original comp's params same as the 0-th comp - comp = comp.setChild( - "container", - comp.children.container.reduce( - ContextContainerComp.setOriginalParamsAction({ - currentItem: getCurrentItemParams(compData), - }) - ) + const nameChanged = + !_.isEqual(this.children.itemIndexName.getView(), comp.children.itemIndexName.getView()) || + !_.isEqual(this.children.itemDataName.getView(), comp.children.itemDataName.getView()); + if (dataChanged || nameChanged) { + const { data: compData, itemCount: compItemCount } = getData( + comp.children.noOfRows.getView() ); + setTimeout(() => { + comp.children.container.dispatch( + ContextContainerComp.resetContextAction({ + itemIndexName: comp.children.itemIndexName.getView(), + itemDataName: comp.children.itemDataName.getView(), + itemCount: compItemCount, + data: compData, + }) + ); + }); } } // console.info("listView reduce. action: ", action, "\nthis: ", this, "\ncomp: ", comp); @@ -102,38 +108,27 @@ export class ListViewImplComp extends ListViewTmpComp implements IContainer { } /** expose the data from inner comps */ itemsNode(): Node[]> { - const { itemCount } = getData(this.children.noOfRows.getView()); + const containers = this.children.container.getView(); + const size = _.size(containers); // for each container expose each comps with params - const exposingRecord = _(_.range(0, itemCount)) + const exposingRecord = _(_.range(0, size)) .toPairs() .fromPairs() - .mapValues((i) => { - let hitCache = true; - let container = this.children.container.getCachedComp(String(i)); - if (!container) { - hitCache = false; - container = this.children.container.getOriginalComp(); - } + .mapValues((itemIdx) => { + let container = containers[itemIdx]; // FIXME: replace allComps as non-list-view comps const allComps = getAllCompItems(container.getComp().getCompTree()); const nodeRecord = _(allComps) .mapKeys((gridItemComp) => gridItemComp.children.name.getView()) .mapValues((gridItemComp) => gridItemComp.children.comp.exposingNode()) .value(); - const resNode = new WrapContextNodeV2(fromRecord(nodeRecord), { - i: fromValue(i), - currentItem: withFunction(this.children.noOfRows.exposingNode(), (value) => - typeof value === "number" ? {} : value[i] - ), - }); - // const resNode = hitCache - // ? new WrapContextNodeV2(fromRecord(nodeRecord), container.getParamNodes()) - // : withFunction(wrapContext(fromRecord(nodeRecord)), (fn) => fn({ i })); + const paramsNodes = container.getParamNodes(); + const resNode = new WrapContextNodeV2(fromRecord(nodeRecord), paramsNodes); const res = lastValueIfEqual( this, - "exposing_row_" + i, - [resNode, nodeRecord] as const, - (a, b) => shallowEqual(a[1], b[1]) + "exposing_row_" + itemIdx, + [resNode, nodeRecord, paramsNodes] as const, + (a, b) => depthEqual(a.slice(1), b.slice(1), 3) )[0]; // console.info("listView exposingRecord. i: ", i, " res id: ", getObjectId(res), " container id: ", getObjectId(container), " hitCache: ", hitCache); return res; @@ -141,7 +136,7 @@ export class ListViewImplComp extends ListViewTmpComp implements IContainer { .value(); // transform record to array const exposings = withFunction(fromRecord(exposingRecord), (record) => { - return _.range(0, itemCount).map((i) => record[i]); + return _.range(0, size).map((i) => record[i]); }); return lastValueIfEqual(this, "exposing_data", [exposings, exposingRecord] as const, (a, b) => @@ -178,7 +173,7 @@ export const ListViewComp = withExposingConfigs(ListViewPropertyComp, [ export function defaultListViewData(compName: string, nameGenerator: NameGenerator) { return { noOfRows: - '[\n {\n "rate": "9.2",\n "title": "The Shawshank Redemption",\n "url": "https://www.imdb.com/title/tt0111161/",\n "cover": "https://m.media-amazon.com/images/M/MV5BMDFkYTc0MGEtZmNhMC00ZDIzLWFmNTEtODM1ZmRlYWMwMWFmXkEyXkFqcGdeQXVyMTMxODk2OTU@._V1_UY67_CR0,0,45,67_AL_.jpg"\n },\n {\n "rate": "9.2",\n "title": "The Godfather",\n "url": "https://www.imdb.com/title/tt0068646/",\n "cover": "https://m.media-amazon.com/images/M/MV5BM2MyNjYxNmUtYTAwNi00MTYxLWJmNWYtYzZlODY3ZTk3OTFlXkEyXkFqcGdeQXVyNzkwMjQ5NzM@._V1_UY67_CR1,0,45,67_AL_.jpg"\n },\n {\n "rate": "9.0",\n "title": "The Dark Knight",\n "url": "https://www.imdb.com/title/tt0468569/",\n "cover": "https://m.media-amazon.com/images/M/MV5BMTMxNTMwODM0NF5BMl5BanBnXkFtZTcwODAyMTk2Mw@@._V1_UY67_CR0,0,45,67_AL_.jpg"\n },\n {\n "rate": "9.0",\n "title": "The Godfather Part II",\n "url": "https://www.imdb.com/title/tt0071562/",\n "cover": "https://m.media-amazon.com/images/M/MV5BMWMwMGQzZTItY2JlNC00OWZiLWIyMDctNDk2ZDQ2YjRjMWQ0XkEyXkFqcGdeQXVyNzkwMjQ5NzM@._V1_UY67_CR1,0,45,67_AL_.jpg"\n },\n {\n "rate": "9.0",\n "title": "12 Angry Men",\n "url": "https://www.imdb.com/title/tt0050083/",\n "cover": "https://m.media-amazon.com/images/M/MV5BMWU4N2FjNzYtNTVkNC00NzQ0LTg0MjAtYTJlMjFhNGUxZDFmXkEyXkFqcGdeQXVyNjc1NTYyMjg@._V1_UX45_CR0,0,45,67_AL_.jpg"\n },\n {\n "rate": "8.9",\n "title": "Schindler\'s List",\n "url": "https://www.imdb.com/title/tt0108052/",\n "cover": "https://m.media-amazon.com/images/M/MV5BNDE4OTMxMTctNmRhYy00NWE2LTg3YzItYTk3M2UwOTU5Njg4XkEyXkFqcGdeQXVyNjU0OTQ0OTY@._V1_UX45_CR0,0,45,67_AL_.jpg"\n }\n]', + '[\n {\n "rate": "9.2",\n "title": "The Shawshank Redemption",\n "url": "https://www.imdb.com/title/tt0111161/",\n "cover": "https://m.media-amazon.com/images/M/MV5BMDFkYTc0MGEtZmNhMC00ZDIzLWFmNTEtODM1ZmRlYWMwMWFmXkEyXkFqcGdeQXVyMTMxODk2OTU@._V1_UY67_CR0,0,45,67_AL_.jpg"\n },\n {\n "rate": "9.2",\n "title": "The Godfather",\n "url": "https://www.imdb.com/title/tt0068646/",\n "cover": "https://m.media-amazon.com/images/M/MV5BM2MyNjYxNmUtYTAwNi00MTYxLWJmNWYtYzZlODY3ZTk3OTFlXkEyXkFqcGdeQXVyNzkwMjQ5NzM@._V1_UY67_CR1,0,45,67_AL_.jpg"\n },\n {\n "rate": "9.0",\n "title": "The Dark Knight",\n "url": "https://www.imdb.com/title/tt0468569/",\n "cover": "https://m.media-amazon.com/images/M/MV5BMTMxNTMwODM0NF5BMl5BanBnXkFtZTcwODAyMTk2Mw@@._V1_UY67_CR0,0,45,67_AL_.jpg"\n }\n]', container: toSimpleContainerData([ { item: { @@ -208,7 +203,10 @@ export function defaultListViewData(compName: string, nameGenerator: NameGenerat name: "click", handler: { compType: "goToURL", - comp: { url: "{{currentItem.url}}" }, + comp: { + url: "{{currentItem.url}}", + inNewTab: true, + }, condition: "", slowdown: "debounce", delay: "", diff --git a/client/packages/openblocks/src/comps/comps/listViewComp/listViewPropertyView.tsx b/client/packages/openblocks/src/comps/comps/listViewComp/listViewPropertyView.tsx index 5b52161b..36d75691 100644 --- a/client/packages/openblocks/src/comps/comps/listViewComp/listViewPropertyView.tsx +++ b/client/packages/openblocks/src/comps/comps/listViewComp/listViewPropertyView.tsx @@ -1,4 +1,4 @@ -import { trans } from "i18n"; +import { trans, transToNode } from "i18n"; import { Section, sectionNames } from "openblocks-design"; import { ListViewImplComp } from "./listViewComp"; import { ListCompType } from "./listViewUtils"; @@ -22,6 +22,26 @@ export function listPropertyView(compType: ListCompType) { children.noOfColumns.propertyView({ label: trans("listView.noOfColumns"), })} + {children.itemIndexName.propertyView({ + label: trans("listView.itemIndexName"), + tooltip: transToNode("listView.itemIndexNameDesc", { + default: ( + + i + + ), + }), + })} + {children.itemDataName.propertyView({ + label: trans("listView.itemDataName"), + tooltip: transToNode("listView.itemDataNameDesc", { + default: ( + + currentItem + + ), + }), + })}
{children.autoHeight.getPropertyView()}
{/*
{children.showBorder.propertyView({ label: "" })}
*/} diff --git a/client/packages/openblocks/src/comps/comps/listViewComp/listViewUtils.tsx b/client/packages/openblocks/src/comps/comps/listViewComp/listViewUtils.tsx index cc5d1e3e..874716e7 100644 --- a/client/packages/openblocks/src/comps/comps/listViewComp/listViewUtils.tsx +++ b/client/packages/openblocks/src/comps/comps/listViewComp/listViewUtils.tsx @@ -1,33 +1,8 @@ -import { AutoHeightControl } from "comps/controls/autoHeightControl"; -import { BoolControl } from "comps/controls/boolControl"; -import { NumberControl, NumberOrJSONObjectArrayControl } from "comps/controls/codeControl"; -import { styleControl } from "comps/controls/styleControl"; -import { ListViewStyle } from "comps/controls/styleControlConstants"; -import { withDefault } from "comps/generators"; -import { withIsLoadingMethod } from "comps/generators/withIsLoading"; -import { withMultiContextWithDefault } from "comps/generators/withMultiContext"; import { JSONObject } from "util/jsonTypes"; -import { SimpleContainerComp } from "../containerBase/simpleContainerComp"; export const EMPTY_OBJECT = {}; export type ListCompType = "listView" | "grid"; -export const ContextContainerComp = withMultiContextWithDefault(SimpleContainerComp, { - i: 0, - currentItem: {} as JSONObject, -}); - -export const childrenMap = { - noOfRows: withIsLoadingMethod(NumberOrJSONObjectArrayControl), // FIXME: migrate "noOfRows" to "data" - noOfColumns: withDefault(NumberControl, 1), - dynamicHeight: AutoHeightControl, - heightUnitOfRow: withDefault(NumberControl, 1), - container: ContextContainerComp, - autoHeight: AutoHeightControl, - showBorder: BoolControl, - style: styleControl(ListViewStyle), -}; - export function getData(data: number | Array): { data: Array; itemCount: number; @@ -39,3 +14,7 @@ export function getData(data: number | Array): { export function getCurrentItemParams(data: Array, idx?: number) { return data[idx ?? 0] ?? EMPTY_OBJECT; } + +export function genKey(i: number) { + return String(i); +} diff --git a/client/packages/openblocks/src/comps/comps/mediaComp/audioComp.tsx b/client/packages/openblocks/src/comps/comps/mediaComp/audioComp.tsx index 731750fc..f7a6f2f1 100644 --- a/client/packages/openblocks/src/comps/comps/mediaComp/audioComp.tsx +++ b/client/packages/openblocks/src/comps/comps/mediaComp/audioComp.tsx @@ -13,6 +13,9 @@ import { BoolControl } from "comps/controls/boolControl"; import { withDefault } from "../../generators/simpleGenerators"; import { trans } from "i18n"; import { hiddenPropertyView } from "comps/utils/propertyUtils"; +import { RefControl } from "comps/controls/refControl"; +import { refMethods } from "comps/generators/withMethodExposing"; +import ReactPlayer from "react-player"; const Container = styled.div` height: 100%; @@ -39,13 +42,12 @@ const EventOptions = [ ] as const; const ContainerAudio = (props: RecordConstructorToView) => { - const audioRef = useRef(null); const conRef = useRef(null); return ( props.onEvent("play")} onPause={() => props.onEvent("pause")} @@ -63,6 +65,7 @@ const childrenMap = { style: styleControl(ImageStyle), autoPlay: BoolControl, loop: BoolControl, + viewRef: RefControl, }; let AudioBasicComp = (function () { @@ -90,6 +93,7 @@ let AudioBasicComp = (function () { ); }) + .setExposeMethodConfigs(refMethods(["seekTo", "showPreview"])) .build(); })(); diff --git a/client/packages/openblocks/src/comps/comps/mediaComp/videoComp.tsx b/client/packages/openblocks/src/comps/comps/mediaComp/videoComp.tsx index d411e77a..a6eb2bb1 100644 --- a/client/packages/openblocks/src/comps/comps/mediaComp/videoComp.tsx +++ b/client/packages/openblocks/src/comps/comps/mediaComp/videoComp.tsx @@ -15,6 +15,8 @@ import { hiddenPropertyView } from "comps/utils/propertyUtils"; import { trans } from "i18n"; import { Video } from "openblocks-design"; import ReactPlayer from "react-player"; +import { RefControl } from "comps/controls/refControl"; +import { refMethods } from "comps/generators/withMethodExposing"; const EventOptions = [ { label: trans("video.play"), value: "play", description: trans("video.playDesc") }, @@ -24,7 +26,7 @@ const EventOptions = [ ] as const; const ContainerVideo = (props: RecordConstructorToView) => { - const videoRef = useRef(null); + const videoRef = useRef(null); const conRef = useRef(null); let [posterClicked, setPosterClicked] = useState(false); return ( @@ -36,7 +38,10 @@ const ContainerVideo = (props: RecordConstructorToView) => { }, }} light={props.autoPlay ? "" : props.poster.value} - ref={videoRef} + ref={(t: ReactPlayer | null) => { + props.viewRef(t); + videoRef.current = t; + }} url={props.src.value} onPlay={() => props.onEvent("play")} onReady={() => { @@ -79,6 +84,7 @@ const childrenMap = { playbackRate: RangeControl.closed(1, 2, 1), currentTimeStamp: numberExposingStateControl("currentTimeStamp", 0), duration: numberExposingStateControl("duration"), + viewRef: RefControl, }; let VideoBasicComp = (function () { @@ -123,6 +129,7 @@ let VideoBasicComp = (function () { ); }) + .setExposeMethodConfigs(refMethods(["seekTo", "showPreview"])) .build(); })(); diff --git a/client/packages/openblocks/src/comps/comps/moduleComp/moduleComp.tsx b/client/packages/openblocks/src/comps/comps/moduleComp/moduleComp.tsx index d69e3429..9836ae50 100644 --- a/client/packages/openblocks/src/comps/comps/moduleComp/moduleComp.tsx +++ b/client/packages/openblocks/src/comps/comps/moduleComp/moduleComp.tsx @@ -256,6 +256,7 @@ class ModuleTmpComp extends ModuleCompBase { // init if (isMyCustomAction(action, "init")) { + if (getReduceContext().disableUpdateState) return this; if (this.loadedAppId === appId) { return this; } @@ -287,6 +288,7 @@ class ModuleTmpComp extends ModuleCompBase { // update dsl to init module root if (isMyCustomAction(action, "updateDsl")) { + if (getReduceContext().disableUpdateState) return this; const { dsl, moduleDsl } = action.value; const moduleRootComp = new RootComp({ dispatch: this.getModuleDispatchFn(), @@ -302,6 +304,7 @@ class ModuleTmpComp extends ModuleCompBase { // module ready if (isMyCustomAction(action, "moduleReady")) { + if (getReduceContext().disableUpdateState) return this; const moduleRootComp = action.value.comp; if (!moduleRootComp) { return this; diff --git a/client/packages/openblocks/src/comps/comps/moduleContainerComp/moduleMethodListComp.tsx b/client/packages/openblocks/src/comps/comps/moduleContainerComp/moduleMethodListComp.tsx index f7ad9d90..1ed8a263 100644 --- a/client/packages/openblocks/src/comps/comps/moduleContainerComp/moduleMethodListComp.tsx +++ b/client/packages/openblocks/src/comps/comps/moduleContainerComp/moduleMethodListComp.tsx @@ -136,7 +136,7 @@ function MethodItem(props: MethodItemProps) { const { name, action, params, onDelete } = props; const handleOnParamsConfigChange = () => { - action.dispatch(WithParamsActionControl.setParamDataAction(params.getParamsData())); + action.dispatch(WithParamsActionControl.setPartialParamDataAction(params.getParamsData())); }; const content = ( diff --git a/client/packages/openblocks/src/comps/comps/moduleContainerComp/moduleMethodListItemComp.tsx b/client/packages/openblocks/src/comps/comps/moduleContainerComp/moduleMethodListItemComp.tsx index 7b0f641e..e46278a1 100644 --- a/client/packages/openblocks/src/comps/comps/moduleContainerComp/moduleMethodListItemComp.tsx +++ b/client/packages/openblocks/src/comps/comps/moduleContainerComp/moduleMethodListItemComp.tsx @@ -33,7 +33,7 @@ export class ModuleMethodListItemComp extends ModuleMethodListItemBase { await getPromiseAfterDispatch( this.children.action.dispatch, - WithParamsActionControl.setParamDataAction(paramsMap), + WithParamsActionControl.setPartialParamDataAction(paramsMap), { autoHandleAfterReduce: true } ); return getPromiseAfterDispatch( diff --git a/client/packages/openblocks/src/comps/comps/numberInputComp/numberInputComp.tsx b/client/packages/openblocks/src/comps/comps/numberInputComp/numberInputComp.tsx index 79db328b..b02faac3 100644 --- a/client/packages/openblocks/src/comps/comps/numberInputComp/numberInputComp.tsx +++ b/client/packages/openblocks/src/comps/comps/numberInputComp/numberInputComp.tsx @@ -27,7 +27,7 @@ import { RecordConstructorToView } from "openblocks-core"; import { InputEventHandlerControl } from "../../controls/eventHandlerControl"; import { UICompBuilder, withDefault } from "../../generators"; import { formDataChildren, FormDataPropertyView } from "../formComp/formDataConstants"; -import { MethodConfigFocus, withMethodExposing } from "../../generators/withMethodExposing"; +import { withMethodExposing, refMethods } from "../../generators/withMethodExposing"; import { RefControl } from "../../controls/refControl"; import { styleControl } from "comps/controls/styleControl"; import { InputLikeStyle, InputLikeStyleType } from "comps/controls/styleControlConstants"; @@ -225,7 +225,7 @@ const childrenMap = { thousandsSeparator: BoolControl.DEFAULT_TRUE, // Whether to display the thousand separator allowNull: BoolControl, onEvent: InputEventHandlerControl, - viewRef: RefControl, + viewRef: RefControl, style: styleControl(InputLikeStyle), prefixIcon: IconControl, @@ -381,7 +381,10 @@ const NumberInputTmpComp = (function () { .build(); })(); -const NumberInputTmp2Comp = withMethodExposing(NumberInputTmpComp, MethodConfigFocus); +const NumberInputTmp2Comp = withMethodExposing( + NumberInputTmpComp, + refMethods(["focus", "blur", "click", "select", "setSelectionRange", "setRangeText"]) +); export const NumberInputComp = withExposingConfigs(NumberInputTmp2Comp, [ depsConfig({ diff --git a/client/packages/openblocks/src/comps/comps/richTextEditorComp.tsx b/client/packages/openblocks/src/comps/comps/richTextEditorComp.tsx index 1e1def7a..9d88fcee 100644 --- a/client/packages/openblocks/src/comps/comps/richTextEditorComp.tsx +++ b/client/packages/openblocks/src/comps/comps/richTextEditorComp.tsx @@ -43,6 +43,18 @@ const localizeStyle = css` .ql-picker-item[data-value="3"]::before { content: "${trans("richTextEditor.title")} 3"; } + + .ql-picker-item[data-value="1"]::before { + font-size: 26px; + } + + .ql-picker-item[data-value="3"]::before { + font-size: 19px; + } + + .ql-picker-item[data-value="3"]::before { + font-size: 15px; + } } & .ql-tooltip.ql-editing a.ql-action::after { content: "${trans("richTextEditor.save")}"; @@ -211,7 +223,7 @@ function RichTextEditor(props: IProps) { useEffect(() => { let finalValue = props.value; - if (!props.value.startsWith(".+<\/\w+>$/.test(props.value)) { finalValue = `

${props.value}

`; } setContent(finalValue); diff --git a/client/packages/openblocks/src/comps/comps/rootComp.tsx b/client/packages/openblocks/src/comps/comps/rootComp.tsx index 9b691040..ef381a08 100644 --- a/client/packages/openblocks/src/comps/comps/rootComp.tsx +++ b/client/packages/openblocks/src/comps/comps/rootComp.tsx @@ -19,6 +19,8 @@ import { ModuleLayoutCompName } from "constants/compConstants"; import { defaultTheme as localDefaultTheme } from "comps/controls/styleControlConstants"; import { ModuleLoading } from "components/ModuleLoading"; import { getGlobalSettings } from "comps/utils/globalSettings"; +import { getCurrentTheme } from "comps/utils/themeUtil"; +import { DataChangeResponderListComp } from "./dataChangeResponderComp"; interface RootViewProps extends HTMLAttributes { comp: InstanceType; @@ -30,6 +32,7 @@ const childrenMap = { queries: QueryListComp, tempStates: TemporaryStateListComp, transformers: TransformerListComp, + dataResponders: DataChangeResponderListComp, hooks: HookListComp, settings: AppSettingsComp, preload: PreloadComp, @@ -39,18 +42,14 @@ function RootView(props: RootViewProps) { const previewTheme = useContext(ThemeContext); const { comp, isModuleRoot, ...divProps } = props; const [editorState, setEditorState] = useState(); - const themeId = comp.children.settings.getView().themeId; + const appThemeId = comp.children.settings.getView().themeId; const { orgCommonSettings } = getGlobalSettings(); const themeList = orgCommonSettings?.themeList || []; - const defaultTheme = orgCommonSettings?.defaultTheme || []; - let theme = localDefaultTheme; - if (themeId === "default") { - theme = themeList.find((theme) => theme.id === defaultTheme)?.theme || localDefaultTheme; - } else if (themeId) { - theme = themeList.find((theme) => theme.id === themeId)?.theme || localDefaultTheme; - } - theme = previewTheme?.previewTheme || theme; + const theme = + previewTheme?.previewTheme || + getCurrentTheme(themeList, appThemeId)?.theme || + localDefaultTheme; useEffect(() => { const newEditorState = new EditorState(comp, (changeEditorStateFn) => { @@ -221,6 +220,7 @@ export class RootComp extends RootCompBase { ...this.children.hooks.nameAndExposingInfo(), ...this.children.tempStates.nameAndExposingInfo(), ...this.children.transformers.nameAndExposingInfo(), + ...this.children.dataResponders.nameAndExposingInfo(), }; } } diff --git a/client/packages/openblocks/src/comps/comps/selectInputComp/cascaderComp.tsx b/client/packages/openblocks/src/comps/comps/selectInputComp/cascaderComp.tsx index 303d5b49..6cc182f6 100644 --- a/client/packages/openblocks/src/comps/comps/selectInputComp/cascaderComp.tsx +++ b/client/packages/openblocks/src/comps/comps/selectInputComp/cascaderComp.tsx @@ -6,6 +6,7 @@ import { UICompBuilder, withDefault } from "../../generators"; import { CommonNameConfig, NameConfig, withExposingConfigs } from "../../generators/withExposing"; import { CascaderChildren, CascaderPropertyView, defaultDataSource } from "./cascaderContants"; import { getStyle } from "./selectCompConstants"; +import { refMethods } from "comps/generators/withMethodExposing"; const CascaderStyle = styled(Cascader)<{ $style: CascaderStyleType }>` width: 100%; @@ -20,6 +21,7 @@ let CascaderBasicComp = (function () { style: props.style, children: ( )) + .setExposeMethodConfigs(refMethods(["focus", "blur"])) .build(); })(); diff --git a/client/packages/openblocks/src/comps/comps/selectInputComp/cascaderContants.tsx b/client/packages/openblocks/src/comps/comps/selectInputComp/cascaderContants.tsx index 97a06acf..732fe7f5 100644 --- a/client/packages/openblocks/src/comps/comps/selectInputComp/cascaderContants.tsx +++ b/client/packages/openblocks/src/comps/comps/selectInputComp/cascaderContants.tsx @@ -15,6 +15,8 @@ import { showSearchPropertyView, } from "comps/utils/propertyUtils"; import { i18nObjs, trans } from "i18n"; +import { RefControl } from "comps/controls/refControl"; +import { CascaderRef } from "antd/lib/cascader"; export const defaultDataSource = JSON.stringify(i18nObjs.cascader, null, " "); @@ -28,6 +30,7 @@ export const CascaderChildren = { options: JSONObjectArrayControl, style: styleControl(CascaderStyle), showSearch: BoolControl.DEFAULT_TRUE, + viewRef: RefControl, }; export const CascaderPropertyView = ( diff --git a/client/packages/openblocks/src/comps/comps/selectInputComp/checkboxComp.tsx b/client/packages/openblocks/src/comps/comps/selectInputComp/checkboxComp.tsx index 9db0ef74..bd8f0706 100644 --- a/client/packages/openblocks/src/comps/comps/selectInputComp/checkboxComp.tsx +++ b/client/packages/openblocks/src/comps/comps/selectInputComp/checkboxComp.tsx @@ -20,6 +20,8 @@ import { dropdownControl } from "../../controls/dropdownControl"; import { ValueFromOption } from "openblocks-design"; import { EllipsisTextCss } from "openblocks-design"; import { trans } from "i18n"; +import { RefControl } from "comps/controls/refControl"; +import { refMethods } from "comps/generators/withMethodExposing"; const getStyle = (style: CheckboxStyleType) => { return css` @@ -106,6 +108,7 @@ const CheckboxBasicComp = (function () { options: SelectInputOptionControl, style: styleControl(CheckboxStyle), layout: dropdownControl(RadioLayoutOptions, "horizontal"), + viewRef: RefControl, ...SelectInputValidationChildren, ...formDataChildren, @@ -117,6 +120,7 @@ const CheckboxBasicComp = (function () { style: props.style, children: ( ) + .setExposeMethodConfigs(refMethods(["focus", "blur"])) .build(); })(); diff --git a/client/packages/openblocks/src/comps/comps/selectInputComp/multiSelectComp.tsx b/client/packages/openblocks/src/comps/comps/selectInputComp/multiSelectComp.tsx index ea0cc405..e7bb654a 100644 --- a/client/packages/openblocks/src/comps/comps/selectInputComp/multiSelectComp.tsx +++ b/client/packages/openblocks/src/comps/comps/selectInputComp/multiSelectComp.tsx @@ -6,8 +6,9 @@ import { UICompBuilder } from "../../generators"; import { CommonNameConfig, NameConfig, withExposingConfigs } from "../../generators/withExposing"; import { SelectChildrenMap, SelectPropertyView, SelectUIView } from "./selectCompConstants"; import { SelectInputInvalidConfig, useSelectInputValidate } from "./selectInputConstants"; +import { refMethods } from "comps/generators/withMethodExposing"; -export const MultiSelectBasicComp = (function () { +const MultiSelectBasicComp = (function () { const childrenMap = { ...SelectChildrenMap, value: arrayStringExposingStateControl("value", ["1", "2"]), @@ -36,6 +37,7 @@ export const MultiSelectBasicComp = (function () { }); }) .setPropertyViewFn((children) => ) + .setExposeMethodConfigs(refMethods(["focus", "blur", "scrollTo"])) .build(); })(); diff --git a/client/packages/openblocks/src/comps/comps/selectInputComp/radioComp.tsx b/client/packages/openblocks/src/comps/comps/selectInputComp/radioComp.tsx index ad431694..8f7e1bf5 100644 --- a/client/packages/openblocks/src/comps/comps/selectInputComp/radioComp.tsx +++ b/client/packages/openblocks/src/comps/comps/selectInputComp/radioComp.tsx @@ -5,9 +5,9 @@ import { UICompBuilder } from "../../generators"; import { CommonNameConfig, NameConfig, withExposingConfigs } from "../../generators/withExposing"; import { RadioChildrenMap, RadioLayoutOptions, RadioPropertyView } from "./radioCompConstants"; import { SelectInputInvalidConfig, useSelectInputValidate } from "./selectInputConstants"; -import { ValueFromOption } from "openblocks-design"; -import { EllipsisTextCss } from "openblocks-design"; +import { EllipsisTextCss, ValueFromOption } from "openblocks-design"; import { trans } from "i18n"; +import { refMethods } from "comps/generators/withMethodExposing"; const getStyle = (style: RadioStyleType) => { return css` @@ -78,7 +78,7 @@ const Radio = styled(AntdRadio.Group)<{ }} `; -export const RadioBasicComp = (function () { +const RadioBasicComp = (function () { return new UICompBuilder(RadioChildrenMap, (props) => { const [validateState, handleValidate] = useSelectInputValidate(props); return props.label({ @@ -86,6 +86,7 @@ export const RadioBasicComp = (function () { style: props.style, children: ( ) + .setExposeMethodConfigs(refMethods(["focus", "blur"])) .build(); })(); diff --git a/client/packages/openblocks/src/comps/comps/selectInputComp/radioCompConstants.tsx b/client/packages/openblocks/src/comps/comps/selectInputComp/radioCompConstants.tsx index f9e36031..651a4d69 100644 --- a/client/packages/openblocks/src/comps/comps/selectInputComp/radioCompConstants.tsx +++ b/client/packages/openblocks/src/comps/comps/selectInputComp/radioCompConstants.tsx @@ -18,6 +18,7 @@ import { RadioStyle } from "comps/controls/styleControlConstants"; import { dropdownControl } from "../../controls/dropdownControl"; import { hiddenPropertyView, disabledPropertyView } from "comps/utils/propertyUtils"; import { trans } from "i18n"; +import { RefControl } from "comps/controls/refControl"; export const RadioLayoutOptions = [ { label: trans("radio.horizontal"), value: "horizontal" }, @@ -33,6 +34,7 @@ export const RadioChildrenMap = { options: SelectInputOptionControl, style: styleControl(RadioStyle), layout: dropdownControl(RadioLayoutOptions, "horizontal"), + viewRef: RefControl, ...SelectInputValidationChildren, ...formDataChildren, diff --git a/client/packages/openblocks/src/comps/comps/selectInputComp/segmentedControl.tsx b/client/packages/openblocks/src/comps/comps/selectInputComp/segmentedControl.tsx index 3baba9b6..9a9e1396 100644 --- a/client/packages/openblocks/src/comps/comps/selectInputComp/segmentedControl.tsx +++ b/client/packages/openblocks/src/comps/comps/selectInputComp/segmentedControl.tsx @@ -20,6 +20,8 @@ import { Section, sectionNames } from "openblocks-design"; import { hiddenPropertyView, disabledPropertyView } from "comps/utils/propertyUtils"; import { trans } from "i18n"; import { hasIcon } from "comps/utils"; +import { RefControl } from "comps/controls/refControl"; +import { refMethods } from "comps/generators/withMethodExposing"; const getStyle = (style: SegmentStyleType) => { return css` @@ -54,19 +56,20 @@ const Segmented = styled(AntdSegmented)<{ $style: SegmentStyleType }>` ${(props) => props.$style && getStyle(props.$style)} `; -export const SegmentChildrenMap = { +const SegmentChildrenMap = { value: stringExposingStateControl("value"), label: LabelControl, disabled: BoolCodeControl, onEvent: ChangeEventHandlerControl, options: SelectOptionControl, style: styleControl(SegmentStyle), + viewRef: RefControl, ...SelectInputValidationChildren, ...formDataChildren, }; -export const SegmentedControlBasicComp = (function () { +const SegmentedControlBasicComp = (function () { return new UICompBuilder(SegmentChildrenMap, (props) => { const [validateState, handleValidate] = useSelectInputValidate(props); return props.label({ @@ -74,6 +77,7 @@ export const SegmentedControlBasicComp = (function () { style: props.style, children: ( {children.style.getPropertyView()} )) + .setExposeMethodConfigs(refMethods(["focus", "blur"])) .build(); })(); diff --git a/client/packages/openblocks/src/comps/comps/selectInputComp/selectComp.tsx b/client/packages/openblocks/src/comps/comps/selectInputComp/selectComp.tsx index d0b5c98b..98353472 100644 --- a/client/packages/openblocks/src/comps/comps/selectInputComp/selectComp.tsx +++ b/client/packages/openblocks/src/comps/comps/selectInputComp/selectComp.tsx @@ -12,8 +12,9 @@ import { } from "./selectInputConstants"; import { useRef } from "react"; import { RecordConstructorToView } from "openblocks-core"; +import { refMethods } from "comps/generators/withMethodExposing"; -export const SelectBasicComp = (function () { +const SelectBasicComp = (function () { const childrenMap = { ...SelectChildrenMap, value: stringExposingStateControl("value"), @@ -46,6 +47,7 @@ export const SelectBasicComp = (function () { }); }) .setPropertyViewFn((children) => ) + .setExposeMethodConfigs(refMethods(["focus", "blur", "scrollTo"])) .build(); })(); diff --git a/client/packages/openblocks/src/comps/comps/selectInputComp/selectCompConstants.tsx b/client/packages/openblocks/src/comps/comps/selectInputComp/selectCompConstants.tsx index 2bafe570..6b5f2bd1 100644 --- a/client/packages/openblocks/src/comps/comps/selectInputComp/selectCompConstants.tsx +++ b/client/packages/openblocks/src/comps/comps/selectInputComp/selectCompConstants.tsx @@ -41,6 +41,8 @@ import { } from "comps/utils/propertyUtils"; import { trans } from "i18n"; import { hasIcon } from "comps/utils"; +import { RefControl } from "comps/controls/refControl"; +import { BaseSelectRef } from "rc-select"; export const getStyle = ( style: SelectStyleType | MultiSelectStyleType | CascaderStyleType | TreeSelectStyleType @@ -167,6 +169,7 @@ export const SelectChildrenMap = { allowClear: BoolControl, inputValue: stateComp(""), // user's input value when search showSearch: BoolControl.DEFAULT_TRUE, + viewRef: RefControl, ...SelectInputValidationChildren, ...formDataChildren, }; @@ -181,6 +184,7 @@ export const SelectUIView = ( } ) => (