Remove standalone playground

Summary: This is an older version of the standalone playground which is outdated and not used anymore

Reviewed By: emilsjolander

Differential Revision: D6998970

fbshipit-source-id: a27bee81b6f85f70c35a8d3e7ea7b06486c52626
This commit is contained in:
Daniel Büchele
2018-02-15 08:25:18 -08:00
committed by Facebook Github Bot
parent 10061af491
commit 4c3eebd3e5
39 changed files with 0 additions and 9838 deletions

View File

@@ -1,3 +0,0 @@
{
"extends": "react-app"
}

View File

@@ -1,11 +0,0 @@
[ignore]
[include]
[libs]
[lints]
[options]
[strict]

21
playground/.gitignore vendored
View File

@@ -1,21 +0,0 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@@ -1,17 +0,0 @@
syntax: re
# Lines that start with '#' are comments.
#
# Do not edit this file! Make changes to a .gitignore instead, then run
# the following from the fbsource root:
# ./tools/generate_hgignore.py xplat/yoga/playground
#
# @generated SignedSource<<86808b604700421b254b8bebf134394b>>
#####
### From .gitignore
#####
^(?:node\_modules|coverage|build)(?:/|$)
(?:^|/)(?:\.DS\_Store|\.env\.local|\.env\.development\.local|\.env\.test\.local|\.env\.production\.local|npm\-debug\.log[^/]*|yarn\-debug\.log[^/]*|yarn\-error\.log[^/]*)(?:/|$)

View File

@@ -1,9 +0,0 @@
# Yoga Playground
Try [Yoga](https://facebook.github.io/yoga/) in your browser and understand how Flexbox is working. This playground uses the asm.js version of Yoga to be able to execute Yoga in the browser.
## Features
* Build a flexbox layout and try how flexbox properties work
* Share your playrgound with other people by just copying the URL
* Generatres ready to use code for Litho, ComponentKit and React Native

View File

@@ -1,17 +0,0 @@
const {injectBabelPlugin} = require('react-app-rewired');
const rewireLess = require('react-app-rewire-less');
module.exports = function override(config, env) {
config = injectBabelPlugin(
['import', {libraryName: 'antd', style: true}],
config,
); // change importing css to less
config = rewireLess.withLoaderOptions({
modifyVars: {
//'@primary-color': '#95ddcf',
'@font-family': 'Barlow',
'@grid-gutter-width': 15,
},
})(config, env);
return config;
};

View File

@@ -1,26 +0,0 @@
{
"name": "yoga-playground",
"version": "0.1.0",
"private": true,
"dependencies": {
"antd": "^3.1.3",
"immutable": "^4.0.0-rc.9",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-scripts": "1.0.17",
"react-syntax-highlighter": "^6.1.2",
"yoga-layout": "^1.8.4"
},
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test --env=jsdom",
"eject": "react-app-rewired eject"
},
"devDependencies": {
"babel-plugin-import": "^1.6.3",
"flow-bin": "^0.63.1",
"react-app-rewire-less": "^2.1.0",
"react-app-rewired": "^1.4.0"
}
}

View File

@@ -1,40 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link href="https://fonts.googleapis.com/css?family=Barlow" rel="stylesheet">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Yoga Playground</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<react component="Playground" />
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@@ -1,9 +0,0 @@
{
"short_name": "Yoga Playground",
"name": "Yoga Playground",
"icons": [],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -1,20 +0,0 @@
.Code {
display: flex;
flex-direction: column;
max-height: 100%;
}
.CodeContainer {
overflow: scroll;
}
.CodeLanguageSelector.ant-radio-group {
display: flex;
margin: 10px 15px;
}
.CodeLanguageSelector.ant-radio-group .ant-radio-button-wrapper {
flex-grow: 1;
flex-basis: 0;
text-align: center;
}

View File

@@ -1,103 +0,0 @@
// @flow
import React, {Component} from 'react';
import SyntaxHighlighter, {
registerLanguage,
} from 'react-syntax-highlighter/prism-light';
import styles from 'react-syntax-highlighter/styles/prism/prism';
import jsx from 'react-syntax-highlighter/languages/prism/jsx';
//import javascript from 'react-syntax-highlighter/languages/prism/javascript';
import java from 'react-syntax-highlighter/languages/prism/java';
import objectivec from 'react-syntax-highlighter/languages/prism/objectivec';
import type {LayoutRecordT} from './LayoutRecord';
import {Radio} from 'antd';
import CodeJavaScript from './CodeJavaScript';
import CodeLitho from './CodeLitho';
import CodeReactNative from './CodeReactNative';
import CodeComponentKit from './CodeComponentKit';
import './Code.css';
import type {Yoga$Direction} from 'yoga-layout';
const RadioButton = Radio.Button;
const RadioGroup = Radio.Group;
registerLanguage('jsx', jsx);
//registerLanguage('javascript', javascript);
registerLanguage('java', java);
registerLanguage('objectivec', objectivec);
type Language = 'JavaScript' | 'Litho' | 'ComponentKit' | 'React Native';
type Props = {
layoutDefinition: LayoutRecordT,
direction: Yoga$Direction,
languages: Array<Language>,
};
type State = {
language: Language,
};
export default class Code extends Component<Props, State> {
static defaultProps = {
languages: [/*'JavaScript', */ 'Litho', 'ComponentKit', 'React Native'],
};
state = {
language: 'Litho',
};
generateCode(lang: Language): string {
if (lang === 'JavaScript') {
return CodeJavaScript(this.props.layoutDefinition, this.props.direction);
} else if (lang === 'Litho') {
return CodeLitho(this.props.layoutDefinition, this.props.direction);
} else if (lang === 'ComponentKit') {
return CodeComponentKit(
this.props.layoutDefinition,
this.props.direction,
);
} else if (lang === 'React Native') {
return CodeReactNative(this.props.layoutDefinition, this.props.direction);
} else {
return '';
}
}
getLanguage(): string {
if (this.state.language === 'Litho') {
return 'java';
} else if (this.state.language === 'React Native') {
return 'javascript';
} else if (this.state.language === 'ComponentKit') {
return 'objectivec';
} else {
return this.state.language;
}
}
render() {
return (
<div className="Code">
<RadioGroup
className="CodeLanguageSelector"
onChange={e => this.setState({language: e.target.value})}
value={this.state.language}>
{this.props.languages.map(lang => (
<RadioButton key={lang} value={lang}>
{lang}
</RadioButton>
))}
</RadioGroup>
<div className="CodeContainer">
<SyntaxHighlighter
language={this.getLanguage()}
style={styles}
customStyle={{fontSize: '13px', backgroundColor: 'white'}}
lineNumberStyle={{userSelect: 'none', opacity: 0.5}}
codeTagProps={{style: {tabSize: 4}}}
showLineNumbers>
{this.generateCode(this.state.language)}
</SyntaxHighlighter>
</div>
</div>
);
}
}

View File

@@ -1,184 +0,0 @@
// @flow
import yoga from 'yoga-layout';
import LayoutRecord from './LayoutRecord';
import PositionRecord from './PositionRecord';
import type {LayoutRecordT} from './LayoutRecord';
import type {Yoga$Direction /* Yoga$Node */} from 'yoga-layout';
type Yoga$Node = any;
const enumLookup = {
flexDirection: {
[yoga.FLEX_DIRECTION_COLUMN]: 'CKFlexboxDirectionVertical',
[yoga.FLEX_DIRECTION_ROW]: 'CKFlexboxDirectionHorizontal',
[yoga.FLEX_DIRECTION_COLUMN_REVERSE]: 'CKFlexboxDirectionVerticalReverse',
[yoga.FLEX_DIRECTION_ROW_REVERSE]: 'CKFlexboxDirectionHorizontalReverse',
},
alignItems: {
[yoga.ALIGN_FLEX_START]: 'CKFlexboxAlignItemsStart',
[yoga.ALIGN_FLEX_END]: 'CKFlexboxAlignItemsEnd',
[yoga.ALIGN_CENTER]: 'CKFlexboxAlignItemsCenter',
[yoga.ALIGN_BASELINE]: 'CKFlexboxAlignItemsBaseline',
[yoga.ALIGN_STRETCH]: 'CKFlexboxAlignItemsStretch',
},
alignSelf: {
[yoga.ALIGN_AUTO]: 'CKFlexboxAlignSelfAuto',
[yoga.ALIGN_FLEX_START]: 'CKFlexboxAlignSelfStart',
[yoga.ALIGN_FLEX_END]: 'CKFlexboxAlignSelfEnd',
[yoga.ALIGN_CENTER]: 'CKFlexboxAlignSelfCenter',
[yoga.ALIGN_BASELINE]: 'CKFlexboxAlignSelfBaseline',
[yoga.ALIGN_STRETCH]: 'CKFlexboxAlignSelfStretch',
},
alignContent: {
[yoga.ALIGN_FLEX_START]: 'CKFlexboxAlignContentStart',
[yoga.ALIGN_FLEX_END]: 'CKFlexboxAlignContentEnd',
[yoga.ALIGN_CENTER]: 'CKFlexboxAlignContentCenter',
[yoga.ALIGN_SPACE_BETWEEN]: 'CKFlexboxAlignContentSpaceBetween',
[yoga.ALIGN_SPACE_AROUND]: 'CKFlexboxAlignContentSpaceAround',
[yoga.ALIGN_STRETCH]: 'CKFlexboxAlignContentStretch',
},
justifyContent: {
[yoga.JUSTIFY_FLEX_START]: 'CKFlexboxJustifyContentStart',
[yoga.JUSTIFY_CENTER]: 'CKFlexboxJustifyContentCenter',
[yoga.JUSTIFY_FLEX_END]: 'CKFlexboxJustifyContentEnd',
[yoga.JUSTIFY_SPACE_BETWEEN]: 'CKFlexboxJustifyContentSpaceBetween',
[yoga.JUSTIFY_SPACE_AROUND]: 'CKFlexboxJustifyContentSpaceAround',
},
flexWrap: {
[yoga.WRAP_NO_WRAP]: 'CKFlexboxWrapNoWrap',
[yoga.WRAP_WRAP]: 'CKFlexboxWrapWrap',
[yoga.WRAP_WRAP_REVERSE]: 'CKFlexboxWrapWrapReverse',
},
positionType: {
[yoga.POSITION_TYPE_RELATIVE]: 'CKFlexboxPositionTypeRelative',
[yoga.POSITION_TYPE_ABSOLUTE]: 'CKFlexboxPositionTypeAbsolute',
},
};
const untouchedLayout = LayoutRecord({});
const untouchedPosition = PositionRecord({});
function keyLookup(key: string): string {
const keyLookup = {
flexWrap: 'wrap',
flexDirection: 'direction',
};
return keyLookup[key] || key;
}
function getLayoutCode(
node: LayoutRecordT,
indent: string = '',
isRoot?: boolean,
): string {
const lines = [];
const isFlexbox = node.children.size > 0;
lines.push(
indent +
`${isRoot ? '' : `.component = \n${indent}`}[${
isFlexbox ? 'CKFlexboxComponent' : 'CKComponent'
}`,
);
lines.push(indent + ` newWithView:{}`);
lines.push(indent + ` size:{${node.width},${node.height}}`);
const CKFlexboxComponentStyle = [
'direction',
'margin',
'justifyContent',
'alignItems',
'alignContent',
'wrap',
'padding',
'border',
];
const CKFlexboxComponentChild = [
'margin',
'padding',
'flexGrow',
'flexShrink',
'flexBasis',
'alignSelf',
'position',
];
if (isFlexbox) {
// render styles
lines.push(indent + ` style:{`);
indent += '\t';
CKFlexboxComponentStyle.forEach(key => {
let line = renderKey(node, key, indent);
if (line) {
lines.push(line);
}
});
indent = indent.substr(-1);
lines.push(indent + ` }`);
// render children
lines.push(indent + ' children:{');
lines.push(
...node.children
.toJSON()
.map(
child =>
`${indent}\t{\n${getLayoutCode(
child,
indent + '\t\t',
)}\n${indent}\t},`,
),
);
lines.push(indent + `}]${isRoot ? ';' : ''}`);
} else {
lines[lines.length - 1] += ']';
CKFlexboxComponentChild.forEach(key => {
let line = renderKey(node, key, indent);
if (line) {
lines.push(line);
}
});
}
return lines.join('\n');
}
function renderKey(node: Yoga$Node, key: string, indent: string): ?string {
if (
node[key] instanceof PositionRecord &&
!node[key].equals(untouchedPosition)
) {
const lines = [];
lines.push(indent + `.${key} = {`);
if (key === 'position') {
lines.push(
indent + `\t.type = ${enumLookup.positionType[node.positionType]},`,
);
}
['top', 'left', 'right', 'bottom'].forEach(pKey => {
if (node[key][pKey]) {
lines.push(indent + `\t.${pKey} = ${node[key][pKey]},`);
}
});
lines.push(indent + `},`);
return lines.join('\n');
} else if (node[key] !== untouchedLayout[key]) {
if (enumLookup[key]) {
return indent + `.${keyLookup(key)} = ${enumLookup[key][node[key]]},`;
} else {
console.error(`Unknown property ${key}`);
}
}
}
export default function generateCode(
root: LayoutRecordT,
direction: Yoga$Direction,
): string {
return ['CKFlexboxComponent *c =', getLayoutCode(root, '\t', true)].join(
'\n',
);
}

View File

@@ -1,127 +0,0 @@
// @flow
import yoga from 'yoga-layout';
import LayoutRecord from './LayoutRecord';
import PositionRecord from './PositionRecord';
import type {LayoutRecordT} from './LayoutRecord';
import type {Yoga$Direction} from 'yoga-layout';
export const JSEnumLookup = {
justifyContent: 'JUSTIFY_',
alignItems: 'ALIGN_',
alignContent: 'ALIGN_',
alignSelf: 'ALIGN_',
position: 'POSITION_',
flexDirection: 'DIRECTION_',
flexWrap: 'WRAP_',
positionType: 'POSITION_TYPE_',
direction: 'DIRECTION_',
};
function getEnum(yogaEnum: string, value: string | number): string {
return `yoga.${Object.keys(yoga)
.filter(key => key.toLowerCase().startsWith(yogaEnum.toLowerCase()))
.find(key => yoga[key] === value) || value}`;
}
function setProperty(
name: string,
key: string,
value: string,
enumValue?: string,
): string {
return [
name,
'.set',
key[0].toUpperCase() + key.substr(1),
'(',
enumValue ? `${enumValue}, ` : '',
JSEnumLookup[key] ? getEnum(JSEnumLookup[key], value) : value,
');',
].join('');
}
function getLayoutCode(
node: LayoutRecordT,
name: string,
index: number,
): string {
const lines = [];
const childName = (i: number) => `${name === 'root' ? 'node' : name}_${i}`;
lines.push(
...node.children.map((node, i) => getLayoutCode(node, childName(i), i)),
);
lines.push('', `// create node ${name}`, `const ${name} = Node.create();`);
const untouchedNode = LayoutRecord({width: '', height: ''});
Object.keys(untouchedNode.toJS()).forEach(key => {
if (key !== 'children' && untouchedNode[key] !== node[key]) {
if (node[key] instanceof PositionRecord) {
// iterate through position record
const {top, left, right, bottom} = node[key].toJS();
if (
top !== untouchedNode[key].top &&
top === left &&
top === right &&
top === bottom
) {
// all edges
lines.push(setProperty(name, key, node[key].top, getEnum('edge', 8)));
return;
}
const alreadySet = [];
if (top !== untouchedNode[key].top && top === bottom) {
lines.push(setProperty(name, key, node[key].top, getEnum('edge', 7)));
alreadySet.push('top', 'bottom');
}
if (left !== untouchedNode[key].left && left === right) {
lines.push(
setProperty(name, key, node[key].left, getEnum('edge', 6)),
);
alreadySet.push('left', 'right');
}
['left', 'top', 'right', 'bottom'].forEach((pKey, i) => {
if (
node[key][pKey] !== untouchedNode[key][pKey] &&
alreadySet.indexOf(pKey) === -1
) {
lines.push(
setProperty(name, key, node[key][pKey], getEnum('edge', i)),
);
}
});
} else {
lines.push(setProperty(name, key, node[key]));
}
}
});
if (node.children && node.children.size > 0) {
lines.push(
'',
'// insert children',
...node.children.map(
(_, i) => `${name}.insertChild(${childName(i)}, ${i});`,
),
);
}
return lines.join('\n');
}
export default function generateCode(
root: LayoutRecordT,
direction: Yoga$Direction,
): string {
const rootNodeName = 'root';
return [
`import yoga, {Node} from 'yoga-layout';`,
getLayoutCode(root, rootNodeName, 0),
'',
`${rootNodeName}.calculateLayout(${root.width}, ${root.height}, ${getEnum(
'direction',
direction,
)});`,
`${rootNodeName}.getComputedLayout();`,
].join('\n');
}

View File

@@ -1,161 +0,0 @@
// @flow
import yoga from 'yoga-layout';
import LayoutRecord from './LayoutRecord';
import PositionRecord from './PositionRecord';
import {JSEnumLookup} from './CodeJavaScript';
import type {LayoutRecordT} from './LayoutRecord';
import type {Yoga$Direction} from 'yoga-layout';
function getEnum(yogaEnum: string, value: string | number): string {
const enumLookup = {
justifyContent: 'Justify',
alignItems: 'Align',
alignContent: 'Align',
alignSelf: 'Align',
position: 'Position',
flexWrap: 'Wrap',
positionType: 'PositionType',
direction: 'Driection',
};
if (!enumLookup[yogaEnum]) {
return String(value);
} else {
const enumValue = Object.keys(yoga)
.filter(key =>
key.toLowerCase().startsWith(JSEnumLookup[yogaEnum].toLowerCase()),
)
.find(key => yoga[key] === value);
return `Yoga${enumLookup[yogaEnum]}.${
enumValue ? enumValue.replace(/^([A-Z]+)_/, '') : value
}`;
}
}
function getLayoutCode(
node: LayoutRecordT,
indent: string = '',
isReturning?: boolean,
): string {
const lines = [];
const flexDirection = {
[yoga.FLEX_DIRECTION_ROW]: 'Row',
[yoga.FLEX_DIRECTION_ROW_REVERSE]: 'RowReverse',
[yoga.FLEX_DIRECTION_COLUMN]: 'Column',
[yoga.FLEX_DIRECTION_COLUMN_REVERSE]: 'ColumnReverse',
};
lines.push(
indent +
`${isReturning ? 'return ' : ''}${
flexDirection[node.flexDirection]
}.create(c)`,
);
if (node.children.size > 0) {
lines.push(
...node.children
.toJSON()
.map(
child =>
`${indent}\t.child(\n${getLayoutCode(child, indent + '\t\t')})`,
),
);
}
const untouchedLayout = LayoutRecord({width: '', height: ''});
const untouchedPosition = PositionRecord({});
Object.keys(node.toJSON()).forEach(key => {
if (
node[key] instanceof PositionRecord &&
!node[key].equals(untouchedPosition)
) {
if (key === 'border') {
lines.push(indent + '\t.border(', indent + '\t\tBorder.create(c)');
}
const {top, left, right, bottom} = node[key].toJS();
if (
top !== untouchedPosition.top &&
top === left &&
top === right &&
top === bottom
) {
// all edges
lines.push(
indent +
(key === 'border'
? `\t\t\t.widthDip(YogaEdge.ALL, ${node[key].top})`
: `\t.${key}Dip(YogaEdge.ALL, ${node[key].top})`),
);
return;
}
const alreadySet = [];
if (top !== untouchedPosition.top && top === bottom) {
lines.push(
indent +
(key === 'border'
? `\t\t\t.widthDip(YogaEdge.VERTICAL, ${node[key].top})`
: `\t.${key}Dip(YogaEdge.VERTICAL, ${node[key].top})`),
);
alreadySet.push('top', 'bottom');
}
if (left !== untouchedPosition.left && left === right) {
lines.push(
indent +
(key === 'border'
? `\t\t\t.widthDip(YogaEdge.HORIZONTAL, ${node[key].left})`
: `\t.${key}Dip(YogaEdge.HORIZONTAL, ${node[key].left})`),
);
alreadySet.push('left', 'right');
}
['left', 'top', 'right', 'bottom'].forEach((pKey, i) => {
if (
node[key][pKey] !== untouchedPosition[pKey] &&
alreadySet.indexOf(pKey) === -1
) {
lines.push(
indent +
(key === 'border'
? `\t\t\t.widthDip(YogaEdge.${pKey.toUpperCase()}, ${
node.border[pKey]
})`
: `\t.${key}Dip(YogaEdge.${pKey.toUpperCase()}, ${
node[key][pKey]
})`),
);
}
});
if (key === 'border') {
lines.push(
indent + '\t\t\t.color(YogaEdge.ALL, 0xfff36b7f)',
indent + '\t\t\t.build())',
);
}
} else if (
key !== 'children' &&
key !== 'flexDirection' &&
node[key] !== untouchedLayout[key]
) {
lines.push(indent + `\t.${key}(${getEnum(key, node[key])})`);
}
});
return lines.join('\n');
}
export default function generateCode(
root: LayoutRecordT,
direction: Yoga$Direction,
): string {
return [
'@LayoutSpec',
'public class PlaygroundComponentSpec {',
'\t@OnCreateLayout',
'\tstatic Component onCreateLayout(ComponentContext c) {',
getLayoutCode(root, '\t\t', true),
'\t\t\t.build();',
'\t}',
'}',
].join('\n');
}

View File

@@ -1,122 +0,0 @@
// @flow
import yoga from 'yoga-layout';
import LayoutRecord from './LayoutRecord';
import PositionRecord from './PositionRecord';
import {JSEnumLookup} from './CodeJavaScript';
import type {LayoutRecordT} from './LayoutRecord';
import type {Yoga$Direction} from 'yoga-layout';
function getEnum(yogaEnum: string, value: string | number): string {
const enumValue = Object.keys(yoga)
.filter(
key =>
JSEnumLookup[yogaEnum] &&
key.toLowerCase().startsWith(JSEnumLookup[yogaEnum].toLowerCase()),
)
.find(key => yoga[key] === value);
return enumValue
? "'" +
enumValue
.replace(/^([A-Z]+)_/, '')
.replace('_', '-')
.toLowerCase() +
"'"
: String(value);
}
function getLayoutCode(node: LayoutRecordT, indent: string = ''): string {
const lines = [];
const untouchedLayout = LayoutRecord({width: '', height: ''});
const untouchedPosition = PositionRecord({});
lines.push(indent + '<View style={{');
lines.push(indent + ' flex: 1,');
Object.keys(node.toJSON()).forEach(key => {
if (key === 'border' && !node.border.equals(untouchedPosition)) {
['Top', 'Left', 'Right', 'Bottom'].forEach(pKey => {
if (
untouchedPosition[pKey.toLowerCase()] !==
node.border[pKey.toLowerCase()]
) {
lines.push(
indent +
` border${pKey}Width: ${node.border[pKey.toLowerCase()]},`,
);
}
});
} else if (
node[key] instanceof PositionRecord &&
!node[key].equals(untouchedPosition)
) {
const {top, left, right, bottom} = node[key].toJS();
if (
top !== untouchedPosition.top &&
top === left &&
top === right &&
top === bottom
) {
// all edges
lines.push(indent + ` ${key}: ${node[key].top},`);
return;
}
const alreadySet = [];
if (top !== untouchedPosition.top && top === bottom) {
lines.push(indent + ` ${key}Vertical: ${node[key].top},`);
alreadySet.push('top', 'bottom');
}
if (left !== untouchedPosition.left && left === right) {
lines.push(indent + ` ${key}Horizontal: ${node[key].left},`);
alreadySet.push('left', 'right');
}
['left', 'top', 'right', 'bottom'].forEach((pKey, i) => {
if (
node[key][pKey] !== untouchedPosition[pKey] &&
alreadySet.indexOf(pKey) === -1
) {
lines.push(
indent +
` ${key}${pKey[0].toUpperCase()}${pKey.substr(1)}: ${
node[key][pKey]
},`,
);
}
});
} else if (key !== 'children' && node[key] !== untouchedLayout[key]) {
lines.push(indent + ` ${key}: ${getEnum(key, node[key])},`);
}
});
if (node.children.size > 0) {
lines.push(indent + '}}>');
} else {
lines.push(indent + '}} />');
}
if (node.children.size > 0) {
lines.push(
...node.children
.toJSON()
.map(child => getLayoutCode(child, indent + ' ')),
);
}
if (node.children.size > 0) {
lines.push(indent + '</View>');
}
return lines.join('\n');
}
export default function generateCode(
root: LayoutRecordT,
direction: Yoga$Direction,
): string {
return [
`import React, {Component} from 'react';`,
`import {View} from 'react-native';`,
'',
'export default class MyLayout extends Component {',
' render() {',
' return (',
getLayoutCode(root, ' '),
' );',
' }',
'};',
].join('\n');
}

View File

@@ -1,10 +0,0 @@
// @flow
import React, {Component} from 'react';
type Props = {};
export default class Demo extends Component<Props> {
render() {
return <div className="Demo">hey i am a demo</div>;
}
}

View File

@@ -1,49 +0,0 @@
.Editor {
display: flex;
flex-direction: column;
height: 100%;
}
.Editor .field {
display: flex;
}
.Editor .ant-btn {
width: 100%;
}
.Editor h2 {
font-size: 16px;
margin-bottom: 8px;
margin-top: 30px;
}
.Editor h2:first-child {
margin-top: 0;
}
.Editor .ant-tabs-nav .ant-tabs-tab {
margin-left: 16px;
}
.Editor .EditorTabs {
flex-grow: 1;
}
.Editor .ant-tabs {
display: flex;
flex-direction: column;
}
.Editor .ant-tabs-bar {
margin-bottom: 0;
}
.Editor .ant-tabs-tabpane {
overflow-y: scroll;
padding: 15px;
}
.Editor .EditorButtons {
padding: 15px;
}

View File

@@ -1,273 +0,0 @@
// @flow
import React, {Component} from 'react';
import {Row, Col, Button, Tabs, Input} from 'antd';
import YogaEnumSelect from './YogaEnumSelect';
import type {LayoutRecordT} from './LayoutRecord';
import type {Yoga$Direction} from 'yoga-layout';
import InfoText from './InfoText';
import YogaPositionEditor from './YogaPositionEditor';
import './Editor.css';
const TabPane = Tabs.TabPane;
type Props = {
node: ?LayoutRecordT,
onChangeLayout: (key: string, value: any) => void,
onChangeSetting: (key: string, value: any) => void,
direction: Yoga$Direction,
selectedNodeIsRoot: boolean,
onRemove?: () => void,
onAdd?: () => void,
};
export default class Editor extends Component<Props> {
componentDidMount() {
document.addEventListener('keydown', this.onKeyDown);
}
componentWillUnmount() {
document.removeEventListener('keydown', this.onKeyDown);
}
onKeyDown = (e: KeyboardEvent) => {
if (
(e.key === 'Delete' || e.key === 'Backspace') &&
this.props.onRemove &&
!(e.target instanceof HTMLInputElement)
) {
this.props.onRemove();
}
};
render() {
const {node, selectedNodeIsRoot} = this.props;
const disabled = !Boolean(node);
return (
<div className="Editor">
<Tabs defaultActiveKey="1" className="EditorTabs">
<TabPane tab="Flex" key="1">
<h2>
Direction
<InfoText>
The direction property specifies the text direction/writing
direction
</InfoText>
</h2>
<YogaEnumSelect
property="DIRECTION"
value={this.props.direction}
onChange={e => this.props.onChangeSetting('direction', e)}
/>
<h2>
Flex direction
<InfoText>Defining the direction of the main-axis</InfoText>
</h2>
<YogaEnumSelect
disabled={disabled}
property="FLEX_DIRECTION"
value={node ? node.flexDirection : ''}
onChange={e => this.props.onChangeLayout('flexDirection', e)}
/>
<Row gutter={15} style={{marginTop: 30}}>
<Col span={12}>
<h2>
Flex grow
<InfoText>
Grow factor defined how much space this element should take
up, relative to it's siblings
</InfoText>
</h2>
<Input
type="text"
disabled={disabled || selectedNodeIsRoot}
value={node ? node.flexGrow : ''}
onChange={e =>
this.props.onChangeLayout('flexGrow', e.target.value)
}
/>
</Col>
<Col span={12}>
<h2>
Flex shrink
<InfoText>
Shrink factor if elements don't fit into the parent node
anymore.
</InfoText>
</h2>
<Input
type="text"
disabled={disabled || selectedNodeIsRoot}
value={node ? node.flexShrink : ''}
onChange={e =>
this.props.onChangeLayout('flexShrink', e.target.value)
}
/>
</Col>
</Row>
<h2>
Flex wrap
<InfoText>
Wrapping behaviour when child nodes don't fit into a single line
</InfoText>
</h2>
<YogaEnumSelect
disabled={disabled}
property="WRAP"
value={node ? node.flexWrap : ''}
onChange={e => this.props.onChangeLayout('flexWrap', e)}
/>
</TabPane>
<TabPane tab="Alignment" key="2">
<h2>
Justify content
<InfoText>Aligns child nodes along the main-axis</InfoText>
</h2>
<YogaEnumSelect
disabled={disabled}
property="JUSTIFY"
value={node ? node.justifyContent : ''}
onChange={e => this.props.onChangeLayout('justifyContent', e)}
/>
<h2>
Align items
<InfoText>Aligns child nodes along the cross-axis</InfoText>
</h2>
<YogaEnumSelect
disabled={disabled}
property="ALIGN"
value={node ? node.alignItems : ''}
onChange={e => this.props.onChangeLayout('alignItems', e)}
/>
<h2>
Align self
<InfoText>
Specifies alignment on the cross-axis for the node itself
</InfoText>
</h2>
<YogaEnumSelect
disabled={disabled || selectedNodeIsRoot}
property="ALIGN"
value={node ? node.alignSelf : ''}
onChange={e => this.props.onChangeLayout('alignSelf', e)}
/>
<h2>
Align content
<InfoText>
Alignment of lines along the cross-axis when wrapping
</InfoText>
</h2>
<YogaEnumSelect
disabled={disabled}
property="ALIGN"
value={node ? node.alignContent : ''}
onChange={e => this.props.onChangeLayout('alignContent', e)}
/>
</TabPane>
<TabPane tab="Layout" key="3">
<h2>
Width &times; height
<InfoText>Dimensions of the node</InfoText>
</h2>
<Row gutter={15}>
<Col span={12}>
<Input
type="text"
placeholder="width"
disabled={disabled}
value={node ? node.width : ''}
onChange={e =>
this.props.onChangeLayout('width', e.target.value)
}
/>
</Col>
<Col span={12}>
<Input
type="text"
placeholder="height"
disabled={disabled}
value={node ? node.height : ''}
onChange={e =>
this.props.onChangeLayout('height', e.target.value)
}
/>
</Col>
</Row>
<h2>
Aspect ratio
<InfoText>
Aspect radio is an additon by Yoga which is handy e.g. for nodes
displaying videos
</InfoText>
</h2>
<Input
type="text"
placeholder="Aspect ratio"
disabled={disabled}
value={node ? node.aspectRatio : ''}
onChange={e =>
this.props.onChangeLayout('aspectRatio', e.target.value)
}
/>
<h2>Box model</h2>
{['padding', 'border', 'margin'].map(property => (
<YogaPositionEditor
property={property}
key={property}
value={node ? node[property] : undefined}
onChange={value => this.props.onChangeLayout(property, value)}
/>
))}
<h2>
Position
<InfoText>
Relative position offsets the node from it's calculated
position. Absolute position removes the node from the flexbox
flow and positions it at the given position.
</InfoText>
</h2>
<YogaEnumSelect
disabled={disabled}
property="POSITION_TYPE"
value={node ? node.positionType : ''}
onChange={e => this.props.onChangeLayout('positionType', e)}
/>
<YogaPositionEditor
property="position"
value={node ? node.position : undefined}
onChange={value => this.props.onChangeLayout('position', value)}
/>
</TabPane>
</Tabs>
<Row gutter={15} className="EditorButtons">
<Col span={12}>
<Button
icon="plus-circle-o"
disabled={!Boolean(this.props.onAdd)}
onClick={this.props.onAdd}
type="primary">
add child node
</Button>
</Col>
<Col span={12}>
<Button
icon="close-circle-o"
disabled={!Boolean(this.props.onRemove)}
onClick={this.props.onRemove}
type="danger">
remove node
</Button>
</Col>
</Row>
</div>
);
}
}

View File

@@ -1,9 +0,0 @@
.InfoText {
max-width: 230px;
line-height: 130%;
}
.InfoTextIcon {
margin-left: 5px;
opacity: 0.5;
}

View File

@@ -1,20 +0,0 @@
// @flow
import React, {Component} from 'react';
import {Popover, Icon} from 'antd';
import './InfoText.css';
type Props = {
children: any,
};
export default class InfoText extends Component<Props> {
render() {
return (
<Popover
content={<div className="InfoText">{this.props.children}</div>}
trigger="hover">
<Icon className="InfoTextIcon" type="info-circle-o" />
</Popover>
);
}
}

View File

@@ -1,56 +0,0 @@
import {Record, List} from 'immutable';
import type {RecordOf} from 'immutable';
import PositionRecord from './PositionRecord';
import type {PositionRecordT} from './PositionRecord';
import yoga from 'yoga-layout';
import type {
Yoga$Align,
Yoga$JustifyContent,
Yoga$FlexDirection,
Yoga$FlexWrap,
Yoga$Yoga$PositionType,
} from 'yoga-layout';
export type LayoutRecordT = RecordOf<{
width?: ?number,
height?: ?number,
justifyContent?: Yoga$JustifyContent,
padding: PositionRecordT,
border: PositionRecordT,
margin: PositionRecordT,
position: PositionRecordT,
positionType: Yoga$Yoga$PositionType,
alignItems?: Yoga$Align,
alignSelf?: Yoga$Align,
alignContent?: Yoga$Align,
flexDirection?: Yoga$FlexDirection,
flexGrow?: number,
flexShrink?: number,
padding?: number,
flexWrap?: Yoga$FlexWrap,
aspectRatio?: number,
children?: List<LayoutDefinition>,
}>;
const r: LayoutRecordT = Record({
width: 100,
height: 100,
justifyContent: yoga.JUSTIFY_FLEX_START,
alignItems: yoga.ALIGN_STRETCH,
alignSelf: yoga.ALIGN_AUTO,
alignContent: yoga.ALIGN_STRETCH,
flexDirection: yoga.FLEX_DIRECTION_ROW,
padding: PositionRecord(),
margin: PositionRecord(),
border: PositionRecord(),
position: PositionRecord(),
positionType: yoga.POSITION_TYPE_RELATIVE,
flexWrap: yoga.WRAP_NO_WRAP,
flexGrow: 0,
flexShrink: 1,
children: List(),
aspectRatio: 'auto',
});
export default r;

View File

@@ -1,25 +0,0 @@
.App {
height: 100vh;
width: 100vw;
background: linear-gradient(-90deg, rgba(0, 0, 0, 0.02) 1px, transparent 1px),
linear-gradient(rgba(0, 0, 0, 0.02) 1px, transparent 1px),
linear-gradient(-90deg, rgba(0, 0, 0, 0.03) 1px, transparent 1px),
linear-gradient(rgba(0, 0, 0, 0.03) 1px, transparent 1px),
linear-gradient(
transparent 4px,
#f5f5f5 4px,
#f5f5f5 97px,
transparent 97px
),
linear-gradient(-90deg, #e5e5e5 1px, transparent 1px),
linear-gradient(
-90deg,
transparent 4px,
#f5f5f5 4px,
#f5f5f5 97px,
transparent 97px
),
linear-gradient(#e5e5e5 1px, transparent 1px), #f5f5f5;
background-size: 10px 10px, 10px 10px, 100px 100px, 100px 100px, 100px 100px,
100px 100px, 100px 100px, 100px 100px;
}

View File

@@ -1,272 +0,0 @@
// @flow
import React, {Component} from 'react';
import yoga from 'yoga-layout';
import YogaNode from './YogaNode';
import Editor from './Editor';
import {List, setIn} from 'immutable';
import PositionRecord from './PositionRecord';
import LayoutRecord from './LayoutRecord';
import Toolbar from './Toolbar';
import Code from './Code';
import Sidebar from './Sidebar';
import type {LayoutRecordT} from './LayoutRecord';
import type {Yoga$Direction} from 'yoga-layout';
import './Playground.css';
type Props = {
layoutDefinition: LayoutRecordT,
direction: Yoga$Direction,
maxDepth: number,
maxChildren?: number,
minChildren?: number,
};
type State = {
selectedNodePath: ?Array<number>,
layoutDefinition: LayoutRecordT,
direction: Yoga$Direction,
showCode: boolean,
};
function getPath(path: Array<number>): Array<mixed> {
return path.reduce((acc, cv) => acc.concat('children', cv), []);
}
export default class App extends Component<Props, State> {
_containerRef: ?HTMLElement;
static defaultProps = {
layoutDefinition: LayoutRecord({
width: 800,
height: 400,
justifyContent: yoga.JUSTIFY_SPACE_BETWEEN,
alignItems: yoga.ALIGN_FLEX_START,
children: List([LayoutRecord(), LayoutRecord()]),
padding: PositionRecord({
left: '10',
top: '10',
right: '10',
bottom: '10',
}),
margin: PositionRecord({
left: '20',
top: '70',
}),
}),
direction: yoga.DIRECTION_LTR,
maxDepth: 3,
showCode: false,
};
state = {
selectedNodePath: null,
layoutDefinition: this.props.layoutDefinition,
direction: this.props.direction,
showCode: false,
};
componentDidMount() {
document.addEventListener('keydown', this.onKeyDown);
// rehydrate
if (window.location.hash && window.location.hash.length > 1) {
try {
const restoredState = JSON.parse(atob(window.location.hash.substr(1)));
this.setState({layoutDefinition: this.rehydrate(restoredState)});
} catch (e) {
window.location.hash = '';
}
}
}
componentWillUnmount() {
document.removeEventListener('keydown', this.onKeyDown);
}
rehydrate = (node: Object): LayoutRecord => {
let record = LayoutRecord(node);
record = record.set('padding', PositionRecord(record.padding));
record = record.set('border', PositionRecord(record.border));
record = record.set('margin', PositionRecord(record.margin));
record = record.set('position', PositionRecord(record.position));
record = record.set('children', List(record.children.map(this.rehydrate)));
return record;
};
onKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
this.hideSidePanes();
}
};
onMouseDown = (e: MouseEvent) => {
if (e.target === this._containerRef) {
this.hideSidePanes();
}
};
hideSidePanes() {
this.setState({
selectedNodePath: null,
showCode: false,
});
}
onChangeLayout = (key: string, value: any) => {
const {selectedNodePath} = this.state;
if (selectedNodePath) {
this.modifyAtPath([...getPath(selectedNodePath), key], value);
}
};
onRemove = () => {
const {selectedNodePath, layoutDefinition} = this.state;
if (selectedNodePath) {
const index = selectedNodePath.pop();
const path = getPath(selectedNodePath).concat('children');
const updatedChildren = layoutDefinition.getIn(path).delete(index);
this.modifyAtPath(path, updatedChildren);
this.setState({selectedNodePath: null});
}
};
onAdd = () => {
const {selectedNodePath, layoutDefinition} = this.state;
if (selectedNodePath) {
const path = getPath(selectedNodePath).concat('children');
const updatedChildren = layoutDefinition.getIn(path).push(LayoutRecord());
this.modifyAtPath(
path,
updatedChildren,
selectedNodePath.concat(updatedChildren.size - 1),
);
}
};
modifyAtPath(
path: Array<any>,
value: any,
selectedNodePath?: ?Array<number> = this.state.selectedNodePath,
) {
// $FlowFixMe
const layoutDefinition = setIn(this.state.layoutDefinition, path, value);
this.setState({
layoutDefinition,
selectedNodePath,
});
window.location.hash = btoa(
JSON.stringify(this.removeUnchangedProperties(layoutDefinition)),
);
}
removeUnchangedProperties = (node: LayoutRecordT): Object => {
const untouchedLayout = LayoutRecord({});
const untouchedPosition = PositionRecord({});
const result = {};
if (!node.equals(untouchedLayout)) {
Object.keys(node.toJS()).forEach(key => {
if (key === 'children' && node.children.size > 0) {
result.children = node.children
.toJSON()
.map(this.removeUnchangedProperties);
} else if (
node[key] instanceof PositionRecord &&
!node[key].equals(untouchedPosition)
) {
result[key] = {};
Object.keys(untouchedPosition.toJS()).forEach(position => {
if (node[key][position] !== untouchedPosition[position]) {
result[key][position] = node[key][position];
}
});
} else if (node[key] !== untouchedLayout[key]) {
result[key] = node[key];
}
});
}
return result;
};
getChildrenCountForSelectedPath = (): number => {
const selectedNode: ?LayoutRecordT = (
this.state.selectedNodePath || []
).reduce(
(node: LayoutRecordT, cv) => node.children.get(cv),
this.state.layoutDefinition,
);
return selectedNode ? selectedNode.children.size : 0;
};
onToggleCode = () => {
this.setState({
selectedNodePath: this.state.showCode
? this.state.selectedNodePath
: null,
showCode: !this.state.showCode,
});
};
render() {
const {layoutDefinition, selectedNodePath} = this.state;
const selectedNode: ?LayoutRecordT = selectedNodePath
? layoutDefinition.getIn(getPath(selectedNodePath))
: null;
return (
<div
className="App"
onMouseDown={this.onMouseDown}
ref={ref => {
this._containerRef = ref;
}}>
<Toolbar
onShowCode={
!this.state.showCode
? () => this.setState({selectedNodePath: null, showCode: true})
: undefined
}
/>
<YogaNode
layoutDefinition={layoutDefinition}
selectedNodePath={selectedNodePath}
onClick={selectedNodePath =>
this.setState({selectedNodePath, showCode: false})
}
onDoubleClick={this.onAdd}
direction={this.state.direction}
/>
<Sidebar visible={Boolean(selectedNodePath) && !this.state.showCode}>
<Editor
node={selectedNode}
selectedNodeIsRoot={
selectedNodePath ? selectedNodePath.length === 0 : false
}
onChangeLayout={this.onChangeLayout}
onChangeSetting={(key, value) => this.setState({[key]: value})}
direction={this.state.direction}
onRemove={
selectedNodePath && selectedNodePath.length > 0
? this.onRemove
: undefined
}
onAdd={
selectedNodePath && selectedNodePath.length < this.props.maxDepth
? this.onAdd
: undefined
}
/>
</Sidebar>
<Sidebar
width={500}
visible={this.state.showCode}
onClose={() => this.setState({showCode: false})}>
<Code
layoutDefinition={layoutDefinition}
direction={this.state.direction}
/>
</Sidebar>
</div>
);
}
}

View File

@@ -1,9 +0,0 @@
.PositionGuide {
position: absolute;
pointer-events: none;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
user-select: none;
}

View File

@@ -1,137 +0,0 @@
// @flow
import React, {Component} from 'react';
import PositionRecord from './PositionRecord';
import type {PositionRecordT} from './PositionRecord';
import './PositionGuide.css';
type Props = {
inset?: boolean,
reverse?: boolean,
position: PositionRecordT,
offset: PositionRecordT,
color: string,
};
export default class PositionGuide extends Component<Props> {
static defaultProps = {
offset: PositionRecord({}),
};
render() {
const {position, offset, inset, color, reverse} = this.props;
let {top, left, right, bottom} = position;
let {top: oTop, left: oLeft, right: oRight, bottom: oBottom} = offset;
if (reverse) {
let temp1 = left;
left = right;
right = temp1;
temp1 = oLeft;
oLeft = oRight;
oRight = temp1;
}
if (!top) {
top = 0;
}
if (!left) {
left = 0;
}
if (!right) {
right = 0;
}
if (!bottom) {
bottom = 0;
}
if (!oTop) {
oTop = 0;
}
if (!oLeft) {
oLeft = 0;
}
if (!oRight) {
oRight = 0;
}
if (!oBottom) {
oBottom = 0;
}
if (!inset) {
if (top < 0) {
bottom -= top;
top = 0;
}
if (bottom < 0) {
top -= bottom;
bottom = 0;
}
if (left < 0) {
right -= left;
left = 0;
}
if (right < 0) {
left -= right;
right = 0;
}
}
return [
top !== 0 ? (
<div
key="top"
className="PositionGuide"
style={{
backgroundColor: color,
height: top,
top: inset ? oTop : -top - oTop,
left: inset ? left + oLeft : -left - oLeft,
right: inset ? right + oRight : -right - oRight,
}}>
{top}
</div>
) : null,
left !== 0 ? (
<div
key="left"
className="PositionGuide"
style={{
backgroundColor: color,
width: left,
top: inset ? oTop : -oTop,
bottom: inset ? oBottom : -oBottom,
left: inset ? oLeft : -left - oLeft,
}}>
{left}
</div>
) : null,
right !== 0 ? (
<div
key="right"
className="PositionGuide"
style={{
backgroundColor: color,
width: right,
top: inset ? oTop : -oTop,
bottom: inset ? oBottom : -oBottom,
right: inset ? oRight : -right - oRight,
}}>
{right}
</div>
) : null,
bottom !== 0 ? (
<div
key="bottom"
className="PositionGuide"
style={{
backgroundColor: color,
height: bottom,
bottom: inset ? oBottom : -bottom - oBottom,
left: inset ? left + oLeft : -left - oLeft,
right: inset ? right + oRight : -right - oRight,
}}>
{bottom}
</div>
) : null,
];
}
}

View File

@@ -1,18 +0,0 @@
import {Record} from 'immutable';
import type {RecordOf} from 'immutable';
export type PositionRecordT = RecordOf<{
top: string,
right: string,
bottom: string,
left: string,
}>;
const r: PositionRecordT = Record({
top: '',
right: '',
bottom: '',
left: '',
});
export default r;

View File

@@ -1,33 +0,0 @@
.Sidebar {
position: fixed;
z-index: 3;
right: 0;
bottom: 0;
width: 350px;
top: 50px;
background: white;
border-left: 1px solid #dddfe2;
transform: translateX(100%);
transition: 0.2s transform;
display: flex;
flex-direction: column;
}
.Sidebar.visible {
transform: translateX(0);
}
.SidebarClose {
text-align: right;
padding: 15px;
padding-bottom: 0;
}
.SidebarClose i {
cursor: pointer;
opacity: 0.4;
}
.SidebarClose i:hover {
opacity: 0.6;
}

View File

@@ -1,27 +0,0 @@
// @flow
import React, {Component} from 'react';
import {Icon} from 'antd';
import './Sidebar.css';
type Props = {
onClose?: () => void,
width?: number,
children: any,
};
export default class Sidebar extends Component<Props> {
render() {
return (
<div
className={`Sidebar ${this.props.visible ? 'visible' : ''}`}
style={{width: this.props.width}}>
{this.props.onClose && (
<div className="SidebarClose">
<Icon type="close" onClick={this.props.onClose} />
</div>
)}
{this.props.children}
</div>
);
}
}

View File

@@ -1,34 +0,0 @@
.Toolbar {
border-bottom: 1px solid #dddfe2;
box-shadow: 0 1px 6px 0 rgba(0, 0, 0, 0.1);
background: white;
height: 50px;
position: fixed;
left: 0;
right: 0;
padding-left: 16px;
padding-right: 16px;
padding-bottom: 1px;
align-items: center;
z-index: 4;
display: flex;
}
.Toolbar h1 {
font-size: 24px;
margin: 0;
padding: 0;
margin-left: 5px;
}
.Toolbar .ToolbarSpacer {
flex-grow: 1;
}
.Toolbar .ToolbarToggle {
font-size: 16px;
}
.Toolbar .ToolbarToggle i {
margin-right: 5px;
}

View File

@@ -1,30 +0,0 @@
// @flow
import React, {Component} from 'react';
import {Icon} from 'antd';
import './Toolbar.css';
type Props = {
onShowCode?: () => void,
};
export default class Toolbar extends Component<Props> {
render() {
return (
<div className="Toolbar">
<img
src="https://facebook.github.io/yoga/static/logo.svg"
width="42"
alt="Yoga logo"
/>
<h1>Yoga Playground</h1>
<div className="ToolbarSpacer" />
{this.props.onShowCode && (
<a className="ToolbarToggle" onClick={this.props.onShowCode}>
<Icon type={'code-o'} />
Code
</a>
)}
</div>
);
}
}

View File

@@ -1,9 +0,0 @@
.YogaEnumSelect.ant-radio-group {
display: flex;
}
.YogaEnumSelect.ant-radio-group .ant-radio-button-wrapper {
flex-grow: 1;
flex-basis: 0;
text-align: center;
}

View File

@@ -1,71 +0,0 @@
// @flow
import React, {Component} from 'react';
import yoga from 'yoga-layout';
import {Radio, Menu, Dropdown, Button, Icon} from 'antd';
import './YogaEnumSelect.css';
const RadioButton = Radio.Button;
const RadioGroup = Radio.Group;
type Props = {
property: string,
disabled?: boolean,
value: string | number,
onChange: (value: number) => void,
};
export default class YogaEnumSelect extends Component<Props> {
values: Array<{key: string, value: number}>;
constructor({property}: Props) {
super();
// $FlowFixMe
this.values = Object.keys(yoga)
.map(key => ({key, value: yoga[key]}))
.filter(
({key}) => key.startsWith(property) && key !== `${property}_COUNT`,
);
}
handleMenuClick = ({key}: {key: string}) => {
this.props.onChange(yoga[key]);
};
render() {
const {property, ...props} = this.props;
const selected = this.values.find(
({key, value}) => value === this.props.value,
);
const replacer = new RegExp(`^${property}_`);
return this.values.length > 3 ? (
<Dropdown
disabled={props.disabled}
overlay={
<Menu onClick={this.handleMenuClick}>
{this.values.map(({key, value}) => (
<Menu.Item key={key} value={value}>
{key.replace(replacer, '')}
</Menu.Item>
))}
</Menu>
}>
<Button>
{selected ? selected.key.replace(replacer, '') : 'undefiend'}{' '}
<Icon type="down" />
</Button>
</Dropdown>
) : (
<RadioGroup
{...props}
onChange={e => this.props.onChange(e.target.value)}
defaultValue="a"
className="YogaEnumSelect">
{this.values.map(({key, value}) => (
<RadioButton key={key} value={value}>
{key.replace(new RegExp(`^${property}_`), '')}
</RadioButton>
))}
</RadioGroup>
);
}
}

View File

@@ -1,51 +0,0 @@
.YogaNode {
background: white;
position: absolute;
transform: scale(1);
box-shadow: inset 0 0 0px 1px rgba(48, 56, 69, 0.2);
transition: 0.2s all, 0s outline, 0s box-shadow;
}
.YogaNode .YogaNode {
background: rgba(255, 255, 255, 0.7);
}
.YogaNode > .info {
position: absolute;
opacity: 0;
font-family: monospace;
}
.YogaNode:hover > .info {
opacity: 1;
}
.YogaNode:focus {
outline: 0;
}
.YogaNode.focused {
box-shadow: 0 0 0 2px #0894fb, 0 0 15px rgba(0, 0, 0, 0.2),
inset 0 0 0px 1px rgba(255, 255, 255, 0.2);
z-index: 2;
}
.YogaNode.invisible {
transform: scale(0);
}
.YogaNode .label {
user-select: none;
pointer-events: none;
position: absolute;
left: 0;
bottom: 0;
right: 0;
top: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: 300;
letter-spacing: 1px;
}

View File

@@ -1,267 +0,0 @@
// @flow
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<ComputedLayout>,
node: Yoga$Node,
|};
type Props = {|
layoutDefinition: LayoutRecordT,
className?: string,
computedLayout?: ComputedLayout,
path: Array<number>,
selectedNodePath?: ?Array<number>,
direction?: Yoga$Direction,
label?: string,
onClick?: (path: Array<number>) => void,
onDoubleClick?: (path: Array<number>) => void,
|};
type State = {
visible?: boolean,
};
export default class YogaNode extends Component<Props, State> {
node: Yoga$Node;
static defaultProps = {
path: [],
label: 'root',
};
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 [
<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'
}`}
style={{left, top, width, height}}
onDoubleClick={this.onDoubleClick}
onClick={this.onClick}>
{label && <div className="label">{label}</div>}
{isFocused && 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}
/>
))}
</div>
);
}
}

View File

@@ -1,22 +0,0 @@
.YogaPositionEditor {
text-align: center;
margin-top: 20px;
margin-bottom: 20px;
width: 60%;
margin-left: auto;
margin-right: auto;
}
.YogaPositionEditor input {
width: 55px;
text-align: center;
}
.YogaPositionEditorRow {
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: row;
font-size: 16px;
font-weight: bold;
}

View File

@@ -1,49 +0,0 @@
// @flow
import React, {Component} from 'react';
import {Input} from 'antd';
import PositionRecord from './PositionRecord';
import type {PositionRecordT} from './PositionRecord';
import './YogaPositionEditor.css';
type Props = {
value: PositionRecordT,
property: string,
onChange: (value: PositionRecordT) => void,
};
export default class YogaPositionEditor extends Component<Props> {
static defaultProps = {
value: PositionRecord(),
};
render() {
const {onChange, value, property} = this.props;
return (
<div className="YogaPositionEditor">
<Input
type="text"
value={value.top}
onChange={e => onChange(value.set('top', e.target.value))}
/>
<div className="YogaPositionEditorRow">
<Input
type="text"
value={value.left}
onChange={e => onChange(value.set('left', e.target.value))}
/>
{property}
<Input
type="text"
value={value.right}
onChange={e => onChange(value.set('right', e.target.value))}
/>
</div>
<Input
type="text"
value={value.bottom}
onChange={e => onChange(value.set('bottom', e.target.value))}
/>
</div>
);
}
}

View File

@@ -1,12 +0,0 @@
html,
body {
margin: 0;
padding: 0;
font-family: 'Barlow', sans-serif;
color: #303845;
}
h1,
h2 {
font-weight: 500;
}

View File

@@ -1,30 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Playground from './Playground';
import Demo from './Demo';
const components = {Playground, Demo};
window.onload = function() {
document.querySelectorAll('react').forEach(node => {
const props = JSON.parse(node.getAttribute('props'));
if (components[node.getAttribute('component')]) {
ReactDOM.render(
React.createElement(
components[node.getAttribute('component')],
props,
node.innerHTML,
),
node,
);
} else {
console.error(
`Component "${node.getAttribute(
'component',
)}" could not be found. Exported components are ${Object.keys(
components,
).join(', ')}`,
);
}
});
};

File diff suppressed because it is too large Load Diff