Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 35 additions & 94 deletions compiler/apps/playground/components/Editor/ConfigEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,8 @@ import type {editor} from 'monaco-editor';
import * as monaco from 'monaco-editor';
import React, {useState, useCallback} from 'react';
import {Resizable} from 're-resizable';
import {useSnackbar} from 'notistack';
import {useStore, useStoreDispatch} from '../StoreContext';
import {monacoOptions} from './monacoOptions';
import {
ConfigError,
generateOverridePragmaFromConfig,
updateSourceWithOverridePragma,
} from '../../lib/configUtils';

// @ts-expect-error - webpack asset/source loader handles .d.ts files as strings
import compilerTypeDefs from 'babel-plugin-react-compiler/dist/index.d.ts';
Expand All @@ -28,61 +22,17 @@ export default function ConfigEditor(): React.ReactElement {
const [isExpanded, setIsExpanded] = useState(false);
const store = useStore();
const dispatchStore = useStoreDispatch();
const {enqueueSnackbar} = useSnackbar();

const toggleExpanded = useCallback(() => {
setIsExpanded(prev => !prev);
}, []);

const handleApplyConfig: () => Promise<void> = async () => {
try {
const config = store.config || '';

if (!config.trim()) {
enqueueSnackbar(
'Config is empty. Please add configuration options first.',
{
variant: 'warning',
},
);
return;
}
const newPragma = await generateOverridePragmaFromConfig(config);
const updatedSource = updateSourceWithOverridePragma(
store.source,
newPragma,
);

dispatchStore({
type: 'updateFile',
payload: {
source: updatedSource,
config: config,
},
});
} catch (error) {
console.error('Failed to apply config:', error);

if (error instanceof ConfigError && error.message.trim()) {
enqueueSnackbar(error.message, {
variant: 'error',
});
} else {
enqueueSnackbar('Unexpected error: failed to apply config.', {
variant: 'error',
});
}
}
};

const handleChange: (value: string | undefined) => void = value => {
if (value === undefined) return;

// Only update the config
dispatchStore({
type: 'updateFile',
type: 'updateConfig',
payload: {
source: store.source,
config: value,
},
});
Expand Down Expand Up @@ -120,49 +70,40 @@ export default function ConfigEditor(): React.ReactElement {
return (
<div className="flex flex-row relative">
{isExpanded ? (
<>
<Resizable
className="border-r"
minWidth={300}
maxWidth={600}
defaultSize={{width: 350, height: 'auto'}}
enable={{right: true}}>
<h2
title="Minimize config editor"
aria-label="Minimize config editor"
onClick={toggleExpanded}
className="p-4 duration-150 ease-in border-b cursor-pointer border-grey-200 font-light text-secondary hover:text-link">
- Config Overrides
</h2>
<div className="h-[calc(100vh_-_3.5rem_-_4rem)]">
<MonacoEditor
path={'config.ts'}
language={'typescript'}
value={store.config}
onMount={handleMount}
onChange={handleChange}
options={{
...monacoOptions,
lineNumbers: 'off',
folding: false,
renderLineHighlight: 'none',
scrollBeyondLastLine: false,
hideCursorInOverviewRuler: true,
overviewRulerBorder: false,
overviewRulerLanes: 0,
fontSize: 12,
}}
/>
</div>
</Resizable>
<button
onClick={handleApplyConfig}
title="Apply config overrides to input"
aria-label="Apply config overrides to input"
className="absolute right-0 top-1/2 transform -translate-y-1/2 translate-x-1/2 z-10 w-8 h-8 bg-blue-500 hover:bg-blue-600 text-white rounded-full border-2 border-white shadow-lg flex items-center justify-center text-sm font-medium transition-colors duration-150">
</button>
</>
<Resizable
className="border-r"
minWidth={300}
maxWidth={600}
defaultSize={{width: 350, height: 'auto'}}
enable={{right: true}}>
<h2
title="Minimize config editor"
aria-label="Minimize config editor"
onClick={toggleExpanded}
className="p-4 duration-150 ease-in border-b cursor-pointer border-grey-200 font-light text-secondary hover:text-link">
- Config Overrides
</h2>
<div className="h-[calc(100vh_-_3.5rem_-_4rem)]">
<MonacoEditor
path={'config.ts'}
language={'typescript'}
value={store.config}
onMount={handleMount}
onChange={handleChange}
options={{
...monacoOptions,
lineNumbers: 'off',
folding: false,
renderLineHighlight: 'none',
scrollBeyondLastLine: false,
hideCursorInOverviewRuler: true,
overviewRulerBorder: false,
overviewRulerLanes: 0,
fontSize: 12,
}}
/>
</div>
</Resizable>
) : (
<div className="relative items-center h-full px-1 py-6 align-middle border-r border-grey-200">
<button
Expand Down
40 changes: 26 additions & 14 deletions compiler/apps/playground/components/Editor/EditorImpl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import BabelPluginReactCompiler, {
parsePluginOptions,
printReactiveFunctionWithOutlined,
printFunctionWithOutlined,
type LoggerEvent,
} from 'babel-plugin-react-compiler';
import clsx from 'clsx';
import invariant from 'invariant';
Expand All @@ -46,7 +47,6 @@ import {
PrintedCompilerPipelineValue,
} from './Output';
import {transformFromAstSync} from '@babel/core';
import {LoggerEvent} from 'babel-plugin-react-compiler/dist/Entrypoint';
import {useSearchParams} from 'next/navigation';

function parseInput(
Expand Down Expand Up @@ -147,6 +147,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
function compile(
source: string,
mode: 'compiler' | 'linter',
configOverrides: string,
): [CompilerOutput, 'flow' | 'typescript'] {
const results = new Map<string, Array<PrintedCompilerPipelineValue>>();
const error = new CompilerError();
Expand Down Expand Up @@ -207,7 +208,7 @@ function compile(
}
}
};
const parsedOptions = parseConfigPragmaForTests(pragma, {
const parsedPragmaOptions = parseConfigPragmaForTests(pragma, {
compilationMode: 'infer',
environment:
mode === 'linter'
Expand All @@ -227,10 +228,26 @@ function compile(
/* use defaults for compiler mode */
},
});

// Parse config overrides from config editor
let configOverrideOptions: any = {};
const configMatch = configOverrides.match(/^\s*import.*?\n\n\((.*)\)/s);
// TODO: initialize store with URL params, not empty store
if (configOverrides.trim()) {
if (configMatch && configMatch[1]) {
const configString = configMatch[1].replace(/satisfies.*$/, '').trim();
configOverrideOptions = new Function(`return (${configString})`)();
} else {
throw new Error('Invalid config overrides');
}
}

const opts: PluginOptions = parsePluginOptions({
...parsedOptions,
...parsedPragmaOptions,
...configOverrideOptions,
environment: {
...parsedOptions.environment,
...parsedPragmaOptions.environment,
...configOverrideOptions.environment,
customHooks: new Map([...COMMON_HOOKS]),
},
logger: {
Expand Down Expand Up @@ -285,19 +302,14 @@ export default function Editor(): JSX.Element {
const dispatchStore = useStoreDispatch();
const {enqueueSnackbar} = useSnackbar();
const [compilerOutput, language] = useMemo(
() => compile(deferredStore.source, 'compiler'),
[deferredStore.source],
() => compile(deferredStore.source, 'compiler', deferredStore.config),
[deferredStore.source, deferredStore.config],
);
const [linterOutput] = useMemo(
() => compile(deferredStore.source, 'linter'),
[deferredStore.source],
() => compile(deferredStore.source, 'linter', deferredStore.config),
[deferredStore.source, deferredStore.config],
);

// TODO: Remove this once the config editor is more stable
const searchParams = useSearchParams();
const search = searchParams.get('showConfig');
const shouldShowConfig = search === 'true';

useMountEffect(() => {
// Initialize store
let mountStore: Store;
Expand Down Expand Up @@ -339,7 +351,7 @@ export default function Editor(): JSX.Element {
return (
<>
<div className="relative flex basis top-14">
{shouldShowConfig && <ConfigEditor />}
<ConfigEditor />
<div className={clsx('relative sm:basis-1/4')}>
<Input language={language} errors={errors} />
</div>
Expand Down
7 changes: 1 addition & 6 deletions compiler/apps/playground/components/Editor/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {useStore, useStoreDispatch} from '../StoreContext';
import {monacoOptions} from './monacoOptions';
// @ts-expect-error TODO: Make TS recognize .d.ts files, in addition to loading them with webpack.
import React$Types from '../../node_modules/@types/react/index.d.ts';
import {parseAndFormatConfig} from '../../lib/configUtils.ts';

loader.config({monaco});

Expand Down Expand Up @@ -83,14 +82,10 @@ export default function Input({errors, language}: Props): JSX.Element {
const handleChange: (value: string | undefined) => void = async value => {
if (!value) return;

// Parse and format the config
const config = await parseAndFormatConfig(value);

dispatchStore({
type: 'updateFile',
type: 'updateSource',
payload: {
source: value,
config,
},
});
};
Expand Down
18 changes: 15 additions & 3 deletions compiler/apps/playground/components/StoreContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,14 @@ type ReducerAction =
};
}
| {
type: 'updateFile';
type: 'updateSource';
payload: {
source: string;
};
}
| {
type: 'updateConfig';
payload: {
config: string;
};
}
Expand All @@ -69,11 +74,18 @@ function storeReducer(store: Store, action: ReducerAction): Store {
const newStore = action.payload.store;
return newStore;
}
case 'updateFile': {
const {source, config} = action.payload;
case 'updateSource': {
const source = action.payload.source;
const newStore = {
...store,
source,
};
return newStore;
}
case 'updateConfig': {
const config = action.payload.config;
const newStore = {
...store,
config,
};
return newStore;
Expand Down
Loading
Loading