/** * Copyright (c) 2014-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * * @flow * @format */ import React, {Component} from 'react'; import yoga, {Node} from 'yoga-layout'; import PositionGuide from './PositionGuide'; import PositionRecord from './PositionRecord'; import type {LayoutRecordT} from './LayoutRecord'; import type {Yoga$Direction} from 'yoga-layout'; import './YogaNode.css'; type Yoga$Node = any; type ComputedLayout = {| left: number, top: number, width: number, height: number, children: Array, node: Yoga$Node, |}; type Props = {| layoutDefinition: LayoutRecordT, className?: string, computedLayout?: ComputedLayout, path: Array, selectedNodePath?: ?Array, direction?: Yoga$Direction, label?: string, showGuides: boolean, onClick?: (path: Array) => void, onDoubleClick?: (path: Array) => void, |}; type State = { visible?: boolean, }; export default class YogaNode extends Component { node: Yoga$Node; static defaultProps = { path: [], label: 'root', showGuides: true, }; state = {}; computedLayout: ?ComputedLayout; rootNode: ?Yoga$Node; constructor(props: Props) { super(); if (!props.computedLayout) { // is root node this.calculateLayout(props); this.state = { visible: !Boolean(props.computedLayout), }; } } componentDidMount() { setTimeout(() => this.setState({visible: true}), 200); } componentWillReceiveProps(nextProps: Props) { if ( !nextProps.computedLayout && (!this.props.layoutDefinition.equals(nextProps.layoutDefinition) || this.props.direction !== nextProps.direction) ) { // is root node and the layout definition or settings changed this.calculateLayout(nextProps); } } componentWillUnmount() { if (this.rootNode) { this.rootNode.freeRecursive(); } } calculateLayout(props: Props) { const root = this.createYogaNodes(props.layoutDefinition); root.calculateLayout( props.layoutDefinition.width, props.layoutDefinition.height, props.direction, ); this.computedLayout = this.getComputedLayout(root); this.rootNode = root; } createYogaNodes = (layoutDefinition: LayoutRecordT): Yoga$Node => { const root = Node.create(); root.setWidth(layoutDefinition.width); root.setHeight(layoutDefinition.height); root.setJustifyContent(layoutDefinition.justifyContent); root.setAlignItems(layoutDefinition.alignItems); root.setAlignSelf(layoutDefinition.alignSelf); root.setFlexGrow(layoutDefinition.flexGrow); root.setFlexShrink(layoutDefinition.flexShrink); root.setPadding(yoga.EDGE_TOP, layoutDefinition.padding.top); root.setPadding(yoga.EDGE_RIGHT, layoutDefinition.padding.right); root.setPadding(yoga.EDGE_BOTTOM, layoutDefinition.padding.bottom); root.setPadding(yoga.EDGE_LEFT, layoutDefinition.padding.left); root.setBorder(yoga.EDGE_TOP, layoutDefinition.border.top); root.setBorder(yoga.EDGE_RIGHT, layoutDefinition.border.right); root.setBorder(yoga.EDGE_BOTTOM, layoutDefinition.border.bottom); root.setBorder(yoga.EDGE_LEFT, layoutDefinition.border.left); root.setMargin(yoga.EDGE_TOP, layoutDefinition.margin.top); root.setMargin(yoga.EDGE_RIGHT, layoutDefinition.margin.right); root.setMargin(yoga.EDGE_BOTTOM, layoutDefinition.margin.bottom); root.setMargin(yoga.EDGE_LEFT, layoutDefinition.margin.left); root.setPosition(yoga.EDGE_TOP, layoutDefinition.position.top); root.setPosition(yoga.EDGE_RIGHT, layoutDefinition.position.right); root.setPosition(yoga.EDGE_BOTTOM, layoutDefinition.position.bottom); root.setPosition(yoga.EDGE_LEFT, layoutDefinition.position.left); root.setPositionType(layoutDefinition.positionType); root.setDisplay(yoga.DISPLAY_FLEX); root.setAspectRatio(layoutDefinition.aspectRatio); root.setFlexWrap(layoutDefinition.flexWrap); root.setFlexDirection(layoutDefinition.flexDirection); (layoutDefinition.children || []) .map(this.createYogaNodes) .forEach((node, i) => { root.insertChild(node, i); }); return root; }; getComputedLayout = (node: Yoga$Node): ComputedLayout => { return { ...node.getComputedLayout(), node, children: Array.apply(null, Array(node.getChildCount())).map((_, i) => this.getComputedLayout(node.getChild(i)), ), }; }; onClick = (e: SyntheticMouseEvent<>) => { const {onClick} = this.props; if (onClick) { e.stopPropagation(); onClick(this.props.path); } }; onDoubleClick = (e: SyntheticMouseEvent<>) => { const {onDoubleClick} = this.props; if (onDoubleClick) { e.stopPropagation(); onDoubleClick(this.props.path); } }; showPositionGuides({node}: ComputedLayout) { const padding = PositionRecord({ top: node.getComputedPadding(yoga.EDGE_TOP), left: node.getComputedPadding(yoga.EDGE_LEFT), right: node.getComputedPadding(yoga.EDGE_RIGHT), bottom: node.getComputedPadding(yoga.EDGE_BOTTOM), }); const border = PositionRecord({ top: node.getComputedBorder(yoga.EDGE_TOP), left: node.getComputedBorder(yoga.EDGE_LEFT), right: node.getComputedBorder(yoga.EDGE_RIGHT), bottom: node.getComputedBorder(yoga.EDGE_BOTTOM), }); const margin = PositionRecord({ top: node.getComputedMargin(yoga.EDGE_TOP), left: node.getComputedMargin(yoga.EDGE_LEFT), right: node.getComputedMargin(yoga.EDGE_RIGHT), bottom: node.getComputedMargin(yoga.EDGE_BOTTOM), }); const position = PositionRecord({ top: node.getPosition(yoga.EDGE_TOP).value, left: node.getPosition(yoga.EDGE_LEFT).value, right: node.getPosition(yoga.EDGE_RIGHT).value, bottom: node.getPosition(yoga.EDGE_BOTTOM).value, }); return [ , , , , ]; } render() { const { layoutDefinition, className, path, selectedNodePath, label, } = this.props; // $FlowFixMe const computedLayout: ComputedLayout = this.props.computedLayout || this.computedLayout; const {left, top, width, height, children} = computedLayout; const isFocused = selectedNodePath && selectedNodePath.length === 0; return (
{label &&
{label}
} {isFocused && this.props.showGuides && this.showPositionGuides(computedLayout)} {(children || []).map((child: ComputedLayout, i) => ( 0 && selectedNodePath[0] === i ? selectedNodePath.slice(1) : null } path={path.concat(i)} onClick={this.props.onClick} onDoubleClick={this.props.onDoubleClick} showGuides={this.props.showGuides} /> ))}
); } }