mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-13 20:36:34 +00:00
359 lines
11 KiB
TypeScript
359 lines
11 KiB
TypeScript
import { KeyboardEvent, FocusEvent, Fragment, PureComponent } from 'react';
|
|
import classNames from 'classnames';
|
|
import { createIcon } from '@alilc/lowcode-utils';
|
|
import { IPublicApiEvent } from '@alilc/lowcode-types';
|
|
import TreeNode from '../controllers/tree-node';
|
|
import { IconLock, IconUnlock, IconArrowRight, IconEyeClose, IconEye, IconCond, IconLoop, IconRadioActive, IconRadio, IconSetting, IconDelete } from '../icons';
|
|
|
|
function emitOutlineEvent(event: IPublicApiEvent, type: string, treeNode: TreeNode, rest?: Record<string, unknown>) {
|
|
const node = treeNode?.node;
|
|
const npm = node?.componentMeta?.npm;
|
|
const selected =
|
|
[npm?.package, npm?.componentName].filter((item) => !!item).join('-') || node?.componentMeta?.componentName || '';
|
|
event.emit(`outlinePane.${type}`, {
|
|
selected,
|
|
...rest,
|
|
});
|
|
}
|
|
|
|
export default class TreeTitle extends PureComponent<{
|
|
treeNode: TreeNode;
|
|
isModal?: boolean;
|
|
expanded: boolean;
|
|
hidden: boolean;
|
|
locked: boolean;
|
|
expandable: boolean;
|
|
}> {
|
|
state: {
|
|
editing: boolean;
|
|
title: string;
|
|
condition?: boolean;
|
|
visible?: boolean;
|
|
filterWorking: boolean;
|
|
keywords: string;
|
|
matchSelf: boolean;
|
|
} = {
|
|
editing: false,
|
|
title: '',
|
|
filterWorking: false,
|
|
keywords: '',
|
|
matchSelf: false,
|
|
};
|
|
|
|
private lastInput?: HTMLInputElement;
|
|
|
|
private enableEdit = (e: MouseEvent) => {
|
|
e.preventDefault();
|
|
this.setState({
|
|
editing: true,
|
|
});
|
|
};
|
|
|
|
private cancelEdit() {
|
|
this.setState({
|
|
editing: false,
|
|
});
|
|
this.lastInput = undefined;
|
|
}
|
|
|
|
private saveEdit = (e: FocusEvent<HTMLInputElement> | KeyboardEvent<HTMLInputElement>) => {
|
|
const { treeNode } = this.props;
|
|
const value = (e.target as HTMLInputElement).value || '';
|
|
treeNode.setTitleLabel(value);
|
|
emitOutlineEvent(this.props.treeNode.pluginContext.event, 'rename', treeNode, { value });
|
|
this.cancelEdit();
|
|
};
|
|
|
|
private handleKeyUp = (e: KeyboardEvent<HTMLInputElement>) => {
|
|
if (e.keyCode === 13) {
|
|
this.saveEdit(e);
|
|
}
|
|
if (e.keyCode === 27) {
|
|
this.cancelEdit();
|
|
}
|
|
};
|
|
|
|
private setCaret = (input: HTMLInputElement | null) => {
|
|
if (!input || this.lastInput === input) {
|
|
return;
|
|
}
|
|
input.focus();
|
|
input.select();
|
|
// 光标定位最后一个
|
|
// input.selectionStart = input.selectionEnd;
|
|
};
|
|
|
|
componentDidMount() {
|
|
const { treeNode } = this.props;
|
|
const { filterWorking, matchSelf, keywords } = treeNode.filterReult;
|
|
this.setState({
|
|
editing: false,
|
|
title: treeNode.titleLabel,
|
|
condition: treeNode.condition,
|
|
visible: !treeNode.hidden,
|
|
filterWorking,
|
|
matchSelf,
|
|
keywords,
|
|
});
|
|
treeNode.onTitleLabelChanged(() => {
|
|
this.setState({
|
|
title: treeNode.titleLabel,
|
|
});
|
|
});
|
|
treeNode.onConditionChanged(() => {
|
|
this.setState({
|
|
condition: treeNode.condition,
|
|
});
|
|
});
|
|
treeNode.onHiddenChanged((hidden: boolean) => {
|
|
this.setState({
|
|
visible: !hidden,
|
|
});
|
|
});
|
|
treeNode.onFilterResultChanged(() => {
|
|
const { filterWorking: newFilterWorking, keywords: newKeywords, matchSelf: newMatchSelf } = treeNode.filterReult;
|
|
this.setState({ filterWorking: newFilterWorking, keywords: newKeywords, matchSelf: newMatchSelf });
|
|
});
|
|
}
|
|
deleteClick = () => {
|
|
const { treeNode } = this.props;
|
|
const { node } = treeNode;
|
|
treeNode.deleteNode(node);
|
|
};
|
|
render() {
|
|
const { treeNode, isModal } = this.props;
|
|
const { pluginContext } = treeNode;
|
|
const { editing, filterWorking, matchSelf, keywords } = this.state;
|
|
const isCNode = !treeNode.isRoot();
|
|
const { node } = treeNode;
|
|
const { componentMeta } = node;
|
|
const availableActions = componentMeta ? componentMeta.availableActions.map((availableAction) => availableAction.name) : [];
|
|
const isNodeParent = node.isParentalNode;
|
|
const isContainer = node.isContainerNode;
|
|
let style: any;
|
|
if (isCNode) {
|
|
const { depth } = treeNode;
|
|
const indent = depth * 12;
|
|
style = {
|
|
paddingLeft: indent + (isModal ? 12 : 0),
|
|
marginLeft: -indent,
|
|
};
|
|
}
|
|
const Extra = pluginContext.extraTitle;
|
|
const { intlNode, common, config } = pluginContext;
|
|
const { Tip, Title } = common.editorCabin;
|
|
const couldHide = availableActions.includes('hide');
|
|
const couldLock = availableActions.includes('lock');
|
|
const couldUnlock = availableActions.includes('unlock');
|
|
const shouldShowHideBtn = isCNode && isNodeParent && !isModal && couldHide;
|
|
const shouldShowLockBtn = config.get('enableCanvasLock', false) && isContainer && isCNode && isNodeParent && ((couldLock && !node.isLocked) || (couldUnlock && node.isLocked));
|
|
const shouldEditBtn = isCNode && isNodeParent;
|
|
const shouldDeleteBtn = isCNode && isNodeParent && node?.canPerformAction('remove');
|
|
return (
|
|
<div
|
|
className={classNames('tree-node-title', { editing })}
|
|
style={style}
|
|
data-id={treeNode.nodeId}
|
|
onClick={() => {
|
|
if (isModal) {
|
|
if (this.state.visible) {
|
|
node.document?.modalNodesManager?.setInvisible(node);
|
|
} else {
|
|
node.document?.modalNodesManager?.setVisible(node);
|
|
}
|
|
return;
|
|
}
|
|
if (node.conditionGroup) {
|
|
node.setConditionalVisible();
|
|
}
|
|
}}
|
|
>
|
|
{isModal && this.state.visible && (
|
|
<div onClick={() => {
|
|
node.document?.modalNodesManager?.setInvisible(node);
|
|
}}
|
|
>
|
|
<IconRadioActive className="tree-node-modal-radio-active" />
|
|
</div>
|
|
)}
|
|
{isModal && !this.state.visible && (
|
|
<div onClick={() => {
|
|
node.document?.modalNodesManager?.setVisible(node);
|
|
}}
|
|
>
|
|
<IconRadio className="tree-node-modal-radio" />
|
|
</div>
|
|
)}
|
|
{isCNode && <ExpandBtn expandable={this.props.expandable} expanded={this.props.expanded} treeNode={treeNode} />}
|
|
<div className="tree-node-icon">{createIcon(treeNode.icon)}</div>
|
|
<div className="tree-node-title-label">
|
|
{editing ? (
|
|
<input
|
|
className="tree-node-title-input"
|
|
defaultValue={this.state.title}
|
|
onBlur={this.saveEdit}
|
|
ref={this.setCaret}
|
|
onKeyUp={this.handleKeyUp}
|
|
/>
|
|
) : (
|
|
<Fragment>
|
|
{/* @ts-ignore */}
|
|
<Title
|
|
title={this.state.title}
|
|
match={filterWorking && matchSelf}
|
|
keywords={keywords}
|
|
/>
|
|
{Extra && <Extra node={treeNode?.node} />}
|
|
{node.slotFor && (
|
|
<a className="tree-node-tag slot">
|
|
{/* todo: click redirect to prop */}
|
|
{/* @ts-ignore */}
|
|
<Tip>{intlNode('Slot for {prop}', { prop: node.slotFor.key })}</Tip>
|
|
</a>
|
|
)}
|
|
{node.hasLoop() && (
|
|
<a className="tree-node-tag loop">
|
|
{/* todo: click todo something */}
|
|
<IconLoop />
|
|
{/* @ts-ignore */}
|
|
<Tip>{intlNode('Loop')}</Tip>
|
|
</a>
|
|
)}
|
|
{this.state.condition && (
|
|
<a className="tree-node-tag cond">
|
|
{/* todo: click todo something */}
|
|
<IconCond />
|
|
{/* @ts-ignore */}
|
|
<Tip>{intlNode('Conditional')}</Tip>
|
|
</a>
|
|
)}
|
|
</Fragment>
|
|
)}
|
|
</div>
|
|
{shouldShowHideBtn && <HideBtn hidden={this.props.hidden} treeNode={treeNode} />}
|
|
{shouldShowLockBtn && <LockBtn locked={this.props.locked} treeNode={treeNode} />}
|
|
{shouldEditBtn && <RenameBtn treeNode={treeNode} onClick={this.enableEdit} />}
|
|
{shouldDeleteBtn && <DeleteBtn treeNode={treeNode} onClick={this.deleteClick} />}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
class DeleteBtn extends PureComponent<{
|
|
treeNode: TreeNode;
|
|
onClick: () => void;
|
|
}> {
|
|
render() {
|
|
const { intl, common } = this.props.treeNode.pluginContext;
|
|
const { Tip } = common.editorCabin;
|
|
return (
|
|
<div
|
|
className="tree-node-delete-btn"
|
|
onClick={this.props.onClick}
|
|
>
|
|
<IconDelete />
|
|
{/* @ts-ignore */}
|
|
<Tip>{intl('Delete')}</Tip>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
class RenameBtn extends PureComponent<{
|
|
treeNode: TreeNode;
|
|
onClick: (e: any) => void;
|
|
}> {
|
|
render() {
|
|
const { intl, common } = this.props.treeNode.pluginContext;
|
|
const { Tip } = common.editorCabin;
|
|
return (
|
|
<div
|
|
className="tree-node-rename-btn"
|
|
onClick={this.props.onClick}
|
|
>
|
|
<IconSetting />
|
|
{/* @ts-ignore */}
|
|
<Tip>{intl('Rename')}</Tip>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
class LockBtn extends PureComponent<{
|
|
treeNode: TreeNode;
|
|
locked: boolean;
|
|
}> {
|
|
render() {
|
|
const { treeNode, locked } = this.props;
|
|
const { intl, common } = this.props.treeNode.pluginContext;
|
|
const { Tip } = common.editorCabin;
|
|
return (
|
|
<div
|
|
className="tree-node-lock-btn"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
treeNode.setLocked(!locked);
|
|
}}
|
|
>
|
|
{locked ? <IconUnlock /> : <IconLock /> }
|
|
{/* @ts-ignore */}
|
|
<Tip>{locked ? intl('Unlock') : intl('Lock')}</Tip>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
class HideBtn extends PureComponent<{
|
|
treeNode: TreeNode;
|
|
hidden: boolean;
|
|
}, {
|
|
hidden: boolean;
|
|
}> {
|
|
render() {
|
|
const { treeNode, hidden } = this.props;
|
|
const { intl, common } = treeNode.pluginContext;
|
|
const { Tip } = common.editorCabin;
|
|
return (
|
|
<div
|
|
className="tree-node-hide-btn"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
emitOutlineEvent(treeNode.pluginContext.event, hidden ? 'show' : 'hide', treeNode);
|
|
treeNode.setHidden(!hidden);
|
|
}}
|
|
>
|
|
{hidden ? <IconEye /> : <IconEyeClose />}
|
|
{/* @ts-ignore */}
|
|
<Tip>{hidden ? intl('Show') : intl('Hide')}</Tip>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
class ExpandBtn extends PureComponent<{
|
|
treeNode: TreeNode;
|
|
expanded: boolean;
|
|
expandable: boolean;
|
|
}> {
|
|
render() {
|
|
const { treeNode, expanded, expandable } = this.props;
|
|
if (!expandable) {
|
|
return <i className="tree-node-expand-placeholder" />;
|
|
}
|
|
return (
|
|
<div
|
|
className="tree-node-expand-btn"
|
|
onClick={(e) => {
|
|
if (expanded) {
|
|
e.stopPropagation();
|
|
}
|
|
emitOutlineEvent(treeNode.pluginContext.event, expanded ? 'collapse' : 'expand', treeNode);
|
|
treeNode.setExpanded(!expanded);
|
|
}}
|
|
>
|
|
<IconArrowRight size="small" />
|
|
</div>
|
|
);
|
|
}
|
|
}
|