mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[playground] refactor output tabs
Removes a bit of indirection and opens the way to more easily customize specific tabs.
This commit is contained in:
@@ -1,24 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import TabbedWindow from "../TabbedWindow";
|
||||
import MonacoEditor from "@monaco-editor/react";
|
||||
import {
|
||||
CompilerOutputs,
|
||||
OutputKind,
|
||||
stringifyCompilerOutputs,
|
||||
type Diagnostic,
|
||||
} from "babel-plugin-react-forget";
|
||||
import clsx from "clsx";
|
||||
import { memo, useEffect, useMemo, useState } from "react";
|
||||
import { memo, useEffect, useMemo } from "react";
|
||||
import compile from "../../lib/compilerDriver";
|
||||
import {
|
||||
getSelectedFile,
|
||||
getVisualizedGraphKind,
|
||||
isGraph,
|
||||
isVisualizedGraph,
|
||||
type OutputTabKind,
|
||||
type Store,
|
||||
} from "../../lib/stores";
|
||||
import { getSelectedFile, type Store } from "../../lib/stores";
|
||||
import GraphView from "../GraphView";
|
||||
import Preview from "../Preview";
|
||||
import { monacoOptions } from "./monacoOptions";
|
||||
@@ -33,118 +30,74 @@ type Props = {
|
||||
};
|
||||
|
||||
function Output({ store, updateDiagnostics }: Props) {
|
||||
const [selectedTab, setSelectedTab] = useState<OutputTabKind>("Preview");
|
||||
const { outputs, diagnostics } = useMemo(
|
||||
() => compile(getSelectedFile(store), store.compilerFlags),
|
||||
[store]
|
||||
);
|
||||
useEffect(() => {
|
||||
updateDiagnostics(diagnostics);
|
||||
}, [diagnostics, updateDiagnostics]);
|
||||
return (
|
||||
<TabbedWindow
|
||||
defaultTab="HIR"
|
||||
tabs={{
|
||||
Preview: <Preview store={store} />,
|
||||
IR: <TextTabContent outputs={outputs} kind={OutputKind.IR} />,
|
||||
HIR: <TextTabContent outputs={outputs} kind={OutputKind.HIR} />,
|
||||
CFG: <TextTabContent outputs={outputs} kind={OutputKind.CFG} />,
|
||||
ValGraph: (
|
||||
<GraphTabContent outputs={outputs} kind={OutputKind.ValGraph} />
|
||||
),
|
||||
SCCGraph: (
|
||||
<GraphTabContent outputs={outputs} kind={OutputKind.SCCGraph} />
|
||||
),
|
||||
RedGraph: (
|
||||
<GraphTabContent outputs={outputs} kind={OutputKind.RedGraph} />
|
||||
),
|
||||
LIR: <TextTabContent outputs={outputs} kind={OutputKind.LIR} />,
|
||||
JS: <TextTabContent outputs={outputs} kind={OutputKind.JS} />,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function TextTabContent({
|
||||
outputs,
|
||||
kind,
|
||||
}: {
|
||||
outputs: CompilerOutputs;
|
||||
kind: OutputKind;
|
||||
}) {
|
||||
const prettyOutputs = useMemo(
|
||||
() => stringifyCompilerOutputs(outputs),
|
||||
[outputs]
|
||||
);
|
||||
const outputTabs = [
|
||||
["Preview", "Preview"] as const,
|
||||
...Object.entries(OutputKind),
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
updateDiagnostics(diagnostics);
|
||||
}, [diagnostics, updateDiagnostics]);
|
||||
|
||||
const outputContainer = () => {
|
||||
// Special-case Preview to keep it mounted, saving its state.
|
||||
if (selectedTab === "Preview") return null;
|
||||
if (isVisualizedGraph(selectedTab)) {
|
||||
const graphKind = getVisualizedGraphKind(selectedTab);
|
||||
return (
|
||||
<div className="w-full h-full overflow-auto">
|
||||
<GraphView graphs={outputs[graphKind]} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="w-full h-full">
|
||||
<MonacoEditor
|
||||
path={selectedTab}
|
||||
defaultLanguage="javascript"
|
||||
value={prettyOutputs[selectedTab] ?? "(Empty)"}
|
||||
options={{
|
||||
...monacoOptions,
|
||||
readOnly: true,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// TODO: Abstract into "TabbedWindow" component to reuse across input/output.
|
||||
// TODO: Add indicator that output tabs have overflowed as part of responsive design.
|
||||
return (
|
||||
<div className="flex flex-col w-full h-full">
|
||||
<div className="flex flex-none overflow-x-auto border-b border-gray-200 h-9 no-scrollbar">
|
||||
{outputTabs.map(([outputName, outputKind], index) => {
|
||||
const isGraphViewSelected = selectedTab === `Visualized${outputKind}`;
|
||||
const isTextViewSelected = selectedTab === outputKind;
|
||||
const shouldDivide = index < outputTabs.length - 1;
|
||||
<div className="w-full h-full">
|
||||
<MonacoEditor
|
||||
path={kind}
|
||||
defaultLanguage="javascript"
|
||||
value={prettyOutputs[kind] ?? "(Empty)"}
|
||||
options={{
|
||||
...monacoOptions,
|
||||
readOnly: true,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center h-full" key={outputName}>
|
||||
<div
|
||||
className={clsx(
|
||||
"h-full border-b-2 border-white px-4 py-0.5 flex items-center",
|
||||
{
|
||||
"border-link": isTextViewSelected || isGraphViewSelected,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<button
|
||||
disabled={isTextViewSelected}
|
||||
onClick={() => setSelectedTab(outputKind)}
|
||||
className="transition-colors duration-150 ease-in"
|
||||
>
|
||||
{outputName}
|
||||
</button>
|
||||
{isGraph(outputKind) ? (
|
||||
<button
|
||||
title={`Visualize ${outputName}`}
|
||||
onClick={() =>
|
||||
setSelectedTab(
|
||||
isGraphViewSelected
|
||||
? outputKind
|
||||
: `Visualized${outputKind}`
|
||||
)
|
||||
}
|
||||
className="flex-none pl-2 group"
|
||||
>
|
||||
<div
|
||||
title={`Show visualized ${outputName}`}
|
||||
className={clsx(
|
||||
"px-1 text-sm font-medium tracking-wide uppercase rounded text-link bg-highlight whitespace-nowrap",
|
||||
{
|
||||
"bg-link text-highlight": isGraphViewSelected,
|
||||
}
|
||||
)}
|
||||
>
|
||||
Vis
|
||||
</div>
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
{shouldDivide && (
|
||||
<div className="w-[1px] h-[18px] border-gray-200 border-l mx-1" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className={clsx("w-full h-full bg-card", {
|
||||
hidden: selectedTab !== "Preview",
|
||||
})}
|
||||
>
|
||||
<Preview store={store} />
|
||||
</div>
|
||||
{outputContainer()}
|
||||
function GraphTabContent({
|
||||
outputs,
|
||||
kind,
|
||||
}: {
|
||||
outputs: CompilerOutputs;
|
||||
kind: OutputKind.ValGraph | OutputKind.SCCGraph | OutputKind.RedGraph;
|
||||
}) {
|
||||
return (
|
||||
<div className="w-full h-full overflow-auto">
|
||||
<GraphView graphs={outputs[kind]} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import clsx from "clsx";
|
||||
import React, { useState } from "react";
|
||||
|
||||
export default function TabbedWindow(props: {
|
||||
defaultTab: string | null;
|
||||
tabs: { [name: string]: React.ReactNode };
|
||||
}): React.ReactElement {
|
||||
let [selected, setSelected] = useState<string | null>(props.defaultTab);
|
||||
if (selected === null) {
|
||||
selected = Object.keys(props.tabs)[0];
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full h-full">
|
||||
<div className="flex flex-none overflow-x-auto border-b border-gray-200 h-9 no-scrollbar">
|
||||
{Object.keys(props.tabs).map((name, index, all) => {
|
||||
const isFirst = index === 0;
|
||||
const isSelected = name === selected;
|
||||
return (
|
||||
<React.Fragment key={name}>
|
||||
{isFirst ? null : (
|
||||
<div className="w-[1px] h-[18px] self-center border-gray-200 border-l mx-1" />
|
||||
)}
|
||||
<div
|
||||
className={clsx(
|
||||
"h-full border-b-2 border-white px-4 py-0.5 flex items-center",
|
||||
{
|
||||
"border-link": isSelected,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<button
|
||||
disabled={isSelected}
|
||||
onClick={() => setSelected(name)}
|
||||
className="transition-colors duration-150 ease-in"
|
||||
>
|
||||
{name}
|
||||
</button>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{props.tabs[selected]}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -9,37 +9,6 @@ import { codec } from "../utils";
|
||||
import { ForgetCompilerFlags } from "../compilerDriver";
|
||||
import { parseCompilerFlags } from "babel-plugin-react-forget";
|
||||
|
||||
type VisualizedGraph =
|
||||
| `Visualized${OutputKind.ValGraph}`
|
||||
| `Visualized${OutputKind.SCCGraph}`
|
||||
| `Visualized${OutputKind.RedGraph}`;
|
||||
|
||||
export type OutputTabKind = OutputKind | VisualizedGraph | "Preview";
|
||||
|
||||
export function isGraph(
|
||||
tabKind: OutputTabKind
|
||||
): tabKind is
|
||||
| OutputKind.ValGraph
|
||||
| OutputKind.SCCGraph
|
||||
| OutputKind.RedGraph
|
||||
| VisualizedGraph {
|
||||
return tabKind.endsWith("Graph");
|
||||
}
|
||||
|
||||
export function isVisualizedGraph(
|
||||
tabKind: OutputTabKind
|
||||
): tabKind is VisualizedGraph {
|
||||
return tabKind.startsWith("Visualized");
|
||||
}
|
||||
|
||||
export function getVisualizedGraphKind(graphKind: VisualizedGraph) {
|
||||
const kind = graphKind.slice("Visualized".length);
|
||||
return kind as
|
||||
| OutputKind.ValGraph
|
||||
| OutputKind.SCCGraph
|
||||
| OutputKind.RedGraph;
|
||||
}
|
||||
|
||||
export enum FileExt {
|
||||
js = "javascript",
|
||||
jsx = "javascript",
|
||||
|
||||
Reference in New Issue
Block a user