complete outline-ui

This commit is contained in:
kangwei 2020-03-24 00:27:48 +08:00
parent dc8fdae09d
commit 9a2e5e97de
62 changed files with 1112 additions and 730 deletions

View File

@ -1,5 +1,5 @@
import { Component } from 'react';
import { observer, obx } from '../../../../globals';
import { observer, obx, Title } from '../../../../globals';
import Designer from '../../designer/designer';
import { isDragNodeObject, DragObject, isDragNodeDataObject } from '../../designer/helper/dragon';
import './ghost.less';
@ -53,7 +53,7 @@ export default class Ghost extends Component<{ designer: Designer }> {
return dragObject.nodes.map(node => {
const ghost = (
<div className="lc-ghost" key={node.id}>
<div className="lc-ghost-title">{node.title}</div>
<Title title={node.title} />
</div>
);
return ghost;

View File

@ -108,7 +108,8 @@ export class ComponentMeta {
return this._title || this.componentName;
}
get icon() {
@computed get icon() {
// give Slot default icon
return (
this._transformedMetadata?.icon ||
(this.componentName === 'Page' ? IconPage : this.isContainer ? IconContainer : IconComponent)

View File

@ -0,0 +1,73 @@
import { obx, computed, TitleContent } from '../../../../../globals';
import { uniqueId } from '../../../../../utils/unique-id';
import Node from './node';
import { intl } from '../../../locale';
export default class ExclusiveGroup {
readonly isExclusiveGroup = true;
readonly id = uniqueId('cond-grp');
@obx.val readonly children: Node[] = [];
@obx private visibleIndex = 0;
@computed get document() {
return this.visibleNode.document;
}
@computed get zLevel() {
return this.visibleNode.zLevel;
}
@computed get length() {
return this.children.length;
}
@computed get visibleNode(): Node {
return this.children[this.visibleIndex];
}
@computed get firstNode(): Node {
return this.children[0]!;
}
add(node: Node) {
if (node.nextSibling && node.nextSibling.conditionGroup === this) {
const i = this.children.indexOf(node.nextSibling);
this.children.splice(i, 0, node);
} else {
this.children.push(node);
}
}
remove(node: Node) {
const i = this.children.indexOf(node);
if (i > -1) {
this.children.splice(i, 1);
if (this.visibleIndex > i) {
this.visibleIndex -= 1;
} else if (this.visibleIndex >= this.children.length) {
this.visibleIndex = this.children.length - 1;
}
}
}
setVisible(node: Node) {
const i = this.children.indexOf(node);
if (i > -1) {
this.visibleIndex = i;
}
}
isVisible(node: Node) {
const i = this.children.indexOf(node);
return i === this.visibleIndex;
}
readonly title: TitleContent;
constructor(readonly name: string, title?: TitleContent) {
this.title = title || {
type: 'i18n',
intl: intl('Condition Group'),
};
}
}
export function isExclusiveGroup(obj: any): obj is ExclusiveGroup {
return obj && obj.isExclusiveGroup;
}

View File

@ -5,12 +5,14 @@ export default class NodeChildren {
@obx.val private children: Node[];
constructor(readonly owner: NodeParent, data: NodeData | NodeData[]) {
this.children = (Array.isArray(data) ? data : [data]).map(child => {
const node = this.owner.document.createNode(child);
node.internalSetParent(this.owner);
return node;
return this.owner.document.createNode(child);
});
}
interalInitParent() {
this.children.forEach(child => child.internalSetParent(this.owner));
}
/**
* schema
* @param serialize id
@ -109,11 +111,24 @@ export default class NodeChildren {
}
// check condition group
node.conditionGroup = null;
if (node.conditionGroup) {
if (
!(
// just sort at condition group
(
(node.prevSibling && node.prevSibling.conditionGroup === node.conditionGroup) ||
(node.nextSibling && node.nextSibling.conditionGroup === node.conditionGroup)
)
)
) {
node.setConditionGroup(null);
}
}
if (node.prevSibling && node.nextSibling) {
const conditionGroup = node.prevSibling.conditionGroup;
// insert at condition group
if (conditionGroup && conditionGroup === node.nextSibling.conditionGroup) {
node.conditionGroup = conditionGroup;
node.setConditionGroup(conditionGroup);
}
}
}

View File

@ -14,6 +14,7 @@ import {
obx,
computed,
} from '../../../../../globals';
import ExclusiveGroup, { isExclusiveGroup } from './exclusive-group';
/**
*
@ -29,10 +30,10 @@ import {
* condition
* ------- future support -----
* conditionGroup
* x-title
* x-ignore
* x-locked
* x-hidden
* title
* ignored
* locked
* hidden
*/
export default class Node {
/**
@ -80,7 +81,7 @@ export default class Node {
if (this._parent) {
return this._parent.zLevel + 1;
}
return -1;
return 0;
}
@computed get title(): TitleContent {
@ -110,6 +111,7 @@ export default class Node {
if (isNodeParent(this)) {
_props = new Props(this, props, extras);
this._children = new NodeChildren(this as NodeParent, children || []);
this._children.interalInitParent();
} else {
_props = new Props(this, {
children: isDOMText(children) || isJSExpression(children) ? children : '',
@ -138,6 +140,13 @@ export default class Node {
}
this._parent = parent;
if (parent && !this.conditionGroup) {
// initial conditionGroup
const grp = this.getExtraProp('conditionGroup', false)?.getAsString();
if (grp) {
this.setConditionGroup(grp);
}
}
}
private _slotFor?: Prop | null = null;
@ -214,37 +223,53 @@ export default class Node {
return slots;
}
private _conditionGroup: string | null = null;
/**
*
*/
get conditionGroup(): string | null {
if (this._conditionGroup) {
@obx.ref private _conditionGroup: ExclusiveGroup | null = null;
get conditionGroup(): ExclusiveGroup | null {
return this._conditionGroup;
}
// 如果 condition 有值,且没有 group
if (this._condition) {
return this.id;
setConditionGroup(grp: ExclusiveGroup | string | null) {
if (!grp) {
this.getExtraProp('conditionGroup', false)?.remove();
if (this._conditionGroup) {
this._conditionGroup.remove(this);
this._conditionGroup = null;
}
return null;
return;
}
if (!isExclusiveGroup(grp)) {
if (this.prevSibling?.conditionGroup?.name === grp) {
grp = this.prevSibling.conditionGroup;
} else {
grp = new ExclusiveGroup(grp);
}
}
if (this._conditionGroup !== grp) {
this.getExtraProp('conditionGroup', true)?.setValue(grp.name);
if (this._conditionGroup) {
this._conditionGroup.remove(this);
}
this._conditionGroup = grp;
grp.add(this);
}
set conditionGroup(val) {
this._conditionGroup = val;
}
private _condition: any;
/**
*
*/
get condition() {
if (this._condition == null) {
if (this._conditionGroup) {
// FIXME: should be expression
return true;
@computed isConditionalVisible(): boolean | undefined {
return this._conditionGroup?.isVisible(this);
}
return null;
setConditionalVisible() {
this._conditionGroup?.setVisible(this);
}
return this._condition;
@computed hasCondition() {
const v = this.getExtraProp('condition', false)?.getValue();
return v != null && v !== '';
}
@computed hasLoop() {
const v = this.getExtraProp('loop', false)?.getValue();
return v != null && v !== '';
}
wrapWith(schema: NodeSchema) {

View File

@ -1,4 +1,5 @@
{
"copy": "Copy",
"remove": "Remove"
"remove": "Remove",
"Condition Group": "Condition Group"
}

View File

@ -1,4 +1,5 @@
{
"copy": "复制",
"remove": "删除"
"remove": "删除",
"Condition Group": "条件组"
}

View File

@ -14,6 +14,7 @@ import undoRedo from '../plugins/undoRedo';
import Designer from '../plugins/designer';
import logo from '../plugins/logo';
import save from '../plugins/save';
import OutlineTree from '../../../plugin-outline-tree';
import PluginFactory from '../framework/pluginFactory';
@ -22,6 +23,7 @@ export default {
save: PluginFactory(save),
designer: PluginFactory(Designer),
settings: PluginFactory(Settings),
outlineTree: PluginFactory(OutlineTree),
undoRedo: PluginFactory(undoRedo),
topBalloonIcon: PluginFactory(topBalloonIcon),
topDialogIcon: PluginFactory(topDialogIcon),

View File

@ -238,6 +238,7 @@ export default {
}
],
rightArea: [
/*
{
pluginKey: 'settings',
type: 'Panel',
@ -246,52 +247,16 @@ export default {
version: '^1.0.0'
},
pluginProps: {}
},*/
{
pluginKey: 'outlineTree',
type: 'Panel',
props: {},
config: {
version: '^1.0.0'
},
pluginProps: {}
}
// {
// pluginKey: 'rightPanel1',
// type: 'TabPanel',
// props: {
// title: '样式'
// },
// config: {
// version: '^1.0.0'
// },
// pluginProps: {}
// },
// {
// pluginKey: 'rightPanel2',
// type: 'TabPanel',
// props: {
// title: '属性',
// icon: 'dengpao'
// },
// config: {
// version: '^1.0.0'
// },
// pluginProps: {}
// },
// {
// pluginKey: 'rightPanel3',
// type: 'TabPanel',
// props: {
// title: '事件'
// },
// config: {
// version: '^1.0.0'
// },
// pluginProps: {}
// },
// {
// pluginKey: 'rightPanel4',
// type: 'TabPanel',
// props: {
// title: '数据'
// },
// config: {
// version: '^1.0.0'
// },
// pluginProps: {}
// }
],
centerArea: [
{

View File

@ -81,7 +81,13 @@ const SCHEMA = {
{
componentName: 'Form.Item',
props: {
label: '职业:',
label: {
type: 'JSSlot',
value: {
componentName: 'Div',
children: '职业:',
}
},
name: 'profession'
},
children: [
@ -127,7 +133,10 @@ const SCHEMA = {
},
htmlType: 'submit'
},
children: '提交'
children: '提交',
condition: true,
loop: [1,2,3],
conditionGroup: '1'
},
{
componentName: 'Button',
@ -138,7 +147,9 @@ const SCHEMA = {
},
htmlType: 'reset'
},
children: '重置'
children: '重置',
condition: false,
conditionGroup: '1'
}
]
}

View File

@ -1,6 +1,6 @@
import { IconBase, IconBaseProps } from "./icon-base";
import { IconBase, IconProps } from "./icon-base";
export function IconClone(props: IconBaseProps) {
export function IconClone(props: IconProps) {
return (
<IconBase viewBox="0 0 1024 1024" {...props}>
<path d="M192 256.16C192 220.736 220.704 192 256.16 192h639.68C931.264 192 960 220.704 960 256.16v639.68A64.16 64.16 0 0 1 895.84 960H256.16A64.16 64.16 0 0 1 192 895.84V256.16z m64 31.584v576.512a32 32 0 0 0 31.744 31.744h576.512a32 32 0 0 0 31.744-31.744V287.744A32 32 0 0 0 864.256 256H287.744A32 32 0 0 0 256 287.744zM288 192v64h64V192H288z m128 0v64h64V192h-64z m128 0v64h64V192h-64z m128 0v64h64V192h-64z m128 0v64h64V192h-64z m96 96v64h64V288h-64z m0 128v64h64v-64h-64z m0 128v64h64v-64h-64z m0 128v64h64v-64h-64z m0 128v64h64v-64h-64z m-96 96v64h64v-64h-64z m-128 0v64h64v-64h-64z m-128 0v64h64v-64h-64z m-128 0v64h64v-64h-64z m-128 0v64h64v-64H288z m-96-96v64h64v-64H192z m0-128v64h64v-64H192z m0-128v64h64v-64H192z m0-128v64h64v-64H192z m0-128v64h64V288H192z m160 416c0-17.664 14.592-32 32.064-32h319.872a31.968 31.968 0 1 1 0 64h-319.872A31.968 31.968 0 0 1 352 704z m0-128c0-17.664 14.4-32 32.224-32h383.552c17.792 0 32.224 14.208 32.224 32 0 17.664-14.4 32-32.224 32H384.224A32.032 32.032 0 0 1 352 576z m0-128c0-17.664 14.4-32 32.224-32h383.552c17.792 0 32.224 14.208 32.224 32 0 17.664-14.4 32-32.224 32H384.224A32.032 32.032 0 0 1 352 448z m512 47.936V192h-64V159.968A31.776 31.776 0 0 0 768.032 128H160A31.776 31.776 0 0 0 128 159.968V768c0 17.92 14.304 31.968 31.968 31.968H192v64h303.936H128.128A63.968 63.968 0 0 1 64 799.872V128.128C64 92.704 92.48 64 128.128 64h671.744C835.296 64 864 92.48 864 128.128v367.808z"/>

View File

@ -1,6 +1,6 @@
import { IconBase, IconBaseProps } from "./icon-base";
import { IconBase, IconProps } from "./icon-base";
export function IconComponent(props: IconBaseProps) {
export function IconComponent(props: IconProps) {
return (
<IconBase viewBox="0 0 1024 1024" {...props}>
<path d="M783.5648 437.4528h-18.0224V336.6912c0-43.8272-35.6352-79.4624-79.4624-79.4624h-110.592V241.664c0-90.9312-73.728-164.6592-164.6592-164.6592-90.9312 0-164.6592 73.728-164.6592 164.6592v15.5648H155.2384c-43.8272 0-79.4624 35.6352-79.4624 79.4624v131.4816c0 16.7936 13.9264 30.72 30.72 30.72h56.1152c56.9344 0 103.2192 46.2848 103.2192 103.2192s-46.2848 103.2192-103.2192 103.2192H106.496c-16.7936 0-30.72 13.9264-30.72 30.72v131.4816c0 43.8272 35.6352 79.4624 79.4624 79.4624h531.2512c43.8272 0 79.4624-35.6352 79.4624-79.4624v-100.7616h18.0224c90.9312 0 164.6592-73.728 164.6592-164.6592-0.4096-90.9312-74.1376-164.6592-165.0688-164.6592z m0 267.8784h-48.7424c-16.7936 0-30.72 13.9264-30.72 30.72v131.4816c0 9.8304-8.192 18.0224-18.0224 18.0224H155.2384c-9.8304 0-18.0224-8.192-18.0224-18.0224v-100.7616h25.3952c90.9312 0 164.6592-73.728 164.6592-164.6592 0-90.9312-73.728-164.6592-164.6592-164.6592h-25.3952V336.6912c0-9.8304 8.192-18.0224 18.0224-18.0224h121.6512c16.7936 0 30.72-13.9264 30.72-30.72V241.664c0-56.9344 46.2848-103.2192 103.2192-103.2192s103.2192 46.2848 103.2192 103.2192v46.2848c0 16.7936 13.9264 30.72 30.72 30.72h141.312c9.8304 0 18.0224 8.192 18.0224 18.0224v131.4816c0 16.7936 13.9264 30.72 30.72 30.72h48.7424c56.9344 0 103.2192 46.2848 103.2192 103.2192s-46.2848 103.2192-103.2192 103.2192z" />

View File

@ -1,6 +1,6 @@
import { IconBase, IconBaseProps } from "./icon-base";
import { IconBase, IconProps } from "./icon-base";
export function IconContainer(props: IconBaseProps) {
export function IconContainer(props: IconProps) {
return (
<IconBase viewBox="0 0 1024 1024" {...props}>
<path d="M800 800h64v64h-64v-64z m-128 0h64v64h-64v-64z m-128 0h64v64h-64v-64z m-128 0h64v64h-64v-64z m-256 0h64v64h-64v-64z m0-640h64v64h-64v-64z m128 640h64v64h-64v-64zM160 672h64v64h-64v-64z m0-128h64v64h-64v-64z m0-128h64v64h-64v-64z m0-128h64v64h-64v-64z m640 384h64v64h-64v-64z m0-128h64v64h-64v-64z m0-128h64v64h-64v-64z m0-128h64v64h-64v-64z m0-128h64v64h-64v-64z m-128 0h64v64h-64v-64z m-128 0h64v64h-64v-64z m-128 0h64v64h-64v-64z m-128 0h64v64h-64v-64z" />

View File

@ -1,6 +1,6 @@
import { IconBase, IconBaseProps } from "./icon-base";
import { IconBase, IconProps } from "./icon-base";
export function IconHidden(props: IconBaseProps) {
export function IconHidden(props: IconProps) {
return (
<IconBase viewBox="0 0 1024 1024" {...props}>
<path d="M183.423543 657.078213l163.499771-98.484012c-4.233418-14.908548-6.646374-30.585599-6.646374-46.852074 0-94.665033 76.739778-171.404812 171.404812-171.404812 45.983287 0 87.641059 18.20871 118.42518 47.679929l129.791042-78.17957c-73.254398-41.73145-157.866471-65.812915-248.216221-65.812915-192.742792 0-360.068705 108.505249-444.453604 267.715321C96.636944 567.228859 136.301316 616.355743 183.423543 657.078213zM841.253886 367.552144l-164.382884 99.015108c3.934612 14.415314 6.215562 29.513174 6.215562 45.174875 0 94.665033-76.739778 171.404812-171.404812 171.404812-45.361117 0-86.484723-17.747199-117.142977-46.515407l-129.419582 77.955466c72.874751 41.149189 156.893306 64.871473 246.563582 64.871473 192.742792 0 360.068705-108.505249 444.453604-267.717368C927.000805 456.773188 887.794875 408.054603 841.253886 367.552144zM420.280042 511.741104c0 0.550539 0.152473 1.060145 0.161682 1.608637l135.080511-81.366146c-13.065574-7.198959-27.854395-11.658528-43.826158-11.658528C461.20922 420.325068 420.280042 461.254246 420.280042 511.741104zM447.739441 576.947198l69.02098-41.574884L948.364369 275.395234c10.812253-6.512321 14.297634-20.558222 7.785314-31.369452-6.512321-10.812253-20.556175-14.296611-31.368428-7.785314L575.654762 446.537056l0 0-151.20577 91.078345 0 0L75.027787 748.090043c-10.812253 6.512321-14.297634 20.556175-7.785314 31.368428 6.512321 10.812253 20.556175 14.297634 31.369452 7.785314L447.739441 576.947198 447.739441 576.947198zM511.696078 603.157139c50.487881 0 91.416036-40.928155 91.416036-91.416036 0-0.549515-0.152473-1.057075-0.161682-1.605567l-135.079488 81.364099C480.935494 598.699618 495.724315 603.157139 511.696078 603.157139z" />

View File

@ -8,16 +8,24 @@ const SizePresets: any = {
xlarge: 30,
};
export interface IconBaseProps {
export interface IconProps {
className?: string;
fill?: string;
size?: 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | number;
viewBox: string;
children?: ReactNode;
style?: object;
};
}
export function IconBase({ fill, size = 'medium', viewBox, style, children, ...props }: IconBaseProps) {
export function IconBase({
fill,
size = 'medium',
viewBox,
style,
children,
...props
}: IconProps & {
viewBox: string;
}) {
if (SizePresets.hasOwnProperty(size)) {
size = SizePresets[size];
}
@ -31,10 +39,11 @@ export function IconBase({ fill, size = 'medium', viewBox, style, children, ...p
viewBox={viewBox}
{...props}
style={{
verticalAlign: 'middle',
color: fill,
...style,
}}
>{children}</svg>
>
{children}
</svg>
);
}

View File

@ -1,6 +1,6 @@
import { IconBase, IconBaseProps } from "./icon-base";
import { IconBase, IconProps } from "./icon-base";
export function IconPage(props: IconBaseProps) {
export function IconPage(props: IconProps) {
return (
<IconBase viewBox="0 0 1024 1024" {...props}>
<path d="M381.6 864H162.4c-6.9 0-12.4 4.6-12.4 10.3v19.3c0 5.7 5.6 10.3 12.4 10.3h219.1c6.8 0 12.4-4.6 12.4-10.3v-19.3c0.1-5.7-5.5-10.3-12.3-10.3zM382 780.6H162c-6.9 0-12.5 4.6-12.5 10.3v19.3c0 5.7 5.6 10.3 12.5 10.3h220c6.9 0 12.5-4.6 12.5-10.3v-19.3c0-5.7-5.6-10.3-12.5-10.3zM162.4 737.2h219.1c6.8 0 12.4-4.6 12.4-10.3v-19.3c0-5.7-5.6-10.3-12.4-10.3H162.4c-6.9 0-12.4 4.6-12.4 10.3v19.3c0 5.7 5.6 10.3 12.4 10.3z" />

View File

@ -1,6 +1,6 @@
import { IconBase, IconBaseProps } from './icon-base';
import { IconBase, IconProps } from './icon-base';
export function IconRemove(props: IconBaseProps) {
export function IconRemove(props: IconProps) {
return (
<IconBase viewBox="0 0 1024 1024" {...props}>
<path d="M224 256v639.84A64 64 0 0 0 287.84 960h448.32A64 64 0 0 0 800 895.84V256h64a32 32 0 1 0 0-64H160a32 32 0 1 0 0 64h64zM384 96c0-17.664 14.496-32 31.904-32h192.192C625.696 64 640 78.208 640 96c0 17.664-14.496 32-31.904 32H415.904A31.872 31.872 0 0 1 384 96z m-96 191.744C288 270.208 302.4 256 320.224 256h383.552C721.6 256 736 270.56 736 287.744v576.512C736 881.792 721.6 896 703.776 896H320.224A32.224 32.224 0 0 1 288 864.256V287.744zM352 352c0-17.696 14.208-32.032 32-32.032 17.664 0 32 14.24 32 32v448c0 17.664-14.208 32-32 32-17.664 0-32-14.24-32-32V352z m128 0c0-17.696 14.208-32.032 32-32.032 17.664 0 32 14.24 32 32v448c0 17.664-14.208 32-32 32-17.664 0-32-14.24-32-32V352z m128 0c0-17.696 14.208-32.032 32-32.032 17.664 0 32 14.24 32 32v448c0 17.664-14.208 32-32 32-17.664 0-32-14.24-32-32V352z" />

View File

@ -1,6 +1,6 @@
import { IconBase, IconBaseProps } from './icon-base';
import { IconBase, IconProps } from './icon-base';
export function IconSetting(props: IconBaseProps) {
export function IconSetting(props: IconProps) {
return (
<IconBase viewBox="0 0 1024 1024" {...props}>
<path d="M965.824 405.952a180.48 180.48 0 0 1-117.12-85.376 174.464 174.464 0 0 1-16-142.08 22.208 22.208 0 0 0-7.04-23.552 480.576 480.576 0 0 0-153.6-89.216 23.104 23.104 0 0 0-24.32 5.76 182.208 182.208 0 0 1-135.68 57.92 182.208 182.208 0 0 1-133.12-56.64 23.104 23.104 0 0 0-26.88-7.04 478.656 478.656 0 0 0-153.6 89.856 22.208 22.208 0 0 0-7.04 23.552 174.464 174.464 0 0 1-16 141.44A180.48 180.48 0 0 1 58.24 405.952a22.4 22.4 0 0 0-17.28 17.792 455.08 455.08 0 0 0 0 176.512 22.4 22.4 0 0 0 17.28 17.792 180.48 180.48 0 0 1 117.12 84.736c25.408 42.944 31.232 94.592 16 142.08a22.208 22.208 0 0 0 7.04 23.552A480.576 480.576 0 0 0 352 957.632h7.68a23.04 23.04 0 0 0 16.64-7.04 184.128 184.128 0 0 1 266.944 0c6.592 8.96 18.752 11.968 28.8 7.04a479.36 479.36 0 0 0 156.16-88.576 22.208 22.208 0 0 0 7.04-23.552 174.464 174.464 0 0 1 13.44-142.72 180.48 180.48 0 0 1 117.12-84.736 22.4 22.4 0 0 0 17.28-17.792 452.613 452.613 0 0 0 0-176.512 23.04 23.04 0 0 0-17.28-17.792z m-42.88 169.408a218.752 218.752 0 0 0-128 98.112 211.904 211.904 0 0 0-21.76 156.736 415.936 415.936 0 0 1-112 63.68 217.472 217.472 0 0 0-149.12-63.68 221.312 221.312 0 0 0-149.12 63.68 414.592 414.592 0 0 1-112-63.68c12.8-53.12 4.288-109.12-23.68-156.096A218.752 218.752 0 0 0 101.12 575.36a386.176 386.176 0 0 1 0-127.36 218.752 218.752 0 0 0 128-98.112c27.2-47.552 34.944-103.68 21.76-156.8a415.296 415.296 0 0 1 112-63.68A221.44 221.44 0 0 0 512 187.392a218.24 218.24 0 0 0 149.12-57.984 413.952 413.952 0 0 1 112 63.744 211.904 211.904 0 0 0 23.04 156.096 218.752 218.752 0 0 0 128 98.112 386.65 386.65 0 0 1 0 127.36l-1.28 0.64z" />

View File

@ -62,6 +62,9 @@ class Intl extends PureComponent<{ data: any; params?: object }> {
export function intl(data: any, params?: object): ReactNode {
if (isI18nData(data)) {
if (data.intl) {
return data.intl;
}
return <Intl data={data} params={params} />;
}
return data;

View File

@ -1,6 +1,9 @@
import { ReactNode } from 'react';
export interface I18nData {
type: 'i18n';
[key: string]: string;
intl?: ReactNode;
[key: string]: any;
}
// type checks

View File

@ -1,13 +0,0 @@
import { INode, isElementNode, isRootNode } from '../../../../document/node';
export function isContainer(node: INode): boolean {
if (isRootNode(node)) {
return true;
}
if (isElementNode(node)) {
// TODO: check from prototype
// block Fragment
return true;
}
return false;
}

View File

@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1562677537258" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="907" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M965.46812628 219.26174551H58.53187372c-27.20256421 0-42.39181327 28.72148912-25.54555523 48.3294288l453.46812628 525.82418542c12.97990373 15.05116498 37.97312263 15.05116498 51.09111046 0L991.01368151 267.59117431c16.84625804-19.6079397 1.65700898-48.3294288-25.54555523-48.3294288z" fill="#ffffff" p-id="908"></path></svg>

Before

Width:  |  Height:  |  Size: 700 B

View File

@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1562677542938" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1019" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M793.41535973 486.45444477L267.59117431 32.98631849c-19.6079397-16.84625804-48.3294288-1.65700898-48.3294288 25.54555523v906.93625256c0 27.20256421 28.72148912 42.39181327 48.3294288 25.54555523l525.82418542-453.46812628c15.05116498-12.97990373 15.05116498-38.11120672 0-51.09111046z" fill="#ffffff" p-id="1020"></path></svg>

Before

Width:  |  Height:  |  Size: 702 B

View File

@ -1,4 +0,0 @@
{
"Designer not found": "Designer not found",
"No opened document": "No opened document",
}

View File

@ -1,4 +0,0 @@
{
"Designer not found": "未发现设计器模块",
"No opened document": "没有打开的文档"
}

View File

@ -1,251 +0,0 @@
/* 面板背景的颜色 */
@pane-bgcolor: #333131; // #1a1c23;
/* 标题背景色 */
//@title-bgcolor: var(--pane-bg-color; // backup rgba(0, 0, 0, 0.2);
/* 标题边框色 */
@title-bdcolor: transparent;
@title-selectedcolor: #111;
@section-bgcolor: transparent;
@section-bdcolor: rgba(0, 0, 0, 0.1);
/* 文字颜色 */
@text-color: #ffffff;
.hidden {
display: none;
}
.my-outline-pane {
top: 0;
left: 0;
height: 100%;
width: 100%;
position: absolute;
> .tree-scroll-container {
top: 0;
left: 0;
bottom: 0;
right: 0;
position: absolute;
overflow: auto;
}
}
.my-outline-tree {
overflow: hidden;
margin-bottom: 20px;
padding-left: 5px;
// 禁用 Text Select
user-select: none;
.insertion {
pointer-events: none !important;
border: 1px dashed var(--color-brand-light);
height: 25px;
transform: translateZ(0);
}
.condition-flow-container {
@bd-setting: 1px solid #7b605b;
border-top: @bd-setting;
border-bottom: @bd-setting;
position: relative;
&:before {
position: absolute;
display: block;
width: 0;
border-left: @bd-setting;
height: 100%;
top: 0;
left: 0;
content: ' ';
z-index: 2;
}
}
.tree-node {
color: rgb(217, 217, 217);
.c-control-flow-title {
text-align: center;
background-color: #7b605b;
height: 14px;
> b {
transform: scale(0.75);
transform-origin: top;
text-shadow: 0px 0px 2px black;
display: block;
}
}
.tree-node-collapsed-icon {
transition: transform 0.01s;
margin-left: 4px;
filter: opacity(0.5);
& > svg {
width: 8px;
height: 8px;
}
&:hover {
filter: opacity(1);
}
}
.tree-node-icon {
transform: translateZ(0);
display: flex;
align-items: center;
margin-right: 5px;
margin-left: 5px;
& > svg {
width: 16px;
height: 16px;
}
}
.tree-node-ignored-icon {
display: none;
position: absolute;
right: 8px;
top: 6px;
}
.tree-node-title {
font-size: var(--font-size-text);
padding: 0;
cursor: pointer;
background: var(--pane-bg-color);
display: flex;
align-items: center;
position: relative;
transform: translateZ(0);
.tree-node-title-inner {
flex: 1;
height: 26px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
display: flex;
align-items: center;
}
.tree-node-title-input {
flex: 1;
border: none;
background-color: var(--pane-bg-color);
color: var(--color-pane-label);
height: 24px;
line-height: 24px;
}
.tree-node-title-text {
flex: 1;
color: rgb(217, 217, 217);
// problem
//width: 100%;
//height: 100%;
display: flex;
align-items: center;
&.x-for-text {
color: #9370db;
}
&.x-if-text {
color: #ff6308;
}
.info {
margin-left: 10px;
}
.editable {
display: flex;
justify-content: space-between;
}
}
}
&.expanded {
> .tree-node-title > .tree-node-collapsed-icon {
transform-origin: center;
transform: rotate(90deg);
}
//> .branches {
// > .tree-node > .tree-node-title > .tree-node-icon {
// margin-left: 9px;
// }
//}
}
&.hover {
& > .tree-node-title {
background: @title-selectedcolor * 1.6;
.tree-node-ignored-icon {
display: block;
}
}
}
// 忽略节点处理
&.ignored {
.tree-node-title-text {
color: #9b9b9b;
}
.tree-node-collapsed-icon,
.tree-node-ignored-icon {
display: block;
filter: opacity(0.5);
}
}
// 选中节点处理
&.selected {
& > .tree-node-title {
background: @title-selectedcolor;
}
}
// 处理拖入节点
&.dropping {
& > .tree-node-title {
background: @title-selectedcolor * 0.75;
}
& > .branches:before {
border-left: 1px solid var(--color-brand-light);
}
}
.branches {
padding-left: 12px;
position: relative;
&:before {
position: absolute;
display: block;
width: 0;
border-left: 0.5px solid rgba(149, 216, 160, 0.25);
height: 100%;
top: 0;
left: 10px;
content: ' ';
z-index: 2;
}
&.x-flow {
&:before {
border-left: 1px solid #ff6308;
}
}
}
}
}

View File

@ -1,56 +0,0 @@
import { observer } from '../../../globals/src';
@observer
export default class TreeBranches extends Component<TreeNodeProps> {
shouldComponentUpdate() {
return false;
}
render() {
const treeNode = this.props.treeNode;
const { expanded } = treeNode;
if (!expanded) {
return null;
}
const branchClassName = classNames({
branches: !isRootNode(treeNode.node),
// 'x-branch': treeNode.hasXIf() && treeNode.branchIndex !== treeNode.branchNode!.children.length - 1,
});
let children: any = [];
if (treeNode.hasChildren() /* || node.hasSlots() */) {
children = treeNode.children.map((child: TreeNode) => {
if (child.hasXIf()) {
if (child.flowIndex === 0) {
const conditionFlowContainer = classNames('condition-group-container', {
hidden: child.hidden,
});
return (
<div key={child.id} className={conditionFlowContainer} data-id={child.id}>
<div className="c-control-flow-title"><b>Condition Flow</b></div>
{child.conditionGroup!.children.map(c => {
return <TreeNodeView key={c.id} treeNode={tree.getTreeNode(c)} />;
})}
</div>
);
} else {
return null;
}
}
return <TreeNodeView key={child.id} treeNode={child} />;
});
}
if (treeNode.dropIndex != null) {
children.splice(
treeNode.dropIndex,
0,
<div key="insertion" ref={ref => tree.mountInsertion(ref)} className="insertion" />,
);
}
return children.length > 0 && <div className={branchClassName}>{children}</div>;
}
}

View File

@ -1,23 +0,0 @@
import React from 'react';
import DIVIcon from 'my-icons/container.svg';
import IMGIcon from 'my-icons/image.svg';
import { observer } from '@ali/recore';
@observer
export default class TreeNodeIconView extends React.Component<{ tagName: string }> {
shouldComponentUpdate(): boolean {
return false;
}
render() {
const { tagName } = this.props;
switch (tagName) {
case 'img': {
console.log('>>> tag:', tagName);
return <IMGIcon />;
}
default:
return <DIVIcon />;
}
}
}

View File

@ -1,58 +0,0 @@
export interface TreeNodeProps {
treeNode: TreeNode;
}
@observer
export default class TreeNodeView extends Component<TreeNodeProps> {
shouldComponentUpdate() {
return false;
}
render() {
const { treeNode } = this.props;
const className = classNames('tree-node', {
// 是否展开
expanded: treeNode.expanded,
// 是否悬停
hover: treeNode.hover,
// 是否选中
selected: treeNode.selected,
// 是否隐藏
hidden: treeNode.hidden,
// 是否忽略的
ignored: treeNode.ignored,
// 是否锁定的
locked: treeNode.locked,
// 是否投放响应
dropping: treeNode.dropIndex != null,
// 是否?
highlight: treeNode.isDropContainer() && treeNode.dropIndex == null,
});
return (
<div className={className} data-id={treeNode.id}>
<TreeTitle treeNode={treeNode} />
<TreeBranches treeNode={treeNode} />
</div>
);
}
}
export function findTargetByEvent(e: MouseEvent): HTMLElement | null {
return (e.target as HTMLElement).closest('.tree-node') as HTMLElement;
}
export function getNodeIDFromTarget(target: HTMLElement): string | null {
return target.getAttribute('data-id');
}
export function getNodeIDByEvent(e: MouseEvent): string | null {
const target = findTargetByEvent(e);
if (target) {
return getNodeIDFromTarget(target);
}
return null;
}

View File

@ -1,176 +0,0 @@
import { observer } from '@ali/recore';
import React, { Component, KeyboardEvent } from 'react';
import classNames from 'classnames';
import ElementNode from '../../../../document/node/element-node';
import { isElementNode } from '../../../../document/node';
import { TreeNodeProps } from './tree-node';
import TreeNodeIconView from './tree-node-icon-view';
import CollapsedIcon from '../icons/caret-right.svg';
import EyeCloseIcon from 'my-icons/eye-close.svg';
interface IState {
editing: boolean;
}
@observer
export default class TreeNodeTitle extends Component<TreeNodeProps, IState> {
private inputRef = React.createRef<HTMLInputElement>();
constructor(props: TreeNodeProps) {
super(props);
this.state = {
editing: false,
};
}
toggleIgnored() {
const treeNode = this.props.treeNode;
const node = treeNode.node as ElementNode;
if (treeNode.ignored) {
node.getDirective('x-ignore').remove();
} else {
node.getDirective('x-ignore').value = true;
}
}
toggleExpanded() {
const treeNode = this.props.treeNode;
const { expanded } = treeNode;
treeNode.expanded = !expanded;
}
renderExpandIcon() {
const node = this.props.treeNode;
if (!node.expandable) {
return null;
}
return (
<div
className="tree-node-collapsed-icon"
onClick={e => {
e.stopPropagation();
this.toggleExpanded();
}}
>
<CollapsedIcon />
</div>
);
}
setTitle(xtitle: string = '') {
const { treeNode } = this.props;
const node = treeNode.node as ElementNode;
const title = node.getProp('x-title');
if (xtitle && xtitle !== node.tagName) {
title.code = `"${xtitle}"`;
} else {
title.remove();
}
}
enableEdit = () => {
this.setState({
editing: true,
});
}
cancelEdit() {
this.setState({
editing: false,
});
}
saveEdit = () => {
const { current } = this.inputRef;
if (current) {
this.setTitle(current.value);
}
this.cancelEdit();
}
handleKeyUp(e: KeyboardEvent<HTMLInputElement>) {
if (e.keyCode === 13) {
this.saveEdit();
}
if (e.keyCode === 27) {
this.cancelEdit();
}
}
componentDidUpdate() {
const { current } = this.inputRef;
if (current) {
current.select();
}
}
render() {
const { treeNode } = this.props;
const { editing } = this.state;
const { title } = treeNode;
const depth = treeNode.depth;
const indent = depth * 12;
const titleClassName = classNames('tree-node-title');
const titleTextClassName = classNames('tree-node-title-text', {
'x-if-text': treeNode.hasXIf(),
'x-for-text': treeNode.hasXFor(),
});
const xForValue = treeNode.xForValue;
return (
<div
className={titleClassName}
ref={ref => treeNode.mount(ref)}
style={{ paddingLeft: indent, marginLeft: -indent }}
>
{this.renderExpandIcon()}
<div className="tree-node-icon">
<TreeNodeIconView tagName={treeNode.node.tagName} />
</div>
<div className="tree-node-title-inner" onDoubleClick={this.enableEdit}>
{
editing ?
<input
className="tree-node-title-input"
defaultValue={title.label}
onBlur={this.saveEdit}
onKeyUp={e => {this.handleKeyUp(e)}}
ref={this.inputRef}
/>
:
<div className={titleTextClassName}>
{title.label}
{xForValue && (
<span className="info">
(x <b>{xForValue.length}</b>)
</span>
)}
{treeNode.hasXIf() && (
<span className="info">
<b>{treeNode.flowHidden ? '' : '(visible)'}</b>
</span>
)}
</div>
}
</div>
<div className="tree-node-ignored-icon">
{isElementNode(treeNode.node) && !editing && (
<EyeCloseIcon
onClick={(e: MouseEvent) => {
e.stopPropagation();
this.toggleIgnored();
}}
/>
)}
</div>
</div>
);
}
}

View File

@ -1,5 +1,5 @@
{
"name": "@ali/lowcode-plugin-outline-pane",
"name": "@ali/lowcode-plugin-outline-tree",
"version": "0.0.0",
"description": "xxx for Ali lowCode engine",
"main": "src/index.ts",

View File

@ -0,0 +1,11 @@
import { IconBase, IconProps } from '../../../globals';
export function IconArrowRight(props: IconProps) {
return (
<IconBase viewBox="0 0 1024 1024" {...props}>
<path d="M512.002047 771.904425c-10.152221 0.518816-20.442588-2.800789-28.202319-10.598382L77.902254 315.937602c-14.548344-14.618952-14.548344-38.318724 0-52.933583 14.544251-14.614859 38.118156-14.614859 52.662407 0l381.437385 418.531212L893.432269 263.004019c14.544251-14.614859 38.125319-14.614859 52.662407 0 14.552437 14.614859 14.552437 38.314631 0 52.933583L540.205389 761.307066C532.451798 769.103636 522.158361 772.424264 512.002047 771.904425z"/>
</IconBase>
);
}
IconArrowRight.displayName = 'IconArrowRight';

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,11 @@
import { IconBase, IconProps } from '../../../globals';
export function IconCond(props: IconProps) {
return (
<IconBase viewBox="0 0 1024 1024" {...props}>
<path d="M479.552 276.544l296.896 2.752v75.712L960 249.024l-183.552-106.048v92.48h-271.36l-46.656-2.752-190.784 203.648 30.976 30.976 180.928-190.784z m296.896 484.928l-253.056-2.816-262.976-263.04H64v43.904h175.296l262.912 262.976 274.176 2.816v75.712L960 774.976l-183.616-105.984 0.064 92.48z" />
</IconBase>
);
}
IconCond.displayName = 'IconCond';

View File

@ -0,0 +1,12 @@
import { IconBase, IconProps } from '../../../globals';
export function IconEyeClose(props: IconProps) {
return (
<IconBase viewBox="0 0 1024 1024" {...props}>
<path d="M512.7 700.9c-102.1 0-184.9-82.8-184.9-184.9 0-28.6 6.5-55.6 18-79.7l-93.7-93.7C138.9 418.1 65.2 514 65.2 514s200.4 260.7 447.6 260.7c50.2 0 98.6-10.8 143.6-27.9l-63.9-63.9c-24.2 11.5-51.2 18-79.8 18z" />
<path d="M960.3 514S759.9 253.3 512.7 253.3c-49.5 0-97.2 10.5-141.7 27.2L243.5 153.1l-45.3 45.3 262.3 262.2c-13.1 13.3-21.2 31.5-21.2 51.6 0 40.6 32.9 73.4 73.4 73.4 20.1 0 38.4-8.1 51.6-21.2l260.9 260.8 45.3-45.3-95.6-95.6C887.2 609.1 960.3 514 960.3 514z m-376.7-20.9c-6.8-25.2-26.6-45.1-51.9-51.9L437.5 347c23-10.3 48.5-16 75.3-16 102.1 0 184.9 82.8 184.9 184.9 0 26.8-5.7 52.2-15.9 75.2l-98.2-98z" />
</IconBase>
);
}
IconEyeClose.displayName = 'IconEyeClose';

View File

@ -0,0 +1,12 @@
import { IconBase, IconProps } from '../../../globals';
export function IconEye(props: IconProps) {
return (
<IconBase viewBox="0 0 1024 1024" {...props}>
<path d="M512 256c-163.8 0-291.4 97.6-448 256 134.8 135.4 248 256 448 256 199.8 0 346.8-152.8 448-253.2C856.4 397.2 709.6 256 512 256z m0 438.6c-98.8 0-179.2-82-179.2-182.6 0-100.8 80.4-182.6 179.2-182.6s179.2 82 179.2 182.6c0 100.8-80.4 182.6-179.2 182.6z" />
<path d="M512 448c0-15.8 5.8-30.2 15.2-41.4-5-0.8-10-1.2-15.2-1.2-57.6 0-104.6 47.8-104.6 106.6s47 106.6 104.6 106.6 104.6-47.8 104.6-106.6c0-4.6-0.4-9.2-0.8-13.8-11 8.6-24.6 13.8-39.6 13.8-35.6 0-64.2-28.6-64.2-64z" />
</IconBase>
);
}
IconEye.displayName = 'IconEye';

View File

@ -0,0 +1,11 @@
import { IconBase, IconProps } from '../../../globals';
export function IconLock(props: IconProps) {
return (
<IconBase viewBox="0 0 1024 1024" {...props}>
<path d="M832 464h-68V240c0-70.7-57.3-128-128-128H388c-70.7 0-128 57.3-128 128v224h-68c-17.7 0-32 14.3-32 32v384c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V496c0-17.7-14.3-32-32-32zM540 701v53c0 4.4-3.6 8-8 8h-40c-4.4 0-8-3.6-8-8v-53c-12.1-8.7-20-22.9-20-39 0-26.5 21.5-48 48-48s48 21.5 48 48c0 16.1-7.9 30.3-20 39z m152-237H332V240c0-30.9 25.1-56 56-56h248c30.9 0 56 25.1 56 56v224z" />
</IconBase>
);
}
IconLock.displayName = 'IconLock';

View File

@ -0,0 +1,11 @@
import { IconBase, IconProps } from '../../../globals';
export function IconLoop(props: IconProps) {
return (
<IconBase viewBox="0 0 1024 1024" {...props}>
<path d="M60.235294 542.117647c0 132.879059 103.062588 240.941176 229.677176 240.941176l0 60.235294c-159.864471 0-289.912471-135.107765-289.912471-301.176471s130.048-301.176471 289.912471-301.176471l254.735059 0-99.147294-99.147294 42.586353-42.586353 171.911529 171.851294-171.851294 171.911529-42.646588-42.646588 99.207529-99.147294-254.795294 0c-126.614588 0-229.677176 108.062118-229.677176 240.941176zM734.087529 240.941176l0 60.235294c126.614588 0 229.677176 108.062118 229.677176 240.941176s-103.062588 240.941176-229.677176 240.941176l-254.795294 0 99.147294-99.147294-42.586353-42.586353-171.851294 171.851294 171.911529 171.911529 42.586353-42.586353-99.207529-99.207529 254.735059 0c159.924706 0 289.972706-135.107765 289.972706-301.176471s-130.048-301.176471-289.912471-301.176471z" />
</IconBase>
);
}
IconLoop.displayName = 'IconLoop';

View File

@ -0,0 +1,12 @@
import { IconBase, IconProps } from '../../../globals/src';
export function IconSlot(props: IconProps) {
return (
<IconBase viewBox="0 0 1024 1024" {...props}>
<path d="M682.325333 135.509333V204.8H819.2v613.376h-614.741333V204.8h136.874666v-69.290667h-206.165333v752.298667h754.346667V135.509333z" />
<path d="M512 512m-170.325333 0a170.325333 170.325333 0 1 0 340.650666 0 170.325333 170.325333 0 1 0-340.650666 0Z" />
</IconBase>
);
}
IconSlot.displayName = 'IconSlot';

View File

@ -0,0 +1,12 @@
import { IconBase, IconProps } from '../../../globals/src';
export function IconUnlock(props: IconProps) {
return (
<IconBase viewBox="0 0 1024 1024" {...props}>
<path d="M832 464H332V240c0-30.9 25.1-56 56-56h248c30.9 0 56 25.1 56 56v68c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-68c0-70.7-57.3-128-128-128H388c-70.7 0-128 57.3-128 128v224h-68c-17.7 0-32 14.3-32 32v384c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V496c0-17.7-14.3-32-32-32z m-40 376H232V536h560v304z" />
<path d="M484 701v53c0 4.4 3.6 8 8 8h40c4.4 0 8-3.6 8-8v-53c12.1-8.7 20-22.9 20-39 0-26.5-21.5-48-48-48s-48 21.5-48 48c0 16.1 7.9 30.3 20 39z" />
</IconBase>
);
}
IconUnlock.displayName = 'IconUnlock';

View File

@ -1,5 +1,6 @@
import Pane from './views/pane';
/*
export default {
name: 'outline-tree',
title: {
@ -8,3 +9,6 @@ export default {
},
content: Pane,
};
*/
export default Pane;

View File

@ -0,0 +1,14 @@
{
"Designer not found": "Designer not found",
"No opened document": "No opened document",
"Hide": "Hide",
"Show": "Show",
"Lock": "Lock",
"Unlock": "Unlock",
"Expand": "Expand",
"Collapse": "Collapse",
"Conditional": "Condition",
"Loop": "Loop",
"Slots": "Slots",
"Slot for {prop}": "Slot for {prop}"
}

View File

@ -0,0 +1,14 @@
{
"Designer not found": "未发现设计器模块",
"No opened document": "没有打开的文档",
"Hide": "隐藏",
"Show": "显示",
"Lock": "锁定",
"Unlock": "解锁",
"Expand": "展开",
"Collapse": "收起",
"Conditional": "条件式",
"Loop": "循环",
"Slots": "插槽",
"Slot for {prop}": "属性 {prop} 的插槽"
}

View File

@ -5,7 +5,22 @@ import { Tree } from './tree';
import Location from '../../designer/src/designer/helper/location';
class TreeMaster {
constructor(readonly designer: Designer) {}
constructor(readonly designer: Designer) {
designer.dragon.onDragstart((e) => {
const tree = this.currentTree;
if (tree) {
tree.document.selection.getTopNodes().forEach(node => {
tree.getTreeNode(node).setExpanded(false);
});
};
});
designer.activeTracker.onChange((target) => {
const tree = this.currentTree;
if (tree && target.node.document === tree.document) {
tree.getTreeNode(target.node).expandParents();
}
});
}
private treeMap = new Map<string, Tree>();
@computed get currentTree(): Tree | null {
@ -70,7 +85,7 @@ export class OutlineMain implements ISensor {
private setupDesigner(designer: Designer) {
this._designer = designer;
this._master = getTreeMaster(designer);
designer.dragon.addSensor(this);
// designer.dragon.addSensor(this);
}
purge() {
@ -93,7 +108,7 @@ export class OutlineMain implements ISensor {
}
this._shell = shell;
if (shell) {
this._sensorAvailable = true;
// this._sensorAvailable = true;
}
}
}

View File

@ -1,16 +1,10 @@
import { computed, obx, TitleContent } from '../../globals';
import { computed, obx, TitleContent, isI18nData, localeFormat } from '../../globals';
import Node from '../../designer/src/designer/document/node/node';
import DocumentModel from '../../designer/src/designer/document/document-model';
import { isLocationChildrenDetail } from '../../designer/src/designer/helper/location';
import Designer from '../../designer/src/designer/designer';
import { Tree } from './tree';
export interface Title {
label: string;
icon?: string;
actions?: any;
}
export default class TreeNode {
get id(): string {
return this.node.id;
@ -35,6 +29,10 @@ export default class TreeNode {
return this.node.zLevel;
}
isRoot() {
return this.tree.root === this;
}
/**
*
*/
@ -52,10 +50,10 @@ export default class TreeNode {
*/
@obx.ref private _expanded = false;
get expanded(): boolean {
return this.expandable && this._expanded;
return this.isRoot() || (this.expandable && this._expanded);
}
set expanded(value: boolean) {
setExpanded(value: boolean) {
this._expanded = value;
}
@ -64,17 +62,36 @@ export default class TreeNode {
}
@computed get hidden(): boolean {
const cv = this.node.isConditionalVisible();
if (cv == null) {
return this.node.getExtraProp('hidden', false)?.getValue() === true;
}
return !cv;
}
@computed get ignored(): boolean {
return this.node.getExtraProp('ignored', false)?.getValue() === true;
setHidden(flag: boolean) {
if (this.node.conditionGroup) {
return;
}
if (flag) {
this.node.getExtraProp('hidden', true)?.setValue(true);
} else {
this.node.getExtraProp('hidden', false)?.remove();
}
}
@computed get locked(): boolean {
return this.node.getExtraProp('locked', false)?.getValue() === true;
}
setLocked(flag: boolean) {
if (flag) {
this.node.getExtraProp('locked', true)?.setValue(true);
} else {
this.node.getExtraProp('locked', false)?.remove();
}
}
@computed get selected(): boolean {
// TODO: check is dragging
const selection = this.document.selection;
@ -85,6 +102,39 @@ export default class TreeNode {
return this.node.title;
}
@computed get titleLabel() {
let title = this.title;
if (!title) {
return '';
}
if ((title as any).label) {
title = (title as any).label;
}
if (typeof title === 'string') {
return title;
}
if (isI18nData(title)) {
return localeFormat(title);
}
return this.node.componentName;
}
setTitleLabel(label: string) {
const origLabel = this.titleLabel;
if (label === origLabel) {
return;
}
if (label === '') {
this.node.getExtraProp('title', false)?.remove();
} else {
this.node.getExtraProp('title', true)?.setValue(label);
}
}
get icon() {
return this.node.componentMeta.icon;
}
@computed get parent() {
const parent = this.node.parent;
if (parent) {
@ -155,7 +205,7 @@ export default class TreeNode {
// 这边不能直接使用 expanded需要额外判断是否可以展开
// 如果只使用 expanded会漏掉不可以展开的情况即在不可以展开的情况下会触发展开
if (this.expandable && !this._expanded) {
this.expanded = true;
this.setExpanded(true);
}
if (tryExpandParents) {
this.expandParents();
@ -188,7 +238,7 @@ export default class TreeNode {
expandParents() {
let p = this.node.parent;
while (p) {
this.tree.getTreeNode(p).expanded = true;
this.tree.getTreeNode(p).setExpanded(true);
p = p.parent;
}
}
@ -226,8 +276,19 @@ export default class TreeNode {
readonly designer: Designer;
readonly document: DocumentModel;
constructor(readonly tree: Tree, readonly node: Node) {
@obx.ref private _node: Node;
get node() {
return this._node;
}
constructor(readonly tree: Tree, node: Node) {
this.document = node.document;
this.designer = this.document.designer;
this._node = node;
}
setNode(node: Node) {
if (this._node !== node) {
this._node = node;
}
}
}

View File

@ -7,13 +7,18 @@ export class Tree {
readonly root: TreeNode;
readonly id: string;
constructor(readonly document: DocumentModel) {
this.root = this.getTreeNode(document.rootNode);
this.id = document.id;
}
getTreeNode(node: Node): TreeNode {
if (this.treeNodesMap.has(node.id)) {
return this.treeNodesMap.get(node.id)!;
const tnode = this.treeNodesMap.get(node.id)!;
tnode.setNode(node);
return tnode;
}
const treeNode = new TreeNode(this, node);

View File

@ -1,7 +1,8 @@
import React, { Component } from 'react';
import { OutlineMain } from '../main';
import { observer } from '../../../globals';
import { intl } from '../locale';
import { observer } from '../../../globals/src';
import { OutlineMain } from '../main';
import TreeView from './tree';
import './style.less';
@observer
@ -25,7 +26,9 @@ export default class OutlinePane extends Component<{ editor: any }> {
);
}
if (!this.main.master.currentTree) {
const tree = this.main.master.currentTree;
if (!tree) {
return (
<div className="lc-outline-pane">
<p className="lc-outline-notice">{intl('No opened document')}</p>
@ -35,11 +38,8 @@ export default class OutlinePane extends Component<{ editor: any }> {
return (
<div className="lc-outline-pane">
<div
ref={shell => this.main.mount(shell)}
className="lc-outline-tree-container"
>
<TreeView tree={this.main.master.currentTree} />
<div ref={shell => this.main.mount(shell)} className="lc-outline-tree-container">
<TreeView key={tree.id} tree={tree} />
</div>
</div>
);

View File

@ -0,0 +1,295 @@
.lc-outline-pane {
height: 100%;
width: 100%;
position: relative;
> .lc-outline-tree-container {
top: 0;
left: 0;
bottom: 0;
right: 0;
position: absolute;
overflow: auto;
}
}
.lc-outline-tree {
overflow: hidden;
margin-bottom: 20px;
user-select: none;
.tree-node-branches::before {
position: absolute;
display: block;
width: 0;
border-left: 1px solid transparent;
height: 100%;
top: 0;
left: 6px;
content: ' ';
z-index: 2;
}
&:hover {
.tree-node-branches::before {
border-left-color: #ddd;
}
}
.insertion {
pointer-events: none !important;
border: 1px dashed var(--color-brand-light);
height: 18px;
transform: translateZ(0);
}
.condition-group-container {
border-bottom: 1px solid #7b605b;
position: relative;
&:before {
position: absolute;
display: block;
width: 0;
border-left: 0.5px solid #7b605b;
height: 100%;
top: 0;
left: 0;
content: ' ';
z-index: 2;
}
>.condition-group-title {
text-align: center;
background-color: #7b605b;
height: 14px;
> .lc-title {
font-size: 12px;
transform: scale(0.8);
transform-origin: top;
color: white;
text-shadow: 0px 0px 2px black;
display: block;
}
}
}
.tree-node-slots {
border-bottom: 1px solid rgb(144, 94, 190);
position: relative;
&:before {
position: absolute;
display: block;
width: 0;
border-left: 0.5px solid rgb(144, 94, 190);
height: 100%;
top: 0;
left: 0;
content: ' ';
z-index: 2;
}
>.tree-node-slots-title {
text-align: center;
background-color: rgb(144, 94, 190);
height: 14px;
> .lc-title {
font-size: 12px;
transform: scale(0.8);
transform-origin: top;
color: white;
text-shadow: 0px 0px 2px black;
display: block;
}
}
}
.tree-node {
.tree-node-expand-btn {
width: 12px;
line-height: 0;
align-self: stretch;
display: flex;
align-items: center;
transition: color 200ms ease;
color: var(--color-icon-normal);
&:hover {
color: var(--color-icon-hover);
}
> svg {
transform-origin: center;
transform: rotate(-90deg);
transition: transform 100ms ease;
}
margin-right: 4px;
}
.tree-node-expand-placeholder {
width: 12px;
height: 12px;
margin-right: 4px;
}
.tree-node-icon {
transform: translateZ(0);
display: flex;
align-items: center;
margin-right: 4px;
color: var(--color-text);
& > svg {
width: 16px;
height: 16px;
}
}
.tree-node-title {
font-size: var(--font-size-text);
cursor: pointer;
background: var(--color-pane-background);
border-bottom: 1px solid var(--color-line-normal, rgba(31, 56, 88, 0.1));
display: flex;
align-items: center;
height: 30px;
position: relative;
transform: translateZ(0);
padding-right: 5px;
& > :first-child {
margin-left: 2px;
}
.tree-node-title-label {
flex: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
display: flex;
align-items: center;
align-self: stretch;
overflow: visible;
margin-right: 5px;
.tree-node-title-input {
flex: 1;
border: 1px solid var(--color-brand-light);
background-color: var(--color-pane-background);
color: var(--color-text);
line-height: 18px;
padding: 2px;
outline: none;
margin-left: -3px;
border-radius: 2px;
}
}
.tree-node-hide-btn, .tree-node-lock-btn {
opacity: 0;
color: var(--color-text);
line-height: 0;
align-self: stretch;
display: flex;
align-items: center;
justify-content: center;
width: 22px;
&:hover {
opacity: 1 !important;
}
}
&:hover {
.tree-node-hide-btn, .tree-node-lock-btn {
opacity: 0.5;
}
}
&.editing {
& > .tree-node-hide-btn, & >.tree-node-lock-btn {
display: none;
}
}
.tree-node-tag {
margin-left: 5px;
display: flex;
align-items: center;
line-height: 0;
&.cond {
color: rgb(179, 52, 6);
}
&.loop {
color: rgb(103, 187, 187);
}
&.slot {
color: rgb(211, 90, 211);
}
}
}
&.is-root {
> .tree-node-title {
padding-left: 5px;
}
}
&.expanded {
& > .tree-node-title > .tree-node-expand-btn > svg {
transform: rotate(0);
}
}
&.hovering > .tree-node-title {
background: var(--color-block-background-light);
}
// 选中节点处理
&.selected {
& > .tree-node-title {
background: var(--color-block-background-shallow);
}
& > .tree-node-branches::before {
border-left-color: var(--color-brand-light);
}
}
&.hidden {
.tree-node-title-label {
color: #9b9b9b;
}
& > .tree-node-title > .tree-node-hide-btn {
opacity: 0.8;
}
.tree-node-branches {
.tree-node-hide-btn {
opacity: 0;
}
}
}
&.condition-flow {
& > .tree-node-title > .tree-node-hide-btn {
opacity: 1;
}
&.hidden > .tree-node-title > .tree-node-hide-btn {
opacity: 0;
}
}
&.locked {
& > .tree-node-title > .tree-node-lock-btn {
opacity: 0.8;
}
.tree-node-branches {
.tree-node-lock-btn, .tree-node-hide-btn {
opacity: 0;
}
}
}
// 处理拖入节点
&.dropping {
& > .tree-node-branches::before {
border-left: 1px solid var(--color-brand);
}
}
.tree-node-branches {
padding-left: 12px;
position: relative;
}
}
}

View File

@ -0,0 +1,114 @@
import { observer, Title } from '../../../globals';
import { Component } from 'react';
import TreeNode from '../tree-node';
import TreeNodeView from './tree-node';
import ExclusiveGroup from '../../../designer/src/designer/document/node/exclusive-group';
import { intl } from '../locale';
@observer
export default class TreeBranches extends Component<{
treeNode: TreeNode;
}> {
shouldComponentUpdate() {
return false;
}
render() {
const treeNode = this.props.treeNode;
const { expanded } = treeNode;
if (!expanded) {
return null;
}
return (
<div className="tree-node-branches">
<TreeNodeSlots treeNode={treeNode} />
<TreeNodeChildren treeNode={treeNode} />
</div>
);
}
}
@observer
class TreeNodeChildren extends Component<{
treeNode: TreeNode;
}> {
shouldComponentUpdate() {
return false;
}
render() {
const { treeNode } = this.props;
let children: any = [];
let groupContents: any[] = [];
let currentGrp: ExclusiveGroup;
const endGroup = () => {
if (groupContents.length > 0) {
children.push(
<div key={currentGrp.id} className="condition-group-container" data-id={currentGrp.firstNode.id}>
<div className="condition-group-title">
<Title title={currentGrp.title} />
</div>
{groupContents}
</div>,
);
groupContents = [];
}
};
const { dropIndex } = treeNode;
treeNode.children?.forEach((child, index) => {
const { conditionGroup } = child.node;
if (conditionGroup !== currentGrp) {
endGroup();
}
if (conditionGroup) {
currentGrp = conditionGroup;
if (index === dropIndex) {
if (groupContents.length > 0) {
groupContents.push(<div key="insertion" className="insertion" />);
} else {
children.push(<div key="insertion" className="insertion" />);
}
}
groupContents.push(<TreeNodeView key={child.id} treeNode={child} />);
} else {
if (index === dropIndex) {
children.push(<div key="insertion" className="insertion" />);
}
children.push(<TreeNodeView key={child.id} treeNode={child} />);
}
});
endGroup();
if (dropIndex != null && dropIndex === treeNode.children?.length) {
children.push(<div key="insertion" className="insertion" />);
}
return <div className="tree-node-children">{children}</div>;
}
}
@observer
class TreeNodeSlots extends Component<{
treeNode: TreeNode;
}> {
shouldComponentUpdate() {
return false;
}
render() {
const { treeNode } = this.props;
if (!treeNode.isSlotContainer()) {
return null;
}
return (
<div className="tree-node-slots">
<div className="tree-node-slots-title">
<Title title={{ type: 'i18n', intl: intl('Slots')}} />
</div>
{treeNode.slots.map(tnode => (
<TreeNodeView key={tnode.id} treeNode={tnode} />
))}
</div>
);
}
}

View File

@ -0,0 +1,43 @@
import { Component } from 'react';
import classNames from 'classnames';
import { observer } from '../../../globals';
import TreeNode from '../tree-node';
import TreeTitle from './tree-title';
import TreeBranches from './tree-branches';
@observer
export default class TreeNodeView extends Component<{ treeNode: TreeNode }> {
shouldComponentUpdate() {
return false;
}
render() {
const { treeNode } = this.props;
const className = classNames('tree-node', {
// 是否展开
expanded: treeNode.expanded,
// 是否悬停中
hovering: treeNode.hovering,
// 是否选中的
selected: treeNode.selected,
// 是否隐藏的
hidden: treeNode.hidden,
// 是否忽略的
// ignored: treeNode.ignored,
// 是否锁定的
locked: treeNode.locked,
// 是否投放响应
dropping: treeNode.dropIndex != null,
'is-root': treeNode.isRoot(),
'condition-flow': treeNode.node.conditionGroup != null,
// highlight: treeNode.isResponseDropping() && treeNode.dropIndex == null,
});
return (
<div className={className} data-id={treeNode.id}>
<TreeTitle treeNode={treeNode} />
<TreeBranches treeNode={treeNode} />
</div>
);
}
}

View File

@ -0,0 +1,199 @@
import { Component, KeyboardEvent, FocusEvent, Fragment } from 'react';
import classNames from 'classnames';
import { observer, createIcon, Title, EmbedTip } from '../../../globals';
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 TreeNode from '../tree-node';
import { IconEye } from '../icons/eye';
import { IconCond } from '../icons/cond';
import { IconLoop } from '../icons/loop';
import { IconSlot } from '../icons/slot';
@observer
export default class TreeTitle extends Component<{
treeNode: TreeNode;
}> {
state = {
editing: false,
};
private enableEdit = () => {
this.setState({
editing: true,
});
};
private cancelEdit() {
this.setState({
editing: false,
});
}
private saveEdit = (e: FocusEvent<HTMLInputElement> | KeyboardEvent<HTMLInputElement>) => {
const { treeNode } = this.props;
treeNode.setTitleLabel((e.target as HTMLInputElement).value || '');
this.cancelEdit();
};
private handleKeyUp = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.keyCode === 13) {
this.saveEdit(e);
}
if (e.keyCode === 27) {
this.cancelEdit();
}
};
componentDidUpdate() {
// TODO:
/*
const { current } = this.inputRef;
if (current) {
current.select();
}
*/
}
render() {
const { treeNode } = this.props;
const { editing } = this.state;
const isCNode = !treeNode.isRoot();
const isNodeParent = treeNode.node.isNodeParent;
let style: any;
if (isCNode) {
const depth = treeNode.depth;
const indent = depth * 12;
style = {
paddingLeft: indent,
marginLeft: -indent,
};
}
return (
<div
className={classNames('tree-node-title', {
editing,
})}
ref={ref => treeNode.mount(ref)}
style={style}
onClick={treeNode.node.conditionGroup ? () => treeNode.node.setConditionalVisible() : undefined}
>
{isCNode && <ExpandBtn treeNode={treeNode} />}
<div className="tree-node-icon">{createIcon(treeNode.icon)}</div>
<div className="tree-node-title-label" onDoubleClick={isNodeParent ? this.enableEdit : undefined}>
{editing ? (
<input
className="tree-node-title-input"
defaultValue={treeNode.titleLabel}
onBlur={this.saveEdit}
onKeyUp={this.handleKeyUp}
/>
) : (
<Fragment>
<Title title={treeNode.title} />
{treeNode.node.slotFor && (<a className="tree-node-tag slot">
{/* todo: click redirect to prop */}
<IconSlot />
<EmbedTip>{intl('Slot for {prop}', { prop: treeNode.node.slotFor.key })}</EmbedTip>
</a>)}
{treeNode.node.hasLoop() && (
<a className="tree-node-tag loop">
{/* todo: click todo something */}
<IconLoop />
<EmbedTip>{intl('Loop')}</EmbedTip>
</a>
)}
{treeNode.node.hasCondition() && !treeNode.node.conditionGroup && (
<a className="tree-node-tag cond">
{/* todo: click todo something */}
<IconCond />
<EmbedTip>{intl('Conditional')}</EmbedTip>
</a>
)}
</Fragment>
)}
</div>
{isCNode && isNodeParent && <HideBtn treeNode={treeNode} />}
{isCNode && isNodeParent && <LockBtn treeNode={treeNode} />}
</div>
);
}
}
@observer
class LockBtn extends Component<{
treeNode: TreeNode;
}> {
shouldComponentUpdate() {
return false;
}
render() {
const { treeNode } = this.props;
return (
<div
className="tree-node-lock-btn"
onClick={e => {
e.stopPropagation();
treeNode.setLocked(!treeNode.locked);
}}
>
{treeNode.locked ? <IconLock /> : <IconUnlock />}
<EmbedTip>{treeNode.locked ? intl('Unlock') : intl('Lock')}</EmbedTip>
</div>
);
}
}
@observer
class HideBtn extends Component<{
treeNode: TreeNode;
}> {
shouldComponentUpdate() {
return false;
}
render() {
const { treeNode } = this.props;
return (
<div
className="tree-node-hide-btn"
onClick={e => {
e.stopPropagation();
treeNode.setHidden(!treeNode.hidden);
}}
>
{treeNode.hidden ? <IconEyeClose /> : <IconEye />}
<EmbedTip>{treeNode.hidden ? intl('Show') : intl('Hide')}</EmbedTip>
</div>
);
}
}
@observer
class ExpandBtn extends Component<{
treeNode: TreeNode;
}> {
shouldComponentUpdate() {
return false;
}
render() {
const { treeNode } = this.props;
if (!treeNode.expandable) {
return <i className="tree-node-expand-placeholder" />;
}
return (
<div
className="tree-node-expand-btn"
onClick={e => {
e.stopPropagation();
treeNode.setExpanded(!treeNode.expanded);
}}
>
<IconArrowRight size="small" />
<EmbedTip>{treeNode.expanded ? intl('Collapse') : intl('Expand')}</EmbedTip>
</div>
);
}
}

View File

@ -1,8 +1,11 @@
@observer
export default class TreeView extends React.Component {
private ref = React.createRef<HTMLDivElement>();
private dispose?: () => void;
import { Component } from 'react';
import { observer } from '../../../globals';
import { Tree } from '../tree';
import TreeNodeView from './tree-node';
@observer
export default class TreeView extends Component<{ tree: Tree }> {
/*
hover(e: any) {
const treeNode = tree.getTreeNodeByEvent(e);
@ -68,12 +71,13 @@ export default class TreeView extends React.Component {
});
}
}
*/
render() {
const { tree } = this.props;
const root = tree.root;
return (
<div className="my-outline-tree">
<div className="lc-outline-tree">
<TreeNodeView key={root.id} treeNode={root} />
</div>
);