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