diff --git a/compiler/forget/packages/playground/components/Editor/Input.tsx b/compiler/forget/packages/playground/components/Editor/Input.tsx index 09ddf00579..96fe726005 100644 --- a/compiler/forget/packages/playground/components/Editor/Input.tsx +++ b/compiler/forget/packages/playground/components/Editor/Input.tsx @@ -8,9 +8,7 @@ import invariant from "invariant"; import type { editor } from "monaco-editor"; import { useEffect, useState } from "react"; import { renderForgetMarkers } from "../../lib/forgetMonacoDiagnostics"; -import { createInputFile, getSelectedFile } from "../../lib/stores"; import { useStore, useStoreDispatch } from "../StoreContext"; -import InputTabSelector from "./InputTabSelector"; import { monacoOptions } from "./monacoOptions"; // TODO: Make TS recognize .d.ts files, in addition to loading them with webpack. // @ts-ignore @@ -20,37 +18,25 @@ export default function Input({ diagnostics }: { diagnostics: Diagnostic[] }) { const [monaco, setMonaco] = useState(null); const store = useStore(); const dispatchStore = useStoreDispatch(); - const selectedFile = getSelectedFile(store); useEffect(() => { if (!monaco) return; - const uri = monaco.Uri.parse(`file:///${selectedFile.id}`); + const uri = monaco.Uri.parse(`file:///index.js`); const model = monaco.editor.getModel(uri); invariant(model, "Model must exist for the selected input file."); renderForgetMarkers({ monaco, model, diagnostics }); - }, [diagnostics, monaco, selectedFile.id]); + }, [diagnostics, monaco]); // Set tab width to 2 spaces for the selected input file. useEffect(() => { if (!monaco) return; - const uri = monaco.Uri.parse(`file:///${selectedFile.id}`); + const uri = monaco.Uri.parse(`file:///index.js`); const model = monaco.editor.getModel(uri); invariant(model, "Model must exist for the selected input file."); // N.B. that `tabSize` is a model property, not an editor property. // So, the tab size has to be set per model. model.updateOptions({ tabSize: 2 }); - }, [monaco, selectedFile.id]); - - useEffect(() => { - if (!monaco) return; - // Let Monaco Editor know of the input files so that its language - // service can correctly resolve import statements. - store.files.forEach((file) => { - const lib = [file.content, `file:///${file.id}`] as const; - monaco.languages.typescript.javascriptDefaults.addExtraLib(...lib); - monaco.languages.typescript.typescriptDefaults.addExtraLib(...lib); - }); - }, [monaco, store.files]); + }, [monaco]); const handleChange = (value: string | undefined) => { if (!value) return; @@ -58,7 +44,7 @@ export default function Input({ diagnostics }: { diagnostics: Diagnostic[] }) { dispatchStore({ type: "updateFile", payload: { - file: createInputFile(store.selectedFileId, value), + source: value, }, }); }; @@ -113,21 +99,16 @@ export default function Input({ diagnostics }: { diagnostics: Diagnostic[] }) { return (
- {/* Restrict MonacoEditor's height, since the config autoLayout:true will grow the editor to fit within parent element */}
) => void; - onSubmit: (oldTabId: string, newTabId: string) => void; - onDelete: (tabId: string) => void; - onSwitchTab: (tabId: string) => void; -}) { - const [newTabId, setNewTabId] = useState(tabId); - const tabIdWidth = `${newTabId.length}ch`; - const [isEditing, setIsEditing] = useState(autoFocus); - const tabRef = useRef(null); - - useEffect(() => { - onMountEffect(tabId, tabRef); - }, [tabId, onMountEffect]); - - return ( -
{ - onSwitchTab(tabId); - }} - > - {isEditing ? ( -
{ - e.preventDefault(); - onSubmit(tabId, newTabId); - }} - > - setNewTabId(e.target.value)} - onBlur={(e) => { - // Stop if clicking on delete button. - if (e.relatedTarget && e.relatedTarget.id === "delete-button") - return; - setIsEditing(false); - onSubmit(tabId, newTabId); - }} - /> -
- ) : ( -
{ - if (allowEdit) setIsEditing(true); - }} - > - {tabId} -
- )} - {isDeletable ? ( - - ) : null} -
- ); -} diff --git a/compiler/forget/packages/playground/components/Editor/InputTabSelector.tsx b/compiler/forget/packages/playground/components/Editor/InputTabSelector.tsx deleted file mode 100644 index 531a2de6b5..0000000000 --- a/compiler/forget/packages/playground/components/Editor/InputTabSelector.tsx +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - */ - -import { PlusIcon } from "@heroicons/react/solid"; -import invariant from "invariant"; -import { useSnackbar } from "notistack"; -import { useState, type RefObject } from "react"; -import { - checkInputFile, - createInputFile, - createMessage, - MessageLevel, - MessageSource, - type Store, -} from "../../lib/stores"; -import { useStore, useStoreDispatch } from "../StoreContext"; -import InputTab from "./InputTab"; - -export default function InputTabSelector() { - const store = useStore(); - const dispatchStore = useStoreDispatch(); - const [isCreating, setIsCreating] = useState(false); - const tabIds = isCreating - ? [...store.files.map((f) => f.id), generateUniqueFileId(store.files)] - : store.files.map((f) => f.id); - const { enqueueSnackbar } = useSnackbar(); - - const handleMountEffect = ( - tabId: string, - tabRef: RefObject - ) => { - const isPendingCreation = isCreating && tabIds[tabIds.length - 1] === tabId; - if (isPendingCreation || store.selectedFileId === tabId) { - // Scroll new or selected tab into view when loading the page, switching tabs, - // and adding new tabs. - tabRef.current?.scrollIntoView({ - block: "nearest", - inline: "start", - behavior: "smooth", - }); - } - }; - - // Create a new tab. - const handleAddTab = (_oldTabId: string, newTabId: string) => { - try { - const newTab = createInputFile(newTabId, ""); - checkInputFile(newTab, undefined, store); - - dispatchStore({ - type: "addFile", - payload: { file: newTab }, - }); - setIsCreating(false); - } catch (e) { - if (e instanceof Error) { - enqueueSnackbar(e.message, { - variant: "message", - ...createMessage( - e.message, - MessageLevel.Error, - MessageSource.Playground - ), - }); - } - } - }; - - // Update ID of an existing tab. - const handleUpdateId = (oldTabId: string, newTabId: string) => { - if (newTabId === oldTabId) return; - const oldTab = store.files.find((f) => f.id === oldTabId); - invariant(oldTab, "Tab being updated must exist."); - - try { - const newTab = createInputFile(newTabId, oldTab.content); - checkInputFile(newTab, oldTab, store); - - dispatchStore({ - type: "updateFile", - payload: { oldFileId: oldTabId, file: newTab }, - }); - } catch (e) { - if (e instanceof Error) { - enqueueSnackbar(e.message, { - variant: "message", - ...createMessage( - e.message, - MessageLevel.Error, - MessageSource.Playground - ), - }); - } - } - }; - - const handleDeleteTab = (tabId: string) => { - // Tab being created. - if (isCreating) { - setIsCreating(false); - } - // Existing tabs. - else { - const ok = confirm(`Are you sure you want to delete ${tabId}?`); - if (!ok) return; - - dispatchStore({ - type: "deleteFile", - payload: { fileId: tabId }, - }); - } - }; - - const handleSwitchTab = (tabId: string) => { - const isPendingCreation = isCreating && tabIds[tabIds.length - 1] === tabId; - if (!isPendingCreation) { - dispatchStore({ - type: "switchInputTab", - payload: { selectedFileId: tabId }, - }); - } - }; - - return ( -
-
- {tabIds.map((tabId, index) => { - // Hide _app.js on UI. - if (tabId === "_app.js") return; - - const isSelected = store.selectedFileId === tabId; - const isPendingCreation = isCreating && index === tabIds.length - 1; - const isIndexTab = index === 1; - const isDeletable = !isIndexTab || isPendingCreation; - const shouldDivide = index < tabIds.length - 1; - - return ( -
- - {shouldDivide && ( -
- )} -
- ); - })} -
- -
-
- ); -} - -/** - * Generate a file ID different from those of exsiting @param inputFiles. - */ -function generateUniqueFileId(inputFiles: Store["files"]) { - let name = "Component"; - const ext = "js"; - - let i = 0; - while (true) { - if (inputFiles.find((f) => f.id === `${name}.${ext}`)) { - name = `Component${++i}`; - } else { - break; - } - } - - return `${name}.${ext}`; -} diff --git a/compiler/forget/packages/playground/components/Editor/Output.tsx b/compiler/forget/packages/playground/components/Editor/Output.tsx index efc8125eeb..c41d0e3e84 100644 --- a/compiler/forget/packages/playground/components/Editor/Output.tsx +++ b/compiler/forget/packages/playground/components/Editor/Output.tsx @@ -15,9 +15,8 @@ import { } from "babel-plugin-react-forget"; import { memo, useEffect, useMemo } from "react"; import compile from "../../lib/compilerDriver"; -import { getSelectedFile, type Store } from "../../lib/stores"; +import type { Store } from "../../lib/stores"; import GraphView from "../GraphView"; -import Preview from "../Preview"; import { monacoOptions } from "./monacoOptions"; import HIRTabContent from "./HIRTabContent"; @@ -32,7 +31,7 @@ type Props = { function Output({ store, updateDiagnostics }: Props) { const { outputs, diagnostics } = useMemo( - () => compile(getSelectedFile(store), store.compilerFlags), + () => compile(store.source, store.compilerFlags), [store] ); useEffect(() => { @@ -42,7 +41,6 @@ function Output({ store, updateDiagnostics }: Props) { , IR: , HIR: , CFG: , diff --git a/compiler/forget/packages/playground/components/Preview.tsx b/compiler/forget/packages/playground/components/Preview.tsx deleted file mode 100644 index 0d502e28c4..0000000000 --- a/compiler/forget/packages/playground/components/Preview.tsx +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - */ - -import LightBulbIcon from "@heroicons/react/solid/LightBulbIcon"; -import { FormControlLabel, Switch } from "@mui/material"; -import clsx from "clsx"; -import invariant from "invariant"; -import { useEffect, useRef, useState } from "react"; -import { useMountEffect } from "../hooks"; -import { bundle, BundleResult, defaultBundleRes } from "../lib/moduleBundler"; -import { Store } from "../lib/stores"; - -const srcDoc = ` - - - - - - - - - - -
- - -`; - -enum MessageType { - CodeUpdate = "CodeUpdate", -} - -export default function Preview({ store }: { store: Store }) { - const [iframeReady, setIframeReady] = useState(false); - const iframeRef = useRef(null); - const [bundleRes, setBundleRes] = useState(defaultBundleRes); - const [enableForget, setEnableForget] = useState(true); - // used to invalidate import cache (when toggling enableForget) - const [toggleCount, setToggleCount] = useState(0); - - useMountEffect(() => { - if (!iframeRef || !iframeRef.current) return; - iframeRef.current.srcdoc = srcDoc; - iframeRef.current.addEventListener("load", () => { - setIframeReady(true); - }); - }); - - // Perform async operations which will trigger a 2nd render. - useEffect(() => { - async function bundleInputs() { - const bundleRes = await bundle(store.files, store.compilerFlags); - setBundleRes(bundleRes); - } - bundleInputs(); - }, [store.files, store.compilerFlags]); - - // Synchronzie with iframe and messaging UIs. - useEffect(() => { - if (!bundleRes) return; - const { res } = bundleRes; - - if (iframeReady && res) { - iframeRef.current!.contentWindow!.postMessage({ - event: MessageType.CodeUpdate, - source: res.getCode(enableForget ? "after" : "before", toggleCount), - }); - } - }, [iframeReady, bundleRes, store.selectedFileId, enableForget, toggleCount]); - - const diff = bundleRes.res?.codeSizeDiff; - const showCodeSizeChange = diff != null && enableForget; - - const ticker = (percentage: number) => ( - <> - {percentage >= 0 ? "increased" : "decreased"} by{" "} - - {Math.abs(percentage).toFixed(2)}% - - - ); - - return ( -
- { - setEnableForget(e.target.checked); - setToggleCount(toggleCount + 1); - }} - /> - } - label="Compile using Forget?" - /> - {showCodeSizeChange && ( -
- -

- Compiled code size {ticker(diff.minPercent)} ({diff.min} minified) - and {ticker(diff.gzipPercent)} ({diff.gzip} minified & gzipped) from - the original. -

-
- )} -