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:
committed by
Facebook Github Bot
parent
10061af491
commit
4c3eebd3e5
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "react-app"
|
|
||||||
}
|
|
@@ -1,11 +0,0 @@
|
|||||||
[ignore]
|
|
||||||
|
|
||||||
[include]
|
|
||||||
|
|
||||||
[libs]
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
|
|
||||||
[options]
|
|
||||||
|
|
||||||
[strict]
|
|
21
playground/.gitignore
vendored
21
playground/.gitignore
vendored
@@ -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*
|
|
@@ -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[^/]*)(?:/|$)
|
|
||||||
|
|
@@ -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
|
|
@@ -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;
|
|
||||||
};
|
|
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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>
|
|
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"short_name": "Yoga Playground",
|
|
||||||
"name": "Yoga Playground",
|
|
||||||
"icons": [],
|
|
||||||
"start_url": "./index.html",
|
|
||||||
"display": "standalone",
|
|
||||||
"theme_color": "#000000",
|
|
||||||
"background_color": "#ffffff"
|
|
||||||
}
|
|
@@ -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;
|
|
||||||
}
|
|
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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',
|
|
||||||
);
|
|
||||||
}
|
|
@@ -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');
|
|
||||||
}
|
|
@@ -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');
|
|
||||||
}
|
|
@@ -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');
|
|
||||||
}
|
|
@@ -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>;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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;
|
|
||||||
}
|
|
@@ -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 × 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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,9 +0,0 @@
|
|||||||
.InfoText {
|
|
||||||
max-width: 230px;
|
|
||||||
line-height: 130%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.InfoTextIcon {
|
|
||||||
margin-left: 5px;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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;
|
|
@@ -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;
|
|
||||||
}
|
|
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,9 +0,0 @@
|
|||||||
.PositionGuide {
|
|
||||||
position: absolute;
|
|
||||||
pointer-events: none;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 10px;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
@@ -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,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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;
|
|
@@ -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;
|
|
||||||
}
|
|
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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;
|
|
||||||
}
|
|
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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;
|
|
||||||
}
|
|
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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;
|
|
||||||
}
|
|
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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;
|
|
||||||
}
|
|
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,12 +0,0 @@
|
|||||||
html,
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
font-family: 'Barlow', sans-serif;
|
|
||||||
color: #303845;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2 {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
@@ -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(', ')}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
7455
playground/yarn.lock
7455
playground/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user