[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",