2020-04-27 00:07:43 +08:00

220 lines
5.4 KiB
TypeScript

import {
Component,
Fragment,
ReactNodeArray,
isValidElement,
cloneElement,
createElement,
ReactNode,
ComponentType,
} from 'react';
import classNames from 'classnames';
import { observer, computed, Tip } from '@ali/lowcode-editor-core';
import { createIcon, isReactComponent } from '@ali/lowcode-utils';
import { ActionContentObject, isActionContentObject } from '@ali/lowcode-types';
import { SimulatorContext } from '../context';
import { BuiltinSimulatorHost } from '../host';
import { OffsetObserver } from '../../designer';
import { Node } from '../../document';
@observer
export class BorderSelectingInstance extends Component<{
observed: OffsetObserver;
highlight?: boolean;
dragging?: boolean;
}> {
componentWillUnmount() {
this.props.observed.purge();
}
render() {
const { observed, highlight, dragging } = this.props;
if (!observed.hasOffset) {
return null;
}
const { offsetWidth, offsetHeight, offsetTop, offsetLeft } = observed;
const style = {
width: offsetWidth,
height: offsetHeight,
transform: `translate3d(${offsetLeft}px, ${offsetTop}px, 0)`,
};
const className = classNames('lc-borders lc-borders-selecting', {
highlight,
dragging,
});
return (
<div className={className} style={style}>
{!dragging && <Toolbar observed={observed} />}
</div>
);
}
}
@observer
class Toolbar extends Component<{ observed: OffsetObserver }> {
shouldComponentUpdate() {
return false;
}
render() {
const { observed } = this.props;
const { height, width } = observed.viewport;
const BAR_HEIGHT = 20;
const MARGIN = 1;
const BORDER = 2;
const SPACE_HEIGHT = BAR_HEIGHT + MARGIN + BORDER;
let style: any;
if (observed.top > SPACE_HEIGHT) {
style = {
right: Math.max(-BORDER, observed.right - width - BORDER),
top: -SPACE_HEIGHT,
height: BAR_HEIGHT,
};
} else if (observed.bottom + SPACE_HEIGHT < height) {
style = {
bottom: -SPACE_HEIGHT,
height: BAR_HEIGHT,
right: Math.max(-BORDER, observed.right - width - BORDER),
};
} else {
style = {
height: BAR_HEIGHT,
top: Math.max(MARGIN, MARGIN - observed.top),
right: Math.max(MARGIN, MARGIN + observed.right - width),
};
}
const { node } = observed;
const actions: ReactNodeArray = [];
node.componentMeta.availableActions.forEach((action) => {
const { important, condition, content, name } = action;
if (node.isSlot() && (name === 'copy' || name === 'remove')) {
// FIXME: need this?
return;
}
if (important && (typeof condition === 'function' ? condition(node) !== false : condition !== false)) {
actions.push(createAction(content, name, node));
}
});
return (
<div className="lc-borders-actions" style={style}>
{actions}
</div>
);
}
}
function createAction(content: ReactNode | ComponentType<any> | ActionContentObject, key: string, node: Node) {
if (isValidElement(content)) {
return cloneElement(content, { key, node });
}
if (isReactComponent(content)) {
return createElement(content, { key, node });
}
if (isActionContentObject(content)) {
const { action, title, icon } = content;
return (
<div
key={key}
className="lc-borders-action"
onClick={() => {
action && action(node);
}}
>
{icon && createIcon(icon)}
<Tip>{title}</Tip>
</div>
);
}
return null;
}
@observer
export class BorderSelectingForNode extends Component<{ node: Node }> {
static contextType = SimulatorContext;
get host(): BuiltinSimulatorHost {
return this.context;
}
get dragging(): boolean {
return this.host.designer.dragon.dragging;
}
@computed get instances() {
return this.host.getComponentInstances(this.props.node);
}
shouldComponentUpdate() {
return false;
}
render() {
const { instances } = this;
const { node } = this.props;
const designer = this.host.designer;
if (!instances || instances.length < 1) {
return null;
}
return (
<Fragment key={node.id}>
{instances.map((instance) => {
const observed = designer.createOffsetObserver({
node,
instance,
});
if (!observed) {
return null;
}
return <BorderSelectingInstance key={observed.id} dragging={this.dragging} observed={observed} />;
})}
</Fragment>
);
}
}
@observer
export class BorderSelecting extends Component {
static contextType = SimulatorContext;
get host(): BuiltinSimulatorHost {
return this.context;
}
get dragging(): boolean {
return this.host.designer.dragon.dragging;
}
@computed get selecting() {
const doc = this.host.document;
if (doc.suspensed) {
return null;
}
const selection = doc.selection;
return this.dragging ? selection.getTopNodes() : selection.getNodes();
}
shouldComponentUpdate() {
return false;
}
render() {
const selecting = this.selecting;
if (!selecting || selecting.length < 1) {
// DIRTY FIX, recore has a bug!
return <Fragment />;
}
return (
<Fragment>
{selecting.map((node) => (
<BorderSelectingForNode key={node.id} node={node} />
))}
</Fragment>
);
}
}