mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-11 18:42:56 +00:00
feat: add event methods, like rxjs
This commit is contained in:
parent
a02b19e6e3
commit
d4bdf14a1d
@ -83,6 +83,7 @@ export function createRenderer<RenderObject = IRenderObject>(
|
||||
},
|
||||
destroy: () => {
|
||||
lifeCycleService.setPhase(LifecyclePhase.Destroying);
|
||||
instantiationService.dispose();
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/* --------------- api -------------------- */
|
||||
export { createRenderer } from './main';
|
||||
export { createRenderer } from './createRenderer';
|
||||
export { IExtensionHostService } from './services/extension';
|
||||
export { definePackageLoader, IPackageManagementService } from './services/package';
|
||||
export { LifecyclePhase, ILifeCycleService } from './services/lifeCycleService';
|
||||
@ -9,7 +9,6 @@ export { IRuntimeIntlService } from './services/runtimeIntlService';
|
||||
export { IRuntimeUtilService } from './services/runtimeUtilService';
|
||||
export { ISchemaService } from './services/schema';
|
||||
export { Widget } from './widget';
|
||||
export * from './utils/value';
|
||||
|
||||
/* --------------- types ---------------- */
|
||||
export type * from './types';
|
||||
|
||||
@ -1,31 +1,34 @@
|
||||
import {
|
||||
type StringDictionary,
|
||||
type JSNode,
|
||||
type EventDisposable,
|
||||
type IDisposable,
|
||||
type JSExpression,
|
||||
type JSFunction,
|
||||
specTypes,
|
||||
isNode,
|
||||
toDisposable,
|
||||
Disposable,
|
||||
} from '@alilc/lowcode-shared';
|
||||
import { type ICodeScope, CodeScope } from './codeScope';
|
||||
import { isNode } from '../../../../shared/src/utils/node';
|
||||
import { mapValue } from '../../utils/value';
|
||||
import { mapValue } from './value';
|
||||
import { evaluate } from './evaluate';
|
||||
|
||||
export interface CodeRuntimeOptions<T extends StringDictionary = StringDictionary> {
|
||||
initScopeValue?: Partial<T>;
|
||||
|
||||
parentScope?: ICodeScope;
|
||||
|
||||
evalCodeFunction?: EvalCodeFunction;
|
||||
}
|
||||
|
||||
export interface ICodeRuntime<T extends StringDictionary = StringDictionary> {
|
||||
export interface ICodeRuntime<T extends StringDictionary = StringDictionary> extends IDisposable {
|
||||
getScope(): ICodeScope<T>;
|
||||
|
||||
run<R = unknown>(code: string, scope?: ICodeScope): R | undefined;
|
||||
|
||||
resolve(value: StringDictionary): any;
|
||||
|
||||
onResolve(handler: NodeResolverHandler): EventDisposable;
|
||||
onResolve(handler: NodeResolverHandler): IDisposable;
|
||||
|
||||
createChild<V extends StringDictionary = StringDictionary>(
|
||||
options: Omit<CodeRuntimeOptions<V>, 'parentScope'>,
|
||||
@ -34,49 +37,55 @@ export interface ICodeRuntime<T extends StringDictionary = StringDictionary> {
|
||||
|
||||
export type NodeResolverHandler = (node: JSNode) => JSNode | false | undefined;
|
||||
|
||||
let onResolveHandlers: NodeResolverHandler[] = [];
|
||||
|
||||
export type EvalCodeFunction = (code: string, scope: any) => any;
|
||||
|
||||
export class CodeRuntime<T extends StringDictionary = StringDictionary> implements ICodeRuntime<T> {
|
||||
private codeScope: ICodeScope<T>;
|
||||
export class CodeRuntime<T extends StringDictionary = StringDictionary>
|
||||
extends Disposable
|
||||
implements ICodeRuntime<T>
|
||||
{
|
||||
private _codeScope: ICodeScope<T>;
|
||||
|
||||
private evalCodeFunction: EvalCodeFunction = evaluate;
|
||||
private _evalCodeFunction: EvalCodeFunction = evaluate;
|
||||
|
||||
private _resolveHandlers: NodeResolverHandler[] = [];
|
||||
|
||||
constructor(options: CodeRuntimeOptions<T> = {}) {
|
||||
if (options.evalCodeFunction) this.evalCodeFunction = options.evalCodeFunction;
|
||||
super();
|
||||
|
||||
if (options.parentScope) {
|
||||
this.codeScope = options.parentScope.createChild<T>(options.initScopeValue ?? {});
|
||||
} else {
|
||||
this.codeScope = new CodeScope(options.initScopeValue ?? {});
|
||||
}
|
||||
if (options.evalCodeFunction) this._evalCodeFunction = options.evalCodeFunction;
|
||||
this._codeScope = this.addDispose(
|
||||
options.parentScope
|
||||
? options.parentScope.createChild<T>(options.initScopeValue ?? {})
|
||||
: new CodeScope(options.initScopeValue ?? {}),
|
||||
);
|
||||
}
|
||||
|
||||
getScope() {
|
||||
return this.codeScope;
|
||||
return this._codeScope;
|
||||
}
|
||||
|
||||
run<R = unknown>(code: string): R | undefined {
|
||||
this._throwIfDisposed(`this code runtime has been disposed`);
|
||||
|
||||
if (!code) return undefined;
|
||||
|
||||
try {
|
||||
const result = this.evalCodeFunction(code, this.codeScope.value);
|
||||
const result = this._evalCodeFunction(code, this._codeScope.value);
|
||||
|
||||
return result as R;
|
||||
} catch (err) {
|
||||
// todo replace logger
|
||||
console.error('eval error', code, this.codeScope.value, err);
|
||||
console.error('eval error', code, this._codeScope.value, err);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
resolve(data: StringDictionary): any {
|
||||
if (onResolveHandlers.length > 0) {
|
||||
if (this._resolveHandlers.length > 0) {
|
||||
data = mapValue(data, isNode, (node: JSNode) => {
|
||||
let newNode: JSNode | false | undefined = node;
|
||||
|
||||
for (const handler of onResolveHandlers) {
|
||||
for (const handler of this._resolveHandlers) {
|
||||
newNode = handler(newNode as JSNode);
|
||||
if (newNode === false || typeof newNode === 'undefined') {
|
||||
break;
|
||||
@ -110,20 +119,25 @@ export class CodeRuntime<T extends StringDictionary = StringDictionary> implemen
|
||||
/**
|
||||
* 顺序执行 handler
|
||||
*/
|
||||
onResolve(handler: NodeResolverHandler): EventDisposable {
|
||||
onResolveHandlers.push(handler);
|
||||
return () => {
|
||||
onResolveHandlers = onResolveHandlers.filter((h) => h !== handler);
|
||||
};
|
||||
onResolve(handler: NodeResolverHandler): IDisposable {
|
||||
this._resolveHandlers.push(handler);
|
||||
|
||||
return this.addDispose(
|
||||
toDisposable(() => {
|
||||
this._resolveHandlers = this._resolveHandlers.filter((h) => h !== handler);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
createChild<V extends StringDictionary = StringDictionary>(
|
||||
options?: Omit<CodeRuntimeOptions<V>, 'parentScope'>,
|
||||
): ICodeRuntime<V> {
|
||||
return new CodeRuntime({
|
||||
initScopeValue: options?.initScopeValue,
|
||||
parentScope: this.codeScope,
|
||||
evalCodeFunction: options?.evalCodeFunction ?? this.evalCodeFunction,
|
||||
});
|
||||
return this.addDispose(
|
||||
new CodeRuntime({
|
||||
initScopeValue: options?.initScopeValue,
|
||||
parentScope: this._codeScope,
|
||||
evalCodeFunction: options?.evalCodeFunction ?? this._evalCodeFunction,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import {
|
||||
createDecorator,
|
||||
invariant,
|
||||
Disposable,
|
||||
type StringDictionary,
|
||||
type IDisposable,
|
||||
} from '@alilc/lowcode-shared';
|
||||
import { type ICodeRuntime, type CodeRuntimeOptions, CodeRuntime } from './codeRuntime';
|
||||
import { ISchemaService } from '../schema';
|
||||
|
||||
export interface ICodeRuntimeService {
|
||||
export interface ICodeRuntimeService extends IDisposable {
|
||||
readonly rootRuntime: ICodeRuntime;
|
||||
|
||||
createCodeRuntime<T extends StringDictionary = StringDictionary>(
|
||||
@ -18,15 +18,18 @@ export interface ICodeRuntimeService {
|
||||
export const ICodeRuntimeService = createDecorator<ICodeRuntimeService>('codeRuntimeService');
|
||||
|
||||
export class CodeRuntimeService extends Disposable implements ICodeRuntimeService {
|
||||
rootRuntime: ICodeRuntime;
|
||||
private _rootRuntime: ICodeRuntime;
|
||||
get rootRuntime() {
|
||||
return this._rootRuntime;
|
||||
}
|
||||
|
||||
constructor(
|
||||
options: CodeRuntimeOptions = {},
|
||||
@ISchemaService private schemaService: ISchemaService,
|
||||
) {
|
||||
super();
|
||||
this.rootRuntime = new CodeRuntime(options);
|
||||
|
||||
this._rootRuntime = this.addDispose(new CodeRuntime(options));
|
||||
this.addDispose(
|
||||
this.schemaService.onSchemaUpdate(({ key, data }) => {
|
||||
if (key === 'constants') {
|
||||
@ -39,10 +42,10 @@ export class CodeRuntimeService extends Disposable implements ICodeRuntimeServic
|
||||
createCodeRuntime<T extends StringDictionary = StringDictionary>(
|
||||
options: CodeRuntimeOptions<T> = {},
|
||||
): ICodeRuntime<T> {
|
||||
invariant(this.rootRuntime, `please initialize codeRuntimeService on renderer starting!`);
|
||||
this._throwIfDisposed();
|
||||
|
||||
return options.parentScope
|
||||
? new CodeRuntime(options)
|
||||
: this.rootRuntime.createChild<T>(options);
|
||||
return this.addDispose(
|
||||
options.parentScope ? new CodeRuntime(options) : this.rootRuntime.createChild<T>(options),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
import { type StringDictionary, LinkedListNode } from '@alilc/lowcode-shared';
|
||||
import {
|
||||
type StringDictionary,
|
||||
Disposable,
|
||||
type IDisposable,
|
||||
LinkedListNode,
|
||||
} from '@alilc/lowcode-shared';
|
||||
import { trustedGlobals } from './globals-es2015';
|
||||
|
||||
/*
|
||||
@ -9,33 +14,50 @@ const unscopables = trustedGlobals.reduce((acc, key) => ({ ...acc, [key]: true }
|
||||
__proto__: null,
|
||||
});
|
||||
|
||||
export interface ICodeScope<T extends StringDictionary = StringDictionary> {
|
||||
export interface ICodeScope<T extends StringDictionary = StringDictionary> extends IDisposable {
|
||||
readonly value: T;
|
||||
|
||||
set(name: keyof T, value: any): void;
|
||||
|
||||
setValue(value: Partial<T>, replace?: boolean): void;
|
||||
|
||||
createChild<V extends StringDictionary = StringDictionary>(initValue: Partial<V>): ICodeScope<V>;
|
||||
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export class CodeScope<T extends StringDictionary = StringDictionary> implements ICodeScope<T> {
|
||||
export class CodeScope<T extends StringDictionary = StringDictionary>
|
||||
extends Disposable
|
||||
implements ICodeScope<T>
|
||||
{
|
||||
node = LinkedListNode.Undefined;
|
||||
|
||||
private proxyValue: T;
|
||||
private _proxyValue?: T;
|
||||
|
||||
constructor(initValue: Partial<T>) {
|
||||
super();
|
||||
|
||||
this.node.current = initValue;
|
||||
this.proxyValue = this.createProxy();
|
||||
}
|
||||
|
||||
get value(): T {
|
||||
return this.proxyValue;
|
||||
this._throwIfDisposed('code scope has been disposed');
|
||||
|
||||
if (!this._proxyValue) {
|
||||
this._proxyValue = this._createProxy();
|
||||
}
|
||||
return this._proxyValue;
|
||||
}
|
||||
|
||||
set(name: keyof T, value: any): void {
|
||||
this._throwIfDisposed('code scope has been disposed');
|
||||
|
||||
this.node.current[name] = value;
|
||||
}
|
||||
|
||||
setValue(value: Partial<T>, replace = false) {
|
||||
this._throwIfDisposed('code scope has been disposed');
|
||||
|
||||
if (replace) {
|
||||
this.node.current = { ...value };
|
||||
} else {
|
||||
@ -44,24 +66,30 @@ export class CodeScope<T extends StringDictionary = StringDictionary> implements
|
||||
}
|
||||
|
||||
createChild<V extends StringDictionary = StringDictionary>(initValue: Partial<V>): ICodeScope<V> {
|
||||
const childScope = new CodeScope(initValue);
|
||||
const childScope = this.addDispose(new CodeScope(initValue));
|
||||
childScope.node.prev = this.node;
|
||||
|
||||
return childScope;
|
||||
}
|
||||
|
||||
private createProxy(): T {
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
this.node = LinkedListNode.Undefined;
|
||||
this._proxyValue = undefined;
|
||||
}
|
||||
|
||||
private _createProxy(): T {
|
||||
return new Proxy(Object.create(null) as T, {
|
||||
set: (target, p, newValue) => {
|
||||
this.set(p as string, newValue);
|
||||
return true;
|
||||
},
|
||||
get: (_, p) => this.findValue(p) ?? undefined,
|
||||
has: (_, p) => this.hasProperty(p),
|
||||
get: (_, p) => this._findValue(p) ?? undefined,
|
||||
has: (_, p) => this._hasProperty(p),
|
||||
});
|
||||
}
|
||||
|
||||
private findValue(prop: PropertyKey) {
|
||||
private _findValue(prop: PropertyKey) {
|
||||
if (prop === Symbol.unscopables) return unscopables;
|
||||
|
||||
let node = this.node;
|
||||
@ -73,7 +101,7 @@ export class CodeScope<T extends StringDictionary = StringDictionary> implements
|
||||
}
|
||||
}
|
||||
|
||||
private hasProperty(prop: PropertyKey): boolean {
|
||||
private _hasProperty(prop: PropertyKey): boolean {
|
||||
if (prop in unscopables) return true;
|
||||
|
||||
let node = this.node;
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export * from './codeScope';
|
||||
export * from './codeRuntimeService';
|
||||
export * from './codeRuntime';
|
||||
export * from './value';
|
||||
|
||||
@ -10,8 +10,9 @@ import {
|
||||
type InstanceDataSourceApi,
|
||||
type ComponentTree,
|
||||
specTypes,
|
||||
invariant,
|
||||
uniqueId,
|
||||
type IDisposable,
|
||||
Disposable,
|
||||
} from '@alilc/lowcode-shared';
|
||||
import { type ICodeRuntime } from '../code-runtime';
|
||||
import { IWidget, Widget } from '../../widget';
|
||||
@ -60,7 +61,7 @@ export type ModelDataSourceCreator = (
|
||||
codeRuntime: ICodeRuntime<InstanceApi>,
|
||||
) => InstanceDataSourceApi;
|
||||
|
||||
export interface ComponentTreeModelOptions {
|
||||
export interface ComponentTreeModelOptions extends IDisposable {
|
||||
id?: string;
|
||||
metadata?: StringDictionary;
|
||||
|
||||
@ -69,37 +70,53 @@ export interface ComponentTreeModelOptions {
|
||||
}
|
||||
|
||||
export class ComponentTreeModel<Component, ComponentInstance = unknown>
|
||||
extends Disposable
|
||||
implements IComponentTreeModel<Component, ComponentInstance>
|
||||
{
|
||||
private instanceMap = new Map<string, ComponentInstance[]>();
|
||||
private _instanceMap = new Map<string, ComponentInstance[]>();
|
||||
|
||||
public id: string;
|
||||
private _id: string;
|
||||
|
||||
public widgets: IWidget<Component>[] = [];
|
||||
private _widgets?: IWidget<Component>[];
|
||||
|
||||
public metadata: StringDictionary = {};
|
||||
private _metadata: StringDictionary;
|
||||
|
||||
constructor(
|
||||
public componentsTree: ComponentTree,
|
||||
public codeRuntime: ICodeRuntime<InstanceApi>,
|
||||
private _componentsTree: ComponentTree,
|
||||
private _codeRuntime: ICodeRuntime<InstanceApi>,
|
||||
options: ComponentTreeModelOptions,
|
||||
) {
|
||||
invariant(componentsTree, 'componentsTree must to provide', 'ComponentTreeModel');
|
||||
|
||||
this.id = options?.id ?? `model_${uniqueId()}`;
|
||||
if (options?.metadata) {
|
||||
this.metadata = options.metadata;
|
||||
}
|
||||
|
||||
if (componentsTree.children) {
|
||||
this.widgets = this.buildWidgets(componentsTree.children);
|
||||
}
|
||||
super();
|
||||
|
||||
this._id = options?.id ?? `model_${uniqueId()}`;
|
||||
this._metadata = options?.metadata ?? {};
|
||||
this.initialize(options);
|
||||
this.addDispose(_codeRuntime);
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
get codeRuntime() {
|
||||
return this._codeRuntime;
|
||||
}
|
||||
|
||||
get metadata() {
|
||||
return this._metadata;
|
||||
}
|
||||
|
||||
get widgets() {
|
||||
if (!this._componentsTree.children) return [];
|
||||
|
||||
if (!this._widgets) {
|
||||
this._widgets = this.buildWidgets(this._componentsTree.children);
|
||||
}
|
||||
return this._widgets;
|
||||
}
|
||||
|
||||
private initialize({ stateCreator, dataSourceCreator }: ComponentTreeModelOptions) {
|
||||
const { state = {}, defaultProps, props = {}, dataSource, methods = {} } = this.componentsTree;
|
||||
const { state = {}, defaultProps, props = {}, dataSource, methods = {} } = this._componentsTree;
|
||||
const codeScope = this.codeRuntime.getScope();
|
||||
|
||||
const initalProps = this.codeRuntime.resolve(props);
|
||||
@ -119,12 +136,12 @@ export class ComponentTreeModel<Component, ComponentInstance = unknown>
|
||||
Object.assign(
|
||||
{
|
||||
$: (ref: string) => {
|
||||
const insArr = this.instanceMap.get(ref);
|
||||
const insArr = this._instanceMap.get(ref);
|
||||
if (!insArr) return undefined;
|
||||
return insArr[0];
|
||||
},
|
||||
$$: (ref: string) => {
|
||||
return this.instanceMap.get(ref) ?? [];
|
||||
return this._instanceMap.get(ref) ?? [];
|
||||
},
|
||||
},
|
||||
dataSourceApi,
|
||||
@ -140,19 +157,19 @@ export class ComponentTreeModel<Component, ComponentInstance = unknown>
|
||||
}
|
||||
|
||||
getCssText(): string | undefined {
|
||||
return this.componentsTree.css;
|
||||
return this._componentsTree.css;
|
||||
}
|
||||
|
||||
triggerLifeCycle(lifeCycleName: ComponentLifeCycle, ...args: any[]) {
|
||||
// keys 用来判断 lifeCycleName 存在于 schema 对象上,不获取原型链上的对象
|
||||
if (
|
||||
!this.componentsTree.lifeCycles ||
|
||||
!Object.keys(this.componentsTree.lifeCycles).includes(lifeCycleName)
|
||||
!this._componentsTree.lifeCycles ||
|
||||
!Object.keys(this._componentsTree.lifeCycles).includes(lifeCycleName)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lifeCycleSchema = this.componentsTree.lifeCycles[lifeCycleName];
|
||||
const lifeCycleSchema = this._componentsTree.lifeCycles[lifeCycleName];
|
||||
|
||||
const lifeCycleFn = this.codeRuntime.resolve(lifeCycleSchema);
|
||||
if (typeof lifeCycleFn === 'function') {
|
||||
@ -161,22 +178,22 @@ export class ComponentTreeModel<Component, ComponentInstance = unknown>
|
||||
}
|
||||
|
||||
setComponentRef(ref: string, ins: ComponentInstance) {
|
||||
let insArr = this.instanceMap.get(ref);
|
||||
let insArr = this._instanceMap.get(ref);
|
||||
if (!insArr) {
|
||||
insArr = [];
|
||||
this.instanceMap.set(ref, insArr);
|
||||
this._instanceMap.set(ref, insArr);
|
||||
}
|
||||
insArr!.push(ins);
|
||||
}
|
||||
|
||||
removeComponentRef(ref: string, ins?: ComponentInstance) {
|
||||
const insArr = this.instanceMap.get(ref);
|
||||
const insArr = this._instanceMap.get(ref);
|
||||
if (insArr) {
|
||||
if (ins) {
|
||||
const idx = insArr.indexOf(ins);
|
||||
if (idx > 0) insArr.splice(idx, 1);
|
||||
} else {
|
||||
this.instanceMap.delete(ref);
|
||||
this._instanceMap.delete(ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -197,6 +214,11 @@ export class ComponentTreeModel<Component, ComponentInstance = unknown>
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
this._instanceMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeComponentNode(node: ComponentNode): NormalizedComponentNode {
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import {
|
||||
createDecorator,
|
||||
type IDisposable,
|
||||
Disposable,
|
||||
invariant,
|
||||
type ComponentTree,
|
||||
type StringDictionary,
|
||||
@ -16,7 +18,7 @@ export interface CreateComponentTreeModelOptions extends ComponentTreeModelOptio
|
||||
codeScopeValue?: StringDictionary;
|
||||
}
|
||||
|
||||
export interface IComponentTreeModelService {
|
||||
export interface IComponentTreeModelService extends IDisposable {
|
||||
create<Component>(
|
||||
componentsTree: ComponentTree,
|
||||
options?: CreateComponentTreeModelOptions,
|
||||
@ -32,23 +34,28 @@ export const IComponentTreeModelService = createDecorator<IComponentTreeModelSer
|
||||
'componentTreeModelService',
|
||||
);
|
||||
|
||||
export class ComponentTreeModelService implements IComponentTreeModelService {
|
||||
export class ComponentTreeModelService extends Disposable implements IComponentTreeModelService {
|
||||
constructor(
|
||||
@ISchemaService private schemaService: ISchemaService,
|
||||
@ICodeRuntimeService private codeRuntimeService: ICodeRuntimeService,
|
||||
) {}
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
create<Component>(
|
||||
componentsTree: ComponentTree,
|
||||
options: CreateComponentTreeModelOptions,
|
||||
): IComponentTreeModel<Component> {
|
||||
return new ComponentTreeModel(
|
||||
componentsTree,
|
||||
// @ts-expect-error: preset scope value
|
||||
this.codeRuntimeService.createCodeRuntime({
|
||||
initScopeValue: options?.codeScopeValue,
|
||||
}),
|
||||
options,
|
||||
this._throwIfDisposed(`ComponentTreeModelService has been disposed.`);
|
||||
|
||||
return this.addDispose(
|
||||
new ComponentTreeModel(
|
||||
componentsTree,
|
||||
this.codeRuntimeService.createCodeRuntime({
|
||||
initScopeValue: options?.codeScopeValue as any,
|
||||
}),
|
||||
options,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -56,18 +63,11 @@ export class ComponentTreeModelService implements IComponentTreeModelService {
|
||||
id: string,
|
||||
options: CreateComponentTreeModelOptions,
|
||||
): IComponentTreeModel<Component> {
|
||||
const componentsTrees = this.schemaService.get('componentsTree');
|
||||
const componentsTrees = this.schemaService.get<ComponentTree[]>('componentsTree', []);
|
||||
const componentsTree = componentsTrees.find((item) => item.id === id);
|
||||
|
||||
invariant(componentsTree, 'componentsTree not found');
|
||||
|
||||
return new ComponentTreeModel(
|
||||
componentsTree,
|
||||
// @ts-expect-error: preset scope value
|
||||
this.codeRuntimeService.createCodeRuntime({
|
||||
initScopeValue: options?.codeScopeValue,
|
||||
}),
|
||||
options,
|
||||
);
|
||||
return this.create(componentsTree, options);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
type Locale,
|
||||
type Translations,
|
||||
type LocaleTranslationsMap,
|
||||
Disposable,
|
||||
} from '@alilc/lowcode-shared';
|
||||
import { ICodeRuntimeService } from './code-runtime';
|
||||
|
||||
@ -26,27 +27,25 @@ export interface IRuntimeIntlService {
|
||||
|
||||
export const IRuntimeIntlService = createDecorator<IRuntimeIntlService>('IRuntimeIntlService');
|
||||
|
||||
export class RuntimeIntlService implements IRuntimeIntlService {
|
||||
private intl: Intl = new Intl();
|
||||
export class RuntimeIntlService extends Disposable implements IRuntimeIntlService {
|
||||
private _intl: Intl;
|
||||
|
||||
constructor(
|
||||
defaultLocale: string | undefined,
|
||||
i18nTranslations: LocaleTranslationsMap,
|
||||
@ICodeRuntimeService private codeRuntimeService: ICodeRuntimeService,
|
||||
) {
|
||||
if (defaultLocale) this.setLocale(defaultLocale);
|
||||
super();
|
||||
|
||||
this._intl = this.addDispose(new Intl(defaultLocale));
|
||||
for (const key of Object.keys(i18nTranslations)) {
|
||||
this.addTranslations(key, i18nTranslations[key]);
|
||||
this._intl.addTranslations(key, i18nTranslations[key]);
|
||||
}
|
||||
|
||||
this._injectScope();
|
||||
}
|
||||
|
||||
localize(descriptor: MessageDescriptor): string {
|
||||
const formatter = this.intl.getFormatter();
|
||||
|
||||
return formatter.$t(
|
||||
return this._intl.getFormatter().$t(
|
||||
{
|
||||
id: descriptor.key,
|
||||
defaultMessage: descriptor.fallback,
|
||||
@ -56,15 +55,15 @@ export class RuntimeIntlService implements IRuntimeIntlService {
|
||||
}
|
||||
|
||||
setLocale(locale: string): void {
|
||||
this.intl.setLocale(locale);
|
||||
this._intl.setLocale(locale);
|
||||
}
|
||||
|
||||
getLocale(): string {
|
||||
return this.intl.getLocale();
|
||||
return this._intl.getLocale();
|
||||
}
|
||||
|
||||
addTranslations(locale: Locale, translations: Translations) {
|
||||
this.intl.addTranslations(locale, translations);
|
||||
this._intl.addTranslations(locale, translations);
|
||||
}
|
||||
|
||||
private _injectScope(): void {
|
||||
|
||||
@ -18,7 +18,7 @@ export interface IRuntimeUtilService {
|
||||
export const IRuntimeUtilService = createDecorator<IRuntimeUtilService>('rendererUtilService');
|
||||
|
||||
export class RuntimeUtilService implements IRuntimeUtilService {
|
||||
private utilsMap: Map<string, any> = new Map();
|
||||
private _utilsMap: Map<string, any> = new Map();
|
||||
|
||||
constructor(
|
||||
utils: UtilDescription[] = [],
|
||||
@ -28,7 +28,7 @@ export class RuntimeUtilService implements IRuntimeUtilService {
|
||||
for (const util of utils) {
|
||||
this.add(util);
|
||||
}
|
||||
this.injectScope();
|
||||
this._injectScope();
|
||||
}
|
||||
|
||||
add(utilItem: UtilDescription, force?: boolean): void;
|
||||
@ -54,39 +54,39 @@ export class RuntimeUtilService implements IRuntimeUtilService {
|
||||
force = fn as boolean;
|
||||
}
|
||||
|
||||
this.addUtilByName(name, utilObj, force);
|
||||
this._addUtilByName(name, utilObj, force);
|
||||
}
|
||||
|
||||
private addUtilByName(
|
||||
private _addUtilByName(
|
||||
name: string,
|
||||
fn: AnyFunction | StringDictionary | UtilDescription,
|
||||
force?: boolean,
|
||||
): void {
|
||||
if (this.utilsMap.has(name) && !force) return;
|
||||
if (this._utilsMap.has(name) && !force) return;
|
||||
|
||||
if (isPlainObject(fn)) {
|
||||
if ((fn as UtilDescription).type === 'function' || (fn as UtilDescription).type === 'npm') {
|
||||
const utilFn = this.parseUtil(fn as UtilDescription);
|
||||
const utilFn = this._parseUtil(fn as UtilDescription);
|
||||
if (utilFn) {
|
||||
this.addUtilByName(name, utilFn, force);
|
||||
this._addUtilByName(name, utilFn, force);
|
||||
}
|
||||
} else if ((fn as StringDictionary).destructuring) {
|
||||
for (const key of Object.keys(fn)) {
|
||||
this.addUtilByName(key, (fn as StringDictionary)[key], force);
|
||||
this._addUtilByName(key, (fn as StringDictionary)[key], force);
|
||||
}
|
||||
} else {
|
||||
this.utilsMap.set(name, fn);
|
||||
this._utilsMap.set(name, fn);
|
||||
}
|
||||
} else if (typeof fn === 'function') {
|
||||
this.utilsMap.set(name, fn);
|
||||
this._utilsMap.set(name, fn);
|
||||
}
|
||||
}
|
||||
|
||||
remove(name: string): void {
|
||||
this.utilsMap.delete(name);
|
||||
this._utilsMap.delete(name);
|
||||
}
|
||||
|
||||
private parseUtil(utilItem: UtilDescription) {
|
||||
private _parseUtil(utilItem: UtilDescription) {
|
||||
if (utilItem.type === 'function') {
|
||||
return this.codeRuntimeService.rootRuntime.run(utilItem.content.value);
|
||||
} else {
|
||||
@ -94,16 +94,16 @@ export class RuntimeUtilService implements IRuntimeUtilService {
|
||||
}
|
||||
}
|
||||
|
||||
private injectScope(): void {
|
||||
private _injectScope(): void {
|
||||
const exposed = new Proxy(Object.create(null), {
|
||||
get: (_, p: string) => {
|
||||
return this.utilsMap.get(p);
|
||||
return this._utilsMap.get(p);
|
||||
},
|
||||
set() {
|
||||
return false;
|
||||
},
|
||||
has: (_, p: string) => {
|
||||
return this.utilsMap.has(p);
|
||||
return this._utilsMap.has(p);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -22,9 +22,9 @@ export const ISchemaService = createDecorator<ISchemaService>('schemaService');
|
||||
export class SchemaService extends Disposable implements ISchemaService {
|
||||
private store: NormalizedSchema;
|
||||
|
||||
private _observer = this.addDispose(new Events.Observable<SchemaUpdateEvent>());
|
||||
private _observer = this.addDispose(new Events.Emitter<SchemaUpdateEvent>());
|
||||
|
||||
readonly onSchemaUpdate = this._observer.subscribe;
|
||||
readonly onSchemaUpdate = this._observer.event;
|
||||
|
||||
constructor(schema: unknown) {
|
||||
super();
|
||||
|
||||
@ -6,7 +6,7 @@ export interface IWidget<Component, ComponentInstance = unknown> {
|
||||
|
||||
readonly rawNode: NodeType;
|
||||
|
||||
model: IComponentTreeModel<Component, ComponentInstance>;
|
||||
readonly model: IComponentTreeModel<Component, ComponentInstance>;
|
||||
|
||||
children?: IWidget<Component, ComponentInstance>[];
|
||||
}
|
||||
@ -14,17 +14,26 @@ export interface IWidget<Component, ComponentInstance = unknown> {
|
||||
export class Widget<Component, ComponentInstance = unknown>
|
||||
implements IWidget<Component, ComponentInstance>
|
||||
{
|
||||
public rawNode: NodeType;
|
||||
private _key: string;
|
||||
|
||||
public key: string;
|
||||
|
||||
public children?: IWidget<Component, ComponentInstance>[] | undefined;
|
||||
children?: IWidget<Component, ComponentInstance>[] | undefined;
|
||||
|
||||
constructor(
|
||||
node: NodeType,
|
||||
public model: IComponentTreeModel<Component, ComponentInstance>,
|
||||
private _node: NodeType,
|
||||
private _model: IComponentTreeModel<Component, ComponentInstance>,
|
||||
) {
|
||||
this.rawNode = node;
|
||||
this.key = (node as ComponentNode)?.id ?? uniqueId();
|
||||
this._key = (_node as ComponentNode)?.id ?? uniqueId();
|
||||
}
|
||||
|
||||
get rawNode(): NodeType {
|
||||
return this._node;
|
||||
}
|
||||
|
||||
get key(): string {
|
||||
return this._key;
|
||||
}
|
||||
|
||||
get model(): IComponentTreeModel<Component, ComponentInstance> {
|
||||
return this._model;
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,6 +32,14 @@ export abstract class Disposable implements IDisposable {
|
||||
|
||||
private readonly _store = new DisposableStore();
|
||||
|
||||
protected _isDisposed = false;
|
||||
|
||||
protected _throwIfDisposed(msg: string = 'this disposable has been disposed'): void {
|
||||
if (this._isDisposed) {
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._store.dispose();
|
||||
}
|
||||
@ -40,6 +48,7 @@ export abstract class Disposable implements IDisposable {
|
||||
* Adds `o` to the collection of disposables managed by this object.
|
||||
*/
|
||||
protected addDispose<T extends IDisposable>(o: T): T {
|
||||
this._throwIfDisposed();
|
||||
if ((o as unknown as Disposable) === this) {
|
||||
throw new Error('Cannot register a disposable on itself!');
|
||||
}
|
||||
|
||||
@ -1,18 +1,35 @@
|
||||
import { Disposable, IDisposable, toDisposable } from './disposable';
|
||||
import { AnyFunction } from '../types';
|
||||
import { combinedDisposable, Disposable, IDisposable, toDisposable } from './disposable';
|
||||
|
||||
export type Event<T> = (listener: (arg: T, thisArg?: any) => any) => IDisposable;
|
||||
export type Event<T, R = any> = (listener: (arg: T) => R, thisArg?: any) => IDisposable;
|
||||
|
||||
export class Observable<T> {
|
||||
export interface EmitterOptions {
|
||||
/**
|
||||
* Optional function that's called *before* the very first listener is added
|
||||
*/
|
||||
onWillAddFirstListener: AnyFunction;
|
||||
/**
|
||||
* Optional function that's called *after* remove the very last listener
|
||||
*/
|
||||
onDidRemoveLastListener: AnyFunction;
|
||||
}
|
||||
|
||||
export class Emitter<T, R = any> {
|
||||
private _isDisposed = false;
|
||||
|
||||
private _event?: Event<T>;
|
||||
private _event?: Event<T, R>;
|
||||
private _listeners?: Set<(arg: T) => void>;
|
||||
|
||||
constructor(private _options?: EmitterOptions) {}
|
||||
|
||||
dispose(): void {
|
||||
if (this._isDisposed) return;
|
||||
|
||||
this._listeners?.clear();
|
||||
this._listeners = undefined;
|
||||
if (this._listeners?.size !== 0) {
|
||||
this._listeners?.clear();
|
||||
this._listeners = undefined;
|
||||
this._options?.onDidRemoveLastListener();
|
||||
}
|
||||
this._event = undefined;
|
||||
this._isDisposed = true;
|
||||
}
|
||||
@ -24,9 +41,9 @@ export class Observable<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* For the public to allow to subscribe to events from this Observable
|
||||
* For the public to allow to subscribe to events from this Emitter
|
||||
*/
|
||||
get subscribe(): Event<T> {
|
||||
get event(): Event<T, R> {
|
||||
if (!this._event) {
|
||||
this._event = (listener: (arg: T) => void, thisArg?: any) => {
|
||||
if (this._isDisposed) {
|
||||
@ -37,7 +54,10 @@ export class Observable<T> {
|
||||
listener = listener.bind(thisArg);
|
||||
}
|
||||
|
||||
if (!this._listeners) this._listeners = new Set();
|
||||
if (!this._listeners) {
|
||||
this._listeners = new Set();
|
||||
this._options?.onWillAddFirstListener();
|
||||
}
|
||||
this._listeners.add(listener);
|
||||
|
||||
return toDisposable(() => {
|
||||
@ -54,6 +74,143 @@ export class Observable<T> {
|
||||
|
||||
if (this._listeners?.has(listener)) {
|
||||
this._listeners.delete(listener);
|
||||
|
||||
if (this._listeners.size === 0) {
|
||||
this._listeners = undefined;
|
||||
this._options?.onDidRemoveLastListener?.(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function snapshot<T>(event: Event<T>): Event<T> {
|
||||
let listener: IDisposable | undefined;
|
||||
|
||||
const options: EmitterOptions | undefined = {
|
||||
onWillAddFirstListener() {
|
||||
listener = event(emitter.notify, emitter);
|
||||
},
|
||||
onDidRemoveLastListener() {
|
||||
listener?.dispose();
|
||||
},
|
||||
};
|
||||
|
||||
const emitter = new Emitter<T>(options);
|
||||
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an event, returns another event which only fires once.
|
||||
*
|
||||
* @param event The event source for the new event.
|
||||
*/
|
||||
export function once<T>(event: Event<T>): Event<T> {
|
||||
return (listener, thisArgs = null) => {
|
||||
// we need this, in case the event fires during the listener call
|
||||
let didFire = false;
|
||||
let result: IDisposable | undefined = undefined;
|
||||
result = event((e) => {
|
||||
if (didFire) {
|
||||
return;
|
||||
} else if (result) {
|
||||
result.dispose();
|
||||
} else {
|
||||
didFire = true;
|
||||
}
|
||||
|
||||
return listener.call(thisArgs, e);
|
||||
}, null);
|
||||
|
||||
if (didFire) {
|
||||
result.dispose();
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
export function forEach<I>(event: Event<I>, each: (i: I) => void): Event<I> {
|
||||
return snapshot((listener, thisArgs = null) =>
|
||||
event((i) => {
|
||||
each(i);
|
||||
listener.call(thisArgs, i);
|
||||
}, null),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps an event of one type into an event of another type using a mapping function, similar to how
|
||||
* `Array.prototype.map` works.
|
||||
*
|
||||
* @param event The event source for the new event.
|
||||
* @param map The mapping function.
|
||||
*/
|
||||
export function map<I, O>(event: Event<I>, map: (i: I) => O): Event<O> {
|
||||
return snapshot((listener, thisArgs = null) =>
|
||||
event((i) => listener.call(thisArgs, map(i)), null),
|
||||
);
|
||||
}
|
||||
|
||||
export function reduce<I, O>(
|
||||
event: Event<I>,
|
||||
merge: (last: O | undefined, event: I) => O,
|
||||
initial?: O,
|
||||
): Event<O> {
|
||||
let output: O | undefined = initial;
|
||||
|
||||
return map<I, O>(event, (e) => {
|
||||
output = merge(output, e);
|
||||
return output;
|
||||
});
|
||||
}
|
||||
|
||||
export function filter<T, U>(event: Event<T | U>, filter: (e: T | U) => e is T): Event<T>;
|
||||
export function filter<T>(event: Event<T>, filter: (e: T) => boolean): Event<T>;
|
||||
export function filter<T, R>(event: Event<T | R>, filter: (e: T | R) => e is R): Event<R>;
|
||||
export function filter<T>(event: Event<T>, filter: (e: T) => boolean): Event<T> {
|
||||
return snapshot((listener, thisArgs = null) =>
|
||||
event((e) => filter(e) && listener.call(thisArgs, e), null),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a collection of events, returns a single event which emits whenever any of the provided events emit.
|
||||
*/
|
||||
export function any<T>(...events: Event<T>[]): Event<T>;
|
||||
export function any(...events: Event<any>[]): Event<void>;
|
||||
export function any<T>(...events: Event<T>[]): Event<T> {
|
||||
return (listener, thisArgs = null) => {
|
||||
return combinedDisposable(...events.map((event) => event((e) => listener.call(thisArgs, e))));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a promise out of an event, using the {@link Event.once} helper.
|
||||
*/
|
||||
export function toPromise<T>(event: Event<T>): Promise<T> {
|
||||
return new Promise((resolve) => once(event)(resolve));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event out of a promise that fires once when the promise is
|
||||
* resolved with the result of the promise or `undefined`.
|
||||
*/
|
||||
export function fromPromise<T>(promise: Promise<T>): Event<T | undefined> {
|
||||
const result = new Emitter<T | undefined>();
|
||||
|
||||
promise
|
||||
.then(
|
||||
(res) => {
|
||||
result.notify(res);
|
||||
},
|
||||
() => {
|
||||
result.notify(undefined);
|
||||
},
|
||||
)
|
||||
.finally(() => {
|
||||
result.dispose();
|
||||
});
|
||||
|
||||
return result.event;
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import { createIntl, createIntlCache, type IntlShape as IntlFormatter } from '@f
|
||||
import { mapKeys } from 'lodash-es';
|
||||
import { signal, computed, effect, type Signal, type ComputedSignal } from './signals';
|
||||
import { platformLocale } from './platform';
|
||||
import { Disposable, toDisposable } from './disposable';
|
||||
|
||||
export { IntlFormatter };
|
||||
|
||||
@ -9,59 +10,65 @@ export type Locale = string;
|
||||
export type Translations = Record<string, string>;
|
||||
export type LocaleTranslationsRecord = Record<Locale, Translations>;
|
||||
|
||||
export class Intl {
|
||||
private locale: Signal<Locale>;
|
||||
private messageStore: Signal<LocaleTranslationsRecord>;
|
||||
private currentMessage: ComputedSignal<Translations>;
|
||||
private intlShape: IntlFormatter;
|
||||
export class Intl extends Disposable {
|
||||
private _locale: Signal<Locale>;
|
||||
private _messageStore: Signal<LocaleTranslationsRecord>;
|
||||
private _currentMessage: ComputedSignal<Translations>;
|
||||
private _intlShape: IntlFormatter;
|
||||
|
||||
constructor(defaultLocale: string = platformLocale, messages: LocaleTranslationsRecord = {}) {
|
||||
if (defaultLocale) {
|
||||
defaultLocale = nomarlizeLocale(defaultLocale);
|
||||
} else {
|
||||
defaultLocale = 'zh-CN';
|
||||
}
|
||||
super();
|
||||
|
||||
const messageStore = mapKeys(messages, (_, key) => {
|
||||
return nomarlizeLocale(key);
|
||||
this._locale = signal(defaultLocale ? nomarlizeLocale(defaultLocale) : 'zh-CN');
|
||||
this._messageStore = signal(
|
||||
mapKeys(messages, (_, key) => {
|
||||
return nomarlizeLocale(key);
|
||||
}),
|
||||
);
|
||||
this._currentMessage = computed(() => {
|
||||
return this._messageStore.value[this._locale.value] ?? {};
|
||||
});
|
||||
|
||||
this.locale = signal(defaultLocale);
|
||||
this.messageStore = signal(messageStore);
|
||||
this.currentMessage = computed(() => {
|
||||
return this.messageStore.value[this.locale.value] ?? {};
|
||||
});
|
||||
|
||||
effect(() => {
|
||||
const cache = createIntlCache();
|
||||
this.intlShape = createIntl(
|
||||
{
|
||||
locale: this.locale.value,
|
||||
messages: this.currentMessage.value,
|
||||
},
|
||||
cache,
|
||||
);
|
||||
});
|
||||
this.addDispose(
|
||||
toDisposable(
|
||||
effect(() => {
|
||||
const cache = createIntlCache();
|
||||
this._intlShape = createIntl(
|
||||
{
|
||||
locale: this._locale.value,
|
||||
messages: this._currentMessage.value,
|
||||
},
|
||||
cache,
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
getLocale() {
|
||||
return this.locale.value;
|
||||
return this._locale.value;
|
||||
}
|
||||
|
||||
setLocale(locale: Locale) {
|
||||
this._throwIfDisposed(`this intl has been disposed`);
|
||||
|
||||
const nomarlizedLocale = nomarlizeLocale(locale);
|
||||
this.locale.value = nomarlizedLocale;
|
||||
this._locale.value = nomarlizedLocale;
|
||||
}
|
||||
|
||||
addTranslations(locale: Locale, messages: Translations) {
|
||||
locale = nomarlizeLocale(locale);
|
||||
const original = this.messageStore.value[locale];
|
||||
this._throwIfDisposed(`this intl has been disposed`);
|
||||
|
||||
this.messageStore.value[locale] = Object.assign(original, messages);
|
||||
locale = nomarlizeLocale(locale);
|
||||
const original = this._messageStore.value[locale];
|
||||
|
||||
this._messageStore.value[locale] = Object.assign(original, messages);
|
||||
}
|
||||
|
||||
getFormatter(): IntlFormatter {
|
||||
return this.intlShape;
|
||||
this._throwIfDisposed(`this intl has been disposed`);
|
||||
|
||||
return this._intlShape;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user