fix outlines

This commit is contained in:
kangwei 2020-02-21 22:15:02 +08:00
parent 4d4cfab271
commit a56faac6ae
12 changed files with 204 additions and 91 deletions

View File

@ -72,10 +72,12 @@ export class OutlineHovering extends Component {
render() {
const host = this.context as SimulatorHost;
const current = this.current;
if (!current) {
console.info('current', current)
if (!current || host.viewport.scrolling) {
return <Fragment />;
}
const instances = host.getComponentInstances(current);
console.info('current instances', instances)
if (!instances || instances.length < 1) {
return <Fragment />;
}

View File

@ -5,6 +5,7 @@ import { SimulatorContext } from '../context';
import { SimulatorHost } from '../host';
import { computed } from '@recore/obx';
import OffsetObserver from '../../../../designer/helper/offset-observer';
import Node from '../../../../designer/document/node/node';
@observer
export class OutlineSelectingInstance extends Component<{ observed: OffsetObserver; highlight?: boolean }> {
@ -12,18 +13,22 @@ export class OutlineSelectingInstance extends Component<{ observed: OffsetObserv
return false;
}
componentWillUnmount() {
this.props.observed.purge();
}
render() {
const { observed, highlight } = this.props;
if (!observed.hasOffset) {
return null;
}
const { scale, width, height, offsetTop, offsetLeft } = observed;
const { offsetWidth, offsetHeight, offsetTop, offsetLeft } = observed;
const style = {
width: width * scale,
height: height * scale,
transform: `translate3d(${offsetLeft * scale}px, ${offsetTop * scale}px, 0)`,
width: offsetWidth,
height: offsetHeight,
transform: `translate3d(${offsetLeft}px, ${offsetTop}px, 0)`,
};
const className = classNames('lc-outlines lc-outlines-selecting', {
@ -39,13 +44,54 @@ export class OutlineSelectingInstance extends Component<{ observed: OffsetObserv
}
@observer
export class OutlineSelecting extends Component {
export class OutlineSelectingForNode extends Component<{ node: Node }> {
static contextType = SimulatorContext;
get host(): SimulatorHost {
return this.context;
}
@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 <OutlineSelectingInstance key={observed.id} observed={observed} />;
})}
</Fragment>
);
}
}
@observer
export class OutlineSelecting extends Component {
static contextType = SimulatorContext;
get host(): SimulatorHost {
return this.context;
}
@computed get selecting() {
const doc = this.host.document;
if (doc.suspensed) {
@ -54,8 +100,8 @@ export class OutlineSelecting extends Component {
return doc.selection.getNodes();
}
@computed get host(): SimulatorHost {
return this.context;
shouldComponentUpdate() {
return false;
}
render() {
@ -65,30 +111,11 @@ export class OutlineSelecting extends Component {
return <Fragment />;
}
const designer = this.host.designer;
return (
<Fragment>
{selecting.map(node => {
const instances = this.host.getComponentInstances(node);
if (!instances || instances.length < 1) {
return null;
}
return (
<Fragment key={node.id}>
{instances.map((instance, i) => {
const observed = designer.createOffsetObserver({
node,
instance,
});
if (!observed) {
return null;
}
return <OutlineSelectingInstance key={`line-s-${i}`} observed={observed} />;
})}
</Fragment>
);
})}
{selecting.map(node => (
<OutlineSelectingForNode key={node.id} node={node} />
))}
</Fragment>
);
}

View File

@ -15,6 +15,7 @@ import {
DragNodeDataObject,
isDragAnyObject,
isDragNodeObject,
isDragNodeDataObject,
} from '../../../designer/helper/dragon';
import {
LocationData,
@ -347,11 +348,20 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
return null;
}
@obx.val private instancesMap = new Map<string, ReactInstance[]>();
setInstance(id: string, instances: ReactInstance[] | null) {
if (instances == null) {
this.instancesMap.delete(id);
} else {
this.instancesMap.set(id, instances.slice());
}
}
/**
* @see ISimulator
*/
getComponentInstances(node: Node): ReactInstance[] | null {
return this._renderer?.getComponentInstances(node.id) || null;
return this.instancesMap.get(node.id) || null;
}
/**
@ -888,12 +898,22 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
*/
checkNesting(dropTarget: NodeParent, dragObject: DragNodeObject | DragNodeDataObject): boolean {
const items: Array<Node | NodeSchema> = dragObject.nodes || (dragObject as DragNodeDataObject).data;
let items: Array<Node | NodeSchema>;
if (isDragNodeDataObject(dragObject)) {
items = Array.isArray(dragObject.data) ? dragObject.data : [dragObject.data];
} else {
items = dragObject.nodes
}
return items.every(item => this.checkNestingDown(dropTarget, item));
}
checkDropTarget(dropTarget: NodeParent, dragObject: DragNodeObject | DragNodeDataObject): boolean {
const items: Array<Node | NodeSchema> = dragObject.nodes || (dragObject as DragNodeDataObject).data;
let items: Array<Node | NodeSchema>;
if (isDragNodeDataObject(dragObject)) {
items = Array.isArray(dragObject.data) ? dragObject.data : [dragObject.data];
} else {
items = dragObject.nodes
}
return items.every(item => this.checkNestingUp(dropTarget, item));
}

View File

@ -98,14 +98,28 @@ export default class Viewport implements IViewport {
return this._scrollTarget;
}
@obx private _scrolling: boolean = false;
get scrolling(): boolean {
return this._scrolling;
}
setScrollTarget(target: Window) {
const scrollTarget = new ScrollTarget(target);
this._scrollX = scrollTarget.left;
this._scrollY = scrollTarget.top;
target.onscroll = () => {
let scrollTimer: any;
target.addEventListener('scroll', () => {
this._scrollX = scrollTarget.left;
this._scrollY = scrollTarget.top;
};
this._scrolling = true;
if (scrollTimer) {
clearTimeout(scrollTimer);
}
scrollTimer = setTimeout(() => {
this._scrolling = false;
}, 80);
});
this._scrollTarget = scrollTarget;
}

View File

@ -40,10 +40,11 @@ class Renderer extends Component<{ renderer: SimulatorRenderer }> {
}
render() {
const { renderer } = this.props;
const { components, schemas } = LowCodeRenderer.others
return (
<LowCodeRenderer
schema={renderer.schema}
components={renderer.components}
components={components /*renderer.components*/}
appHelper={renderer.context}
// context={renderer.context}
designMode={renderer.designMode}
@ -60,11 +61,3 @@ class Renderer extends Component<{ renderer: SimulatorRenderer }> {
);
}
}
/*
class LowCodeRenderer extends Component<any> {
render() {
const { schema } = this.props;
return <div>{JSON.stringify(this.props.schema)}</div>
}
}*/

View File

@ -99,7 +99,18 @@ export class SimulatorRenderer {
load(asset: Asset): Promise<any> {
return loader.load(asset);
}
private instancesMap = new Map<string, ReactInstance[]>();
private unmountIntance(id: string, instance: ReactInstance) {
const instances = this.instancesMap.get(id);
if (instances) {
const i = instances.indexOf(instance);
if (i > -1) {
instances.splice(i, 1);
host.setInstance(id, instances);
}
}
}
mountInstance(id: string, instance: ReactInstance | null) {
const instancesMap = this.instancesMap;
if (instance == null) {
@ -108,41 +119,57 @@ export class SimulatorRenderer {
instances = instances.filter(checkInstanceMounted);
if (instances.length > 0) {
instancesMap.set(id, instances);
host.setInstance(id, instances);
} else {
instancesMap.delete(id);
host.setInstance(id, null);
}
}
return;
}
const unmountIntance = this.unmountIntance.bind(this);
const origId = (instance as any)[SYMBOL_VNID];
if (origId && origId !== id) {
// 另外一个节点的 instance 在此被复用了,需要从原来地方卸载
unmountIntance(origId, instance);
}
if (isElement(instance)) {
cacheReactKey(instance);
} else if (!(instance as any)[SYMBOL_VNID]) {
const origUnmout = instance.componentWillUnmount;
} else if (origId !== id) {
// 涵盖 origId == null || origId !== id 的情况
let origUnmount: any = instance.componentWillUnmount;
if (origUnmount && origUnmount.origUnmount) {
origUnmount = origUnmount.origUnmount;
}
// hack! delete instance from map
instance.componentWillUnmount = function() {
const instances = instancesMap.get(id);
if (instances) {
const i = instances.indexOf(instance);
if (i > -1) {
instances.splice(i, 1);
}
}
origUnmout && origUnmout.call(this);
const newUnmount = function (this: any) {
unmountIntance(id, instance);
origUnmount && origUnmount.call(this);
};
(newUnmount as any).origUnmount = origUnmount;
instance.componentWillUnmount = newUnmount;
}
(instance as any)[SYMBOL_VNID] = id;
let instances = this.instancesMap.get(id);
if (instances) {
const l = instances.length;
instances = instances.filter(checkInstanceMounted);
let updated = instances.length !== l;
if (!instances.includes(instance)) {
instances.push(instance);
updated = true;
}
if (!updated) {
return;
}
instancesMap.set(id, instances);
} else {
instancesMap.set(id, [instance]);
instances = [instance];
}
instancesMap.set(id, instances);
host.setInstance(id, instances);
}
private ctxMap = new Map<string, object>();
mountContext(id: string, ctx: object) {
this.ctxMap.set(id, ctx);
@ -167,21 +194,12 @@ export class SimulatorRenderer {
setNativeSelection(enableFlag: boolean) {
setNativeSelection(enableFlag);
}
/**
* @see ISimulator
*/
setDraggingState(state: boolean) {
cursor.setDragging(state);
}
/**
* @see ISimulator
*/
setCopyState(state: boolean) {
cursor.setCopy(state);
}
/**
* @see ISimulator
*/
clearState() {
cursor.release();
}

View File

@ -56,7 +56,7 @@
.lc-document {
width: 100%;
height: 100%;
&&-hidden {
&-hidden {
// todo:
display: none;
}

View File

@ -3,11 +3,10 @@ import { RootSchema, NodeData, isDOMText, isJSExpression, NodeSchema } from '../
import Node, { isNodeParent, insertChildren, insertChild, NodeParent } from './node/node';
import { Selection } from './selection';
import RootNode from './node/root-node';
import { ISimulator, ComponentInstance, Component, NodeInstance } from '../simulator';
import { ISimulator, Component } from '../simulator';
import { computed, obx } from '@recore/obx';
import Location from '../helper/location';
import { ComponentConfig } from '../component-config';
import { isElement } from '../../utils/is-element';
export default class DocumentModel {
/**
@ -234,8 +233,8 @@ export default class DocumentModel {
private _opened: boolean = true;
private _suspensed: boolean = false;
@obx.ref private _opened: boolean = true;
@obx.ref private _suspensed: boolean = false;
/**
* 
@ -288,6 +287,8 @@ export default class DocumentModel {
this._opened = true;
if (this._suspensed) {
this.setSuspense(false);
} else {
this.project.checkExclusive(this);
}
}

View File

@ -220,7 +220,10 @@ export default class Dragon {
};
const checkcopy = (e: MouseEvent) => {
if (newBie || e.altKey || e.ctrlKey) {
if (newBie) {
return;
}
if (e.altKey || e.ctrlKey) {
this.setCopyState(true);
} else {
this.setCopyState(false);
@ -243,7 +246,9 @@ export default class Dragon {
const dragstart = () => {
const locateEvent = createLocateEvent(boostEvent);
if (!newBie) {
if (newBie) {
this.setCopyState(true);
} else {
chooseSensor(locateEvent);
}
this.setDraggingState(true);
@ -277,19 +282,19 @@ export default class Dragon {
lastSensor.deactiveSensor();
}
this.setNativeSelection(true);
const copy = !newBie && this.isCopyState();
this.clearState();
let exception;
if (this._dragging) {
this._dragging = false;
try {
this.emitter.emit('dragend', { dragObject, copy: this.isCopyState() });
this.emitter.emit('dragend', { dragObject, copy });
} catch (ex) {
exception = ex;
}
}
this.clearState();
alwaysListen.removeEventListener('mousemove', move, true);
alwaysListen.removeEventListener('mouseup', over, true);
alwaysListen.removeEventListener('mousedown', over, true);

View File

@ -1,21 +1,44 @@
import { obx, computed } from '@recore/obx';
import { INodeSelector, IViewport } from '../simulator';
import Viewport from '../../builtins/simulator/host/viewport';
import { uniqueId } from '../../utils/unique-id';
export default class OffsetObserver {
@obx.ref hasOffset = false;
readonly id = uniqueId('oobx');
private lastOffsetLeft?: number;
private lastOffsetTop?: number;
private lastOffsetHeight?: number;
private lastOffsetWidth?: number;
@obx private height = 0;
@obx private width = 0;
@obx private left = 0;
@obx private top = 0;
@obx hasOffset = false;
@computed get offsetLeft() {
return this.left + this.viewport.scrollX;
if (!this.viewport.scrolling || this.lastOffsetLeft == null) {
this.lastOffsetLeft = (this.left + this.viewport.scrollX) * this.scale;
}
return this.lastOffsetLeft;
}
@computed get offsetTop() {
return this.top + this.viewport.scrollY;
if (!this.viewport.scrolling || this.lastOffsetTop == null) {
this.lastOffsetTop = (this.top + this.viewport.scrollY) * this.scale;
}
return this.lastOffsetTop;
}
@computed get offsetHeight() {
if (!this.viewport.scrolling || this.lastOffsetHeight == null) {
this.lastOffsetHeight = this.height * this.scale;
}
return this.lastOffsetHeight;
}
@computed get offsetWidth() {
if (!this.viewport.scrolling || this.lastOffsetWidth == null) {
this.lastOffsetWidth = this.width * this.scale;
}
return this.lastOffsetWidth;
}
@obx.ref height = 0;
@obx.ref width = 0;
@obx.ref left = 0;
@obx.ref top = 0;
@computed get scale() {
return this.viewport.scale;
@ -44,11 +67,13 @@ export default class OffsetObserver {
if (!rect) {
this.hasOffset = false;
} else {
this.hasOffset = true;
this.height = rect.height;
this.width = rect.width;
this.left = rect.left;
this.top = rect.top;
if (!this.viewport.scrolling || !this.hasOffset) {
this.height = rect.height;
this.width = rect.width;
this.left = rect.left;
this.top = rect.top;
this.hasOffset = true;
}
}
this.pid = pid = (window as any).requestIdleCallback(compute);
};
@ -59,7 +84,7 @@ export default class OffsetObserver {
this.pid = pid = (window as any).requestIdleCallback(compute);
}
destroy() {
purge() {
if (this.pid) {
(window as any).cancelIdleCallback(this.pid);
}

View File

@ -8,6 +8,7 @@ export default class ProjectView extends Component<{ designer: Designer }> {
render() {
const { designer } = this.props;
// TODO: support splitview
console.info(designer.project.documents);
return (
<div className="lc-project">
{designer.project.documents.map(doc => {

View File

@ -34,7 +34,14 @@ export interface IViewport extends IScrollable {
*
*/
readonly contentBounds: DOMRect;
/**
*
*/
readonly scrollTarget?: ScrollTarget;
/**
*
*/
readonly scrolling: boolean;
/**
* X
*/