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() { render() {
const host = this.context as SimulatorHost; const host = this.context as SimulatorHost;
const current = this.current; const current = this.current;
if (!current) { console.info('current', current)
if (!current || host.viewport.scrolling) {
return <Fragment />; return <Fragment />;
} }
const instances = host.getComponentInstances(current); const instances = host.getComponentInstances(current);
console.info('current instances', instances)
if (!instances || instances.length < 1) { if (!instances || instances.length < 1) {
return <Fragment />; return <Fragment />;
} }

View File

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

View File

@ -15,6 +15,7 @@ import {
DragNodeDataObject, DragNodeDataObject,
isDragAnyObject, isDragAnyObject,
isDragNodeObject, isDragNodeObject,
isDragNodeDataObject,
} from '../../../designer/helper/dragon'; } from '../../../designer/helper/dragon';
import { import {
LocationData, LocationData,
@ -347,11 +348,20 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
return null; 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 * @see ISimulator
*/ */
getComponentInstances(node: Node): ReactInstance[] | null { 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 { 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)); return items.every(item => this.checkNestingDown(dropTarget, item));
} }
checkDropTarget(dropTarget: NodeParent, dragObject: DragNodeObject | DragNodeDataObject): boolean { 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)); return items.every(item => this.checkNestingUp(dropTarget, item));
} }

View File

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

View File

@ -40,10 +40,11 @@ class Renderer extends Component<{ renderer: SimulatorRenderer }> {
} }
render() { render() {
const { renderer } = this.props; const { renderer } = this.props;
const { components, schemas } = LowCodeRenderer.others
return ( return (
<LowCodeRenderer <LowCodeRenderer
schema={renderer.schema} schema={renderer.schema}
components={renderer.components} components={components /*renderer.components*/}
appHelper={renderer.context} appHelper={renderer.context}
// context={renderer.context} // context={renderer.context}
designMode={renderer.designMode} 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> { load(asset: Asset): Promise<any> {
return loader.load(asset); return loader.load(asset);
} }
private instancesMap = new Map<string, ReactInstance[]>(); 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) { mountInstance(id: string, instance: ReactInstance | null) {
const instancesMap = this.instancesMap; const instancesMap = this.instancesMap;
if (instance == null) { if (instance == null) {
@ -108,41 +119,57 @@ export class SimulatorRenderer {
instances = instances.filter(checkInstanceMounted); instances = instances.filter(checkInstanceMounted);
if (instances.length > 0) { if (instances.length > 0) {
instancesMap.set(id, instances); instancesMap.set(id, instances);
host.setInstance(id, instances);
} else { } else {
instancesMap.delete(id); instancesMap.delete(id);
host.setInstance(id, null);
} }
} }
return; 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)) { if (isElement(instance)) {
cacheReactKey(instance); cacheReactKey(instance);
} else if (!(instance as any)[SYMBOL_VNID]) { } else if (origId !== id) {
const origUnmout = instance.componentWillUnmount; // 涵盖 origId == null || origId !== id 的情况
let origUnmount: any = instance.componentWillUnmount;
if (origUnmount && origUnmount.origUnmount) {
origUnmount = origUnmount.origUnmount;
}
// hack! delete instance from map // hack! delete instance from map
instance.componentWillUnmount = function() { const newUnmount = function (this: any) {
const instances = instancesMap.get(id); unmountIntance(id, instance);
if (instances) { origUnmount && origUnmount.call(this);
const i = instances.indexOf(instance);
if (i > -1) {
instances.splice(i, 1);
}
}
origUnmout && origUnmout.call(this);
}; };
(newUnmount as any).origUnmount = origUnmount;
instance.componentWillUnmount = newUnmount;
} }
(instance as any)[SYMBOL_VNID] = id; (instance as any)[SYMBOL_VNID] = id;
let instances = this.instancesMap.get(id); let instances = this.instancesMap.get(id);
if (instances) { if (instances) {
const l = instances.length;
instances = instances.filter(checkInstanceMounted); instances = instances.filter(checkInstanceMounted);
let updated = instances.length !== l;
if (!instances.includes(instance)) { if (!instances.includes(instance)) {
instances.push(instance); instances.push(instance);
updated = true;
}
if (!updated) {
return;
} }
instancesMap.set(id, instances);
} else { } else {
instancesMap.set(id, [instance]); instances = [instance];
} }
instancesMap.set(id, instances);
host.setInstance(id, instances);
} }
private ctxMap = new Map<string, object>(); private ctxMap = new Map<string, object>();
mountContext(id: string, ctx: object) { mountContext(id: string, ctx: object) {
this.ctxMap.set(id, ctx); this.ctxMap.set(id, ctx);
@ -167,21 +194,12 @@ export class SimulatorRenderer {
setNativeSelection(enableFlag: boolean) { setNativeSelection(enableFlag: boolean) {
setNativeSelection(enableFlag); setNativeSelection(enableFlag);
} }
/**
* @see ISimulator
*/
setDraggingState(state: boolean) { setDraggingState(state: boolean) {
cursor.setDragging(state); cursor.setDragging(state);
} }
/**
* @see ISimulator
*/
setCopyState(state: boolean) { setCopyState(state: boolean) {
cursor.setCopy(state); cursor.setCopy(state);
} }
/**
* @see ISimulator
*/
clearState() { clearState() {
cursor.release(); cursor.release();
} }

View File

@ -56,7 +56,7 @@
.lc-document { .lc-document {
width: 100%; width: 100%;
height: 100%; height: 100%;
&&-hidden { &-hidden {
// todo: // todo:
display: none; 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 Node, { isNodeParent, insertChildren, insertChild, NodeParent } from './node/node';
import { Selection } from './selection'; import { Selection } from './selection';
import RootNode from './node/root-node'; 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 { computed, obx } from '@recore/obx';
import Location from '../helper/location'; import Location from '../helper/location';
import { ComponentConfig } from '../component-config'; import { ComponentConfig } from '../component-config';
import { isElement } from '../../utils/is-element';
export default class DocumentModel { export default class DocumentModel {
/** /**
@ -234,8 +233,8 @@ export default class DocumentModel {
private _opened: boolean = true; @obx.ref private _opened: boolean = true;
private _suspensed: boolean = false; @obx.ref private _suspensed: boolean = false;
/** /**
*  * 
@ -288,6 +287,8 @@ export default class DocumentModel {
this._opened = true; this._opened = true;
if (this._suspensed) { if (this._suspensed) {
this.setSuspense(false); this.setSuspense(false);
} else {
this.project.checkExclusive(this);
} }
} }

View File

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

View File

@ -1,21 +1,44 @@
import { obx, computed } from '@recore/obx'; import { obx, computed } from '@recore/obx';
import { INodeSelector, IViewport } from '../simulator'; import { INodeSelector, IViewport } from '../simulator';
import Viewport from '../../builtins/simulator/host/viewport'; import { uniqueId } from '../../utils/unique-id';
export default class OffsetObserver { 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() { @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() { @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() { @computed get scale() {
return this.viewport.scale; return this.viewport.scale;
@ -44,11 +67,13 @@ export default class OffsetObserver {
if (!rect) { if (!rect) {
this.hasOffset = false; this.hasOffset = false;
} else { } else {
this.hasOffset = true; if (!this.viewport.scrolling || !this.hasOffset) {
this.height = rect.height; this.height = rect.height;
this.width = rect.width; this.width = rect.width;
this.left = rect.left; this.left = rect.left;
this.top = rect.top; this.top = rect.top;
this.hasOffset = true;
}
} }
this.pid = pid = (window as any).requestIdleCallback(compute); this.pid = pid = (window as any).requestIdleCallback(compute);
}; };
@ -59,7 +84,7 @@ export default class OffsetObserver {
this.pid = pid = (window as any).requestIdleCallback(compute); this.pid = pid = (window as any).requestIdleCallback(compute);
} }
destroy() { purge() {
if (this.pid) { if (this.pid) {
(window as any).cancelIdleCallback(this.pid); (window as any).cancelIdleCallback(this.pid);
} }

View File

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

View File

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