mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-03-06 10:27:22 +00:00
feat: 继续完善
This commit is contained in:
parent
23c08b2108
commit
ece6c76b46
@ -18,6 +18,7 @@
|
|||||||
"@ali/lowcode-editor-core": "1.0.74",
|
"@ali/lowcode-editor-core": "1.0.74",
|
||||||
"@ali/lowcode-types": "1.0.74",
|
"@ali/lowcode-types": "1.0.74",
|
||||||
"@ali/lowcode-utils": "1.0.74",
|
"@ali/lowcode-utils": "1.0.74",
|
||||||
|
"@ali/lowcode-shell": "1.0.74",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"enzyme": "^3.11.0",
|
"enzyme": "^3.11.0",
|
||||||
"enzyme-adapter-react-16": "^1.15.5",
|
"enzyme-adapter-react-16": "^1.15.5",
|
||||||
|
|||||||
@ -79,7 +79,7 @@ export class BorderDetecting extends Component<{ host: BuiltinSimulatorHost }> {
|
|||||||
|
|
||||||
|
|
||||||
const canHoverHook = current?.componentMeta.getMetadata()?.experimental?.callbacks?.onHoverHook;
|
const canHoverHook = current?.componentMeta.getMetadata()?.experimental?.callbacks?.onHoverHook;
|
||||||
const canHover = (canHoverHook && typeof canHoverHook === 'function') ? canHoverHook(current) : true;
|
const canHover = (canHoverHook && typeof canHoverHook === 'function') ? canHoverHook(current.internalToShellNode()) : true;
|
||||||
|
|
||||||
|
|
||||||
if (!canHover || !current || host.viewport.scrolling || host.liveEditing.editing) {
|
if (!canHover || !current || host.viewport.scrolling || host.liveEditing.editing) {
|
||||||
|
|||||||
@ -151,7 +151,8 @@ export class BoxResizingInstance extends Component<{
|
|||||||
(e as any).trigger = direction;
|
(e as any).trigger = direction;
|
||||||
(e as any).deltaX = moveX;
|
(e as any).deltaX = moveX;
|
||||||
(e as any).deltaY = moveY;
|
(e as any).deltaY = moveY;
|
||||||
metaData.experimental.callbacks.onResize(e, node);
|
const cbNode = node?.isNode ? node.internalToShellNode() : node;
|
||||||
|
metaData.experimental.callbacks.onResize(e, cbNode);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -164,7 +165,8 @@ export class BoxResizingInstance extends Component<{
|
|||||||
typeof metaData.experimental.callbacks.onResizeStart === 'function'
|
typeof metaData.experimental.callbacks.onResizeStart === 'function'
|
||||||
) {
|
) {
|
||||||
(e as any).trigger = direction;
|
(e as any).trigger = direction;
|
||||||
metaData.experimental.callbacks.onResizeStart(e, node);
|
const cbNode = node?.isNode ? node.internalToShellNode() : node;
|
||||||
|
metaData.experimental.callbacks.onResizeStart(e, cbNode);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -177,7 +179,8 @@ export class BoxResizingInstance extends Component<{
|
|||||||
typeof metaData.experimental.callbacks.onResizeEnd === 'function'
|
typeof metaData.experimental.callbacks.onResizeEnd === 'function'
|
||||||
) {
|
) {
|
||||||
(e as any).trigger = direction;
|
(e as any).trigger = direction;
|
||||||
metaData.experimental.callbacks.onResizeEnd(e, node);
|
const cbNode = node?.isNode ? node.internalToShellNode() : node;
|
||||||
|
metaData.experimental.callbacks.onResizeEnd(e, cbNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
const editor = globalContext.get(Editor);
|
const editor = globalContext.get(Editor);
|
||||||
|
|||||||
@ -109,7 +109,7 @@ export default class DragResizeEngine {
|
|||||||
doc.addEventListener('mouseup', over, true);
|
doc.addEventListener('mouseup', over, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.emitter.emit('resizestart', e, direction, node);
|
this.emitter.emit('resizeStart', e, direction, node);
|
||||||
this.dragResizing = true;
|
this.dragResizing = true;
|
||||||
this.designer.detecting.enable = false;
|
this.designer.detecting.enable = false;
|
||||||
cursor.addState('ew-resize');
|
cursor.addState('ew-resize');
|
||||||
@ -121,9 +121,9 @@ export default class DragResizeEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onResizeStart(func: (e: MouseEvent, direction: string, node: any) => any) {
|
onResizeStart(func: (e: MouseEvent, direction: string, node: any) => any) {
|
||||||
this.emitter.on('resizestart', func);
|
this.emitter.on('resizeStart', func);
|
||||||
return () => {
|
return () => {
|
||||||
this.emitter.removeListener('resizestart', func);
|
this.emitter.removeListener('resizeStart', func);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -39,6 +39,7 @@ import {
|
|||||||
} from '@ali/lowcode-utils';
|
} from '@ali/lowcode-utils';
|
||||||
import {
|
import {
|
||||||
DragObjectType,
|
DragObjectType,
|
||||||
|
DragNodeObject,
|
||||||
isShaken,
|
isShaken,
|
||||||
LocateEvent,
|
LocateEvent,
|
||||||
isDragAnyObject,
|
isDragAnyObject,
|
||||||
@ -1135,11 +1136,11 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
|||||||
*/
|
*/
|
||||||
locate(e: LocateEvent): any {
|
locate(e: LocateEvent): any {
|
||||||
const { dragObject } = e;
|
const { dragObject } = e;
|
||||||
const { nodes } = dragObject;
|
const { nodes } = dragObject as DragNodeObject;
|
||||||
|
|
||||||
const operationalNodes = nodes?.filter((node: any) => {
|
const operationalNodes = nodes?.filter((node) => {
|
||||||
const onMoveHook = node.componentMeta?.getMetadata()?.experimental?.callbacks?.onMoveHook;
|
const onMoveHook = node.componentMeta?.getMetadata()?.experimental?.callbacks?.onMoveHook;
|
||||||
const canMove = onMoveHook && typeof onMoveHook === 'function' ? onMoveHook(node) : true;
|
const canMove = onMoveHook && typeof onMoveHook === 'function' ? onMoveHook(node.internalToShellNode()) : true;
|
||||||
|
|
||||||
return canMove;
|
return canMove;
|
||||||
});
|
});
|
||||||
|
|||||||
@ -419,7 +419,11 @@ export class NodeChildren {
|
|||||||
const callbacks = owner.componentMeta.getMetadata().experimental?.callbacks;
|
const callbacks = owner.componentMeta.getMetadata().experimental?.callbacks;
|
||||||
if (callbacks?.onSubtreeModified) {
|
if (callbacks?.onSubtreeModified) {
|
||||||
try {
|
try {
|
||||||
callbacks?.onSubtreeModified.call(node, owner, options);
|
callbacks?.onSubtreeModified.call(
|
||||||
|
node.internalToShellNode(),
|
||||||
|
owner.internalToShellNode(),
|
||||||
|
options,
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('error when excute experimental.callbacks.onSubtreeModified', e);
|
console.error('error when excute experimental.callbacks.onSubtreeModified', e);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import {
|
|||||||
} from '@ali/lowcode-types';
|
} from '@ali/lowcode-types';
|
||||||
import { compatStage } from '@ali/lowcode-utils';
|
import { compatStage } from '@ali/lowcode-utils';
|
||||||
import { SettingTopEntry } from '@ali/lowcode-designer';
|
import { SettingTopEntry } from '@ali/lowcode-designer';
|
||||||
|
import { Node as ShellNode } from '@ali/lowcode-shell';
|
||||||
import { Props, getConvertedExtraKey } from './props/props';
|
import { Props, getConvertedExtraKey } from './props/props';
|
||||||
import { DocumentModel } from '../document-model';
|
import { DocumentModel } from '../document-model';
|
||||||
import { NodeChildren } from './node-children';
|
import { NodeChildren } from './node-children';
|
||||||
@ -299,7 +300,8 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
|||||||
private didDropIn(dragment: Node) {
|
private didDropIn(dragment: Node) {
|
||||||
const callbacks = this.componentMeta.getMetadata().experimental?.callbacks;
|
const callbacks = this.componentMeta.getMetadata().experimental?.callbacks;
|
||||||
if (callbacks?.onNodeAdd) {
|
if (callbacks?.onNodeAdd) {
|
||||||
callbacks?.onNodeAdd.call(this, dragment, this);
|
const cbThis = this.internalToShellNode();
|
||||||
|
callbacks?.onNodeAdd.call(cbThis, dragment.internalToShellNode(), cbThis);
|
||||||
}
|
}
|
||||||
if (this._parent) {
|
if (this._parent) {
|
||||||
this._parent.didDropIn(dragment);
|
this._parent.didDropIn(dragment);
|
||||||
@ -309,7 +311,8 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
|||||||
private didDropOut(dragment: Node) {
|
private didDropOut(dragment: Node) {
|
||||||
const callbacks = this.componentMeta.getMetadata().experimental?.callbacks;
|
const callbacks = this.componentMeta.getMetadata().experimental?.callbacks;
|
||||||
if (callbacks?.onNodeRemove) {
|
if (callbacks?.onNodeRemove) {
|
||||||
callbacks?.onNodeRemove.call(this, dragment, this);
|
const cbThis = this.internalToShellNode();
|
||||||
|
callbacks?.onNodeRemove.call(cbThis, dragment.internalToShellNode(), cbThis);
|
||||||
}
|
}
|
||||||
if (this._parent) {
|
if (this._parent) {
|
||||||
this._parent.didDropOut(dragment);
|
this._parent.didDropOut(dragment);
|
||||||
@ -361,6 +364,10 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
|||||||
this._slotFor = slotFor;
|
this._slotFor = slotFor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internalToShellNode(): ShellNode | null {
|
||||||
|
return ShellNode.create(this);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关联属性
|
* 关联属性
|
||||||
*/
|
*/
|
||||||
|
|||||||
21
packages/shell/src/canvas.ts
Normal file
21
packages/shell/src/canvas.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Designer, Prop as InnerProp } from '@ali/lowcode-designer';
|
||||||
|
import { CompositeValue, TransformStage } from '@ali/lowcode-types';
|
||||||
|
import { designerSymbol } from './symbols';
|
||||||
|
import DropLocation from './drop-location';
|
||||||
|
|
||||||
|
export default class Canvas {
|
||||||
|
private readonly [designerSymbol]: Designer;
|
||||||
|
|
||||||
|
constructor(designer: Designer) {
|
||||||
|
this[designerSymbol] = designer;
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(designer: Designer) {
|
||||||
|
if (!designer) return null;
|
||||||
|
return new Canvas(designer);
|
||||||
|
}
|
||||||
|
|
||||||
|
get dropLocation() {
|
||||||
|
return DropLocation.create(this[designerSymbol].dropLocation || null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,6 +13,7 @@ import Detecting from './detecting';
|
|||||||
import History from './history';
|
import History from './history';
|
||||||
import Project from './project';
|
import Project from './project';
|
||||||
import Prop from './prop';
|
import Prop from './prop';
|
||||||
|
import Canvas from './canvas';
|
||||||
import { documentSymbol, editorSymbol, nodeSymbol } from './symbols';
|
import { documentSymbol, editorSymbol, nodeSymbol } from './symbols';
|
||||||
|
|
||||||
type IOnChangeOptions = {
|
type IOnChangeOptions = {
|
||||||
@ -34,6 +35,7 @@ export default class DocumentModel {
|
|||||||
public selection: Selection;
|
public selection: Selection;
|
||||||
public detecting: Detecting;
|
public detecting: Detecting;
|
||||||
public history: History;
|
public history: History;
|
||||||
|
public canvas: Canvas;
|
||||||
|
|
||||||
constructor(document: InnerDocumentModel) {
|
constructor(document: InnerDocumentModel) {
|
||||||
this[documentSymbol] = document;
|
this[documentSymbol] = document;
|
||||||
@ -41,6 +43,7 @@ export default class DocumentModel {
|
|||||||
this.selection = new Selection(document);
|
this.selection = new Selection(document);
|
||||||
this.detecting = new Detecting(document);
|
this.detecting = new Detecting(document);
|
||||||
this.history = new History(document);
|
this.history = new History(document);
|
||||||
|
this.canvas = new Canvas(document.designer);
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(document: InnerDocumentModel | undefined | null) {
|
static create(document: InnerDocumentModel | undefined | null) {
|
||||||
|
|||||||
22
packages/shell/src/drop-location.ts
Normal file
22
packages/shell/src/drop-location.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import {
|
||||||
|
DropLocation as InnerDropLocation,
|
||||||
|
} from '@ali/lowcode-designer';
|
||||||
|
import { dropLocationSymbol } from './symbols';
|
||||||
|
import Node from './node';
|
||||||
|
|
||||||
|
export default class DropLocation {
|
||||||
|
private readonly [dropLocationSymbol]: InnerDropLocation;
|
||||||
|
|
||||||
|
constructor(dropLocation: InnerDropLocation) {
|
||||||
|
this[dropLocationSymbol] = dropLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(dropLocation: InnerDropLocation | null) {
|
||||||
|
if (!dropLocation) return null;
|
||||||
|
return new DropLocation(dropLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
get target() {
|
||||||
|
return Node.create(this[dropLocationSymbol].target);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,6 +12,11 @@ export default class Event {
|
|||||||
private readonly [editorSymbol]: InnerEditor;
|
private readonly [editorSymbol]: InnerEditor;
|
||||||
private readonly options: EventOptions;
|
private readonly options: EventOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内核触发的事件名
|
||||||
|
*/
|
||||||
|
readonly names = [];
|
||||||
|
|
||||||
constructor(editor: InnerEditor, options: EventOptions) {
|
constructor(editor: InnerEditor, options: EventOptions) {
|
||||||
this[editorSymbol] = editor;
|
this[editorSymbol] = editor;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import {
|
|||||||
} from '@ali/lowcode-designer';
|
} from '@ali/lowcode-designer';
|
||||||
import { CompositeValue, NodeSchema, TransformStage } from '@ali/lowcode-types';
|
import { CompositeValue, NodeSchema, TransformStage } from '@ali/lowcode-types';
|
||||||
import Prop from './prop';
|
import Prop from './prop';
|
||||||
|
import Props from './props';
|
||||||
import DocumentModel from './document-model';
|
import DocumentModel from './document-model';
|
||||||
import NodeChildren from './node-children';
|
import NodeChildren from './node-children';
|
||||||
import ComponentMeta from './component-meta';
|
import ComponentMeta from './component-meta';
|
||||||
@ -134,6 +135,17 @@ export default class Node {
|
|||||||
return Prop.create(this[nodeSymbol].slotFor);
|
return Prop.create(this[nodeSymbol].slotFor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get props() {
|
||||||
|
return Props.create(this[nodeSymbol].props);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use .children instead
|
||||||
|
*/
|
||||||
|
getChildren() {
|
||||||
|
return this.children;
|
||||||
|
}
|
||||||
|
|
||||||
hasSlots() {
|
hasSlots() {
|
||||||
return this[nodeSymbol].hasSlots();
|
return this[nodeSymbol].hasSlots();
|
||||||
}
|
}
|
||||||
@ -146,6 +158,13 @@ export default class Node {
|
|||||||
return this[nodeSymbol].hasLoop();
|
return this[nodeSymbol].hasLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use .props instead
|
||||||
|
*/
|
||||||
|
getProps() {
|
||||||
|
return this.props;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定 path 的属性模型实例
|
* 获取指定 path 的属性模型实例
|
||||||
* @param path 属性路径,支持 a / a.b / a.0 等格式
|
* @param path 属性路径,支持 a / a.b / a.0 等格式
|
||||||
|
|||||||
@ -30,6 +30,17 @@ export default class Project {
|
|||||||
return this[projectSymbol].documents.map((doc) => DocumentModel.create(doc)!);
|
return this[projectSymbol].documents.map((doc) => DocumentModel.create(doc)!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get simulatorHost() {
|
||||||
|
return SimulatorHost.create(this[projectSymbol].simulator as any || this[simulatorHostSymbol]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use simulatorHost instead.
|
||||||
|
*/
|
||||||
|
get simulator() {
|
||||||
|
return this.simulatorHost;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 打开一个 document
|
* 打开一个 document
|
||||||
* @param doc
|
* @param doc
|
||||||
|
|||||||
88
packages/shell/src/props.ts
Normal file
88
packages/shell/src/props.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { Props as InnerProps, getConvertedExtraKey } from '@ali/lowcode-designer';
|
||||||
|
import { CompositeValue, TransformStage } from '@ali/lowcode-types';
|
||||||
|
import { propsSymbol } from './symbols';
|
||||||
|
import Node from './node';
|
||||||
|
import Prop from './prop';
|
||||||
|
|
||||||
|
export default class Props {
|
||||||
|
private readonly [propsSymbol]: InnerProps;
|
||||||
|
|
||||||
|
constructor(props: InnerProps) {
|
||||||
|
this[propsSymbol] = props;
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(props: InnerProps | undefined | null) {
|
||||||
|
if (!props) return null;
|
||||||
|
return new Props(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
get id() {
|
||||||
|
return this[propsSymbol].id;
|
||||||
|
}
|
||||||
|
|
||||||
|
get path() {
|
||||||
|
return this[propsSymbol].path;
|
||||||
|
}
|
||||||
|
|
||||||
|
get node(): Node | null {
|
||||||
|
return Node.create(this[propsSymbol].getNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定 path 的属性模型实例
|
||||||
|
* @param path 属性路径,支持 a / a.b / a.0 等格式
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
getProp(path: string): Prop | null {
|
||||||
|
return Prop.create(this[propsSymbol].getProp(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定 path 的属性模型实例值
|
||||||
|
* @param path 属性路径,支持 a / a.b / a.0 等格式
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
getPropValue(path: string) {
|
||||||
|
return this.getProp(path)?.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定 path 的属性模型实例,
|
||||||
|
* 注:导出时,不同于普通属性,该属性并不挂载在 props 之下,而是与 props 同级
|
||||||
|
* @param path 属性路径,支持 a / a.b / a.0 等格式
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
getExtraProp(path: string): Prop | null {
|
||||||
|
return Prop.create(this[propsSymbol].getProp(getConvertedExtraKey(path)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定 path 的属性模型实例,
|
||||||
|
* 注:导出时,不同于普通属性,该属性并不挂载在 props 之下,而是与 props 同级
|
||||||
|
* @param path 属性路径,支持 a / a.b / a.0 等格式
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
getExtraPropValue(path: string) {
|
||||||
|
return this.getExtraProp(path)?.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置指定 path 的属性模型实例值
|
||||||
|
* @param path 属性路径,支持 a / a.b / a.0 等格式
|
||||||
|
* @param value 值
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
setPropValue(path: string, value: CompositeValue) {
|
||||||
|
return this.getProp(path)?.setValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置指定 path 的属性模型实例值
|
||||||
|
* @param path 属性路径,支持 a / a.b / a.0 等格式
|
||||||
|
* @param value 值
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
setExtraPropValue(path: string, value: CompositeValue) {
|
||||||
|
return this.getExtraProp(path)?.setValue(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,6 +15,14 @@ export default class SimulatorHost {
|
|||||||
return new SimulatorHost(host);
|
return new SimulatorHost(host);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get contentWindow() {
|
||||||
|
return this[simulatorHostSymbol].contentWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
get contentDocument() {
|
||||||
|
return this[simulatorHostSymbol].contentDocument;
|
||||||
|
}
|
||||||
|
|
||||||
set(key: string, value: any) {
|
set(key: string, value: any) {
|
||||||
this[simulatorHostSymbol].set(key, value);
|
this[simulatorHostSymbol].set(key, value);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,8 @@ export const propSymbol = Symbol('prop');
|
|||||||
export const detectingSymbol = Symbol('detecting');
|
export const detectingSymbol = Symbol('detecting');
|
||||||
export const selectionSymbol = Symbol('selection');
|
export const selectionSymbol = Symbol('selection');
|
||||||
export const historySymbol = Symbol('history');
|
export const historySymbol = Symbol('history');
|
||||||
|
export const canvasSymbol = Symbol('canvas');
|
||||||
export const componentMetaSymbol = Symbol('componentMeta');
|
export const componentMetaSymbol = Symbol('componentMeta');
|
||||||
|
export const dropLocationSymbol = Symbol('dropLocation');
|
||||||
export const simulatorHostSymbol = Symbol('simulatorHost');
|
export const simulatorHostSymbol = Symbol('simulatorHost');
|
||||||
export const simulatorRendererSymbol = Symbol('simulatorRenderer');
|
export const simulatorRendererSymbol = Symbol('simulatorRenderer');
|
||||||
Loading…
x
Reference in New Issue
Block a user