import * as React from "react"; import * as ReactDOM from "react-dom"; import * as path from "path"; import scrollIntoView from "scroll-into-view-if-needed"; import { FocusComponent } from "../../../../focus"; import * as cx from "classnames"; import { compose } from "recompose"; import { DragSource } from "react-dnd"; import { withNodeDropTarget } from "./dnd-controller"; import { BeforeDropZone, AfterDropZone } from "./drop-zones.pc"; import { PCSourceTagNames, getPCNode, PCVisibleNode, getPCNodeDependency, PCElementLikeNode, getPCNodeContentNode, getNativeComponentName, PCModule, getPCNodeModule } from "paperclip"; import { InspectorNode, InspectorTreeNodeName } from "paperclip"; import { Dispatch } from "redux"; import { sourceInspectorLayerClicked, sourceInspectorLayerArrowClicked, sourceInspectorLayerLabelChanged, pcLayerRightClicked, pcLayerDoubleClicked } from "../../../../../actions"; import { containsNestedTreeNodeById, TreeMoveOffset } from "tandem-common"; import { BaseNodeLayerProps } from "./layer.pc"; import { withLayersPaneContext, LayersPaneContextProps } from "./contexts"; import { getInspectorContentNodeContainingChild } from "paperclip"; export type Props = { depth?: number; inShadow?: boolean; inspectorNode: InspectorNode; first?: boolean; last?: boolean; }; type ContextProps = { isSelected: boolean; editingLabel: boolean; isHovering: boolean; dispatch: Dispatch; label: string; }; type InnerProps = { editingLabel: boolean; isOver: boolean; canDrop: boolean; onLabelClick: () => any; nativeTagName: string; connectDragSource?: any; connectDropTarget?: any; onLabelRightClick: () => any; onLabelDoubleClick: () => any; onArrowButtonClick: () => any; onLabelInputKeyDown: () => any; onLabelInputBlur: () => any; assocSourceNodeName: string; } & ContextProps & Props; const DRAG_TYPE = "INSPECTOR_NODE"; const LAYER_PADDING = 16; const DROP_ZONE_PADDING = LAYER_PADDING + 4; const CONTAINER_STYLE = { display: "inline-block", minWidth: "100%" }; export default (Base: React.ComponentClass) => { let EnhancedLayer: React.ComponentClass; const enhance = compose( withLayersPaneContext( ( { inspectorNode }: Props, { graph, selectedInspectorNodes, hoveringInspectorNodes, dispatch, renameInspectorNodeId, rootInspectorNode }: LayersPaneContextProps ) => { const assocSourceNode = getPCNode( inspectorNode.name === InspectorTreeNodeName.CONTENT ? inspectorNode.sourceSlotNodeId : inspectorNode.sourceNodeId, graph ); const nativeTagName = getNativeComponentName( assocSourceNode as PCElementLikeNode, graph ); let label = assocSourceNode && (assocSourceNode as PCVisibleNode).label; // note that "Layer" is used as a default label here // to show layers that have an undefined label (which shouldn't exist) if (!label) { if (assocSourceNode.name === PCSourceTagNames.MODULE) { const dependency = getPCNodeDependency( inspectorNode.sourceNodeId, graph ); label = path.basename(dependency.uri); } else if (assocSourceNode.name === PCSourceTagNames.ELEMENT) { label = assocSourceNode.is || "Layer"; } else if ( assocSourceNode.name === PCSourceTagNames.COMPONENT_INSTANCE ) { label = "Layer"; } else if (assocSourceNode.name === PCSourceTagNames.TEXT) { label = "Layer"; } else if (assocSourceNode.name === PCSourceTagNames.COMPONENT) { label = "Layer"; } else if (assocSourceNode.name === PCSourceTagNames.SLOT) { label = assocSourceNode.label; } } return { dispatch, nativeTagName, editingLabel: renameInspectorNodeId === inspectorNode.id, isSelected: selectedInspectorNodes .map(node => node.id) .indexOf(inspectorNode.id) !== -1, isHovering: hoveringInspectorNodes .map(node => node.id) .indexOf(inspectorNode.id) !== -1, label, graph, inspectorNode, contentNode: getInspectorContentNodeContainingChild( inspectorNode, rootInspectorNode ), assocSourceNodeName: assocSourceNode.name }; } ), withNodeDropTarget(TreeMoveOffset.PREPEND), DragSource( DRAG_TYPE, { beginDrag({ inspectorNode }: InnerProps) { return inspectorNode; }, canDrag({ inspectorNode, graph }: any) { const sourceNode = getPCNode(inspectorNode.sourceNodeId, graph); const module = getPCNodeModule( inspectorNode.sourceNodeId, graph ) as PCModule; const contentSourceNode = getPCNodeContentNode( inspectorNode.sourceNodeId, module ); const canDrag = contentSourceNode && containsNestedTreeNodeById(sourceNode.id, contentSourceNode); return canDrag; } }, (connect, monitor) => ({ connectDragSource: connect.dragSource(), connectDragPreview: connect.dragPreview(), isDragging: monitor.isDragging() }) ), Base => { return class LayerController extends React.Component { onLabelClick = event => { this.props.dispatch( sourceInspectorLayerClicked(this.props.inspectorNode, event) ); }; onArrowButtonClick = event => { event.stopPropagation(); this.props.dispatch( sourceInspectorLayerArrowClicked(this.props.inspectorNode, event) ); }; onLabelDoubleClick = event => { if ( this.props.inspectorNode.name === InspectorTreeNodeName.SOURCE_REP ) { this.props.dispatch( pcLayerDoubleClicked(this.props.inspectorNode, event) ); } }; onLabelRightClick = (event: React.MouseEvent) => { this.props.dispatch( pcLayerRightClicked(this.props.inspectorNode, event) ); }; onLabelInputKeyDown = event => { if (event.key === "Enter") { this.persistLabelChange(event); } }; onLabelInputBlur = event => { this.persistLabelChange(event); }; private persistLabelChange = event => { const label = String((event.target as any).value || "").trim(); if (!label) { return; } this.props.dispatch( sourceInspectorLayerLabelChanged( this.props.inspectorNode, label, event ) ); }; componentDidMount() { this.makeVisible(this.props.isSelected); } componentDidUpdate(prevProps: InnerProps) { this.makeVisible(this.props.isSelected && !prevProps.isSelected); } private makeVisible(selected: boolean) { if (selected) { const self = ReactDOM.findDOMNode(this) as HTMLSpanElement; setTimeout(() => { const label = self.children[1].children[0].children[1]; // icky, but we're picking the label here scrollIntoView(label, { scrollMode: "if-needed" }); }, 10); } } shouldComponentUpdate(nextProps: InnerProps) { return ( this.props.depth !== nextProps.depth || this.props.first !== nextProps.first || this.props.last !== nextProps.last || this.props.isSelected !== nextProps.isSelected || this.props.isHovering !== nextProps.isHovering || this.props.isOver !== nextProps.isOver || this.props.canDrop !== nextProps.canDrop || this.props.inspectorNode !== nextProps.inspectorNode || this.props.connectDragSource !== nextProps.connectDragSource || this.props.label !== nextProps.label || this.props.connectDropTarget !== nextProps.connectDropTarget || this.props.inShadow !== nextProps.inShadow || this.props.assocSourceNodeName !== nextProps.assocSourceNodeName || this.props.editingLabel !== nextProps.editingLabel || this.props.nativeTagName !== nextProps.nativeTagName ); } render() { const { depth, isSelected, isHovering, nativeTagName, isOver, canDrop, inspectorNode, connectDragSource, label, first, last, connectDropTarget, inShadow, editingLabel, assocSourceNodeName } = this.props; const { onLabelClick, onArrowButtonClick, onLabelDoubleClick, onLabelRightClick, onLabelInputKeyDown, onLabelInputBlur } = this; return ( ); } }; }, (Base: React.ComponentClass) => ({ depth = 1, onLabelClick, editingLabel, isSelected, isHovering, isOver, canDrop, inspectorNode, nativeTagName, onArrowButtonClick, onLabelDoubleClick, onLabelRightClick, onLabelInputKeyDown, onLabelInputBlur, connectDragSource, label, first, last, connectDropTarget, inShadow, assocSourceNodeName }: InnerProps) => { const expanded = inspectorNode.expanded; const isSourceRep = inspectorNode.name === InspectorTreeNodeName.SOURCE_REP; inShadow = inShadow || inspectorNode.name === InspectorTreeNodeName.SHADOW; let children; isHovering = isHovering || (canDrop && isOver); if (expanded) { const childDepth = depth + 1; children = inspectorNode.children.map((child, i) => { return ( ); }); } const dropZoneStyle = { width: `calc(100% - ${depth * DROP_ZONE_PADDING}px)` }; const isFile = isSourceRep && assocSourceNodeName === PCSourceTagNames.MODULE; return ( {connectDropTarget( connectDragSource(
) )}
{children} {expanded && children.length ? null : ( )}
); } ); return (EnhancedLayer = enhance(Base)); };