Docusaurus: Replant Playground (#1331)
Summary:
Pull Request resolved: https://github.com/facebook/yoga/pull/1331
This lifts and copies code from the Yoga Playground component of the old website, to the new one, using a live workspace version of JS Yoga. This may eventually be used if the new website is fleshed out, but this also gives us real-world validation of the Yoga bindings in a web scenario, compared to the Node test runner. There is still some cleanup here needed, but we are able to bundle, typecheck, and render the playground now.
The code here is mostly the same as before, but I removed some of the cruftier bits (e.g. URL shortener that goes to some private Heroku instance), and needed to do some tweaks for the new Yoga package, and TypeScript.
This is currently using `yoga-layout/sync`, the asmjs bindings. But I had previously checked bundling against the wasm ones.
Reviewed By: cortinico
Differential Revision: D46884439
fbshipit-source-id: f53f0855c131cd2b81975bf05f71c43713600616
2023-07-13 14:08:07 -07:00
|
|
|
/**
|
|
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the MIT license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree.
|
|
|
|
*
|
|
|
|
* @format
|
|
|
|
*/
|
|
|
|
|
|
|
|
import React, {Component} from 'react';
|
Consolidate JavaScript Flavors
Fixes https://github.com/facebook/yoga/issues/1417
This dramatically simplifies the matrix of Node vs web, ASM vs WASM, sync vs async compilation, or CommonJS vs ES Modules. We have one variant, using without, with ESModule top-level await to do async compilation. Web/node share the same binary, and we base64 encode the WASM into a wrapper JS file for compatibility with Node and bundlers.
After this change we target:
This has some downsides, like requiring an environment with top level await, but also has upsides, like a consistent, sync looking API compatible with older Yoga, and mitigating TypeScript issues with package exports and typings resolution.
## Test Plan
1. `yarn test`
2. `yarn lint`
3. `yarn tsc`
4. `yarn build` website-next
5. Locally test website
5. Examine package artifact created by GitHub
2023-10-19 04:59:10 -07:00
|
|
|
import {Direction} from 'yoga-layout';
|
Docusaurus: Replant Playground (#1331)
Summary:
Pull Request resolved: https://github.com/facebook/yoga/pull/1331
This lifts and copies code from the Yoga Playground component of the old website, to the new one, using a live workspace version of JS Yoga. This may eventually be used if the new website is fleshed out, but this also gives us real-world validation of the Yoga bindings in a web scenario, compared to the Node test runner. There is still some cleanup here needed, but we are able to bundle, typecheck, and render the playground now.
The code here is mostly the same as before, but I removed some of the cruftier bits (e.g. URL shortener that goes to some private Heroku instance), and needed to do some tweaks for the new Yoga package, and TypeScript.
This is currently using `yoga-layout/sync`, the asmjs bindings. But I had previously checked bundling against the wasm ones.
Reviewed By: cortinico
Differential Revision: D46884439
fbshipit-source-id: f53f0855c131cd2b81975bf05f71c43713600616
2023-07-13 14:08:07 -07:00
|
|
|
import YogaNode from './YogaNode';
|
|
|
|
import Editor from './Editor';
|
|
|
|
import {List, setIn} from 'immutable';
|
|
|
|
import PositionRecord from './PositionRecord';
|
|
|
|
import LayoutRecord from './LayoutRecord';
|
|
|
|
import Sidebar from './Sidebar';
|
|
|
|
import type {LayoutRecordType} from './LayoutRecord';
|
|
|
|
import './index.css';
|
|
|
|
|
|
|
|
type Props = {
|
2023-07-17 14:27:32 -07:00
|
|
|
layoutDefinition: LayoutRecordType;
|
|
|
|
direction: Direction;
|
|
|
|
maxDepth: number;
|
|
|
|
maxChildren?: number;
|
|
|
|
minChildren?: number;
|
|
|
|
selectedNodePath?: Array<number>;
|
|
|
|
showGuides: boolean;
|
|
|
|
className?: string;
|
|
|
|
height?: string | number;
|
|
|
|
persist?: boolean;
|
|
|
|
renderSidebar?: (
|
|
|
|
layoutDefinition: LayoutRecordType,
|
|
|
|
onChange: () => any,
|
|
|
|
) => any;
|
Docusaurus: Replant Playground (#1331)
Summary:
Pull Request resolved: https://github.com/facebook/yoga/pull/1331
This lifts and copies code from the Yoga Playground component of the old website, to the new one, using a live workspace version of JS Yoga. This may eventually be used if the new website is fleshed out, but this also gives us real-world validation of the Yoga bindings in a web scenario, compared to the Node test runner. There is still some cleanup here needed, but we are able to bundle, typecheck, and render the playground now.
The code here is mostly the same as before, but I removed some of the cruftier bits (e.g. URL shortener that goes to some private Heroku instance), and needed to do some tweaks for the new Yoga package, and TypeScript.
This is currently using `yoga-layout/sync`, the asmjs bindings. But I had previously checked bundling against the wasm ones.
Reviewed By: cortinico
Differential Revision: D46884439
fbshipit-source-id: f53f0855c131cd2b81975bf05f71c43713600616
2023-07-13 14:08:07 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
type State = {
|
2023-07-17 14:27:32 -07:00
|
|
|
selectedNodePath?: Array<number>;
|
|
|
|
layoutDefinition: LayoutRecordType;
|
|
|
|
direction: Direction;
|
Docusaurus: Replant Playground (#1331)
Summary:
Pull Request resolved: https://github.com/facebook/yoga/pull/1331
This lifts and copies code from the Yoga Playground component of the old website, to the new one, using a live workspace version of JS Yoga. This may eventually be used if the new website is fleshed out, but this also gives us real-world validation of the Yoga bindings in a web scenario, compared to the Node test runner. There is still some cleanup here needed, but we are able to bundle, typecheck, and render the playground now.
The code here is mostly the same as before, but I removed some of the cruftier bits (e.g. URL shortener that goes to some private Heroku instance), and needed to do some tweaks for the new Yoga package, and TypeScript.
This is currently using `yoga-layout/sync`, the asmjs bindings. But I had previously checked bundling against the wasm ones.
Reviewed By: cortinico
Differential Revision: D46884439
fbshipit-source-id: f53f0855c131cd2b81975bf05f71c43713600616
2023-07-13 14:08:07 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
function getPath(path: Array<number>): Array<unknown> {
|
|
|
|
return path.reduce((acc, cv) => acc.concat('children', cv), []);
|
|
|
|
}
|
|
|
|
|
|
|
|
export default class Playground extends Component<Props, State> {
|
|
|
|
_containerRef?: HTMLElement;
|
|
|
|
|
|
|
|
static defaultProps = {
|
|
|
|
layoutDefinition: {
|
|
|
|
width: 500,
|
|
|
|
height: 500,
|
|
|
|
children: [
|
|
|
|
{width: 100, height: 100},
|
|
|
|
{width: 100, height: 100},
|
|
|
|
{width: 100, height: 100},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
direction: Direction.LTR,
|
|
|
|
maxDepth: 3,
|
|
|
|
showGuides: true,
|
|
|
|
persist: false,
|
|
|
|
};
|
|
|
|
|
2023-07-17 14:27:32 -07:00
|
|
|
rehydrate = (node: LayoutRecordType): LayoutRecordType => {
|
Docusaurus: Replant Playground (#1331)
Summary:
Pull Request resolved: https://github.com/facebook/yoga/pull/1331
This lifts and copies code from the Yoga Playground component of the old website, to the new one, using a live workspace version of JS Yoga. This may eventually be used if the new website is fleshed out, but this also gives us real-world validation of the Yoga bindings in a web scenario, compared to the Node test runner. There is still some cleanup here needed, but we are able to bundle, typecheck, and render the playground now.
The code here is mostly the same as before, but I removed some of the cruftier bits (e.g. URL shortener that goes to some private Heroku instance), and needed to do some tweaks for the new Yoga package, and TypeScript.
This is currently using `yoga-layout/sync`, the asmjs bindings. But I had previously checked bundling against the wasm ones.
Reviewed By: cortinico
Differential Revision: D46884439
fbshipit-source-id: f53f0855c131cd2b81975bf05f71c43713600616
2023-07-13 14:08:07 -07:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
|
|
|
state = {
|
|
|
|
selectedNodePath: this.props.selectedNodePath,
|
|
|
|
layoutDefinition: this.rehydrate(this.props.layoutDefinition),
|
|
|
|
direction: this.props.direction,
|
|
|
|
};
|
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
document.addEventListener('keydown', this.onKeyDown);
|
|
|
|
|
|
|
|
// rehydrate
|
|
|
|
if (window.location.search && window.location.search.length > 1) {
|
|
|
|
try {
|
|
|
|
const restoredState = JSON.parse(
|
|
|
|
atob(window.location.search.substr(1)),
|
|
|
|
);
|
|
|
|
this.setState({layoutDefinition: this.rehydrate(restoredState)});
|
|
|
|
} catch (e) {
|
|
|
|
window.history.replaceState(
|
|
|
|
{},
|
|
|
|
null,
|
|
|
|
window.location.origin + window.location.pathname,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
|
|
|
document.removeEventListener('keydown', this.onKeyDown);
|
|
|
|
}
|
|
|
|
|
|
|
|
onKeyDown = (e: KeyboardEvent) => {
|
|
|
|
if (e.key === 'Escape') {
|
|
|
|
this.hideSidePanes();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
onMouseDown = (e: React.MouseEvent) => {
|
|
|
|
if (e.target === this._containerRef) {
|
|
|
|
this.hideSidePanes();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
hideSidePanes() {
|
2023-07-17 14:27:32 -07:00
|
|
|
if (!this.props.renderSidebar) {
|
Docusaurus: Replant Playground (#1331)
Summary:
Pull Request resolved: https://github.com/facebook/yoga/pull/1331
This lifts and copies code from the Yoga Playground component of the old website, to the new one, using a live workspace version of JS Yoga. This may eventually be used if the new website is fleshed out, but this also gives us real-world validation of the Yoga bindings in a web scenario, compared to the Node test runner. There is still some cleanup here needed, but we are able to bundle, typecheck, and render the playground now.
The code here is mostly the same as before, but I removed some of the cruftier bits (e.g. URL shortener that goes to some private Heroku instance), and needed to do some tweaks for the new Yoga package, and TypeScript.
This is currently using `yoga-layout/sync`, the asmjs bindings. But I had previously checked bundling against the wasm ones.
Reviewed By: cortinico
Differential Revision: D46884439
fbshipit-source-id: f53f0855c131cd2b81975bf05f71c43713600616
2023-07-13 14:08:07 -07:00
|
|
|
// only unselect if we don't have an external sidebar, otherwise the
|
|
|
|
// sidebar may rely on a certain node to be selected
|
|
|
|
this.setState({
|
|
|
|
selectedNodePath: null,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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');
|
|
|
|
// @ts-ignore
|
|
|
|
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)
|
|
|
|
// @ts-ignore
|
|
|
|
.push(LayoutRecord({width: 100, height: 100}));
|
|
|
|
this.modifyAtPath(path, updatedChildren);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
modifyAtPath(
|
|
|
|
path: Array<any>,
|
|
|
|
value: any,
|
|
|
|
selectedNodePath: Array<number> = this.state.selectedNodePath,
|
|
|
|
) {
|
|
|
|
const layoutDefinition = setIn(this.state.layoutDefinition, path, value);
|
|
|
|
this.setState({
|
|
|
|
layoutDefinition,
|
|
|
|
selectedNodePath,
|
|
|
|
});
|
|
|
|
|
|
|
|
if (this.props.persist) {
|
|
|
|
window.history.replaceState(
|
|
|
|
{},
|
|
|
|
null,
|
|
|
|
window.location.origin +
|
|
|
|
window.location.pathname +
|
|
|
|
'?' +
|
|
|
|
this.getHash(layoutDefinition),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
getHash = (
|
|
|
|
layoutDefinition: LayoutRecordType = this.state.layoutDefinition,
|
|
|
|
): string =>
|
|
|
|
btoa(JSON.stringify(this.removeUnchangedProperties(layoutDefinition)));
|
|
|
|
|
2023-07-17 14:27:32 -07:00
|
|
|
removeUnchangedProperties = (
|
|
|
|
node: LayoutRecordType,
|
|
|
|
): {children?: unknown} => {
|
Docusaurus: Replant Playground (#1331)
Summary:
Pull Request resolved: https://github.com/facebook/yoga/pull/1331
This lifts and copies code from the Yoga Playground component of the old website, to the new one, using a live workspace version of JS Yoga. This may eventually be used if the new website is fleshed out, but this also gives us real-world validation of the Yoga bindings in a web scenario, compared to the Node test runner. There is still some cleanup here needed, but we are able to bundle, typecheck, and render the playground now.
The code here is mostly the same as before, but I removed some of the cruftier bits (e.g. URL shortener that goes to some private Heroku instance), and needed to do some tweaks for the new Yoga package, and TypeScript.
This is currently using `yoga-layout/sync`, the asmjs bindings. But I had previously checked bundling against the wasm ones.
Reviewed By: cortinico
Differential Revision: D46884439
fbshipit-source-id: f53f0855c131cd2b81975bf05f71c43713600616
2023-07-13 14:08:07 -07:00
|
|
|
const untouchedLayout = LayoutRecord({});
|
|
|
|
const untouchedPosition = PositionRecord({});
|
|
|
|
const result: {children?: unknown} = {};
|
|
|
|
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: LayoutRecordType = (
|
|
|
|
this.state.selectedNodePath || []
|
|
|
|
).reduce(
|
|
|
|
(node: LayoutRecordType, cv) => node.children.get(cv),
|
|
|
|
this.state.layoutDefinition,
|
|
|
|
);
|
|
|
|
return selectedNode ? selectedNode.children.size : 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
render() {
|
|
|
|
const {layoutDefinition, selectedNodePath, direction} = this.state;
|
|
|
|
const {height} = this.props;
|
|
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
const selectedNode: LayoutRecordType | null = selectedNodePath
|
|
|
|
? layoutDefinition.getIn(getPath(selectedNodePath))
|
|
|
|
: null;
|
|
|
|
|
|
|
|
const playground = (
|
2023-10-19 22:36:48 -07:00
|
|
|
<div className="playground-background">
|
|
|
|
<div
|
|
|
|
className={`Playground ${
|
|
|
|
this.props.renderSidebar ? '' : 'standalone'
|
|
|
|
}`}
|
|
|
|
onMouseDown={this.onMouseDown}
|
|
|
|
style={{height, maxHeight: height}}
|
|
|
|
ref={ref => {
|
|
|
|
this._containerRef = ref;
|
|
|
|
}}>
|
|
|
|
<YogaNode
|
|
|
|
layoutDefinition={layoutDefinition}
|
|
|
|
selectedNodePath={selectedNodePath}
|
|
|
|
onClick={selectedNodePath => this.setState({selectedNodePath})}
|
|
|
|
onDoubleClick={this.onAdd}
|
|
|
|
direction={direction}
|
|
|
|
showGuides={this.props.showGuides}
|
|
|
|
/>
|
|
|
|
{!this.props.renderSidebar && (
|
|
|
|
<Sidebar>
|
|
|
|
{this.state.selectedNodePath ? (
|
|
|
|
<Editor
|
|
|
|
node={selectedNode}
|
|
|
|
selectedNodeIsRoot={
|
|
|
|
selectedNodePath ? selectedNodePath.length === 0 : false
|
|
|
|
}
|
|
|
|
onChangeLayout={this.onChangeLayout}
|
|
|
|
// @ts-ignore
|
|
|
|
onChangeSetting={(key, value) =>
|
|
|
|
this.setState({[key]: value})
|
|
|
|
}
|
|
|
|
direction={direction}
|
|
|
|
onRemove={
|
|
|
|
selectedNodePath && selectedNodePath.length > 0
|
|
|
|
? this.onRemove
|
|
|
|
: undefined
|
|
|
|
}
|
|
|
|
onAdd={
|
|
|
|
selectedNodePath &&
|
|
|
|
selectedNodePath.length < this.props.maxDepth
|
|
|
|
? this.onAdd
|
|
|
|
: undefined
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
) : (
|
|
|
|
<div className="NoContent">
|
|
|
|
Select a node to edit its properties
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</Sidebar>
|
|
|
|
)}
|
|
|
|
</div>
|
Docusaurus: Replant Playground (#1331)
Summary:
Pull Request resolved: https://github.com/facebook/yoga/pull/1331
This lifts and copies code from the Yoga Playground component of the old website, to the new one, using a live workspace version of JS Yoga. This may eventually be used if the new website is fleshed out, but this also gives us real-world validation of the Yoga bindings in a web scenario, compared to the Node test runner. There is still some cleanup here needed, but we are able to bundle, typecheck, and render the playground now.
The code here is mostly the same as before, but I removed some of the cruftier bits (e.g. URL shortener that goes to some private Heroku instance), and needed to do some tweaks for the new Yoga package, and TypeScript.
This is currently using `yoga-layout/sync`, the asmjs bindings. But I had previously checked bundling against the wasm ones.
Reviewed By: cortinico
Differential Revision: D46884439
fbshipit-source-id: f53f0855c131cd2b81975bf05f71c43713600616
2023-07-13 14:08:07 -07:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
|
|
|
|
if (this.props.renderSidebar) {
|
|
|
|
return (
|
|
|
|
<div className={`PlaygroundContainer ${this.props.className || ''}`}>
|
|
|
|
<div>
|
|
|
|
{this.props.renderSidebar(
|
|
|
|
// @ts-ignore
|
|
|
|
layoutDefinition.getIn(getPath(selectedNodePath)),
|
|
|
|
this.onChangeLayout,
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
{playground}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
return playground;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|