Replace Playground with JSX Editor (#1500)
Summary: Pull Request resolved: https://github.com/facebook/yoga/pull/1500 Inspired by the frequent usage of Expo snacks to run RN, to repro Yoga issues, this replaces the Playground port with a new ground up Playground UI. This UI right now is pretty simple, with a JSX editor which creates a Yoga tree, which is then rendered using the WebAssembly variant of Yoga. There are a lot of ways we can continue to improve this, but this merges the foundation. Subjectively, I find this more useful as a tool to play with Yoga behavior than the GUI. This also replaces some of the bits of the homepage, and adds a playground entrypoint (though it's pretty identical to the one I've been testing on the home page). Reviewed By: yungsters Differential Revision: D51963201 fbshipit-source-id: 1265cb1784151b685686e189d47ecd42cbacdf8f
This commit is contained in:
committed by
Facebook GitHub Bot
parent
0d03d8a06d
commit
77742af676
165
website-next/src/components/Playground.tsx
Normal file
165
website-next/src/components/Playground.tsx
Normal file
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* 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, {
|
||||
CSSProperties,
|
||||
Suspense,
|
||||
lazy,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import {usePrismTheme} from '@docusaurus/theme-common';
|
||||
import clsx from 'clsx';
|
||||
import nullthrows from 'nullthrows';
|
||||
import {LiveProvider, LiveEditor, LivePreview, LiveError} from 'react-live';
|
||||
import EditorToolbar from './EditorToolbar';
|
||||
|
||||
import type {FlexStyle} from './FlexStyle';
|
||||
import type {StyleNode} from './YogaViewer';
|
||||
|
||||
import styles from './Playground.module.css';
|
||||
|
||||
const defaultCode = `
|
||||
<Layout config={{useWebDefaults: false}}>
|
||||
<Node style={{width: 350, height: 350, padding: 20}}>
|
||||
<Node style={{flex: 1}} />
|
||||
</Node>
|
||||
</Layout>
|
||||
`.trim();
|
||||
|
||||
export type Props = Readonly<{
|
||||
code?: string;
|
||||
height?: CSSProperties['height'];
|
||||
autoFocus?: boolean;
|
||||
}>;
|
||||
|
||||
export default function Playground({code, height, autoFocus}: Props) {
|
||||
const prismTheme = usePrismTheme();
|
||||
const playgroundRef = useRef<HTMLDivElement>(null);
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
|
||||
const LivePreviewWrapper = useCallback(
|
||||
(props: React.ComponentProps<'div'>) => {
|
||||
useEffect(() => {
|
||||
setIsLoaded(true);
|
||||
}, []);
|
||||
|
||||
return <div {...props} className={styles.livePreviewWrapper} />;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// TODO: This is hacky and relies on being called after some operation
|
||||
// "react-live" does which itself can manipulate global focus
|
||||
if (isLoaded && autoFocus) {
|
||||
const codeElem = playgroundRef?.current?.querySelector('.prism-code');
|
||||
const sel = window.getSelection();
|
||||
if (codeElem != null && sel != null) {
|
||||
sel.selectAllChildren(codeElem);
|
||||
sel.collapseToStart();
|
||||
}
|
||||
}
|
||||
}, [isLoaded, autoFocus]);
|
||||
|
||||
const heightStyle = height
|
||||
? ({'--yg-playground-height': height} as React.CSSProperties)
|
||||
: undefined;
|
||||
|
||||
const resolvedCode = code ?? defaultCode;
|
||||
|
||||
return (
|
||||
<LiveProvider code={resolvedCode} theme={prismTheme} scope={{Layout, Node}}>
|
||||
<div className={styles.wrapper} ref={playgroundRef} style={heightStyle}>
|
||||
<div className={clsx(styles.playgroundRow, 'container')}>
|
||||
<div className={clsx(styles.editorColumn)}>
|
||||
<EditorToolbar
|
||||
getCode={useCallback(
|
||||
() =>
|
||||
nullthrows(
|
||||
playgroundRef.current?.querySelector('.prism-code')
|
||||
?.textContent,
|
||||
),
|
||||
[],
|
||||
)}
|
||||
/>
|
||||
<LiveEditor className={clsx(styles.playgroundEditor)} />
|
||||
</div>
|
||||
<div className={clsx(styles.previewColumn)}>
|
||||
<LivePreview
|
||||
className={clsx(styles.livePreview)}
|
||||
Component={LivePreviewWrapper}
|
||||
/>
|
||||
<LiveError className={clsx(styles.liveError)} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LiveProvider>
|
||||
);
|
||||
}
|
||||
|
||||
type LayoutProps = Readonly<{
|
||||
children: React.ReactNode;
|
||||
config?: {useWebDefaults?: boolean};
|
||||
}>;
|
||||
|
||||
function Layout({children, config}: LayoutProps) {
|
||||
if (React.Children.count(children) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const child = React.Children.only(children);
|
||||
if (!React.isValidElement(child) || child.type !== Node) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const styleNode = styleNodeFromYogaNode(child as unknown as Node);
|
||||
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<LazyYogaViewer
|
||||
rootNode={styleNode}
|
||||
useWebDefaults={config?.useWebDefaults}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
type NodeProps = Readonly<{
|
||||
children: React.ReactNode;
|
||||
style: FlexStyle;
|
||||
}>;
|
||||
|
||||
class Node extends React.PureComponent<NodeProps> {}
|
||||
|
||||
function styleNodeFromYogaNode(
|
||||
yogaNode: React.ElementRef<typeof Node>,
|
||||
): StyleNode {
|
||||
const children: StyleNode[] = [];
|
||||
|
||||
React.Children.forEach(yogaNode.props.children, child => {
|
||||
if (React.isValidElement(child) && child.type === Node) {
|
||||
children.push(styleNodeFromYogaNode(child as unknown as Node));
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
style: yogaNode.props.style,
|
||||
children,
|
||||
};
|
||||
}
|
||||
|
||||
// Docusaurus SSR does not correctly support top-level await in the import
|
||||
// chain
|
||||
// 1. https://github.com/facebook/docusaurus/issues/7238
|
||||
// 2. https://github.com/facebook/docusaurus/issues/9468
|
||||
const LazyYogaViewer = lazy(() => import('./YogaViewer'));
|
||||
Reference in New Issue
Block a user