Files
yoga/website/src/components/Playground/YogaNode.js
Héctor Ramos e224a29d1c Update copyright headers to yearless format
Summary: This change drops the year from the copyright headers and the LICENSE file.

Reviewed By: yungsters

Differential Revision: D9727774

fbshipit-source-id: df4fc1e4390733fe774b1a160dd41b4a3d83302a
2018-09-11 15:53:35 -07:00

302 lines
7.9 KiB
JavaScript

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
import React, {Component} from 'react';
import yoga, {Node} from 'yoga-layout/dist/entry-browser';
import PositionGuide from './PositionGuide';
import PositionRecord from './PositionRecord';
import LayoutRecord from './LayoutRecord';
import type {LayoutRecordT} from './LayoutRecord';
import type {Yoga$Direction, Yoga$Node} from 'yoga-layout';
import './YogaNode.css';
type ComputedLayout = {|
left: number,
top: number,
width: number,
height: number,
children: Array<ComputedLayout>,
node: Yoga$Node,
|};
type Props = {|
layoutDefinition: LayoutRecordT,
className?: string,
computedLayout?: ComputedLayout,
path: Array<number>,
selectedNodePath?: ?Array<number>,
direction?: Yoga$Direction,
label?: string,
showGuides: boolean,
onClick?: (path: Array<number>) => void,
onDoubleClick?: (path: Array<number>) => void,
|};
type State = {
visible?: boolean,
hovered: boolean,
};
export default class YogaNode extends Component<Props, State> {
node: Yoga$Node;
static defaultProps = {
path: [],
label: 'root',
showGuides: true,
};
state = {
hovered: false,
};
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();
}
}
onMouseMove = e => {
this.setState({hovered: e.target === this._ref});
};
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();
const defaultLayout = LayoutRecord({});
[
'width',
'height',
'minWidth',
'maxWidth',
'minHeight',
'maxHeight',
'justifyContent',
'alignItems',
'alignSelf',
'alignContent',
'flexGrow',
'flexShrink',
'positionType',
'aspectRatio',
'flexWrap',
'flexDirection',
].forEach(key => {
try {
const value =
layoutDefinition[key] === ''
? defaultLayout[key]
: layoutDefinition[key];
root[`set${key[0].toUpperCase()}${key.substr(1)}`](value);
} catch (e) {}
});
['padding', 'margin', 'position', 'border'].forEach(key => {
['top', 'right', 'bottom', 'left'].forEach(direction => {
try {
root[`set${key[0].toUpperCase()}${key.substr(1)}`](
yoga[`EDGE_${direction.toUpperCase()}`],
layoutDefinition[key][direction],
);
} catch (e) {}
});
});
root.setDisplay(yoga.DISPLAY_FLEX);
(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);
}
};
onMouseLeave = (e: SyntheticMouseEvent<>) => this.setState({hovered: false});
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 [
<PositionGuide
key="border"
inset
position={border}
color="rgba(251, 170, 51, 0.15)"
reverse={node.getFlexWrap() === yoga.WRAP_WRAP_REVERSE}
/>,
<PositionGuide
key="padding"
inset
offset={border}
position={padding}
color="rgba(123, 179, 41, 0.1)"
reverse={node.getFlexWrap() === yoga.WRAP_WRAP_REVERSE}
/>,
<PositionGuide
key="margin"
position={margin}
color="rgba(214, 43, 28, 0.1)"
/>,
<PositionGuide
key="position"
offset={margin}
position={position}
color="rgba(115, 51, 205, 0.1)"
/>,
];
}
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 (
<div
className={`YogaNode ${isFocused ? 'focused' : ''} ${className || ''} ${
this.state.visible ? '' : 'invisible'
} ${this.state.hovered ? 'hover' : ''}`}
style={path.length == 0 ? {width, height} : {left, top, width, height}}
onDoubleClick={this.onDoubleClick}
onMouseMove={this.onMouseMove}
onMouseLeave={this.onMouseLeave}
ref={ref => {
this._ref = ref;
}}
onClick={this.onClick}>
{label && <div className="label">{label}</div>}
{isFocused &&
this.props.showGuides &&
this.showPositionGuides(computedLayout)}
{(children || []).map((child: ComputedLayout, i) => (
<YogaNode
key={i}
computedLayout={child}
label={String(i + 1)}
layoutDefinition={layoutDefinition.children.get(i)}
selectedNodePath={
selectedNodePath &&
selectedNodePath.length > 0 &&
selectedNodePath[0] === i
? selectedNodePath.slice(1)
: null
}
path={path.concat(i)}
onClick={this.props.onClick}
onDoubleClick={this.props.onDoubleClick}
showGuides={this.props.showGuides}
/>
))}
</div>
);
}
}