Merge branch 'polyfill/vision' of gitlab.alibaba-inc.com:ali-lowcode/ali-lowcode-engine into polyfill/vision

This commit is contained in:
飞百 2020-05-11 16:52:48 +08:00
commit 38599fffa1
68 changed files with 1437 additions and 304 deletions

View File

@ -41,7 +41,8 @@
"@alife/theme-lowcode-light": "^0.1.0",
"react": "^16.8.1",
"react-dom": "^16.8.1",
"@ali/vu-function-parser": "^2.5.0-beta.0"
"@ali/vu-function-parser": "^2.5.0-beta.0",
"compare-versions": "^3.0.1"
},
"devDependencies": {
"@ali/iceluna-cli": "^0.0.16",

View File

@ -18,8 +18,11 @@ import { upgradeAssetsBundle } from './upgrade-assets';
import { isCSSUrl } from '@ali/lowcode-utils';
import { I18nSetter } from '@ali/visualengine-utils';
import VariableSetter from '@ali/vs-variable-setter';
import { isObject, isArray } from 'lodash';
import _isArray from "lodash/isArray";
import _isObject from "lodash/isObject";
import _get from 'lodash/get';
import funcParser from '@ali/vu-function-parser';
import cv from 'compare-versions';
const { editor, skeleton, context, HOOKS, Trunk } = Engine;
@ -45,11 +48,7 @@ async function loadAssets() {
if (assets.packages) {
assets.packages.forEach((item: any) => {
if (item.package.indexOf('@ali/vc-') === 0 && item.urls) {
item.urls = item.urls.filter((url: string) => {
return url.indexOf('view.mobile') < 0;
});
} else if (item.package && externals.indexOf(item.package) > -1) {
if (item.package && externals.indexOf(item.package) > -1) {
item.urls = null;
}
});
@ -103,7 +102,7 @@ function initDemoPanes() {
props: {
align: 'bottom',
icon: 'set',
description: '设置',
description: '设置'
},
});
skeleton.add({
@ -113,7 +112,7 @@ function initDemoPanes() {
props: {
align: 'bottom',
icon: 'help',
description: '帮助',
description: '帮助'
},
});
@ -336,9 +335,9 @@ function replaceFuncProp(props?: any){
}
if ((prop.compiled && prop.source) || prop.type === 'actionRef' || prop.type === 'js') {
replaceProps[name] = funcParser(prop);
} else if (isObject(prop)) {
} else if (_isObject(prop)) {
replaceFuncProp(prop);
} else if (isArray(prop)) {
} else if (_isArray(prop)) {
prop.map((propItem) => {
replaceFuncProp(propItem);
});
@ -348,7 +347,6 @@ function replaceFuncProp(props?: any){
for (const name in replaceProps) {
props[name] = replaceProps[name];
}
return props;
};
@ -399,7 +397,7 @@ function initHistoryPane() {
historyManager: {
historyManager,
app: {
}
},
index: -940,

View File

@ -10,3 +10,4 @@ declare module '@ali/ve-i18n-manage-pane';
declare module '@ali/ve-action-pane';
declare module '@ali/vu-legao-design-fetch-context';
declare module "@ali/vu-function-parser";
declare module "compare-versions";

View File

@ -39,27 +39,25 @@ export class BorderHoveringInstance extends PureComponent<{
}
@observer
export class BorderHovering extends Component {
static contextType = SimulatorContext;
export class BorderHovering extends Component<{ host: BuiltinSimulatorHost }> {
shouldComponentUpdate() {
return false;
}
@computed get scale() {
return (this.context as BuiltinSimulatorHost).viewport.scale;
return this.props.host.viewport.scale;
}
@computed get scrollX() {
return (this.context as BuiltinSimulatorHost).viewport.scrollX;
return this.props.host.viewport.scrollX;
}
@computed get scrollY() {
return (this.context as BuiltinSimulatorHost).viewport.scrollY;
return this.props.host.viewport.scrollY;
}
@computed get current() {
const host = this.context as BuiltinSimulatorHost;
const host = this.props.host;
const doc = host.document;
const selection = doc.selection;
const current = host.designer.hovering.current;
@ -70,7 +68,7 @@ export class BorderHovering extends Component {
}
render() {
const host = this.context as BuiltinSimulatorHost;
const host = this.props.host;
const current = this.current;
if (!current || host.viewport.scrolling) {
return <Fragment />;

View File

@ -132,11 +132,9 @@ function createAction(content: ReactNode | ComponentType<any> | ActionContentObj
}
@observer
export class BorderSelectingForNode extends Component<{ node: Node }> {
static contextType = SimulatorContext;
export class BorderSelectingForNode extends Component<{ host: BuiltinSimulatorHost; node: Node }> {
get host(): BuiltinSimulatorHost {
return this.context;
return this.props.host;
}
get dragging(): boolean {
@ -177,11 +175,9 @@ export class BorderSelectingForNode extends Component<{ node: Node }> {
}
@observer
export class BorderSelecting extends Component {
static contextType = SimulatorContext;
export class BorderSelecting extends Component<{ host: BuiltinSimulatorHost }> {
get host(): BuiltinSimulatorHost {
return this.context;
return this.props.host;
}
get dragging(): boolean {
@ -211,7 +207,7 @@ export class BorderSelecting extends Component {
return (
<Fragment>
{selecting.map((node) => (
<BorderSelectingForNode key={node.id} node={node} />
<BorderSelectingForNode key={node.id} host={this.props.host} node={node} />
))}
</Fragment>
);

View File

@ -25,6 +25,9 @@
align-items: stretch;
justify-content: flex-end;
pointer-events: all;
> * {
flex-shrink: 0;
}
}
&-action {

View File

@ -1,7 +1,6 @@
import { Component } from 'react';
import { observer } from '@ali/lowcode-editor-core';
import { BorderHovering } from './border-hovering';
import { SimulatorContext } from '../context';
import { BuiltinSimulatorHost } from '../host';
import { BorderSelecting } from './border-selecting';
import BorderResizing from './border-resizing';
@ -10,15 +9,13 @@ import './bem-tools.less';
import './borders.less';
@observer
export class BemTools extends Component {
static contextType = SimulatorContext;
export class BemTools extends Component<{ host: BuiltinSimulatorHost }> {
shouldComponentUpdate() {
return false;
}
render() {
const host = this.context as BuiltinSimulatorHost;
const host = this.props.host;
const { scrollX, scrollY, scale } = host.viewport;
return (
<div className="lc-bem-tools" style={{ transform: `translate(${-scrollX * scale}px,${-scrollY * scale}px)` }}>

View File

@ -112,24 +112,19 @@ function processDetail({ target, detail, document }: DropLocation): InsertionDat
}
@observer
export class InsertionView extends Component {
static contextType = SimulatorContext;
@computed get host(): BuiltinSimulatorHost {
return this.context;
}
export class InsertionView extends Component<{ host: BuiltinSimulatorHost }> {
shouldComponentUpdate() {
return false;
}
render() {
const loc = this.host.document.dropLocation;
const { host } = this.props;
const loc = host.document.dropLocation;
if (!loc) {
return null;
}
const { scale, scrollX, scrollY } = this.host.viewport;
const { scale, scrollX, scrollY } = host.viewport;
const { edge, insertType, coverRect, nearRect, vertical } = processDetail(loc);
if (!edge) {

View File

@ -41,20 +41,17 @@ export class BuiltinSimulatorHostView extends Component<SimulatorHostProps> {
const { Provider } = SimulatorContext;
return (
<div className="lc-simulator">
<Provider value={this.host}>
{/*progressing.visible ? <PreLoaderView /> : null*/}
<Canvas />
</Provider>
{/*progressing.visible ? <PreLoaderView /> : null*/}
<Canvas host={this.host} />
</div>
);
}
}
@observer
class Canvas extends Component {
static contextType = SimulatorContext;
class Canvas extends Component<{ host: BuiltinSimulatorHost }> {
render() {
const sim = this.context as BuiltinSimulatorHost;
const sim = this.props.host;
let className = 'lc-simulator-canvas';
if (sim.deviceClassName) {
className += ` ${sim.deviceClassName}`;
@ -65,8 +62,8 @@ class Canvas extends Component {
return (
<div className={className}>
<div ref={elmt => sim.mountViewport(elmt)} className="lc-simulator-canvas-viewport">
<BemTools />
<Content />
<BemTools host={sim} />
<Content host={sim} />
</div>
</div>
);
@ -74,10 +71,9 @@ class Canvas extends Component {
}
@observer
class Content extends Component {
static contextType = SimulatorContext;
class Content extends Component<{ host: BuiltinSimulatorHost }> {
render() {
const sim = this.context as BuiltinSimulatorHost;
const sim = this.props.host;
const viewport = sim.viewport;
let frameStyle = {};
if (viewport.scale < 1) {

View File

@ -7,6 +7,8 @@ import {
TitleContent,
TransformedComponentMetadata,
NestingFilter,
isTitleConfig,
I18nData,
} from '@ali/lowcode-types';
import { computed } from '@ali/lowcode-editor-core';
import { Node, ParentalNode } from './document';
@ -17,6 +19,8 @@ import { IconPage } from './icons/page';
import { IconComponent } from './icons/component';
import { IconRemove } from './icons/remove';
import { IconClone } from './icons/clone';
import { ReactElement } from 'react';
import { IconHidden } from './icons/hidden';
function ensureAList(list?: string | string[]): string[] | null {
if (!list) {
@ -91,12 +95,20 @@ export class ComponentMeta {
private childWhitelist?: NestingFilter | null;
private _title?: TitleContent;
get title() {
get title(): string | I18nData | ReactElement {
// TODO: 标记下。这块需要康师傅加一下API页面正常渲染。
// string | i18nData | ReactElement
// TitleConfig title.label
if (isTitleConfig(this._title)) {
return (this._title.label as any) || this.componentName;
}
return this._title || this.componentName;
}
@computed get icon() {
// TODO: 标记下。这块需要康师傅加一下API页面正常渲染。
// give Slot default icon
// if _title is TitleConfig get _title.icon
return (
this._transformedMetadata?.icon ||
(this.componentName === 'Page' ? IconPage : this.isContainer ? IconContainer : IconComponent)
@ -131,10 +143,10 @@ export class ComponentMeta {
this._title =
typeof title === 'string'
? {
type: 'i18n',
'en-US': this.componentName,
'zh-CN': title,
}
type: 'i18n',
'en-US': this.componentName,
'zh-CN': title,
}
: title;
}
@ -319,6 +331,20 @@ const builtinComponentActions: ComponentAction[] = [
},
important: true,
},
{
name: 'hide',
content: {
icon: IconHidden,
title: intlNode('hide'),
action(node: Node) {
node.getExtraProp('hidden', true)?.setValue(true);
},
},
condition: (node: Node) => {
return node.componentMeta.isModal;
},
important: true,
},
{
name: 'copy',
content: {
@ -326,6 +352,8 @@ const builtinComponentActions: ComponentAction[] = [
title: intlNode('copy'),
action(node: Node) {
// node.remove();
const { document: doc, parent, schema, index } = node;
parent && doc.insertNode(parent, schema, index);
},
},
important: true,

View File

@ -4,6 +4,67 @@ import { focusing } from './focusing';
import { insertChildren, TransformStage } from '../document';
import clipboard from './clipboard';
function getNextForSelect(next: any, head?: any, parent?: any): any {
if (next) {
if (!head) {
return next;
}
let ret;
if (next.isContainer()) {
const children = next.getChildren() || [];
if (children && !children.isEmpty()) {
ret = getNextForSelect(children.get(0));
if (ret) {
return ret;
}
}
}
ret = getNextForSelect(next.nextSibling);
if (ret) {
return ret;
}
}
if (parent) {
return getNextForSelect(parent.nextSibling, false, parent.getParent());
}
return null;
}
function getPrevForSelect(prev: any, head?: any, parent?: any): any {
if (prev) {
debugger;
let ret;
if (!head && prev.isContainer()) {
const children = prev.getChildren() || [];
const lastChild = children && !children.isEmpty() ? children.get(children.size - 1) : null;
ret = getPrevForSelect(lastChild);
if (ret) {
return ret;
}
}
if (!head) {
return prev;
}
ret = getPrevForSelect(prev.prevSibling);
if (ret) {
return ret;
}
}
if (parent) {
return parent;
}
return null;
}
// hotkey binding
hotkey.bind(['backspace', 'del'], (e: KeyboardEvent) => {
const doc = focusing.focusDesigner?.currentDocument;
@ -58,14 +119,15 @@ hotkey.bind(['command+c', 'ctrl+c', 'command+x', 'ctrl+x'], (e, action) => {
const data = { type: 'nodeSchema', componentsMap, componentsTree };
clipboard.setData(data);
/*
const cutMode = action.indexOf('x') > 0;
if (cutMode) {
const parentNode = selected.getParent();
parentNode.select();
selected.remove();
selected.forEach((node) => {
const parentNode = node.getParent();
parentNode?.select();
node.remove();
});
}
*/
});
// command + v paste
@ -111,3 +173,92 @@ hotkey.bind(['command+y', 'ctrl+y', 'command+shift+z'], (e) => {
his.forward();
});
// sibling selection
hotkey.bind(['left', 'right'], (e, action) => {
const designer = focusing.focusDesigner;
const doc = designer?.currentDocument;
if (isFormEvent(e) || !doc) {
return;
}
e.preventDefault();
const selected = doc.selection.getTopNodes(true);
if (!selected || selected.length < 1) {
return;
}
const firstNode = selected[0];
const silbing = action === 'left' ? firstNode?.prevSibling : firstNode?.nextSibling;
silbing?.select();
});
hotkey.bind(['up', 'down'], (e, action) => {
const designer = focusing.focusDesigner;
const doc = designer?.currentDocument;
if (isFormEvent(e) || !doc) {
return;
}
e.preventDefault();
const selected = doc.selection.getTopNodes(true);
if (!selected || selected.length < 1) {
return;
}
const firstNode = selected[0];
if (action === 'down') {
const next = getNextForSelect(firstNode, true, firstNode.getParent());
next?.select();
} else if (action === 'up') {
const prev = getPrevForSelect(firstNode, true, firstNode.getParent());
prev?.select();
}
});
hotkey.bind(['option+up', 'option+down', 'option+left', 'option+right'], (e, action) => {
const designer = focusing.focusDesigner;
const doc = designer?.currentDocument;
if (isFormEvent(e) || !doc) {
return;
}
e.preventDefault();
const selected = doc.selection.getTopNodes(true);
if (!selected || selected.length < 1) {
return;
}
// TODO: 此处需要增加判断当前节点是否可被操作移动原ve里是用 node.canOperating()来判断
const firstNode = selected[0];
const parent = firstNode.getParent();
if (!parent) return;
const isPrev = /(up|left)$/.test(action);
const isTravel = /(up|down)$/.test(action);
const silbing = isPrev ? firstNode.prevSibling : firstNode.nextSibling;
if (silbing) {
if (isTravel && silbing.isContainer()) {
const place = silbing.getSuitablePlace(firstNode, null);
if (isPrev) {
place.container.insertAfter(firstNode, place.ref);
} else {
place.container.insertBefore(firstNode, place.ref);
}
} else if (isPrev) {
parent.insertBefore(firstNode, silbing);
} else {
parent.insertAfter(firstNode, silbing);
}
firstNode?.select();
return;
}
if (isTravel) {
const place = parent.getSuitablePlace(firstNode, null); // upwards
if (place) {
if (isPrev) {
place.container.insertBefore(firstNode, place.ref);
} else {
place.container.insertAfter(firstNode, place.ref);
}
firstNode?.select();
}
}
});

View File

@ -180,6 +180,11 @@ export class SettingPropEntry implements SettingEntry {
return this.top;
}
// add settingfield props
get props() {
return this.top;
}
onValueChange(func: () => any) {
this.emitter.on('valuechange', func);

View File

@ -1,6 +1,8 @@
// all this file for polyfill vision logic
import { isValidElement } from 'react';
import { isSetterConfig } from '@ali/lowcode-types';
import { getSetter } from '@ali/lowcode-editor-core';
function getHotterFromSetter(setter) {
return setter && (setter.Hotter || (setter.type && setter.type.Hotter)) || []; // eslint-disable-line
@ -29,9 +31,9 @@ export class Transducer {
constructor(context, config) {
let { setter } = config;
// 1. validElement
// 1. validElement
// 2. SetterConfig
// 3. SetterConfig[]
// 3. SetterConfig[]
if (Array.isArray(setter)) {
setter = setter[0];
} else if (isValidElement(setter) && setter.type.displayName === 'MixedSetter') {
@ -40,6 +42,13 @@ export class Transducer {
setter = setter.props.setters[0];
}
if (isSetterConfig(setter)) {
setter = setter.componentName;
}
if (typeof setter === 'string') {
setter = getSetter(setter);
}
this.setterTransducer = combineTransducer(
getTransducerFromSetter(setter),
getHotterFromSetter(setter),

View File

@ -41,6 +41,7 @@ export class DocumentModel {
private seqId = 0;
private _simulator?: ISimulatorHost;
/**
*
*/
@ -110,6 +111,14 @@ export class DocumentModel {
}
readonly designer = this.project.designer;
// getAddonData(name: string) {
// const addon = this.addons.find((item) => item.name === name);
// if (addon) {
// return addon.exportData();
// }
// return this.addonsData[name];
// }
/**
* id
@ -230,6 +239,14 @@ export class DocumentModel {
this.selection.remove(node.id);
node.remove();
}
getAddonData(name: string) {
const addon = this.getNode(name)
if (addon) {
// 无法确定是否有这个api
// return addon.exportData();
}
return addon
}
@obx.ref private _dropLocation: DropLocation | null = null;
/**
@ -461,14 +478,16 @@ export class DocumentModel {
return config.checkNestingDown(parent, obj) && this.checkNestingUp(parent, obj);
}
// ======= compatibles
// ======= compatibles for vision
getRoot() {
return this.rootNode;
}
/**
* vision
*/
// add toData
toData() {
return { componentsTree: [this.project?.currentDocument?.export(TransformStage.Save)] };
}
getHistory(): History {
return this.history;
}

View File

@ -174,6 +174,10 @@ export class History {
this.emitter.removeAllListeners();
this.records = [];
}
isModified() {
return this.point !== this.session.cursor;
}
}
class Session {

View File

@ -7,6 +7,7 @@ import {
PropsList,
NodeData,
TitleContent,
I18nData,
SlotSchema,
PageSchema,
ComponentSchema,
@ -19,6 +20,7 @@ import { Prop } from './props/prop';
import { ComponentMeta } from '../../component-meta';
import { ExclusiveGroup, isExclusiveGroup } from './exclusive-group';
import { TransformStage } from './transform-stage';
import { ReactElement } from 'react';
/**
*
@ -122,7 +124,7 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
return 0;
}
@computed get title(): TitleContent {
@computed get title(): string | I18nData | ReactElement {
let t = this.getExtraProp('title');
if (!t && this.componentMeta.descriptor) {
t = this.getProp(this.componentMeta.descriptor, false);
@ -136,6 +138,10 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
return this.componentMeta.title;
}
get icon() {
return this.componentMeta.icon;
}
constructor(readonly document: DocumentModel, nodeSchema: Schema) {
const { componentName, id, children, props, ...extras } = nodeSchema;
this.id = id || `node$${document.nextId()}`;
@ -154,8 +160,9 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
private transformProps(props: any): any {
// FIXME! support PropsList
return this.document.designer.transformProps(props, this, TransformStage.Init);
const x = this.document.designer.transformProps(props, this, TransformStage.Init);
return x;
// TODO: run transducers in metadata.experimental
}
@ -177,19 +184,19 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
return this.isParental() && this.componentMeta.isContainer;
}
isRoot(): this is RootNode {
isRoot(): boolean {
return this.document.rootNode == (this as any);
}
isPage(): this is PageNode {
isPage(): boolean {
return this.isRoot() && this.componentName === 'Page';
}
isComponent(): this is ComponentNode {
isComponent(): boolean {
return this.isRoot() && this.componentName === 'Component';
}
isSlot(): this is SlotNode {
isSlot(): boolean {
return this._slotFor != null && this.componentName === 'Slot';
}
@ -219,8 +226,12 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
return;
}
if (!this.isSlot() && this._parent) {
this._parent.children.delete(this);
if (this._parent) {
if (this.isSlot()) {
this._parent.removeSlot(this, false);
} else {
this._parent.children.delete(this);
}
}
this._parent = parent;
@ -252,8 +263,12 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
*
*/
remove() {
if (!this.isSlot() && this.parent) {
this.parent.children.delete(this, true);
if (this.parent) {
if (this.isSlot()) {
this.parent.removeSlot(this, true);
} else {
this.parent.children.delete(this, true);
}
}
}
@ -289,24 +304,13 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
return this.props.export(TransformStage.Serilize).props || null;
}
@obx.val _slots: Node[] = [];
@computed hasSlots() {
for (const item of this.props) {
if (item.type === 'slot') {
return true;
}
}
return false;
return this._slots.length > 0;
}
@computed get slots() {
// TODO: optimize recore/obx, array maked every time, donot as changed
const slots: Node[] = [];
this.props.forEach((item) => {
if (item.type === 'slot') {
slots.push(item.slotNode!);
}
});
return slots;
get slots() {
return this._slots;
}
@obx.ref private _conditionGroup: ExclusiveGroup | null = null;
@ -352,7 +356,7 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
@computed hasCondition() {
const v = this.getExtraProp('condition', false)?.getValue();
return v != null && v !== '';
return v != null && v !== '' && v !== true;
}
@computed hasLoop() {
@ -536,6 +540,28 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
return comparePosition(this, otherNode);
}
/**
* Slot节点
*/
removeSlot(slotNode: Node, purge = false): boolean {
const i = this._slots.indexOf(slotNode);
if (i < 0) {
return false;
}
const deleted = this._slots.splice(i, 1)[0];
if (purge) {
// should set parent null
deleted.internalSetParent(null);
deleted.purge();
}
return false;
}
addSlot(slotNode: Node) {
slotNode.internalSetParent(this as ParentalNode);
this._slots.push(slotNode);
}
private purged = false;
/**
*
@ -679,9 +705,20 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
}
getRect(): DOMRect | null {
if (this.isRoot()) {
return this.document.simulator?.viewport.contentBounds || null;
}
return this.document.simulator?.computeRect(this) || null;
}
getPrototype() {
return this;
}
getIcon() {
return this.icon;
}
toString() {
return this.id;
}

View File

@ -221,7 +221,7 @@ export class Prop implements IPropParent {
} else {
const owner = this.props.owner;
this._slotNode = owner.document.createNode<SlotNode>(slotSchema);
this._slotNode.internalSetParent(owner as any);
owner.addSlot(this._slotNode);
this._slotNode.internalSetSlotFor(this);
}
this.dispose();

View File

@ -309,4 +309,11 @@ export class Props implements IPropParent {
getPropValue(path: string): any {
return this.getProp(path, false)?.value;
}
/**
*
*/
setPropValue(path: string, value: any) {
this.getProp(path, true)!.setValue(value);
}
}

View File

@ -1,6 +1,7 @@
{
"copy": "Copy",
"remove": "Remove",
"hide": "Hide",
"Condition Group": "Condition Group",
"No opened document": "No opened document, open some document to editing"
}

View File

@ -1,6 +1,7 @@
{
"copy": "复制",
"remove": "删除",
"hide": "隐藏",
"Condition Group": "条件组",
"No opened document": "没有打开的页面,请选择页面打开编辑"
}

View File

@ -49,13 +49,14 @@ class AliGlobalLocale {
}
} else if (g_config) {
if (g_config.locale) {
return languageMap[g_config.locale] || (g_config.locale || '').replace('_', '-');
return languageMap[g_config.locale] || g_config.locale.replace('_', '-');
}
}
let locale: string = '';
if (navigator.language) {
locale = (navigator.language as string).replace('_', '-');
const lang = (navigator.language as string);
return languageMap[lang] || lang.replace('_', '-');
}
// IE10 及更低版本使用 browserLanguage

View File

@ -37,10 +37,13 @@ function injectVars(msg: string, params: any, locale: string): string {
});*/
}
export function intl(data: any, params?: object): string {
export function intl(data: any, params?: object): ReactNode {
if (!isI18nData(data)) {
return data;
}
if (data.intl) {
return data.intl;
}
const locale = globalLocale.getLocale();
const tries = generateTryLocales(locale);
let msg: string | undefined;

View File

@ -0,0 +1,48 @@
class FocusingManager {
deploy() {
}
send(e: MouseEvent | KeyboardEvent) {
}
addModalCheck() {
}
create(config: FocusableConfig) {
}
activeItem() {
}
suspenceItem() {
}
}
export interface FocusableConfig {
range: HTMLElement | ((e: MouseEvent) => boolean);
modal?: boolean;
onEsc?: () => void;
onBlur?: () => void;
}
class Focusable {
readonly isModal: boolean;
constructor(private manager: FocusingManager, { range, modal }: FocusableConfig) {
this.isModal = modal == null ? false : modal;
}
checkRange(e: MouseEvent) {
}
active() {
this.manager.activeItem(this);
}
suspence() {
this.manager.suspenceItem(this);
}
purge() {
}
}

View File

@ -13,7 +13,7 @@
align-items: center;
}
.lc-field-icon {
margin-right: @x-gap;
// margin-right: @x-gap;
transform-origin: center;
transition: transform 0.1s;
}
@ -21,7 +21,7 @@
&.lc-plain-field {
// for top-level style
padding: 8px 10px;
// padding: 8px 10px;
> .lc-field-body {
flex: 1;
min-width: 0;
@ -34,7 +34,16 @@
display: flex;
align-items: center;
// for top-level style
padding: 8px 10px;
padding: 16px;
&:first-child{
padding-top: 16px;
}
&:last-child{
padding-bottom: 16px;
}
&+.lc-inline-field{
padding-top: 0;
}
> .lc-field-head {
width: 70px;
@ -58,19 +67,29 @@
border-top: 1px solid var(--color-line-normal);
}
> .lc-field-head {
padding-left: @x-gap;
// padding-left: @x-gap;
height: 32px;
display: flex;
align-items: center;
font-weight: 500;
background: var(--color-block-background-shallow, rgba(31,56,88,.06));
border-bottom: 1px solid var(--color-line-normal);
color: var(--color-title);
// background: var(--color-block-background-shallow, rgba(31,56,88,.06));
// border-bottom: 1px solid var(--color-line-normal);
// color: var(--color-title);
padding: 0 16px;
background-color: #F7F9FC;
color: #8F9BB3;
user-select: none;
}
> .lc-field-body {
padding: @y-gap @x-gap/2;
// padding: @y-gap @x-gap/2;
padding: 16px;
.lc-inline-field{
margin-bottom: 16px;
&:last-child{
margin-bottom: 0;
}
}
}
+ .lc-inline-field {
@ -94,7 +113,11 @@
}
&.lc-accordion-field {
position: relative;
// collapsed
&:last-child.lc-field-is-collapsed{
border-bottom: 1px solid var(--color-line-normal);
}
&.lc-field-is-collapsed {
> .lc-field-head .lc-field-icon {
transform: rotate(180deg);
@ -106,14 +129,15 @@
// 邻近的保持上下距离
+ .lc-field {
margin-top: @y-gap;
// margin-top: @y-gap;
}
}
// 2rd level reset
.lc-field-body {
.lc-inline-field {
padding: @y-gap @x-gap/2 0 @x-gap/2;
// padding: @y-gap @x-gap/2 0 @x-gap/2;
padding: 0;
&:first-child {
padding-top: 0;
}
@ -130,9 +154,10 @@
> .lc-field-head {
padding-left: @x-gap/2;
background: var(--color-block-background-light);
border-bottom-color: var(--color-line-light);
border-bottom-color: var(--color-line-light, rgba(31, 56, 88, .1));
> .lc-field-icon {
margin-right: @x-gap/2;
// margin-right: @x-gap/2;
margin-right: 0;
}
}
}
@ -146,5 +171,8 @@
}
}
}
>.lc-block-setter {
flex: 1;
}
}
}

View File

@ -42,9 +42,22 @@
margin-right: 0;
>.lc-setter-actions {
position: absolute;
right: 10px;
right: 16px;
top: 0;
height: 32px;
transform: none;
}
}
.lc-block-field > .lc-field-body > .lc-setter-mixed{
}
.lc-accordion-field > .lc-field-body > .lc-setter-mixed{
position: static;
margin-right: 0;
> .lc-setter-actions{
right: 32px;
top: 6px;
transform: none;
}
}

View File

@ -70,13 +70,6 @@ export class SettingsMain {
this._settings = this.designer.createSettingEntry(this.editor, nodes);
}
onceOutlineVisible(fn: () => void): () => void {
this.emitter.on('outline-visible', fn);
return () => {
this.emitter.removeListener('outline-visible', fn);
};
}
purge() {
this.disposeListener();
this.emitter.removeAllListeners();

View File

@ -15,7 +15,7 @@
height: 30px;
display: flex;
align-items: center;
padding-left: 5px;
padding: 0 16px;
border-bottom: 1px solid var(--color-line-normal);
.lc-settings-navigator-icon {
width: 16px;

View File

@ -12,6 +12,8 @@ export default class LeftFloatPane extends Component<{ area: Area<any, Panel> }>
}
private dispose?: () => void;
// private focusing?: FocusingItem;
private shell: HTMLElement | null = null;
componentDidMount() {
const { area } = this.props;
const triggerClose = () => area.setVisible(false);
@ -19,18 +21,44 @@ export default class LeftFloatPane extends Component<{ area: Area<any, Panel> }>
this.dispose = () => {
area.skeleton.editor.removeListener('designer.dragstart', triggerClose);
}
/*
this.focusing = focusingTrack.create(this.shell!, {
onEsc: () => {
this.props.area.setVisible(false);
},
onBlur: () => {
this.props.area.setVisible(false);
},
// modal: boolean
});
*/
this.onEffect();
}
onEffect() {
/*
const { area } = this.props;
if (area.visible) {
this.focusing?.active();
} else {
this.focusing?.suspense();
}
*/
}
componentDidUpdate() {
this.onEffect();
}
componentWillUnmount() {
// this.focusing?.purge();
this.dispose?.();
}
render() {
const { area } = this.props;
// TODO: add focusingManager
// focusin set focus (push|replace)
// focusout remove focus
// onEsc
const width = area.current?.config.props?.width;
const hideTitleBar = area.current?.config.props?.hideTitleBar;
const style = width ? {
@ -38,6 +66,7 @@ export default class LeftFloatPane extends Component<{ area: Area<any, Panel> }>
} : undefined;
return (
<div
ref={(ref) => { this.shell = ref }}
className={classNames('lc-left-float-pane', {
'lc-area-visible': area.visible,
})}

View File

@ -13,7 +13,7 @@ export default class MainArea extends Component<{ area: Area<any, Panel | Widget
render() {
const { area } = this.props;
return (
<div className={classNames('lc-main-area')}>
<div className={classNames('lc-main-area engine-workspacepane')}>
{area.container.items.map((item) => item.content)}
</div>
);

View File

@ -12,7 +12,7 @@ export default class RightArea extends Component<{ area: Area<any, Panel> }> {
render() {
const { area } = this.props;
return (
<div className={classNames('lc-right-area', {
<div className={classNames('lc-right-area engine-tabpane', {
'lc-area-visible': area.visible,
})}>
<Contents area={area} />

View File

@ -4,23 +4,23 @@ import { observer } from '@ali/lowcode-editor-core';
import Area from '../area';
@observer
export default class TopArea extends Component<{ area: Area }> {
export default class TopArea extends Component<{ area: Area, itemClassName?: string }> {
render() {
const { area } = this.props;
const { area, itemClassName } = this.props;
return (
<div className={classNames("lc-top-area", {
<div className={classNames("lc-top-area engine-actionpane", {
'lc-area-visible': area.visible
})}>
<Contents area={area} />
<Contents area={area} itemClassName={itemClassName} />
</div>
);
}
}
@observer
class Contents extends Component<{ area: Area }> {
class Contents extends Component<{ area: Area, itemClassName?: string }> {
render() {
const { area } = this.props;
const { area, itemClassName } = this.props;
const left: any[] = [];
const center: any[] = [];
const right: any[] = [];
@ -29,12 +29,17 @@ class Contents extends Component<{ area: Area }> {
const index2 = b.config?.index || 0;
return index1 === index2 ? 0 : (index1 > index2 ? 1 : -1);
}).forEach(item => {
const content = (
<div className={itemClassName || ''}>
{item.content}
</div>
);
if (item.align === 'center') {
center.push(item.content);
center.push(content);
} else if (item.align === 'left') {
left.push(item.content);
left.push(content);
} else {
right.push(item.content);
right.push(content);
}
});
return (

View File

@ -54,14 +54,11 @@ body {
display: none;
}
.lc-panel-title {
height: 38px;
font-size: 14px;
background-color: var(--pane-title-bg-color,rgba(31,56,88,.04));
// background-color: var(--pane-title-bg-color,rgba(31,56,88,.04));
display: flex;
align-items: center;
justify-content: center;
justify-content: flex-start;
padding: 0 15px;
border-bottom: 1px solid var(--color-line-normal,rgba(31,56,88,.1));
.lc-help-tip {
margin-left: 4px;
@ -69,10 +66,18 @@ body {
cursor: pointer;
}
}
> .lc-panel-title {
height: 48px;
font-size: 16px;
padding: 0 15px;
// border-bottom: 1px solid var(--color-line-normal,rgba(31,56,88,.1));
color: #0F1726;
font-weight: bold;
}
.lc-panel-body {
position: absolute;
top: 38px;
top: 48px;
bottom: 0;
left: 0;
right: 0;
@ -111,6 +116,9 @@ body {
}
*/
}
.lc-outline-pane{
border-top: 1px solid var(--color-line-normal,rgba(31,56,88,.1));
}
}
.lc-panel {
height: 100%;
@ -153,23 +161,88 @@ body {
}
}
*/
}
/*覆盖旧面板*/
/*组件面板*/
// .ve-component-list {
// .ve-component-list-body{
// .ve-component-list-sidebar{
// .ve-component-list-navigator{
// .navigator-group{
// &:last-child{
// &::after{
// display: none;
// }
// }
// &::after{
// content: '';
// display: block;
// height: 1px;
// background-color: #EDEFF3;
// line-height: 0;
// margin: 4px 12px 0;
// }
// .navigator-group-head{
// .navigator-group-title{
// border-bottom: none;
// }
// }
// .navigator-group-item{
// border-left: 2px solid transparent;
// &.active{
// border-left-color: #0079f2;
// border-right: none;
// }
// }
// }
// }
// }
// }
// }
/*数据源*/
// .engine-datapool{
// .engine-datapool-view-group{
// padding-top: 48px;
// .engine-datapool-view-group-title{
// height: 48px;
// line-height: 48px;
// font-size: 16px;
// background-color: transparent;
// padding: 0 16px;
// border-bottom: 1px solid #EDEFF3;
// }
// }
// }
/*动作面板*/
// .ve-action-pane{
// border-top: none;
// .rc-tabs{
// .rc-tabs-bar{
// background-color: transparent;
// .rc-tabs-tab{
// line-height: 1;
// &.rc-tabs-tab-active{
.my-dock {
padding: 0px 10px;
cursor: pointer;
align-self: stretch;
display: flex;
align-items: center;
.my-title-label {
user-select: none;
}
&.actived, &:hover {
background-color: var(--pane-title-bg-color);
.my-title {
color: var(--color-actived);
}
}
// }
// }
// }
// }
// }
/*设置面板*/
// .ve-field .ve-field-head,
// .ve-field.ve-accordion2-field > .ve-field-head .ve-field-title-content{
// padding: 0;
// }
// .ve-field.ve-accordion2-field > .ve-field-split-line{
// display: none;
// }
// .vs-style .vs-style-source{
// margin: 0 0 16px;
// }
// .vs-code-button,
// .vs-json-button{
// margin: 0;
// }
}
@ -184,13 +257,13 @@ body {
width: 100%;
display: flex;
margin-bottom: 2px;
padding: 8px;
.lc-top-area-left{}
padding: 8px 12px 8px 16px;
.lc-top-area-center{
flex: 1;
display: flex;
justify-content: flex-end;
margin-right: 8px;
justify-content: center;
margin: 0 8px;
}
.lc-top-area-right{
display: flex;
@ -222,8 +295,9 @@ body {
}
.lc-pane-close{
position: absolute;
right: 10px;
top: 6px;
right: 16px;
top: 16px;
height: auto;
z-index: 2;
.next-icon{
line-height: 1;
@ -231,17 +305,32 @@ body {
}
.lc-tabs-title {
width: 100%;
height: 36px;
height: 32px;
position: relative;
display: center;
display: flex;
justify-content: center;
align-items: center;
background: rgba(31,56,88,0.04);
// background: rgba(31,56,88,0.04);
border-bottom: 1px solid #EDEFF3;
.lc-tab-title{
flex: 1;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-bottom: 2px solid transparent;
cursor: pointer;
font-size: 12px;
&.actived {
color: #0079F2;
border-bottom-color: #0079F2;
}
}
}
.lc-tabs-content {
position: absolute;
top: 36px;
top: 32px;
bottom: 0;
left: 0;
right: 0;
@ -265,16 +354,23 @@ body {
flex-direction: column;
justify-content: flex-start;
align-items: center;
.lc-title{
padding: 12px;
.lc-title {
flex-direction: column;
&.has-tip{
width: 46px;
height: 46px;
display: flex;
align-items: center;
justify-content: center;
&.has-tip {
cursor: pointer;
}
&.actived{
color: #0079F2;
}
.lc-title-icon{
.lc-title-icon {
height: 20px;
width: 20px;
margin: 0;
.next-icon:before {
line-height: 1 !important;
@ -301,8 +397,9 @@ body {
}
.lc-pane-close {
position: absolute;
right: 10px;
top: 6px;
right: 16px;
top: 16px;
height: auto;
z-index: 2;
.next-icon {
line-height: 1;
@ -324,6 +421,7 @@ body {
flex: 1;
display: flex;
flex-direction: column;
z-index: 10;
.lc-toolbar {
height: var(--toolbar-height);
background-color: var(--color-pane-background);

View File

@ -13,16 +13,16 @@ import RightArea from './right-area';
import './workbench.less';
@observer
export class Workbench extends Component<{ skeleton: Skeleton, className?: string }> {
export class Workbench extends Component<{ skeleton: Skeleton, className?: string, topAreaItemClassName?: string }> {
shouldComponentUpdate() {
return false;
}
render() {
const { skeleton, className } = this.props;
const { skeleton, className, topAreaItemClassName } = this.props;
return (
<div className={classNames('lc-workbench', className)}>
<TopArea area={skeleton.topArea} />
<TopArea area={skeleton.topArea} itemClassName={topAreaItemClassName} />
<div className="lc-workbench-body">
<LeftArea area={skeleton.leftArea} />
<LeftFloatPane area={skeleton.leftFloatArea} />

View File

@ -1,4 +1,5 @@
import { TransformedComponentMetadata, FieldConfig, SettingTarget } from '@ali/lowcode-types';
import { IconSlot } from '../icons/slot';
export default function(metadata: TransformedComponentMetadata): TransformedComponentMetadata {
const { componentName, configure = {} } = metadata;
@ -46,7 +47,7 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
const supportedLifecycles =
events.supportedLifecycles ||
(isRoot
? [
? /*[
{
description: '初始化时',
name: 'constructor',
@ -63,7 +64,7 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
description: '卸载时',
name: 'componentWillUnmount',
},
]
]*/ null
: null);
if (supportedLifecycles) {
eventsDefinition.push({
@ -80,7 +81,22 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
});
}
// 通用设置
const propsGroup = props || [];
let propsGroup = props || [];
const basicInfo: any = {};
if (componentName === 'Slot') {
basicInfo.icon = IconSlot;
propsGroup = [{
name: '___title',
title: {
type: 'i18n',
'en-US': 'Slot Title',
'zh-CN': '插槽标题'
},
setter: 'StringSetter',
defaultValue: '插槽容器'
}]
}
/*
propsGroup.push({
name: '#generals',
title: { type: 'i18n', 'zh-CN': '通用', 'en-US': 'General' },
@ -101,14 +117,14 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
title: 'Ref',
setter: 'StringSetter',
},
/*
{
name: '!more',
title: '更多',
setter: 'PropertiesSetter',
},*/
},
],
});
*/
const combined: FieldConfig[] = [
{
title: { type: 'i18n', 'zh-CN': '属性', 'en-US': 'Props' },
@ -182,8 +198,15 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
items: [
{
name: '___condition',
title: { type: 'i18n', 'zh-CN': '条件显示', 'en-US': 'Condition' },
setter: 'ExpressionSetter',
title: { type: 'i18n', 'zh-CN': '是否渲染', 'en-US': 'Condition' },
setter: [{
componentName: 'BoolSetter',
props: {
defaultValue: true,
}
}, {
componentName: 'VariableSetter'
}],
},
{
name: '#loop',
@ -192,27 +215,14 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
{
name: '___loop',
title: { type: 'i18n', 'zh-CN': '循环数据', 'en-US': 'Loop Data' },
setter: {
componentName: 'MixinSetter',
setter: [{
componentName: 'JsonSetter',
props: {
// TODO:
setters: [
{
componentName: 'JSONSetter',
props: {
mode: 'popup',
placeholder: { type: 'i18n', 'zh-CN': '编辑数据', 'en-US': 'Edit Data' },
},
},
{
componentName: 'ExpressionSetter',
props: {
placeholder: { type: 'i18n', 'zh-CN': '绑定数据', 'en-US': 'Bind Data' },
},
},
],
label: { type: 'i18n', 'zh-CN': '编辑数据', 'en-US': 'Edit Data'},
},
},
}, {
componentName: 'VariableSetter'
}],
},
{
name: '___loopArgs.0',
@ -236,8 +246,12 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
},
{
name: 'key',
title: 'Key',
setter: 'ExpressionSetter',
title: '循环 Key',
setter: [{
componentName: 'StringSetter',
}, {
componentName: 'VariableSetter'
}],
},
],
},
@ -247,6 +261,7 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
return {
...metadata,
...basicInfo,
configure: {
...configure,
combined,

View File

@ -45,6 +45,8 @@ export default class Dock implements IWidget {
} else {
this._body = createElement(DockView, props);
}
this.inited = true;
return this._body;
}

View File

@ -59,14 +59,14 @@ export default class PanelDock implements IWidget {
this.id = uniqueId(`dock:${name}$`);
this.panelName = config.panelName || name;
if (content) {
const _panelProps: any = { ...panelProps };
if (_panelProps.title == null && props) {
_panelProps.title = composeTitle(props.title, undefined, props.description, true, true);
}
this._panel = this.skeleton.add({
type: "Panel",
name: this.panelName,
props: {
// FIXME! give default title for panel
title: props ? composeTitle(props?.title, props?.icon, props?.description, true) : '',
...panelProps,
},
props: _panelProps,
contentProps,
content,
area: panelProps?.area

View File

@ -1,3 +1,4 @@
import { EventEmitter } from 'events';
import { createElement, ReactNode } from 'react';
import { obx } from '@ali/lowcode-editor-core';
import { uniqueId, createContent } from '@ali/lowcode-utils';
@ -13,8 +14,9 @@ export default class Panel implements IWidget {
readonly isWidget = true;
readonly name: string;
readonly id: string;
@obx.ref inited: boolean = false;
@obx.ref private _actived: boolean = false;
@obx.ref inited = false;
@obx.ref private _actived = false;
private emitter = new EventEmitter();
get actived(): boolean {
return this._actived;
}
@ -46,7 +48,7 @@ export default class Panel implements IWidget {
readonly title: TitleContent;
readonly help?: HelpTipConfig;
private plain: boolean = false;
private plain = false;
private container?: WidgetContainer<Panel, PanelConfig>;
private parent?: WidgetContainer;
@ -60,6 +62,9 @@ export default class Panel implements IWidget {
this.plain = hideTitleBar || !title;
this.help = help;
if (Array.isArray(content)) {
if (content.length === 1) {
// todo: not show tabs
}
this.container = this.skeleton.createContainer(
name,
(item) => {
@ -121,7 +126,11 @@ export default class Panel implements IWidget {
}
active(item?: Panel | string | null) {
this.container?.active(item);
if (item) {
this.container?.active(item);
} else {
this.setActive(true);
}
}
getName() {
@ -143,9 +152,11 @@ export default class Panel implements IWidget {
}
this._actived = true;
this.parent?.active(this);
this.emitter.emit('activechange', true);
} else if (this.inited) {
this._actived = false;
this.parent?.unactive(this);
this.emitter.emit('activechange', false);
}
}
@ -179,6 +190,16 @@ export default class Panel implements IWidget {
* @deprecated
*/
setPosition(position: string) {
// noop
}
/**
* @deprecated
*/
onActiveChange(fn: (flag: boolean) => void): () => void {
this.emitter.on('activechange', fn);
return () => {
this.emitter.removeListener('activechange', fn);
};
}
}

View File

@ -1,7 +1,7 @@
import { IconType, TitleContent, isI18nData, TipContent } from '@ali/lowcode-types';
import { IconType, TitleContent, isI18nData, TipContent, isTitleConfig } from '@ali/lowcode-types';
import { isValidElement } from 'react';
export function composeTitle(title?: TitleContent, icon?: IconType, tip?: TipContent, tipAsTitle?: boolean) {
export function composeTitle(title?: TitleContent, icon?: IconType, tip?: TipContent, tipAsTitle?: boolean, noIcon?: boolean) {
if (!title) {
title = {};
if (!icon || tipAsTitle) {
@ -11,6 +11,19 @@ export function composeTitle(title?: TitleContent, icon?: IconType, tip?: TipCon
}
if (icon || tip) {
if (typeof title !== 'object' || isValidElement(title) || isI18nData(title)) {
if (isValidElement(title)) {
if (title.type === 'svg' || (title.type as any).getIcon) {
if (!icon) {
icon = title as any;
}
if (tipAsTitle) {
title = tip as any;
tip = null;
} else {
title = undefined;
}
}
}
title = {
label: title,
icon,
@ -24,5 +37,8 @@ export function composeTitle(title?: TitleContent, icon?: IconType, tip?: TipCon
};
}
}
if (isTitleConfig(title) && noIcon) {
title.icon = undefined;
}
return title;
}

View File

@ -39,7 +39,7 @@ export default class WidgetContainer<T extends WidgetItem = any, G extends Widge
if (nameOrItem && typeof nameOrItem === 'string') {
item = this.get(nameOrItem);
}
if (!isActiveable(nameOrItem)) {
if (!isActiveable(item)) {
item = null;
}
@ -63,7 +63,7 @@ export default class WidgetContainer<T extends WidgetItem = any, G extends Widge
if (nameOrItem && typeof nameOrItem === 'string') {
item = this.get(nameOrItem);
}
if (!isActiveable(nameOrItem)) {
if (!isActiveable(item)) {
item = null;
}
if (this._current === item) {

View File

@ -1,12 +1,12 @@
import Pane from './views/pane';
import { IconOutline } from './icons/outline';
import { intl } from './locale';
import { intlNode } from './locale';
export default {
name: 'outline-pane',
props: {
icon: IconOutline,
description: intl('Outline Tree'),
description: intlNode('Outline Tree'),
},
content: Pane,
};

View File

@ -2,9 +2,9 @@ import { createIntl } from '@ali/lowcode-editor-core';
import en_US from './en-US.json';
import zh_CN from './zh-CN.json';
const { intl, getLocale, setLocale } = createIntl({
const { intl, intlNode, getLocale, setLocale } = createIntl({
'en-US': en_US,
'zh-CN': zh_CN,
});
export { intl, getLocale, setLocale };
export { intl, intlNode, getLocale, setLocale };

View File

@ -122,7 +122,7 @@ export default class TreeNode {
return title;
}
if (isI18nData(title)) {
return intl(title);
return intl(title) as string;
}
return this.node.componentName;
}

View File

@ -4,7 +4,7 @@ import { observer, Title } from '@ali/lowcode-editor-core';
import { ExclusiveGroup } from '@ali/lowcode-designer';
import TreeNode from '../tree-node';
import TreeNodeView from './tree-node';
import { intl } from '../locale';
import { intlNode } from '../locale';
@observer
export default class TreeBranches extends Component<{
@ -119,7 +119,7 @@ class TreeNodeSlots extends Component<{
data-id={treeNode.id}
>
<div className="tree-node-slots-title">
<Title title={{ type: 'i18n', intl: intl('Slots') }} />
<Title title={{ type: 'i18n', intl: intlNode('Slots') }} />
</div>
{treeNode.slots.map(tnode => (
<TreeNodeView key={tnode.id} treeNode={tnode} />

View File

@ -5,12 +5,11 @@ import { IconArrowRight } from '../icons/arrow-right';
import { IconEyeClose } from '../icons/eye-close';
import { IconLock } from '../icons/lock';
import { IconUnlock } from '../icons/unlock';
import { intl } from '../locale';
import { intl, intlNode } from '../locale';
import TreeNode from '../tree-node';
import { IconEye } from '../icons/eye';
import { IconCond } from '../icons/cond';
import { IconLoop } from '../icons/loop';
import { IconSlot } from '../icons/slot';
import { createIcon } from '@ali/lowcode-utils';
@observer
@ -104,29 +103,28 @@ export default class TreeTitle extends Component<{
{node.slotFor && (
<a className="tree-node-tag slot">
{/* todo: click redirect to prop */}
<IconSlot />
<Tip>{intl('Slot for {prop}', { prop: node.slotFor.key })}</Tip>
<Tip>{intlNode('Slot for {prop}', { prop: node.slotFor.key })}</Tip>
</a>
)}
{node.hasLoop() && (
<a className="tree-node-tag loop">
{/* todo: click todo something */}
<IconLoop />
<Tip>{intl('Loop')}</Tip>
<Tip>{intlNode('Loop')}</Tip>
</a>
)}
{node.hasCondition() && !node.conditionGroup && (
<a className="tree-node-tag cond">
{/* todo: click todo something */}
<IconCond />
<Tip>{intl('Conditional')}</Tip>
<Tip>{intlNode('Conditional')}</Tip>
</a>
)}
</Fragment>
)}
</div>
{isCNode && isNodeParent && <HideBtn treeNode={treeNode} />}
{isCNode && isNodeParent && <LockBtn treeNode={treeNode} />}
{/*isCNode && isNodeParent && <LockBtn treeNode={treeNode} />*/}
</div>
);
}

View File

@ -4,6 +4,6 @@
"outDir": "lib"
},
"include": [
"./src/"
"./src/",
]
}

View File

@ -228,6 +228,10 @@ export default class BaseEngine extends PureComponent {
let Comp = components[schema.componentName] || Div;
if (schema.hidden) {
return null;
}
if (schema.loop !== undefined) {
return this.__createLoopVirtualDom(
{

View File

@ -21,6 +21,7 @@
"@recore/obx-react": "^1.0.7",
"classnames": "^2.2.6",
"react": "^16",
"@ali/vu-css-style": "^1.0.2",
"react-dom": "^16.7.0"
},
"devDependencies": {

View File

@ -6,7 +6,7 @@ class Slot extends Component {
componentName: 'Slot',
configure: {
props: [{
name: '___title___',
name: '___title',
title: {
type: 'i18n',
'en-US': 'Slot Title',
@ -15,7 +15,7 @@ class Slot extends Component {
setter: 'StringSetter',
defaultValue: '插槽容器'
}, {
name: '___params___',
name: '___params',
title: {
type: 'i18n',
'en-US': 'Slot Params',

View File

@ -1,4 +1,5 @@
import LowCodeRenderer from '@ali/lowcode-react-renderer';
import { isObject } from 'lodash';
import { ReactInstance, Fragment, Component, createElement } from 'react';
import { observer } from '@recore/obx-react';
import { SimulatorRenderer } from './renderer';
@ -7,7 +8,7 @@ import './renderer.less';
// patch cloneElement avoid lost keyProps
const originCloneElement = window.React.cloneElement;
(window as any).React.cloneElement = (child: any, { _leaf, ...props}: any = {}) => {
(window as any).React.cloneElement = (child: any, { _leaf, ...props }: any = {}) => {
if (child.ref && props.ref) {
const dRef = props.ref;
const cRef = child.ref;
@ -18,7 +19,7 @@ const originCloneElement = window.React.cloneElement;
} else {
try {
cRef.current = x;
} catch (e) { }
} catch (e) {}
}
}
if (dRef) {
@ -27,13 +28,13 @@ const originCloneElement = window.React.cloneElement;
} else {
try {
dRef.current = x;
} catch (e) { }
} catch (e) {}
}
}
}
};
};
}
return originCloneElement(child, props);
}
};
export default class SimulatorRendererView extends Component<{ renderer: SimulatorRenderer }> {
render() {
@ -84,10 +85,11 @@ class Renderer extends Component<{ renderer: SimulatorRenderer }> {
const { __id, __desingMode, ...viewProps } = props;
viewProps.componentId = __id;
viewProps._leaf = host.document.getNode(__id);
return createElement(
Component,
viewProps,
children == null ? null : Array.isArray(children) ? children : [children],
children == null ? [] : Array.isArray(children) ? children : [children],
);
}}
onCompGetRef={(schema: any, ref: ReactInstance | null) => {

View File

@ -1,5 +1,5 @@
import { ReactElement, ReactNode } from 'react';
import { I18nData } from './i18n';
import { I18nData, isI18nData } from './i18n';
import { TipContent } from './tip';
import { IconType } from './icon';
@ -13,3 +13,14 @@ export interface TitleConfig {
export type TitleContent = string | I18nData | ReactElement | TitleConfig;
function isPlainObject(value: any): value is object {
if (typeof value !== 'object') {
return false;
}
const proto = Object.getPrototypeOf(value);
return proto === Object.prototype || proto === null || Object.getPrototypeOf(proto) === null;
}
export function isTitleConfig(obj: any): obj is TitleConfig {
return isPlainObject(obj) && !isI18nData(obj);
}

View File

@ -1,6 +1,6 @@
import { isObject } from './is-object';
export function isPlainObject(value: any) {
export function isPlainObject(value: any): value is object {
if (!isObject(value)) {
return false;
}

View File

@ -100,6 +100,22 @@ export default class Bundle {
cp.setView(view);
}
/**
* TODO dirty fix
*/
addComponentBundle(bundles: any) {
/**
* Normal Component bundle: [ Prototype, PrototypeView ]
* Component without Prototype.js: [ View ]
*/
if (bundles.length >= 2) {
const prototype = bundles[0];
const prototypeView = bundles[1];
prototype.setView(prototypeView);
this.registerPrototype(prototype);
}
}
private recursivelyRegisterViews(list: any[], viewName?: string): void {
list.forEach((item: any) => {
if (Array.isArray(item.module)) {

View File

@ -237,7 +237,7 @@ export function upgradePropConfig(config: OldPropConfig, addInitial: AddIntial)
}
}
if (collapse || collapsed || fieldCollapsed) {
if (collapse || collapsed || fieldCollapsed || extraProps.display === DISPLAY_TYPE.ENTRY) {
extraProps.defaultCollapsed = true;
}
function isDisabled(field: Field) {
@ -287,10 +287,14 @@ export function upgradePropConfig(config: OldPropConfig, addInitial: AddIntial)
if (slotName && initialValue === true) {
initialFn = (field: any, value: any) => {
if (isJSSlot(value)) {
return value;
return {
title: slotTitle || title,
...value,
};
}
return {
type: 'JSSlot',
title: slotTitle || title,
value: initialChildren,
};
};
@ -351,10 +355,14 @@ export function upgradePropConfig(config: OldPropConfig, addInitial: AddIntial)
componentName: 'SlotSetter',
initialValue: (field: any, value: any) => {
if (isJSSlot(value)) {
return value;
return {
title: slotTitle || title,
...value,
};
}
return {
type: 'JSSlot',
title: slotTitle || title,
value: value == null ? initialChildren : value,
};
},

View File

@ -0,0 +1,82 @@
@import '~@ali/ve-less-variables/index.less';
// 样式直接沿用之前的样式,优化了下命名
.instance-node-selector {
position: relative;
margin-right: 2px;
color: var(--color-icon-white, @title-bgcolor);
border-radius: @global-border-radius;
margin-right: 2px;
pointer-events: auto;
flex-grow: 0;
flex-shrink: 0;
svg {
width: 16px;
height: 16px;
margin-right: 5px;
flex-grow: 0;
flex-shrink: 0;
max-width: inherit;
path {
fill: var(--color-icon-white, @title-bgcolor);
}
}
&-current {
background: var(--color-brand, @brand-color-1);
padding: 0 6px;
display: flex;
align-items: center;
height: 20px;
cursor: pointer;
color: var(--color-icon-white, @title-bgcolor);
border-radius: 3px;
&-title {
padding-right: 6px;
color: var(--color-icon-white, @title-bgcolor);
}
}
&-list {
position: absolute;
left: 0;
right: 0;
opacity: 0;
visibility: hidden;
}
&-node {
margin: 2px 0;
&-content {
padding-left: 6px;
background: #78869a;
display: inline-flex;
border-radius: 3px;
align-items: center;
height: 20px;
color: var(--color-icon-white, @title-bgcolor);
cursor: pointer;
overflow: visible;
}
&-title {
padding-right: 6px;
// margin-left: 5px;
color: var(--color-icon-white, @title-bgcolor);
cursor: pointer;
overflow: visible;
}
&:hover {
opacity: 0.8;
}
}
}
&:hover {
.instance-node-selector-current {
color: ar(--color-text-reverse, @white-alpha-2);
}
.instance-node-selector-popup {
visibility: visible;
opacity: 1;
transition: 0.2s all ease-in;
}
}

View File

@ -0,0 +1,110 @@
import { Overlay } from '@alifd/next';
import React from 'react';
import './index.less';
import { Title } from '@ali/lowcode-editor-core';
import { Node, ParentalNode } from '@ali/lowcode-designer';
const { Popup } = Overlay;
export interface IProps {
node: Node;
}
export interface IState {
parentNodes: Node[];
}
type UnionNode = Node | ParentalNode | null;
export class InstanceNodeSelector extends React.Component<IProps, IState> {
state: IState = {
parentNodes: [],
};
componentDidMount() {
const parentNodes = this.getParentNodes(this.props.node);
this.setState({
parentNodes,
});
}
// 获取节点的父级节点最多获取5层
getParentNodes = (node: Node) => {
const parentNodes = [];
let currentNode: UnionNode = node;
while (currentNode && parentNodes.length < 5) {
currentNode = currentNode.getParent();
if (currentNode) {
parentNodes.push(currentNode);
}
}
return parentNodes;
};
onSelect = (node: Node) => () => {
if (node && typeof node.select === 'function') {
node.select();
}
};
onMouseOver = (node: Node) => (_: any, flag = true) => {
if (node && typeof node.hover === 'function') {
node.hover(flag);
}
};
onMouseOut = (node: Node) => (_: any, flag = false) => {
if (node && typeof node.hover === 'function') {
node.hover(flag);
}
};
renderNodes = (node: Node) => {
const nodes = this.state.parentNodes || [];
const children = nodes.map((node, key) => {
return (
<div
key={key}
onClick={this.onSelect(node)}
onMouseEnter={this.onMouseOver(node)}
onMouseLeave={this.onMouseOut(node)}
className="instance-node-selector-node"
>
<div className="instance-node-selector-node-content">
<Title
className="instance-node-selector-node-title"
title={{
label: node.title,
icon: node.icon,
}}
/>
</div>
</div>
);
});
return children;
};
render() {
const { node } = this.props;
return (
<div className="instance-node-selector">
<Popup
trigger={
<div className="instance-node-selector-current">
<Title
className="instance-node-selector-node-title"
title={{
label: node.title,
icon: node.icon,
}}
/>
</div>
}
triggerType="hover"
>
<div className="instance-node-selector">{this.renderNodes(node)}</div>
</Popup>
</div>
);
}
}

View File

@ -1,15 +1,16 @@
import { isJSBlock, isJSSlot } from '@ali/lowcode-types';
import { isPlainObject } from '@ali/lowcode-utils';
import { globalContext, Editor, registerSetter } from '@ali/lowcode-editor-core';
import { Designer, TransformStage } from '@ali/lowcode-designer';
import { globalContext, Editor } from '@ali/lowcode-editor-core';
import { Designer, TransformStage, addBuiltinComponentAction } from '@ali/lowcode-designer';
// import { registerSetters } from '@ali/lowcode-setters';
import Outline from '@ali/lowcode-plugin-outline-pane';
import { toCss } from '@ali/vu-css-style';
import DesignerPlugin from '@ali/lowcode-plugin-designer';
import { Skeleton, SettingsPrimaryPane } from '@ali/lowcode-editor-skeleton';
import Preview from '@ali/lowcode-plugin-sample-preview';
// import SourceEditor from '@ali/lowcode-plugin-source-editor';
import { i18nReducer } from './i18n-reducer';
import { InstanceNodeSelector } from './components';
export const editor = new Editor();
globalContext.register(editor, Editor);
@ -66,6 +67,37 @@ function upgradePropsReducer(props: any) {
}
designer.addPropsReducer(upgradePropsReducer, TransformStage.Init);
// 设计器组件样式处理
function stylePropsReducer(props: any, node: any) {
if (props && typeof props === 'object' && props.__style__) {
const doc = designer.currentDocument?.simulator?.contentDocument;
if (!doc) {
return;
}
const cssId = '_style_pesudo_' + node.id.replace(/\$/g, '_');
const cssClass = '_css_pesudo_' + node.id.replace(/\$/g, '_');
const dom = doc.getElementById(cssId);
if (dom) {
dom.parentNode?.removeChild(dom);
}
let styleProp = props.__style__;
if (typeof styleProp === 'object') {
styleProp = toCss(styleProp);
}
if (typeof styleProp === 'string') {
const s = doc.createElement('style');
props.className = cssClass;
s.setAttribute('type', 'text/css');
s.setAttribute('id', cssId);
doc.getElementsByTagName('head')[0].appendChild(s);
s.appendChild(doc.createTextNode(styleProp.replace(/:root/g, '.' + cssClass)));
}
}
return props;
}
designer.addPropsReducer(stylePropsReducer, TransformStage.Render);
skeleton.add({
area: 'mainArea',
name: 'designer',
@ -88,16 +120,6 @@ skeleton.add({
},
});
skeleton.add({
area: 'topArea',
type: 'Dock',
name: 'preview',
props: {
align: 'right',
},
content: Preview,
});
// skeleton.add({
// name: 'sourceEditor',
// type: 'PanelDock',
@ -112,3 +134,11 @@ skeleton.add({
// },
// content: SourceEditor,
// });
// 实例节点选择器,线框高亮
addBuiltinComponentAction({
name: 'instance-node-selector',
content: InstanceNodeSelector,
important: true,
condition: 'always'
});

View File

@ -1,13 +1,13 @@
import { EventEmitter } from 'events';
import { ALI_SCHEMA_VERSION } from './base/const';
import { obx } from '@ali/lowcode-editor-core';
interface ILiteralObject {
[key: string]: any;
}
export class Env {
public envs: ILiteralObject;
@obx.val envs: ILiteralObject = {};
private emitter: EventEmitter;
private featureMap: ILiteralObject;
@ -15,23 +15,22 @@ export class Env {
constructor() {
this.emitter = new EventEmitter();
this.emitter.setMaxListeners(0);
this.envs = {};
this.featureMap = {};
}
public get(name: string): any {
get(name: string): any {
return this.getEnv(name);
}
public getEnv(name: string): any {
getEnv(name: string): any {
return this.envs[name];
}
public set(name: string, value: any) {
set(name: string, value: any) {
return this.setEnv(name, value);
}
public setEnv(name: string, value: any) {
setEnv(name: string, value: any) {
const orig = this.envs[name];
if (JSON.stringify(orig) === JSON.stringify(value)) {
return;
@ -40,47 +39,47 @@ export class Env {
this.emitter.emit('envchange', this.envs, name, value);
}
public setEnvMap(envs: ILiteralObject): void {
setEnvMap(envs: ILiteralObject): void {
this.envs = Object.assign(this.envs, envs);
this.emitter.emit('envchange', this.envs);
}
public getLocale(): string {
getLocale(): string {
return this.getEnv('locale') || 'zh_CN';
}
public setLocale(locale: string) {
setLocale(locale: string) {
this.setEnv('locale', locale);
}
public setExpertMode(flag: string) {
setExpertMode(flag: string) {
this.setEnv('expertMode', !!flag);
}
public isExpertMode() {
isExpertMode() {
return !!this.getEnv('expertMode');
}
public getSupportFeatures() {
getSupportFeatures() {
return Object.assign({}, this.featureMap);
}
public setSupportFeatures(features: ILiteralObject) {
setSupportFeatures(features: ILiteralObject) {
this.featureMap = Object.assign({}, this.featureMap, features);
}
public supports(name = 'supports') {
supports(name = 'supports') {
return !!this.featureMap[name];
}
public onEnvChange(func: (envs: ILiteralObject, name: string, value: any) => any) {
onEnvChange(func: (envs: ILiteralObject, name: string, value: any) => any) {
this.emitter.on('envchange', func);
return () => {
this.emitter.removeListener('envchange', func);
};
}
public getAliSchemaVersion() {
getAliSchemaVersion() {
return ALI_SCHEMA_VERSION;
}
}

View File

@ -12,4 +12,13 @@ export default {
const nodes = designer.currentSelection?.getNodes();
return nodes?.[0];
},
/**
* TODO dirty fix
*/
onIntoView(func: (node: any, insertion: any) => any) {
// this.emitter.on('intoview', func);
return () => {
// this.emitter.removeListener('intoview', func);
};
}
}

View File

@ -1,5 +1,5 @@
const I18nUtil = require('@ali/ve-i18n-util');
import Env from './env';
const I18nUtil = require('@ali/ve-i18n-util');
interface I18nObject {
type?: string;
@ -9,7 +9,9 @@ interface I18nObject {
}
export function i18nReducer(obj?: any): any {
if (!obj) { return obj; }
if (!obj) {
return obj;
}
if (Array.isArray(obj)) {
return obj.map((item) => i18nReducer(item));
}
@ -18,6 +20,7 @@ export function i18nReducer(obj?: any): any {
// FIXME! use editor.get
let locale = Env.getLocale();
if (obj.key) {
// FIXME: 此处需要升级I18nUtil改成响应式
return I18nUtil.get(obj.key, locale);
}
if (locale !== 'zh_CN' && locale !== 'zh_TW' && !obj[locale]) {

View File

@ -1,11 +1,12 @@
import * as utils from '@ali/ve-utils';
import Popup from '@ali/ve-popups';
import Icons from '@ali/ve-icons';
import logger from '@ali/vu-logger';
import { render } from 'react-dom';
import I18nUtil from '@ali/ve-i18n-util';
import { hotkey as Hotkey } from '@ali/lowcode-editor-core';
import { createElement } from 'react';
import { VE_EVENTS as EVENTS, VE_HOOKS as HOOKS } from './base/const';
import { VE_EVENTS as EVENTS, VE_HOOKS as HOOKS, VERSION as Version } from './base/const';
import Bus from './bus';
import { skeleton } from './editor';
import { Workbench } from '@ali/lowcode-editor-skeleton';
@ -21,7 +22,10 @@ import * as Field from './fields';
import Prop from './prop';
import Env from './env';
import DragEngine from './drag-engine';
import Viewport from './viewport';
import Project from './project';
import { designer, editor } from './editor';
import Symbols from './symbols';
import './vision.less';
@ -41,6 +45,7 @@ function init(container?: Element) {
createElement(Workbench, {
skeleton,
className: 'engine-main',
topAreaItemClassName: 'engine-actionitem',
}),
container,
);
@ -101,6 +106,11 @@ const VisualEngine = {
Bundle,
Pages,
DragEngine,
Viewport,
Version,
Project,
logger,
Symbols,
};
(window as any).VisualEngine = VisualEngine;
@ -144,6 +154,11 @@ export {
Bundle,
Pages,
DragEngine,
Viewport,
Version,
Project,
logger,
Symbols,
};

View File

@ -6,17 +6,28 @@ const { project } = designer;
export interface OldPageData {
id: string;
layout: RootSchema;
componentsTree: RootSchema[];
[dataAddon: string]: any;
}
const pages = Object.assign(project, {
setPages(pages: OldPageData[]) {
if (!pages || !Array.isArray(pages) || pages.length === 0) {
throw new Error('pages schema 不合法');
}
if (pages[0].componentsTree[0]) {
pages[0].componentsTree[0].componentName = 'Page';
// FIXME
pages[0].componentsTree[0].lifeCycles = {};
pages[0].componentsTree[0].methods = {};
}
project.load({
version: '1.0.0',
componentsMap: [],
componentsTree: pages.map(page => page.layout),
});
componentsTree: pages[0].componentsTree,
}, true);
},
addPage(data: OldPageData) {
return project.open(data.layout);

View File

@ -69,17 +69,7 @@ function upgradeConfig(config: OldPaneConfig): IWidgetBaseConfig & { area: strin
newConfig.type = 'PanelDock';
newConfig.area = 'left';
newConfig.props.description = description || title;
const {
contents,
hideTitleBar,
tip,
width,
maxWidth,
height,
maxHeight,
menu,
isAction
} = config;
const { contents, hideTitleBar, tip, width, maxWidth, height, maxHeight, menu, isAction } = config;
if (menu) {
newConfig.props.title = menu;
}
@ -95,15 +85,16 @@ function upgradeConfig(config: OldPaneConfig): IWidgetBaseConfig & { area: strin
};
if (contents && Array.isArray(contents)) {
newConfig.content = contents.map(({ title, content, tip }) => {
newConfig.content = contents.map(({ title, content, tip }, index) => {
return {
type: "Panel",
type: 'Panel',
name: typeof title === 'string' ? title : `${name}:${index}`,
content,
props: {
title,
help: tip,
}
}
},
};
});
}
}
@ -162,7 +153,11 @@ const dockPane = Object.assign(skeleton.leftArea, {
return;
}
const name = item.name || item;
skeleton.getPanel(name)?.active();
const pane = skeleton.getPanel(name);
if (!pane) {
console.warn(`Could not find pane with name ${name}`);
}
pane?.active();
},
/**

View File

@ -0,0 +1,17 @@
class Project {
private schema: any;
constructor() {
this.schema = {};
}
getSchema() {
return this.schema;
}
setSchema(schema: any) {
this.schema = schema;
}
}
export default new Project();

View File

@ -2,11 +2,12 @@ import { Component } from 'react';
import { EventEmitter } from 'events';
import { fromJS, Iterable, Map as IMMap } from 'immutable';
import logger from '@ali/vu-logger';
import { uniqueId, cloneDeep, isDataEqual, combineInitial, Transducer } from '@ali/ve-utils';
import { cloneDeep, isDataEqual, combineInitial, Transducer } from '@ali/ve-utils';
import I18nUtil from '@ali/ve-i18n-util';
import { getSetter } from '@ali/lowcode-editor-core';
import { editor } from './editor';
import { OldPropConfig, DISPLAY_TYPE } from './bundle/upgrade-metadata';
import { uniqueId } from '@ali/lowcode-utils';
type IPropConfig = OldPropConfig;
@ -108,7 +109,7 @@ export default class Prop implements IVariableSettable {
this.parent = parent;
}
this.id = uniqueId(null as any, 'prop', 'engine-prop');
this.id = uniqueId('prop');
if (typeof config.setter === 'string') {
config.setter = getSetter(config.setter)?.component as any;

View File

@ -0,0 +1,17 @@
export class SymbolManager {
private symbolMap: { [symbolName: string]: symbol } = {};
public create(name: string): symbol {
if (this.symbolMap[name]) {
return this.symbolMap[name];
}
this.symbolMap[name] = Symbol(name);
return this.symbolMap[name];
}
public get(name: string) {
return this.symbolMap[name];
}
}
export default new SymbolManager();

View File

@ -0,0 +1,278 @@
import { EventEmitter } from 'events';
const domReady = require('domready');
import Flags from './flags';
function enterFullscreen() {
const elem = document.documentElement;
if (elem.requestFullscreen) {
elem.requestFullscreen();
}
}
function exitFullscreen() {
if (document.exitFullscreen) {
document.exitFullscreen();
}
}
function isFullscreen() {
return document.fullscreen || false;
}
interface IStyleResourceConfig {
media?: string;
type?: string;
content?: string;
}
class StyleResource {
config: IStyleResourceConfig;
styleElement: HTMLStyleElement;
mounted: boolean;
inited: boolean;
constructor(config: IStyleResourceConfig) {
this.config = config || {};
}
matchDevice(device: string) {
const media = this.config.media;
if (!media || media === 'ALL' || media === '*') {
return true;
}
return media.toUpperCase() === device.toUpperCase();
}
init() {
if (this.inited) {
return;
}
this.inited = true;
const { type, content } = this.config;
let styleElement;
if (type === 'URL') {
styleElement = document.createElement('link');
styleElement.href = content || '';
styleElement.rel = 'stylesheet';
} else {
styleElement = document.createElement('style');
styleElement.setAttribute('type', 'text/css');
if (styleElement.styleSheet) {
styleElement.styleSheet.cssText = content;
} else {
styleElement.appendChild(document.createTextNode(content || ''));
}
}
this.styleElement = styleElement;
}
apply() {
if (this.mounted) {
return;
}
this.init();
document.head.appendChild(this.styleElement);
this.mounted = true;
}
unmount() {
if (!this.mounted) {
return;
}
document.head.removeChild(this.styleElement);
this.mounted = false;
}
}
export class Viewport {
preview: boolean;
focused: boolean;
slateFixed: boolean;
emitter: EventEmitter;
device: string;
focusTarget: any;
cssResourceSet: StyleResource[];
constructor() {
this.preview = false;
this.emitter = new EventEmitter();
document.addEventListener('webkitfullscreenchange', () => {
this.emitter.emit('fullscreenchange', this.isFullscreen());
});
domReady(() => this.applyMediaCSS());
}
setFullscreen(flag: boolean) {
const fullscreen = this.isFullscreen();
if (fullscreen && !flag) {
exitFullscreen();
} else if (!fullscreen && flag) {
enterFullscreen();
}
}
toggleFullscreen() {
if (this.isFullscreen()) {
exitFullscreen();
} else {
enterFullscreen();
}
}
isFullscreen() {
return isFullscreen();
}
setFocus(flag: boolean) {
if (this.focused && !flag) {
this.focused = false;
Flags.remove('view-focused');
this.emitter.emit('focuschange', false);
} else if (!this.focused && flag) {
this.focused = true;
Flags.add('view-focused');
this.emitter.emit('focuschange', true);
}
}
setFocusTarget(focusTarget: any) {
this.focusTarget = focusTarget;
}
returnFocus() {
if (this.focusTarget) {
this.focusTarget.focus();
}
}
isFocus() {
return this.focused;
}
setPreview(flag: boolean) {
if (this.preview && !flag) {
this.preview = false;
Flags.setPreviewMode(false);
this.emitter.emit('preview', false);
this.changeViewport();
} else if (!this.preview && flag) {
this.preview = true;
Flags.setPreviewMode(true);
this.emitter.emit('preview', true);
this.changeViewport();
}
}
togglePreview() {
if (this.isPreview()) {
this.setPreview(false);
} else {
this.setPreview(true);
}
}
isPreview() {
return this.preview;
}
setDevice(device = 'pc') {
if (this.getDevice() !== device) {
this.device = device;
Flags.setSimulator(device);
this.applyMediaCSS();
this.emitter.emit('devicechange', device);
this.changeViewport();
}
}
getDevice() {
return this.device || 'pc';
}
changeViewport() {
this.emitter.emit('viewportchange', this.getViewport());
}
getViewport() {
return `${this.isPreview() ? 'preview' : 'design'}-${this.getDevice()}`;
}
applyMediaCSS() {
if (!document.head || !this.cssResourceSet) {
return;
}
const device = this.getDevice();
this.cssResourceSet.forEach((item) => {
if (item.matchDevice(device)) {
item.apply();
} else {
item.unmount();
}
});
}
setGlobalCSS(resourceSet: IStyleResourceConfig[]) {
if (this.cssResourceSet) {
this.cssResourceSet.forEach((item) => {
item.unmount();
});
}
this.cssResourceSet = resourceSet.map((item: IStyleResourceConfig) => new StyleResource(item)).reverse();
this.applyMediaCSS();
}
setWithShell(shell: string) {
Flags.setWithShell(shell);
}
onFullscreenChange(func: () => any) {
this.emitter.on('fullscreenchange', func);
return () => {
this.emitter.removeListener('fullscreenchange', func);
};
}
onPreview(func: () => any) {
this.emitter.on('preview', func);
return () => {
this.emitter.removeListener('preview', func);
};
}
onDeviceChange(func: () => any) {
this.emitter.on('devicechange', func);
return () => {
this.emitter.removeListener('devicechange', func);
};
}
onSlateFixedChange(func: (flag: boolean) => any) {
this.emitter.on('slatefixed', func);
return () => {
this.emitter.removeListener('slatefixed', func);
};
}
onViewportChange(func: () => any) {
this.emitter.on('viewportchange', func);
return () => {
this.emitter.removeListener('viewportchange', func);
};
}
onFocusChange(func: (flag: boolean) => any) {
this.emitter.on('focuschange', func);
return () => {
this.emitter.removeListener('focuschange', func);
};
}
}
export default new Viewport();

View File

@ -92,7 +92,8 @@ html.engine-blur #engine {
.next-checkbox-group,.next-date-picker,.next-input,.next-month-picker,
.next-number-picker,.next-radio-group,.next-range,.next-range-picker,
.next-rating,.next-select,.next-switch,.next-time-picker,.next-upload,
.next-year-picker {
.next-year-picker,
.next-breadcrumb-item,.next-calendar-header,.next-calendar-table {
pointer-events: auto !important;
}
}
@ -100,3 +101,9 @@ html.engine-blur #engine {
.lc-left-float-pane {
font-size: 14px;
}
html.engine-preview-mode {
.lc-left-area, .lc-right-area {
display: none !important;
}
}