mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-01-20 07:14:23 +00:00
feat: 补充一些 vision API
This commit is contained in:
parent
aac8126de9
commit
933cef1c8a
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"lerna": "2.11.0",
|
"lerna": "2.11.0",
|
||||||
"version": "1.0.9-2",
|
"version": "0.10.0",
|
||||||
"npmClient": "tyarn",
|
"npmClient": "tyarn",
|
||||||
"registry": "http://registry.npm.alibaba-inc.com",
|
"registry": "http://registry.npm.alibaba-inc.com",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
|
|||||||
@ -30,13 +30,15 @@ import {
|
|||||||
getRectTarget,
|
getRectTarget,
|
||||||
Rect,
|
Rect,
|
||||||
CanvasPoint,
|
CanvasPoint,
|
||||||
|
Designer,
|
||||||
} from '../designer';
|
} from '../designer';
|
||||||
import { parseMetadata } from './utils/parse-metadata';
|
import { parseMetadata } from './utils/parse-metadata';
|
||||||
import { ComponentMetadata, NodeSchema, ComponentSchema } from '@ali/lowcode-types';
|
import { ComponentMetadata, ComponentSchema } from '@ali/lowcode-types';
|
||||||
import { BuiltinSimulatorRenderer } from './renderer';
|
import { BuiltinSimulatorRenderer } from './renderer';
|
||||||
import clipboard from '../designer/clipboard';
|
import clipboard from '../designer/clipboard';
|
||||||
import { LiveEditing } from './live-editing/live-editing';
|
import { LiveEditing } from './live-editing/live-editing';
|
||||||
import { Project } from '../project';
|
import { Project } from '../project';
|
||||||
|
import { Scroller } from '../designer/scroller';
|
||||||
|
|
||||||
export interface LibraryItem {
|
export interface LibraryItem {
|
||||||
package: string;
|
package: string;
|
||||||
@ -110,9 +112,19 @@ const defaultRaxEnvironment = [
|
|||||||
export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProps> {
|
export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProps> {
|
||||||
readonly isSimulator = true;
|
readonly isSimulator = true;
|
||||||
|
|
||||||
constructor(readonly project: Project) {}
|
readonly project: Project;
|
||||||
|
|
||||||
readonly designer = this.project.designer;
|
readonly designer: Designer;
|
||||||
|
|
||||||
|
readonly viewport = new Viewport();
|
||||||
|
|
||||||
|
readonly scroller: Scroller;
|
||||||
|
|
||||||
|
constructor(project: Project) {
|
||||||
|
this.project = project;
|
||||||
|
this.designer = project?.designer;
|
||||||
|
this.scroller = this.designer.createScroller(this.viewport);
|
||||||
|
}
|
||||||
|
|
||||||
get currentDocument() {
|
get currentDocument() {
|
||||||
return this.project.currentDocument;
|
return this.project.currentDocument;
|
||||||
@ -182,9 +194,6 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
|||||||
// todo
|
// todo
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly viewport = new Viewport();
|
|
||||||
readonly scroller = this.designer.createScroller(this.viewport);
|
|
||||||
|
|
||||||
mountViewport(viewport: Element | null) {
|
mountViewport(viewport: Element | null) {
|
||||||
this.viewport.mount(viewport);
|
this.viewport.mount(viewport);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { EventEmitter } from 'events';
|
|||||||
import { Project } from '../project';
|
import { Project } from '../project';
|
||||||
import { ISimulatorHost } from '../simulator';
|
import { ISimulatorHost } from '../simulator';
|
||||||
import { ComponentMeta } from '../component-meta';
|
import { ComponentMeta } from '../component-meta';
|
||||||
import { isDragNodeDataObject, DragNodeObject, DragNodeDataObject, DropLocation } from '../designer';
|
import { isDragNodeDataObject, DragNodeObject, DragNodeDataObject, DropLocation, Designer } from '../designer';
|
||||||
import { Node, insertChildren, insertChild, isNode, RootNode, ParentalNode } from './node/node';
|
import { Node, insertChildren, insertChild, isNode, RootNode, ParentalNode } from './node/node';
|
||||||
import { Selection } from './selection';
|
import { Selection } from './selection';
|
||||||
import { History } from './history';
|
import { History } from './history';
|
||||||
@ -56,6 +56,10 @@ export class DocumentModel {
|
|||||||
|
|
||||||
private _nodesMap = new Map<string, Node>();
|
private _nodesMap = new Map<string, Node>();
|
||||||
|
|
||||||
|
readonly project: Project;
|
||||||
|
|
||||||
|
readonly designer: Designer;
|
||||||
|
|
||||||
@obx.val private nodes = new Set<Node>();
|
@obx.val private nodes = new Set<Node>();
|
||||||
|
|
||||||
private seqId = 0;
|
private seqId = 0;
|
||||||
@ -83,28 +87,20 @@ export class DocumentModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get fileName(): string {
|
get fileName(): string {
|
||||||
return this.rootNode.getExtraProp('fileName')?.getAsString() || this.id;
|
return this.rootNode?.getExtraProp('fileName')?.getAsString() || this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
set fileName(fileName: string) {
|
set fileName(fileName: string) {
|
||||||
this.rootNode.getExtraProp('fileName', true)?.setValue(fileName);
|
this.rootNode?.getExtraProp('fileName', true)?.setValue(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _modalNode?: ParentalNode;
|
private _modalNode?: ParentalNode;
|
||||||
|
|
||||||
private _blank?: boolean;
|
private _blank?: boolean;
|
||||||
|
|
||||||
get modalNode() {
|
|
||||||
return this._modalNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
get currentRoot() {
|
|
||||||
return this.modalNode || this.rootNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
private inited = false;
|
private inited = false;
|
||||||
|
|
||||||
constructor(readonly project: Project, schema?: RootSchema) {
|
constructor(project: Project, schema?: RootSchema) {
|
||||||
/*
|
/*
|
||||||
// TODO
|
// TODO
|
||||||
// use special purge process
|
// use special purge process
|
||||||
@ -112,6 +108,8 @@ export class DocumentModel {
|
|||||||
console.info(this.willPurgeSpace);
|
console.info(this.willPurgeSpace);
|
||||||
}, true);
|
}, true);
|
||||||
*/
|
*/
|
||||||
|
this.project = project;
|
||||||
|
this.designer = this.project?.designer;
|
||||||
this.emitter = new EventEmitter();
|
this.emitter = new EventEmitter();
|
||||||
|
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
@ -140,6 +138,14 @@ export class DocumentModel {
|
|||||||
|
|
||||||
@obx.val private willPurgeSpace: Node[] = [];
|
@obx.val private willPurgeSpace: Node[] = [];
|
||||||
|
|
||||||
|
get modalNode() {
|
||||||
|
return this._modalNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
get currentRoot() {
|
||||||
|
return this.modalNode || this.rootNode;
|
||||||
|
}
|
||||||
|
|
||||||
addWillPurge(node: Node) {
|
addWillPurge(node: Node) {
|
||||||
this.willPurgeSpace.push(node);
|
this.willPurgeSpace.push(node);
|
||||||
}
|
}
|
||||||
@ -155,8 +161,6 @@ export class DocumentModel {
|
|||||||
return this._blank && !this.isModified();
|
return this._blank && !this.isModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly designer = this.project.designer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成唯一id
|
* 生成唯一id
|
||||||
*/
|
*/
|
||||||
@ -186,10 +190,6 @@ export class DocumentModel {
|
|||||||
|
|
||||||
@obx.val private activeNodes?: Node[];
|
@obx.val private activeNodes?: Node[];
|
||||||
|
|
||||||
private setupListenActiveNodes() {
|
|
||||||
// todo:
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据 schema 创建一个节点
|
* 根据 schema 创建一个节点
|
||||||
*/
|
*/
|
||||||
@ -664,6 +664,17 @@ export class DocumentModel {
|
|||||||
onRefresh(/* func: () => void */) {
|
onRefresh(/* func: () => void */) {
|
||||||
console.warn('onRefresh method is deprecated');
|
console.warn('onRefresh method is deprecated');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onReady(fn: Function) {
|
||||||
|
this.designer.editor.on('document-open', fn);
|
||||||
|
return () => {
|
||||||
|
this.designer.editor.removeListener('document-open', fn);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupListenActiveNodes() {
|
||||||
|
// todo:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isDocumentModel(obj: any): obj is DocumentModel {
|
export function isDocumentModel(obj: any): obj is DocumentModel {
|
||||||
|
|||||||
@ -261,7 +261,4 @@ export class Project {
|
|||||||
this.emitter.removeListener('current-document.change', fn);
|
this.emitter.removeListener('current-document.change', fn);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// 通知标记删除,需要告知服务端
|
|
||||||
// 项目角度编辑不是全量打开所有文档,是按需加载,哪个更新就通知更新谁,
|
|
||||||
// 哪个删除就
|
|
||||||
}
|
}
|
||||||
|
|||||||
147
packages/editor-preset-vision/src/base/flags.ts
Normal file
147
packages/editor-preset-vision/src/base/flags.ts
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import domReady from 'domready';
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
|
const Shells = ['iphone6'];
|
||||||
|
|
||||||
|
export class Flags {
|
||||||
|
public emitter: EventEmitter;
|
||||||
|
public flags: string[];
|
||||||
|
public ready: boolean;
|
||||||
|
public lastFlags: string[];
|
||||||
|
public lastShell: string;
|
||||||
|
|
||||||
|
private lastSimulatorDevice: string;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.emitter = new EventEmitter();
|
||||||
|
this.flags = ['design-mode'];
|
||||||
|
|
||||||
|
domReady(() => {
|
||||||
|
this.ready = true;
|
||||||
|
this.applyFlags();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public setDragMode(flag: boolean) {
|
||||||
|
if (flag) {
|
||||||
|
this.add('drag-mode');
|
||||||
|
} else {
|
||||||
|
this.remove('drag-mode');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public setPreviewMode(flag: boolean) {
|
||||||
|
if (flag) {
|
||||||
|
this.add('preview-mode');
|
||||||
|
this.remove('design-mode');
|
||||||
|
} else {
|
||||||
|
this.add('design-mode');
|
||||||
|
this.remove('preview-mode');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public setWithShell(shell: string) {
|
||||||
|
if (shell === this.lastShell) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.lastShell) {
|
||||||
|
this.remove(`with-${this.lastShell}shell`);
|
||||||
|
}
|
||||||
|
if (shell) {
|
||||||
|
if (Shells.indexOf(shell) < 0) {
|
||||||
|
shell = Shells[0];
|
||||||
|
}
|
||||||
|
this.add(`with-${shell}shell`);
|
||||||
|
this.lastShell = shell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public setSimulator(device: string) {
|
||||||
|
if (this.lastSimulatorDevice) {
|
||||||
|
this.remove(`simulator-${this.lastSimulatorDevice}`);
|
||||||
|
}
|
||||||
|
if (device !== '' && device !== 'pc') {
|
||||||
|
this.add(`simulator-${device}`);
|
||||||
|
}
|
||||||
|
this.lastSimulatorDevice = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setHideSlate(flag: boolean) {
|
||||||
|
if (this.has('slate-fixed')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (flag) {
|
||||||
|
this.add('hide-slate');
|
||||||
|
} else {
|
||||||
|
this.remove('hide-slate');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public setSlateFixedMode(flag: boolean) {
|
||||||
|
if (flag) {
|
||||||
|
this.remove('hide-slate');
|
||||||
|
this.add('slate-fixed');
|
||||||
|
} else {
|
||||||
|
this.remove('slate-fixed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public setSlateFullMode(flag: boolean) {
|
||||||
|
if (flag) {
|
||||||
|
this.add('slate-full-screen');
|
||||||
|
} else {
|
||||||
|
this.remove('slate-full-screen');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFlags() {
|
||||||
|
return this.flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public applyFlags(modifiedFlag?: string) {
|
||||||
|
if (!this.ready) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const doe = document.documentElement;
|
||||||
|
if (this.lastFlags) {
|
||||||
|
this.lastFlags.filter((flag: string) => this.flags.indexOf(flag) < 0).forEach((flag) => {
|
||||||
|
doe.classList.remove(`engine-${flag}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.flags.forEach((flag) => {
|
||||||
|
doe.classList.add(`engine-${flag}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.lastFlags = this.flags.slice(0);
|
||||||
|
this.emitter.emit('flagschange', this.flags, modifiedFlag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public has(flag: string) {
|
||||||
|
return this.flags.indexOf(flag) > -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public add(flag: string) {
|
||||||
|
if (!this.has(flag)) {
|
||||||
|
this.flags.push(flag);
|
||||||
|
this.applyFlags(flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public remove(flag: string) {
|
||||||
|
const i = this.flags.indexOf(flag);
|
||||||
|
if (i > -1) {
|
||||||
|
this.flags.splice(i, 1);
|
||||||
|
this.applyFlags(flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public onFlagsChange(func: () => any) {
|
||||||
|
this.emitter.on('flagschange', func);
|
||||||
|
return () => {
|
||||||
|
this.emitter.removeListener('flagschange', func);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new Flags();
|
||||||
@ -14,6 +14,7 @@ import Panes from './panes';
|
|||||||
import Exchange from './exchange';
|
import Exchange from './exchange';
|
||||||
import context from './context';
|
import context from './context';
|
||||||
import VisualManager from './base/visualManager';
|
import VisualManager from './base/visualManager';
|
||||||
|
import VisualDesigner from './base/visualDesigner';
|
||||||
import Trunk from './bundle/trunk';
|
import Trunk from './bundle/trunk';
|
||||||
import Prototype from './bundle/prototype';
|
import Prototype from './bundle/prototype';
|
||||||
import Bundle from './bundle/bundle';
|
import Bundle from './bundle/bundle';
|
||||||
@ -22,6 +23,7 @@ import * as Field from './fields';
|
|||||||
import Prop from './prop';
|
import Prop from './prop';
|
||||||
import Env from './env';
|
import Env from './env';
|
||||||
import DragEngine from './drag-engine';
|
import DragEngine from './drag-engine';
|
||||||
|
import Flags from './base/flags';
|
||||||
import Viewport from './viewport';
|
import Viewport from './viewport';
|
||||||
import Project from './project';
|
import Project from './project';
|
||||||
|
|
||||||
@ -60,6 +62,7 @@ const ui = {
|
|||||||
|
|
||||||
const modules = {
|
const modules = {
|
||||||
VisualManager,
|
VisualManager,
|
||||||
|
VisualDesigner,
|
||||||
I18nUtil,
|
I18nUtil,
|
||||||
Prop,
|
Prop,
|
||||||
};
|
};
|
||||||
@ -107,6 +110,7 @@ const VisualEngine = {
|
|||||||
Project,
|
Project,
|
||||||
logger,
|
logger,
|
||||||
Symbols,
|
Symbols,
|
||||||
|
Flags,
|
||||||
};
|
};
|
||||||
|
|
||||||
(window as any).VisualEngine = VisualEngine;
|
(window as any).VisualEngine = VisualEngine;
|
||||||
|
|||||||
@ -124,6 +124,9 @@ Object.defineProperty(pages, 'currentPage', {
|
|||||||
get() {
|
get() {
|
||||||
return project.currentDocument;
|
return project.currentDocument;
|
||||||
},
|
},
|
||||||
|
set(_currentPage) {
|
||||||
|
// do nothing
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
pages.onCurrentPageChange((page: DocumentModel) => {
|
pages.onCurrentPageChange((page: DocumentModel) => {
|
||||||
|
|||||||
@ -38,7 +38,9 @@ export function composeTitle(title?: TitleContent, icon?: IconType, tip?: TipCon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isTitleConfig(title) && noIcon) {
|
if (isTitleConfig(title) && noIcon) {
|
||||||
|
if (!isValidElement(title)) {
|
||||||
title.icon = undefined;
|
title.icon = undefined;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -160,7 +160,7 @@ export default class DataHelper {
|
|||||||
const _tb_token_ = csrfInput && csrfInput.value;
|
const _tb_token_ = csrfInput && csrfInput.value;
|
||||||
asyncDataList.forEach((req) => {
|
asyncDataList.forEach((req) => {
|
||||||
const { id, type, options } = req;
|
const { id, type, options } = req;
|
||||||
if (!id || !type) return;
|
if (!id || !type || type === 'legao') return;
|
||||||
if (type === 'doServer') {
|
if (type === 'doServer') {
|
||||||
const { uri, params } = options || {};
|
const { uri, params } = options || {};
|
||||||
if (!uri) return;
|
if (!uri) return;
|
||||||
|
|||||||
@ -151,18 +151,18 @@ class Renderer extends Component<{
|
|||||||
viewProps._leaf = leaf;
|
viewProps._leaf = leaf;
|
||||||
viewProps._componentName = leaf?.componentName;
|
viewProps._componentName = leaf?.componentName;
|
||||||
// 如果是容器 && 无children && 高宽为空 增加一个占位容器,方便拖动
|
// 如果是容器 && 无children && 高宽为空 增加一个占位容器,方便拖动
|
||||||
if (
|
// if (
|
||||||
!viewProps.dataSource &&
|
// !viewProps.dataSource &&
|
||||||
leaf?.isContainer() &&
|
// leaf?.isContainer() &&
|
||||||
(children == null || (Array.isArray(children) && !children.length)) &&
|
// (children == null || (Array.isArray(children) && !children.length)) &&
|
||||||
(!viewProps.style || Object.keys(viewProps.style).length === 0)
|
// (!viewProps.style || Object.keys(viewProps.style).length === 0)
|
||||||
) {
|
// ) {
|
||||||
children = (
|
// children = (
|
||||||
<div className="lc-container-placeholder" style={viewProps.placeholderStyle}>
|
// <div className="lc-container-placeholder" style={viewProps.placeholderStyle}>
|
||||||
{viewProps.placeholder || '拖拽组件或模板到这里'}
|
// {viewProps.placeholder || '拖拽组件或模板到这里'}
|
||||||
</div>
|
// </div>
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
if (viewProps._componentName === 'a') {
|
if (viewProps._componentName === 'a') {
|
||||||
delete viewProps.href;
|
delete viewProps.href;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,16 +3,15 @@ import { render as reactRender } from 'react-dom';
|
|||||||
import { host } from './host';
|
import { host } from './host';
|
||||||
import SimulatorRendererView from './renderer-view';
|
import SimulatorRendererView from './renderer-view';
|
||||||
import { computed, obx } from '@recore/obx';
|
import { computed, obx } from '@recore/obx';
|
||||||
import { Asset } from '@ali/lowcode-utils';
|
|
||||||
import { getClientRects } from './utils/get-client-rects';
|
import { getClientRects } from './utils/get-client-rects';
|
||||||
import { reactFindDOMNodes, FIBER_KEY } from './utils/react-find-dom-nodes';
|
import { reactFindDOMNodes, FIBER_KEY } from './utils/react-find-dom-nodes';
|
||||||
import {
|
import {
|
||||||
|
Asset,
|
||||||
isElement,
|
isElement,
|
||||||
cursor,
|
cursor,
|
||||||
setNativeSelection,
|
setNativeSelection,
|
||||||
buildComponents,
|
buildComponents,
|
||||||
getSubComponent,
|
getSubComponent,
|
||||||
AssetLoader,
|
|
||||||
} from '@ali/lowcode-utils';
|
} from '@ali/lowcode-utils';
|
||||||
import { RootSchema, ComponentSchema, TransformStage, NodeSchema } from '@ali/lowcode-types';
|
import { RootSchema, ComponentSchema, TransformStage, NodeSchema } from '@ali/lowcode-types';
|
||||||
// import { isESModule, isElement, acceptsRef, wrapReactClass, cursor, setNativeSelection } from '@ali/lowcode-utils';
|
// import { isESModule, isElement, acceptsRef, wrapReactClass, cursor, setNativeSelection } from '@ali/lowcode-utils';
|
||||||
@ -270,7 +269,7 @@ export class SimulatorRendererContainer implements BuiltinSimulatorRenderer {
|
|||||||
this._components = {
|
this._components = {
|
||||||
...builtinComponents,
|
...builtinComponents,
|
||||||
...this._components,
|
...this._components,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
@obx.ref private _components: any = {};
|
@obx.ref private _components: any = {};
|
||||||
@computed get components(): object {
|
@computed get components(): object {
|
||||||
@ -365,7 +364,7 @@ export class SimulatorRendererContainer implements BuiltinSimulatorRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const node = host.currentDocument?.createNode(_schema);
|
const node = host.currentDocument?.createNode(_schema);
|
||||||
_schema = node?.export(TransformStage.Render);
|
_schema = node?.export(TransformStage.Render) || {};
|
||||||
|
|
||||||
const processPropsSchema = (propsSchema: any, propsMap: any): any => {
|
const processPropsSchema = (propsSchema: any, propsMap: any): any => {
|
||||||
if (!propsSchema) {
|
if (!propsSchema) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user