mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-14 21:12:53 +00:00
fix: fix some bugs
This commit is contained in:
parent
f1711e0fc9
commit
0157bbd68f
@ -3,7 +3,6 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"playground": "pnpm --filter playground dev",
|
|
||||||
"build": "node ./scripts/build.js",
|
"build": "node ./scripts/build.js",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"clean": "rimraf ./packages/*/dist",
|
"clean": "rimraf ./packages/*/dist",
|
||||||
@ -35,6 +34,7 @@
|
|||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"globals": "^15.0.0",
|
"globals": "^15.0.0",
|
||||||
"husky": "^9.0.11",
|
"husky": "^9.0.11",
|
||||||
|
"jsdom": "^24.1.0",
|
||||||
"lint-staged": "^15.2.2",
|
"lint-staged": "^15.2.2",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"rimraf": "^5.0.2",
|
"rimraf": "^5.0.2",
|
||||||
|
|||||||
@ -26,6 +26,11 @@ function RouteOutlet({ pageConfig }: OutletProps) {
|
|||||||
const context = useRenderContext();
|
const context = useRenderContext();
|
||||||
const { schema, packageManager } = context;
|
const { schema, packageManager } = context;
|
||||||
const { type = 'lowCode', mappingId } = pageConfig;
|
const { type = 'lowCode', mappingId } = pageConfig;
|
||||||
|
console.log(
|
||||||
|
'%c [ pageConfig ]-29',
|
||||||
|
'font-size:13px; background:pink; color:#bf2c9f;',
|
||||||
|
pageConfig,
|
||||||
|
);
|
||||||
|
|
||||||
if (type === 'lowCode') {
|
if (type === 'lowCode') {
|
||||||
// 在页面渲染时重新获取 componentsMap
|
// 在页面渲染时重新获取 componentsMap
|
||||||
|
|||||||
@ -13,12 +13,12 @@ export const createRouterProvider = (router: Router) => {
|
|||||||
return () => remove();
|
return () => remove();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const pageSchema = useMemo(() => {
|
const pageConfig = useMemo(() => {
|
||||||
const pages = schema.get('pages') ?? [];
|
const pages = schema.get('pages') ?? [];
|
||||||
const matched = location.matched[location.matched.length - 1];
|
const matched = location.matched[location.matched.length - 1];
|
||||||
|
|
||||||
if (matched) {
|
if (matched) {
|
||||||
const page = pages.find((item) => matched.page === item.id);
|
const page = pages.find((item) => matched.page === item.mappingId);
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ export const createRouterProvider = (router: Router) => {
|
|||||||
return (
|
return (
|
||||||
<RouterContext.Provider value={router}>
|
<RouterContext.Provider value={router}>
|
||||||
<RouteLocationContext.Provider value={location}>
|
<RouteLocationContext.Provider value={location}>
|
||||||
<PageConfigContext.Provider value={pageSchema}>{children}</PageConfigContext.Provider>
|
<PageConfigContext.Provider value={pageConfig}>{children}</PageConfigContext.Provider>
|
||||||
</RouteLocationContext.Provider>
|
</RouteLocationContext.Provider>
|
||||||
</RouterContext.Provider>
|
</RouterContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1 +1,5 @@
|
|||||||
export const dataSourceCreator = () => ({}) as any;
|
export const dataSourceCreator = () =>
|
||||||
|
({
|
||||||
|
dataSourceMap: {},
|
||||||
|
reloadDataSource: () => {},
|
||||||
|
}) as any;
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
import { processValue, someValue } from '@alilc/lowcode-renderer-core';
|
import { processValue, someValue } from '@alilc/lowcode-renderer-core';
|
||||||
import {
|
import {
|
||||||
watch,
|
|
||||||
isJSExpression,
|
isJSExpression,
|
||||||
isJSFunction,
|
isJSFunction,
|
||||||
isJSSlot,
|
isJSSlot,
|
||||||
invariant,
|
invariant,
|
||||||
isLowCodeComponentSchema,
|
isLowCodeComponentPackage,
|
||||||
isJSI18nNode,
|
isJSI18nNode,
|
||||||
} from '@alilc/lowcode-shared';
|
} from '@alilc/lowcode-shared';
|
||||||
import { forwardRef, useRef, useEffect, createElement, memo } from 'react';
|
import { forwardRef, useRef, useEffect, createElement, memo } from 'react';
|
||||||
import { appendExternalStyle } from '../utils/element';
|
import { appendExternalStyle } from '../../../../playground/renderer/src/plugin/remote/element';
|
||||||
import { reactive } from '../utils/reactive';
|
import { reactive } from '../utils/reactive';
|
||||||
import { useRenderContext } from '../context/render';
|
import { useRenderContext } from '../context/render';
|
||||||
import { reactiveStateCreator } from './reactiveState';
|
import { reactiveStateCreator } from './reactiveState';
|
||||||
@ -63,7 +62,7 @@ export function getComponentByName(
|
|||||||
|
|
||||||
invariant(result, `${name} component not found in componentsRecord`);
|
invariant(result, `${name} component not found in componentsRecord`);
|
||||||
|
|
||||||
if (isLowCodeComponentSchema(result)) {
|
if (isLowCodeComponentPackage(result)) {
|
||||||
const { componentsMap, componentsTree, utils, i18n } = result.schema;
|
const { componentsMap, componentsTree, utils, i18n } = result.schema;
|
||||||
|
|
||||||
if (componentsMap.length > 0) {
|
if (componentsMap.length > 0) {
|
||||||
@ -120,6 +119,7 @@ export function createComponentBySchema(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const model = modelRef.current;
|
const model = modelRef.current;
|
||||||
|
console.log('%c [ model ]-123', 'font-size:13px; background:pink; color:#bf2c9f;', model);
|
||||||
|
|
||||||
const isConstructed = useRef(false);
|
const isConstructed = useRef(false);
|
||||||
const isMounted = useRef(false);
|
const isMounted = useRef(false);
|
||||||
@ -133,7 +133,7 @@ export function createComponentBySchema(
|
|||||||
const scopeValue = model.codeScope.value;
|
const scopeValue = model.codeScope.value;
|
||||||
|
|
||||||
// init dataSource
|
// init dataSource
|
||||||
scopeValue.reloadDataSource();
|
scopeValue.reloadDataSource?.();
|
||||||
|
|
||||||
let styleEl: HTMLElement | undefined;
|
let styleEl: HTMLElement | undefined;
|
||||||
const cssText = model.getCssText();
|
const cssText = model.getCssText();
|
||||||
@ -148,11 +148,11 @@ export function createComponentBySchema(
|
|||||||
model.triggerLifeCycle('componentDidMount');
|
model.triggerLifeCycle('componentDidMount');
|
||||||
|
|
||||||
// 当 state 改变之后调用
|
// 当 state 改变之后调用
|
||||||
const unwatch = watch(scopeValue.state, (_, oldVal) => {
|
// const unwatch = watch(scopeValue.state, (_, oldVal) => {
|
||||||
if (isMounted.current) {
|
// if (isMounted.current) {
|
||||||
model.triggerLifeCycle('componentDidUpdate', props, oldVal);
|
// model.triggerLifeCycle('componentDidUpdate', props, oldVal);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
isMounted.current = true;
|
isMounted.current = true;
|
||||||
|
|
||||||
@ -160,7 +160,7 @@ export function createComponentBySchema(
|
|||||||
// componentWillUnmount?.();
|
// componentWillUnmount?.();
|
||||||
model.triggerLifeCycle('componentWillUnmount');
|
model.triggerLifeCycle('componentWillUnmount');
|
||||||
styleEl?.parentNode?.removeChild(styleEl);
|
styleEl?.parentNode?.removeChild(styleEl);
|
||||||
unwatch();
|
// unwatch();
|
||||||
isMounted.current = false;
|
isMounted.current = false;
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
@ -247,7 +247,7 @@ function createElementByWidget(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 先将 jsslot, jsFunction 对象转换
|
// 先将 jsslot, jsFunction 对象转换
|
||||||
const finalProps = processValue(
|
let finalProps = processValue(
|
||||||
componentProps,
|
componentProps,
|
||||||
(node) => isJSFunction(node) || isJSSlot(node),
|
(node) => isJSFunction(node) || isJSSlot(node),
|
||||||
(node: Spec.JSSlot | Spec.JSFunction) => {
|
(node: Spec.JSSlot | Spec.JSFunction) => {
|
||||||
@ -288,6 +288,17 @@ function createElementByWidget(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
finalProps = processValue(
|
||||||
|
finalProps,
|
||||||
|
(value) => {
|
||||||
|
return value.type === 'JSSlot' && !value.value;
|
||||||
|
},
|
||||||
|
(node) => {
|
||||||
|
console.log('%c [ node ]-303', 'font-size:13px; background:pink; color:#bf2c9f;', node);
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const childElements = children?.map((child) =>
|
const childElements = children?.map((child) =>
|
||||||
createElementByWidget(child, codeScope, renderContext, componentRefAttached),
|
createElementByWidget(child, codeScope, renderContext, componentRefAttached),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,158 +0,0 @@
|
|||||||
const addLeadingSlash = (path: string): string => {
|
|
||||||
return path.charAt(0) === '/' ? path : `/${path}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function getElementById(id: string, tag: string = 'div') {
|
|
||||||
let el = document.getElementById(id);
|
|
||||||
if (!el) {
|
|
||||||
el = document.createElement(tag);
|
|
||||||
el.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
|
|
||||||
const enum AssetType {
|
|
||||||
STYLE = 'style',
|
|
||||||
SCRIPT = 'script',
|
|
||||||
UNKONWN = 'unkonwn',
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAssetTypeByUrl(url: string): AssetType {
|
|
||||||
const IS_CSS_REGEX = /\.css(\?((?!\.js$).)+)?$/;
|
|
||||||
const IS_JS_REGEX = /\.[t|j]sx?(\?((?!\.css$).)+)?$/;
|
|
||||||
const IS_JSON_REGEX = /\.json$/;
|
|
||||||
|
|
||||||
if (IS_CSS_REGEX.test(url)) {
|
|
||||||
return AssetType.STYLE;
|
|
||||||
} else if (IS_JS_REGEX.test(url) || IS_JSON_REGEX.test(url)) {
|
|
||||||
return AssetType.SCRIPT;
|
|
||||||
}
|
|
||||||
|
|
||||||
return AssetType.UNKONWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadPackageUrls(urls: string[]) {
|
|
||||||
const styles: string[] = [];
|
|
||||||
const scripts: string[] = [];
|
|
||||||
|
|
||||||
for (const url of urls) {
|
|
||||||
const type = getAssetTypeByUrl(url);
|
|
||||||
|
|
||||||
if (type === AssetType.SCRIPT) {
|
|
||||||
scripts.push(url);
|
|
||||||
} else if (type === AssetType.STYLE) {
|
|
||||||
styles.push(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(styles.map((item) => appendExternalCss(item)));
|
|
||||||
await Promise.all(scripts.map((item) => appendExternalScript(item)));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function appendExternalScript(
|
|
||||||
url: string,
|
|
||||||
root: HTMLElement = document.body,
|
|
||||||
): Promise<HTMLElement> {
|
|
||||||
if (url) {
|
|
||||||
const el = getIfExistAssetByUrl(url, 'script');
|
|
||||||
if (el) return el;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const scriptElement = document.createElement('script');
|
|
||||||
|
|
||||||
// scriptElement.type = moduleType === 'module' ? 'module' : 'text/javascript';
|
|
||||||
/**
|
|
||||||
* `async=false` is required to make sure all js resources execute sequentially.
|
|
||||||
*/
|
|
||||||
scriptElement.async = false;
|
|
||||||
scriptElement.crossOrigin = 'anonymous';
|
|
||||||
scriptElement.src = url;
|
|
||||||
|
|
||||||
scriptElement.addEventListener(
|
|
||||||
'load',
|
|
||||||
() => {
|
|
||||||
resolve(scriptElement);
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
scriptElement.addEventListener('error', (error) => {
|
|
||||||
if (root.contains(scriptElement)) {
|
|
||||||
root.removeChild(scriptElement);
|
|
||||||
}
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
root.appendChild(scriptElement);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function appendExternalCss(
|
|
||||||
url: string,
|
|
||||||
root: HTMLElement = document.head,
|
|
||||||
): Promise<HTMLElement> {
|
|
||||||
if (url) {
|
|
||||||
const el = getIfExistAssetByUrl(url, 'link');
|
|
||||||
if (el) return el;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const el: HTMLLinkElement = document.createElement('link');
|
|
||||||
el.rel = 'stylesheet';
|
|
||||||
el.href = url;
|
|
||||||
|
|
||||||
el.addEventListener(
|
|
||||||
'load',
|
|
||||||
() => {
|
|
||||||
resolve(el);
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
el.addEventListener('error', (error) => {
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
root.appendChild(el);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function appendExternalStyle(
|
|
||||||
cssText: string,
|
|
||||||
root: HTMLElement = document.head,
|
|
||||||
): Promise<HTMLElement> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const el: HTMLStyleElement = document.createElement('style');
|
|
||||||
el.innerText = cssText;
|
|
||||||
|
|
||||||
el.addEventListener(
|
|
||||||
'load',
|
|
||||||
() => {
|
|
||||||
resolve(el);
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
el.addEventListener('error', (error) => {
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
root.appendChild(el);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getIfExistAssetByUrl(
|
|
||||||
url: string,
|
|
||||||
tag: 'link' | 'script',
|
|
||||||
): HTMLLinkElement | HTMLScriptElement | undefined {
|
|
||||||
return Array.from(document.getElementsByTagName(tag)).find((item) => {
|
|
||||||
const elUrl = (item as HTMLLinkElement).href || (item as HTMLScriptElement).src;
|
|
||||||
|
|
||||||
if (/^(https?:)?\/\/([\w.]+\/?)\S*/gi.test(url)) {
|
|
||||||
// if url === http://xxx.xxx
|
|
||||||
return url === elUrl;
|
|
||||||
} else {
|
|
||||||
// if url === /xx/xx/xx.xx
|
|
||||||
return `${location.origin}${addLeadingSlash(url)}` === elUrl;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -10,5 +10,6 @@ export function normalizeComponentNode(node: Spec.ComponentNode): NormalizedComp
|
|||||||
...node,
|
...node,
|
||||||
loopArgs: node.loopArgs ?? ['item', 'index'],
|
loopArgs: node.loopArgs ?? ['item', 'index'],
|
||||||
props: node.props ?? {},
|
props: node.props ?? {},
|
||||||
|
condition: node.condition || node.condition === false ? node.condition : true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,5 +3,5 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "dist"
|
"outDir": "dist"
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src", "../../playground/renderer/src/plugin/remote/element.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
|
||||||
import { appBoosts } from '../src/boosts';
|
|
||||||
|
|
||||||
describe('appBoosts', () => {
|
|
||||||
it('should add value successfully', () => {
|
|
||||||
appBoosts.add('test', 1);
|
|
||||||
expect(appBoosts.value.test).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should clear removed value', () => {
|
|
||||||
appBoosts.add('test', 1);
|
|
||||||
expect(appBoosts.value.test).toBe(1);
|
|
||||||
|
|
||||||
appBoosts.remove('test');
|
|
||||||
expect(appBoosts.value.test).toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1 +0,0 @@
|
|||||||
import {} from 'vitest';
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
import { expect } from 'vitest';
|
|
||||||
import { createPackageManager } from '../src/package';
|
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
import { describe, it, expect, beforeAll } from 'vitest';
|
||||||
|
import { ICodeScope, CodeScope } from '../../../src/parts/code-runtime';
|
||||||
|
|
||||||
|
describe('codeScope', () => {
|
||||||
|
let scope: ICodeScope;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
scope = new CodeScope({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inject a new value', () => {
|
||||||
|
scope.inject('username', 'Alice');
|
||||||
|
expect(scope.value).toEqual({ username: 'Alice' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not overwrite an existing value without force', () => {
|
||||||
|
scope.inject('username', 'Bob');
|
||||||
|
expect(scope.value).toEqual({ username: 'Alice' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should overwrite an existing value with force', () => {
|
||||||
|
scope.inject('username', 'Bob', true);
|
||||||
|
expect(scope.value).toEqual({ username: 'Bob' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set new value without replacing existing values', () => {
|
||||||
|
scope.setValue({ age: 25 });
|
||||||
|
expect(scope.value).toEqual({ username: 'Bob', age: 25 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set new value and replace all existing values', () => {
|
||||||
|
scope.setValue({ loggedIn: true }, true);
|
||||||
|
expect(scope.value).toEqual({ loggedIn: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a child scope with initial values', () => {
|
||||||
|
const childScope = scope.createChild({ sessionId: 'abc123' });
|
||||||
|
expect(childScope.value).toEqual({ loggedIn: true, sessionId: 'abc123' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set new values in the child scope without affecting the parent scope', () => {
|
||||||
|
const childScope = scope.createChild({ theme: 'dark' });
|
||||||
|
expect(childScope.value).toEqual({ loggedIn: true, sessionId: 'abc123', theme: 'dark' });
|
||||||
|
expect(scope.value).toEqual({ loggedIn: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,12 +0,0 @@
|
|||||||
import { describe, it, expect, expectTypeOf } from 'vitest';
|
|
||||||
import { definePlugin, type Plugin, createPluginManager } from '../src/plugin';
|
|
||||||
|
|
||||||
describe('createPluginManager', () => {
|
|
||||||
it('should install plugin successfully', () => {});
|
|
||||||
|
|
||||||
it('should install plugins when deps installed', () => {});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('definePlugin', () => {
|
|
||||||
it('should return a plugin', () => {});
|
|
||||||
});
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
|
||||||
import { nonSetterProxy } from '../../src/utils/non-setter-proxy';
|
|
||||||
|
|
||||||
describe('nonSetterProxy', () => {
|
|
||||||
it('should non setter on proxy', () => {
|
|
||||||
const target = { a: 1 };
|
|
||||||
const proxy = nonSetterProxy(target);
|
|
||||||
|
|
||||||
expect(() => ((proxy as any).b = 1)).toThrowError(/trap returned falsish for property 'b'/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should correct value when getter', () => {
|
|
||||||
const target = { a: 1 };
|
|
||||||
const proxy = nonSetterProxy(target);
|
|
||||||
|
|
||||||
expect(proxy.a).toBe(1);
|
|
||||||
expect('a' in proxy).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -32,6 +32,8 @@ export class RendererMain {
|
|||||||
// valid schema
|
// valid schema
|
||||||
this.schemaService.initialize(schema);
|
this.schemaService.initialize(schema);
|
||||||
|
|
||||||
|
this.codeRuntimeService.initialize(options);
|
||||||
|
|
||||||
// init intl
|
// init intl
|
||||||
const finalLocale = options.locale ?? navigator.language;
|
const finalLocale = options.locale ?? navigator.language;
|
||||||
const i18nTranslations = this.schemaService.get('i18n') ?? {};
|
const i18nTranslations = this.schemaService.get('i18n') ?? {};
|
||||||
|
|||||||
@ -11,25 +11,43 @@ import {
|
|||||||
import { type ICodeScope, CodeScope } from './codeScope';
|
import { type ICodeScope, CodeScope } from './codeScope';
|
||||||
import { processValue } from '../../utils/value';
|
import { processValue } from '../../utils/value';
|
||||||
|
|
||||||
|
type BeforeResolveCb = (
|
||||||
|
code: Spec.JSExpression | Spec.JSFunction,
|
||||||
|
) => Spec.JSExpression | Spec.JSFunction;
|
||||||
|
|
||||||
export interface ICodeRuntimeService {
|
export interface ICodeRuntimeService {
|
||||||
|
initialize({ evalCodeFunction }: CodeRuntimeInitializeOptions): void;
|
||||||
|
|
||||||
getScope(): ICodeScope;
|
getScope(): ICodeScope;
|
||||||
|
|
||||||
run<R = unknown>(code: string, scope?: ICodeScope): R | undefined;
|
run<R = unknown>(code: string, scope?: ICodeScope): R | undefined;
|
||||||
|
|
||||||
resolve(value: PlainObject, scope?: ICodeScope): any;
|
resolve(value: PlainObject, scope?: ICodeScope): any;
|
||||||
|
|
||||||
beforeRun(fn: (code: string) => string): EventDisposable;
|
beforeResolve(fn: BeforeResolveCb): EventDisposable;
|
||||||
|
|
||||||
createChildScope(value: PlainObject): ICodeScope;
|
createChildScope(value: PlainObject): ICodeScope;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ICodeRuntimeService = createDecorator<ICodeRuntimeService>('codeRuntimeService');
|
export const ICodeRuntimeService = createDecorator<ICodeRuntimeService>('codeRuntimeService');
|
||||||
|
|
||||||
|
export type EvalCodeFunction = (code: string, scope: any) => any;
|
||||||
|
|
||||||
|
export interface CodeRuntimeInitializeOptions {
|
||||||
|
evalCodeFunction?: EvalCodeFunction;
|
||||||
|
}
|
||||||
|
|
||||||
@Provide(ICodeRuntimeService)
|
@Provide(ICodeRuntimeService)
|
||||||
export class CodeRuntimeService implements ICodeRuntimeService {
|
export class CodeRuntimeService implements ICodeRuntimeService {
|
||||||
private codeScope: ICodeScope = new CodeScope({});
|
private codeScope: ICodeScope = new CodeScope({});
|
||||||
|
|
||||||
private callbacks = createCallback<(code: string) => string>();
|
private callbacks = createCallback<BeforeResolveCb>();
|
||||||
|
|
||||||
|
private evalCodeFunction: EvalCodeFunction = evaluate;
|
||||||
|
|
||||||
|
initialize({ evalCodeFunction }: CodeRuntimeInitializeOptions) {
|
||||||
|
if (evalCodeFunction) this.evalCodeFunction = evalCodeFunction;
|
||||||
|
}
|
||||||
|
|
||||||
getScope() {
|
getScope() {
|
||||||
return this.codeScope;
|
return this.codeScope;
|
||||||
@ -39,13 +57,7 @@ export class CodeRuntimeService implements ICodeRuntimeService {
|
|||||||
if (!code) return undefined;
|
if (!code) return undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const cbs = this.callbacks.list();
|
let result = this.evalCodeFunction(code, scope.value);
|
||||||
const finalCode = cbs.reduce((code, cb) => cb(code), code);
|
|
||||||
|
|
||||||
let result = new Function(
|
|
||||||
'scope',
|
|
||||||
`"use strict";return (function(){return (${finalCode})}).bind(scope)();`,
|
|
||||||
)(scope.value);
|
|
||||||
|
|
||||||
if (typeof result === 'function') {
|
if (typeof result === 'function') {
|
||||||
result = result.bind(scope.value);
|
result = result.bind(scope.value);
|
||||||
@ -66,17 +78,20 @@ export class CodeRuntimeService implements ICodeRuntimeService {
|
|||||||
return isJSExpression(data) || isJSFunction(data);
|
return isJSExpression(data) || isJSFunction(data);
|
||||||
},
|
},
|
||||||
(node: Spec.JSExpression | Spec.JSFunction) => {
|
(node: Spec.JSExpression | Spec.JSFunction) => {
|
||||||
const v = this.run(node.value, scope);
|
const cbs = this.callbacks.list();
|
||||||
|
const finalNode = cbs.reduce((node, cb) => cb(node), node);
|
||||||
|
|
||||||
if (typeof v === 'undefined' && (node as any).mock) {
|
const v = this.run(finalNode.value, scope);
|
||||||
return (node as any).mock;
|
|
||||||
|
if (typeof v === 'undefined' && finalNode.mock) {
|
||||||
|
return this.resolve(finalNode.mock, scope);
|
||||||
}
|
}
|
||||||
return v;
|
return v;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeRun(fn: (code: string) => string): EventDisposable {
|
beforeResolve(fn: BeforeResolveCb): EventDisposable {
|
||||||
return this.callbacks.add(fn);
|
return this.callbacks.add(fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,3 +99,9 @@ export class CodeRuntimeService implements ICodeRuntimeService {
|
|||||||
return this.codeScope.createChild(value);
|
return this.codeScope.createChild(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function evaluate(code: string, scope: any) {
|
||||||
|
return new Function('scope', `"use strict";return (function(){return (${code})}).bind(scope)();`)(
|
||||||
|
scope,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -38,11 +38,23 @@ export class CodeScope implements ICodeScope {
|
|||||||
if (Reflect.has(valueTarget.current, p)) {
|
if (Reflect.has(valueTarget.current, p)) {
|
||||||
return Reflect.get(valueTarget.current, p, receiver);
|
return Reflect.get(valueTarget.current, p, receiver);
|
||||||
}
|
}
|
||||||
valueTarget = this.__node.prev;
|
valueTarget = valueTarget.prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Reflect.get(target, p, receiver);
|
return Reflect.get(target, p, receiver);
|
||||||
},
|
},
|
||||||
|
has: (target, p) => {
|
||||||
|
let valueTarget: IScopeNode | undefined = this.__node;
|
||||||
|
|
||||||
|
while (valueTarget) {
|
||||||
|
if (Reflect.has(valueTarget.current, p)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
valueTarget = valueTarget.prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Reflect.has(target, p);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,4 @@
|
|||||||
import {
|
import { type Spec, type PlainObject, isComponentNode, invariant } from '@alilc/lowcode-shared';
|
||||||
type Spec,
|
|
||||||
type PlainObject,
|
|
||||||
isJSFunction,
|
|
||||||
isComponentNode,
|
|
||||||
invariant,
|
|
||||||
type AnyFunction,
|
|
||||||
} from '@alilc/lowcode-shared';
|
|
||||||
import { type ICodeScope, type ICodeRuntimeService } from '../code-runtime';
|
import { type ICodeScope, type ICodeRuntimeService } from '../code-runtime';
|
||||||
import { IWidget, Widget } from '../widget';
|
import { IWidget, Widget } from '../widget';
|
||||||
|
|
||||||
@ -118,8 +111,8 @@ export class ComponentTreeModel<Component, ComponentInstance = unknown>
|
|||||||
);
|
);
|
||||||
|
|
||||||
for (const [key, fn] of Object.entries(methods)) {
|
for (const [key, fn] of Object.entries(methods)) {
|
||||||
const customMethod = this.codeRuntime.run(fn.value, this.codeScope);
|
const customMethod = this.codeRuntime.resolve(fn, this.codeScope);
|
||||||
if (customMethod) {
|
if (typeof customMethod === 'function') {
|
||||||
this.codeScope.inject(key, customMethod);
|
this.codeScope.inject(key, customMethod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -140,11 +133,9 @@ export class ComponentTreeModel<Component, ComponentInstance = unknown>
|
|||||||
|
|
||||||
const lifeCycleSchema = this.componentsTree.lifeCycles[lifeCycleName];
|
const lifeCycleSchema = this.componentsTree.lifeCycles[lifeCycleName];
|
||||||
|
|
||||||
if (isJSFunction(lifeCycleSchema)) {
|
const lifeCycleFn = this.codeRuntime.resolve(lifeCycleSchema, this.codeScope);
|
||||||
const lifeCycleFn = this.codeRuntime.run<AnyFunction>(lifeCycleSchema.value, this.codeScope);
|
if (typeof lifeCycleFn === 'function') {
|
||||||
if (lifeCycleFn) {
|
lifeCycleFn.apply(this.codeScope.value, args);
|
||||||
lifeCycleFn.apply(this.codeScope.value, args);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,23 @@
|
|||||||
import { type Spec, type LowCodeComponent, createDecorator, Provide } from '@alilc/lowcode-shared';
|
import {
|
||||||
|
type Spec,
|
||||||
|
type LowCodeComponent,
|
||||||
|
type ProCodeComponent,
|
||||||
|
createDecorator,
|
||||||
|
Provide,
|
||||||
|
isLowCodeComponentPackage,
|
||||||
|
isProCodeComponentPackage,
|
||||||
|
} from '@alilc/lowcode-shared';
|
||||||
|
import { get as lodashGet } from 'lodash-es';
|
||||||
import { PackageLoader } from './loader';
|
import { PackageLoader } from './loader';
|
||||||
|
|
||||||
|
export interface NormalizedPackage {
|
||||||
|
id: string;
|
||||||
|
package: string;
|
||||||
|
library: string;
|
||||||
|
exportSource?: NormalizedPackage | undefined;
|
||||||
|
raw: ProCodeComponent;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IPackageManagementService {
|
export interface IPackageManagementService {
|
||||||
/**
|
/**
|
||||||
* 新增资产包
|
* 新增资产包
|
||||||
@ -39,36 +56,30 @@ export class PackageManagementService implements IPackageManagementService {
|
|||||||
|
|
||||||
private packageStore: Map<string, any> = ((window as any).__PACKAGE_STORE__ ??= new Map());
|
private packageStore: Map<string, any> = ((window as any).__PACKAGE_STORE__ ??= new Map());
|
||||||
|
|
||||||
private packagesRef: Spec.Package[] = [];
|
private packagesMap: Map<string, NormalizedPackage> = new Map();
|
||||||
|
|
||||||
|
private lowCodeComponentPackages: Map<string, LowCodeComponent> = new Map();
|
||||||
|
|
||||||
private packageLoaders: PackageLoader[] = [];
|
private packageLoaders: PackageLoader[] = [];
|
||||||
|
|
||||||
async loadPackages(packages: Spec.Package[]) {
|
async loadPackages(packages: Spec.Package[]) {
|
||||||
for (const item of packages) {
|
for (const item of packages) {
|
||||||
if (!item.package && !item.id) continue;
|
// low code component not need load
|
||||||
|
if (isLowCodeComponentPackage(item)) {
|
||||||
const newId = item.package ?? item.id!;
|
this.lowCodeComponentPackages.set(item.id, item);
|
||||||
const isExist = this.packagesRef.some((_) => {
|
continue;
|
||||||
const itemId = _.package ?? _.id;
|
|
||||||
return itemId === newId;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isExist) {
|
|
||||||
this.packagesRef.push(item);
|
|
||||||
|
|
||||||
if (!this.packageStore.has(newId)) {
|
|
||||||
const loader = this.packageLoaders.find((loader) => loader.active(item));
|
|
||||||
if (!loader) continue;
|
|
||||||
|
|
||||||
const result = await loader.load.call(this, item);
|
|
||||||
if (result) this.packageStore.set(newId, result);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isProCodeComponentPackage(item)) continue;
|
||||||
|
|
||||||
|
const normalized = this.normalizePackage(item);
|
||||||
|
|
||||||
|
await this.loadPackageByNormalized(normalized);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getPackageInfo(packageName: string) {
|
getPackageInfo(packageName: string) {
|
||||||
return this.packagesRef.find((p) => p.package === packageName);
|
return this.packagesMap.get(packageName)?.raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
getLibraryByPackageName(packageName: string) {
|
getLibraryByPackageName(packageName: string) {
|
||||||
@ -86,9 +97,7 @@ export class PackageManagementService implements IPackageManagementService {
|
|||||||
resolveComponentMaps(componentMaps: Spec.ComponentMap[]) {
|
resolveComponentMaps(componentMaps: Spec.ComponentMap[]) {
|
||||||
for (const map of componentMaps) {
|
for (const map of componentMaps) {
|
||||||
if (map.devMode === 'lowCode') {
|
if (map.devMode === 'lowCode') {
|
||||||
const packageInfo = this.packagesRef.find((_) => {
|
const packageInfo = this.lowCodeComponentPackages.get((map as LowCodeComponent).id);
|
||||||
return _.id === (map as LowCodeComponent).id;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (packageInfo) {
|
if (packageInfo) {
|
||||||
this.componentsRecord[map.componentName] = packageInfo;
|
this.componentsRecord[map.componentName] = packageInfo;
|
||||||
@ -123,7 +132,6 @@ export class PackageManagementService implements IPackageManagementService {
|
|||||||
getComponentsNameRecord(componentMaps?: Spec.ComponentMap[]) {
|
getComponentsNameRecord(componentMaps?: Spec.ComponentMap[]) {
|
||||||
if (componentMaps) {
|
if (componentMaps) {
|
||||||
const newMaps = componentMaps.filter((item) => !this.componentsRecord[item.componentName]);
|
const newMaps = componentMaps.filter((item) => !this.componentsRecord[item.componentName]);
|
||||||
|
|
||||||
this.resolveComponentMaps(newMaps);
|
this.resolveComponentMaps(newMaps);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,4 +151,72 @@ export class PackageManagementService implements IPackageManagementService {
|
|||||||
this.packageLoaders.push(loader);
|
this.packageLoaders.push(loader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private normalizePackage(packageInfo: ProCodeComponent): NormalizedPackage {
|
||||||
|
if (this.packagesMap.has(packageInfo.package)) {
|
||||||
|
return this.packagesMap.get(packageInfo.package)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalized: NormalizedPackage = {
|
||||||
|
package: packageInfo.package,
|
||||||
|
id: packageInfo.id ?? packageInfo.package,
|
||||||
|
library: packageInfo.library,
|
||||||
|
raw: packageInfo,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.packagesMap.set(normalized.package, normalized);
|
||||||
|
|
||||||
|
// add normalized to package exports dependency graph
|
||||||
|
const packagesRef = [...this.packagesMap.values()];
|
||||||
|
|
||||||
|
// set export source
|
||||||
|
if (packageInfo.exportSourceId || packageInfo.exportSourceLibrary) {
|
||||||
|
const found = packagesRef.find((item) => {
|
||||||
|
if (!packageInfo.exportSourceId) {
|
||||||
|
return item.library === packageInfo.exportSourceLibrary;
|
||||||
|
} else {
|
||||||
|
return item.package === packageInfo.exportSourceId;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
normalized.exportSource = found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadPackageByNormalized(normalized: NormalizedPackage) {
|
||||||
|
if (this.packageStore.has(normalized.package)) return;
|
||||||
|
|
||||||
|
// if it has export source package, wait export source package loaded
|
||||||
|
if (normalized.exportSource) {
|
||||||
|
if (this.packageStore.has(normalized.package)) {
|
||||||
|
const library = lodashGet(window, normalized.library);
|
||||||
|
if (library) {
|
||||||
|
this.packageStore.set(normalized.package, library);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const loader = this.packageLoaders.find((loader) => loader.active(normalized.raw));
|
||||||
|
if (!loader) return;
|
||||||
|
|
||||||
|
const result = await loader.load.call(this, normalized.raw);
|
||||||
|
if (result) {
|
||||||
|
this.packageStore.set(normalized.package, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if current package loaded, set the value of the dependency on this package
|
||||||
|
if (this.packageStore.has(normalized.package)) {
|
||||||
|
const chilren = [...this.packagesMap.values()].filter((item) => {
|
||||||
|
return item.exportSource?.package === normalized.package;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const child of chilren) {
|
||||||
|
await this.loadPackageByNormalized(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,7 +33,7 @@ export class RuntimeUtilService implements IRuntimeUtilService {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const fn = this.parseUtil(name);
|
const fn = this.parseUtil(name);
|
||||||
this.utilsMap.set(name.name, fn);
|
if (fn) this.utilsMap.set(name.name, fn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { type Plugin } from './parts/extension';
|
|||||||
import { type ISchemaService } from './parts/schema';
|
import { type ISchemaService } from './parts/schema';
|
||||||
import { type IPackageManagementService } from './parts/package';
|
import { type IPackageManagementService } from './parts/package';
|
||||||
import { type IExtensionHostService } from './parts/extension';
|
import { type IExtensionHostService } from './parts/extension';
|
||||||
|
import { type EvalCodeFunction } from './parts/code-runtime';
|
||||||
|
|
||||||
export interface AppOptions {
|
export interface AppOptions {
|
||||||
schema: Spec.Project;
|
schema: Spec.Project;
|
||||||
@ -18,6 +19,8 @@ export interface AppOptions {
|
|||||||
* 运行模式
|
* 运行模式
|
||||||
*/
|
*/
|
||||||
mode?: 'development' | 'production';
|
mode?: 'development' | 'production';
|
||||||
|
|
||||||
|
evalCodeFunction?: EvalCodeFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RendererApplication<Render = unknown> = {
|
export type RendererApplication<Render = unknown> = {
|
||||||
|
|||||||
@ -100,9 +100,10 @@ function nomarlizeLocale(target: Locale) {
|
|||||||
return navigatorLanguageMapping[target];
|
return navigatorLanguageMapping[target];
|
||||||
}
|
}
|
||||||
|
|
||||||
const replaced = target.replace('_', '-');
|
// const replaced = target.replace('_', '-');
|
||||||
const splited = replaced.split('-').slice(0, 2);
|
// const splited = replaced.split('-').slice(0, 2);
|
||||||
splited[1] = splited[1].toUpperCase();
|
// splited[1] = splited[1].toUpperCase();
|
||||||
|
|
||||||
return splited.join('-');
|
// return splited.join('-');
|
||||||
|
return target;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { Project } from './specs/lowcode-spec';
|
|||||||
export interface ProCodeComponent extends Package {
|
export interface ProCodeComponent extends Package {
|
||||||
package: string;
|
package: string;
|
||||||
type: 'proCode';
|
type: 'proCode';
|
||||||
|
library: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LowCodeComponent extends Package {
|
export interface LowCodeComponent extends Package {
|
||||||
|
|||||||
@ -56,7 +56,7 @@ export interface Package {
|
|||||||
/**
|
/**
|
||||||
* 作为全局变量引用时的名称,和 webpack output.library 字段含义一样,用来定义全局变量名
|
* 作为全局变量引用时的名称,和 webpack output.library 字段含义一样,用来定义全局变量名
|
||||||
*/
|
*/
|
||||||
library: string;
|
library?: string | undefined;
|
||||||
/**
|
/**
|
||||||
* 组件描述导出名字,可以通过 window[exportName] 获取到组件描述的 Object 内容;
|
* 组件描述导出名字,可以通过 window[exportName] 获取到组件描述的 Object 内容;
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -423,6 +423,8 @@ export interface JSSlot {
|
|||||||
type: 'JSSlot';
|
type: 'JSSlot';
|
||||||
value: ComponentNode | ComponentNode[];
|
value: ComponentNode | ComponentNode[];
|
||||||
params?: string[];
|
params?: string[];
|
||||||
|
|
||||||
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -431,6 +433,8 @@ export interface JSSlot {
|
|||||||
export interface JSFunction {
|
export interface JSFunction {
|
||||||
type: 'JSFunction';
|
type: 'JSFunction';
|
||||||
value: string;
|
value: string;
|
||||||
|
|
||||||
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -439,6 +443,8 @@ export interface JSFunction {
|
|||||||
export interface JSExpression {
|
export interface JSExpression {
|
||||||
type: 'JSExpression';
|
type: 'JSExpression';
|
||||||
value: string;
|
value: string;
|
||||||
|
|
||||||
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -456,4 +462,6 @@ export interface JSI18n {
|
|||||||
params?: Record<string, string | number | JSExpression>;
|
params?: Record<string, string | number | JSExpression>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type JSNode = JSSlot | JSExpression | JSExpression | JSI18n;
|
||||||
|
|
||||||
export type NodeType = string | JSExpression | JSI18n | ComponentNode;
|
export type NodeType = string | JSExpression | JSI18n | ComponentNode;
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
export * from './spec';
|
export * from './spec';
|
||||||
|
export * from './material';
|
||||||
|
|||||||
10
packages/shared/src/utils/type-guards/material.ts
Normal file
10
packages/shared/src/utils/type-guards/material.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { LowCodeComponent, ProCodeComponent } from '../../types';
|
||||||
|
import { isPlainObject } from 'lodash-es';
|
||||||
|
|
||||||
|
export function isLowCodeComponentPackage(v: unknown): v is LowCodeComponent {
|
||||||
|
return isPlainObject(v) && (v as any).type === 'lowCode' && (v as any).schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isProCodeComponentPackage(v: unknown): v is ProCodeComponent {
|
||||||
|
return isPlainObject(v) && (v as any).package && (v as any).library;
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Spec, LowCodeComponent } from '../../types';
|
import type { Spec } from '../../types';
|
||||||
import { isPlainObject } from 'lodash-es';
|
import { isPlainObject } from 'lodash-es';
|
||||||
|
|
||||||
export function isJSExpression(v: unknown): v is Spec.JSExpression {
|
export function isJSExpression(v: unknown): v is Spec.JSExpression {
|
||||||
@ -24,7 +24,3 @@ export function isJSI18nNode(v: unknown): v is Spec.JSI18n {
|
|||||||
export function isComponentNode(v: unknown): v is Spec.ComponentNode {
|
export function isComponentNode(v: unknown): v is Spec.ComponentNode {
|
||||||
return isPlainObject(v) && (v as any).componentName;
|
return isPlainObject(v) && (v as any).componentName;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isLowCodeComponentSchema(v: unknown): v is LowCodeComponent {
|
|
||||||
return isPlainObject(v) && (v as any).type === 'lowCode' && (v as any).schema;
|
|
||||||
}
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user