@samitouri / QOS-React-2 / commits / 5e86e6a9c8

[playground] refactor output tabs

Removes a bit of indirection and opens the way to more easily customize specific tabs.

Jan Kassens committed Nov 10, 2022 at 14:32 UTC 5e86e6a9c8f07779b43065e65a5187a23811e860
3 files changed +120 -144
compiler/forget/packages/playground/components/Editor/Output.tsx
+66 -113
@@ -1,24 +1,21 @@
1 -/*
2 - * Copyright (c) Facebook, Inc. and its affiliates.
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 */
7
8 +import TabbedWindow from "../TabbedWindow";
9 import MonacoEditor from "@monaco-editor/react";
10 import {
11 + CompilerOutputs,
12 OutputKind,
13 stringifyCompilerOutputs,
14 type Diagnostic,
15 } from "babel-plugin-react-forget";
11 -import clsx from "clsx";
12 -import { memo, useEffect, useMemo, useState } from "react";
16 +import { memo, useEffect, useMemo } from "react";
17 import compile from "../../lib/compilerDriver";
14 -import {
15 - getSelectedFile,
16 - getVisualizedGraphKind,
17 - isGraph,
18 - isVisualizedGraph,
19 - type OutputTabKind,
20 - type Store,
21 -} from "../../lib/stores";
18 +import { getSelectedFile, type Store } from "../../lib/stores";
19 import GraphView from "../GraphView";
20 import Preview from "../Preview";
21 import { monacoOptions } from "./monacoOptions";
@@ -33,118 +30,74 @@ type Props = {
30 };
31
32 function Output({ store, updateDiagnostics }: Props) {
36 - const [selectedTab, setSelectedTab] = useState<OutputTabKind>("Preview");
33 const { outputs, diagnostics } = useMemo(
34 () => compile(getSelectedFile(store), store.compilerFlags),
35 [store]
36 );
41 - const prettyOutputs = useMemo(
42 - () => stringifyCompilerOutputs(outputs),
43 - [outputs]
44 - );
45 - const outputTabs = [
46 - ["Preview", "Preview"] as const,
47 - ...Object.entries(OutputKind),
48 - ];
49 -
37 useEffect(() => {
38 updateDiagnostics(diagnostics);
39 }, [diagnostics, updateDiagnostics]);
40 + return (
41 + <TabbedWindow
42 + defaultTab="HIR"
43 + tabs={{
44 + Preview: <Preview store={store} />,
45 + IR: <TextTabContent outputs={outputs} kind={OutputKind.IR} />,
46 + HIR: <TextTabContent outputs={outputs} kind={OutputKind.HIR} />,
47 + CFG: <TextTabContent outputs={outputs} kind={OutputKind.CFG} />,
48 + ValGraph: (
49 + <GraphTabContent outputs={outputs} kind={OutputKind.ValGraph} />
50 + ),
51 + SCCGraph: (
52 + <GraphTabContent outputs={outputs} kind={OutputKind.SCCGraph} />
53 + ),
54 + RedGraph: (
55 + <GraphTabContent outputs={outputs} kind={OutputKind.RedGraph} />
56 + ),
57 + LIR: <TextTabContent outputs={outputs} kind={OutputKind.LIR} />,
58 + JS: <TextTabContent outputs={outputs} kind={OutputKind.JS} />,
59 + }}
60 + />
61 + );
62 +}
63
54 - const outputContainer = () => {
55 - // Special-case Preview to keep it mounted, saving its state.
56 - if (selectedTab === "Preview") return null;
57 - if (isVisualizedGraph(selectedTab)) {
58 - const graphKind = getVisualizedGraphKind(selectedTab);
59 - return (
60 - <div className="w-full h-full overflow-auto">
61 - <GraphView graphs={outputs[graphKind]} />
62 - </div>
63 - );
64 - }
65 - return (
66 - <div className="w-full h-full">
67 - <MonacoEditor
68 - path={selectedTab}
69 - defaultLanguage="javascript"
70 - value={prettyOutputs[selectedTab] ?? "(Empty)"}
71 - options={{
72 - ...monacoOptions,
73 - readOnly: true,
74 - }}
75 - />
76 - </div>
77 - );
78 - };
64 +function TextTabContent({
65 + outputs,
66 + kind,
67 +}: {
68 + outputs: CompilerOutputs;
69 + kind: OutputKind;
70 +}) {
71 + const prettyOutputs = useMemo(
72 + () => stringifyCompilerOutputs(outputs),
73 + [outputs]
74 + );
75
80 - // TODO: Abstract into "TabbedWindow" component to reuse across input/output.
81 - // TODO: Add indicator that output tabs have overflowed as part of responsive design.
76 return (
83 - <div className="flex flex-col w-full h-full">
84 - <div className="flex flex-none overflow-x-auto border-b border-gray-200 h-9 no-scrollbar">
85 - {outputTabs.map(([outputName, outputKind], index) => {
86 - const isGraphViewSelected = selectedTab === `Visualized${outputKind}`;
87 - const isTextViewSelected = selectedTab === outputKind;
88 - const shouldDivide = index < outputTabs.length - 1;
77 + <div className="w-full h-full">
78 + <MonacoEditor
79 + path={kind}
80 + defaultLanguage="javascript"
81 + value={prettyOutputs[kind] ?? "(Empty)"}
82 + options={{
83 + ...monacoOptions,
84 + readOnly: true,
85 + }}
86 + />
87 + </div>
88 + );
89 +}
90
90 - return (
91 - <div className="flex items-center h-full" key={outputName}>
92 - <div
93 - className={clsx(
94 - "h-full border-b-2 border-white px-4 py-0.5 flex items-center",
95 - {
96 - "border-link": isTextViewSelected || isGraphViewSelected,
97 - }
98 - )}
99 - >
100 - <button
101 - disabled={isTextViewSelected}
102 - onClick={() => setSelectedTab(outputKind)}
103 - className="transition-colors duration-150 ease-in"
104 - >
105 - {outputName}
106 - </button>
107 - {isGraph(outputKind) ? (
108 - <button
109 - title={`Visualize ${outputName}`}
110 - onClick={() =>
111 - setSelectedTab(
112 - isGraphViewSelected
113 - ? outputKind
114 - : `Visualized${outputKind}`
115 - )
116 - }
117 - className="flex-none pl-2 group"
118 - >
119 - <div
120 - title={`Show visualized ${outputName}`}
121 - className={clsx(
122 - "px-1 text-sm font-medium tracking-wide uppercase rounded text-link bg-highlight whitespace-nowrap",
123 - {
124 - "bg-link text-highlight": isGraphViewSelected,
125 - }
126 - )}
127 - >
128 - Vis
129 - </div>
130 - </button>
131 - ) : null}
132 - </div>
133 - {shouldDivide && (
134 - <div className="w-[1px] h-[18px] border-gray-200 border-l mx-1" />
135 - )}
136 - </div>
137 - );
138 - })}
139 - </div>
140 - <div
141 - className={clsx("w-full h-full bg-card", {
142 - hidden: selectedTab !== "Preview",
143 - })}
144 - >
145 - <Preview store={store} />
146 - </div>
147 - {outputContainer()}
91 +function GraphTabContent({
92 + outputs,
93 + kind,
94 +}: {
95 + outputs: CompilerOutputs;
96 + kind: OutputKind.ValGraph | OutputKind.SCCGraph | OutputKind.RedGraph;
97 +}) {
98 + return (
99 + <div className="w-full h-full overflow-auto">
100 + <GraphView graphs={outputs[kind]} />
101 </div>
102 );
103 }
compiler/forget/packages/playground/components/TabbedWindow.tsx new
+54
@@ -0,0 +1,54 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +
8 +import clsx from "clsx";
9 +import React, { useState } from "react";
10 +
11 +export default function TabbedWindow(props: {
12 + defaultTab: string | null;
13 + tabs: { [name: string]: React.ReactNode };
14 +}): React.ReactElement {
15 + let [selected, setSelected] = useState<string | null>(props.defaultTab);
16 + if (selected === null) {
17 + selected = Object.keys(props.tabs)[0];
18 + }
19 +
20 + return (
21 + <div className="flex flex-col w-full h-full">
22 + <div className="flex flex-none overflow-x-auto border-b border-gray-200 h-9 no-scrollbar">
23 + {Object.keys(props.tabs).map((name, index, all) => {
24 + const isFirst = index === 0;
25 + const isSelected = name === selected;
26 + return (
27 + <React.Fragment key={name}>
28 + {isFirst ? null : (
29 + <div className="w-[1px] h-[18px] self-center border-gray-200 border-l mx-1" />
30 + )}
31 + <div
32 + className={clsx(
33 + "h-full border-b-2 border-white px-4 py-0.5 flex items-center",
34 + {
35 + "border-link": isSelected,
36 + }
37 + )}
38 + >
39 + <button
40 + disabled={isSelected}
41 + onClick={() => setSelected(name)}
42 + className="transition-colors duration-150 ease-in"
43 + >
44 + {name}
45 + </button>
46 + </div>
47 + </React.Fragment>
48 + );
49 + })}
50 + </div>
51 + {props.tabs[selected]}
52 + </div>
53 + );
54 +}
compiler/forget/packages/playground/lib/stores/store.ts
-31
@@ -9,37 +9,6 @@ import { codec } from "../utils";
9 import { ForgetCompilerFlags } from "../compilerDriver";
10 import { parseCompilerFlags } from "babel-plugin-react-forget";
11
12 -type VisualizedGraph =
13 - | `Visualized${OutputKind.ValGraph}`
14 - | `Visualized${OutputKind.SCCGraph}`
15 - | `Visualized${OutputKind.RedGraph}`;
16 -
17 -export type OutputTabKind = OutputKind | VisualizedGraph | "Preview";
18 -
19 -export function isGraph(
20 - tabKind: OutputTabKind
21 -): tabKind is
22 - | OutputKind.ValGraph
23 - | OutputKind.SCCGraph
24 - | OutputKind.RedGraph
25 - | VisualizedGraph {
26 - return tabKind.endsWith("Graph");
27 -}
28 -
29 -export function isVisualizedGraph(
30 - tabKind: OutputTabKind
31 -): tabKind is VisualizedGraph {
32 - return tabKind.startsWith("Visualized");
33 -}
34 -
35 -export function getVisualizedGraphKind(graphKind: VisualizedGraph) {
36 - const kind = graphKind.slice("Visualized".length);
37 - return kind as
38 - | OutputKind.ValGraph
39 - | OutputKind.SCCGraph
40 - | OutputKind.RedGraph;
41 -}
42 -
12 export enum FileExt {
13 js = "javascript",
14 jsx = "javascript",