Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add interface to debug properties for code components #803

Merged
merged 3 commits into from
Aug 17, 2022
Merged
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,16 @@ import createCache from '@emotion/cache';
import { CacheProvider } from '@emotion/react';
import * as ReactDOM from 'react-dom';
import { ErrorBoundary } from 'react-error-boundary';
import { NodeId, createComponent, ToolpadComponent, TOOLPAD_COMPONENT } from '@mui/toolpad-core';
import {
NodeId,
createComponent,
ToolpadComponent,
TOOLPAD_COMPONENT,
ArgTypeDefinitions,
ArgTypeDefinition,
} from '@mui/toolpad-core';
import { useQuery } from '@tanstack/react-query';
import invariant from 'invariant';
import * as appDom from '../../../appDom';
import { useDom, useDomApi } from '../../DomLoader';
import { tryFormat } from '../../../utils/prettier';
Expand All @@ -17,17 +25,61 @@ import usePageTitle from '../../../utils/usePageTitle';
import useLatest from '../../../utils/useLatest';
import AppThemeProvider from '../../../runtime/AppThemeProvider';
import useCodeComponent from './useCodeComponent';
import { mapValues } from '../../../utils/collections';
import { filterValues, mapValues } from '../../../utils/collections';
import ErrorAlert from '../PageEditor/ErrorAlert';
import lazyComponent from '../../../utils/lazyComponent';
import CenteredSpinner from '../../../components/CenteredSpinner';
import SplitPane from '../../../components/SplitPane';
import { getDefaultControl } from '../../propertyControls';
import { WithControlledProp } from '../../../utils/types';

const TypescriptEditor = lazyComponent(() => import('../../../components/TypescriptEditor'), {
noSsr: true,
fallback: <CenteredSpinner />,
});

interface PropertyEditorProps extends WithControlledProp<any> {
name: string;
argType: ArgTypeDefinition;
}

function PropertyEditor({ argType, name, value, onChange }: PropertyEditorProps) {
const Control = getDefaultControl(argType);
if (!Control) {
return null;
}
return (
<ErrorBoundary fallback={null}>
<Control label={name} propType={argType.typeDef} value={value} onChange={onChange} />
</ErrorBoundary>
);
}

interface PropertiesEditorProps extends WithControlledProp<Record<string, any>> {
argTypes: ArgTypeDefinitions;
}

function PropertiesEditor({ argTypes, value, onChange }: PropertiesEditorProps) {
return (
<Stack sx={{ p: 2, gap: 2, overflow: 'auto', height: '100%' }}>
<Typography>Properties:</Typography>
{Object.entries(argTypes).map(([name, argType]) => {
invariant(argType, 'argType not defined');
return (
<ErrorBoundary fallback={<div>{name}</div>} resetKeys={[argType]}>
<PropertyEditor
name={name}
argType={argType}
value={value[name]}
onChange={(newPropValue) => onChange({ ...value, [name]: newPropValue })}
/>
</ErrorBoundary>
);
})}
</Stack>
);
}

const Noop = createComponent(() => null);

const CanvasFrame = styled('iframe')({
Expand Down Expand Up @@ -148,6 +200,8 @@ function CodeComponentEditorContent({ codeComponentNode }: CodeComponentEditorCo
[argTypes],
);

const [props, setProps] = React.useState({});

return (
<React.Fragment>
<Stack sx={{ height: '100%' }}>
Expand All @@ -162,7 +216,10 @@ function CodeComponentEditorContent({ codeComponentNode }: CodeComponentEditorCo
extraLibs={extraLibs}
/>

<CanvasFrame ref={frameRef} title="Code component sandbox" onLoad={onLoad} />
<SplitPane split="horizontal" allowResize size="20%" primary="second">
<CanvasFrame ref={frameRef} title="Code component sandbox" onLoad={onLoad} />
<PropertiesEditor argTypes={argTypes} value={props} onChange={setProps} />
</SplitPane>
</SplitPane>
</Box>
<Toolbar
Expand All @@ -184,7 +241,10 @@ function CodeComponentEditorContent({ codeComponentNode }: CodeComponentEditorCo
fallbackRender={({ error: runtimeError }) => <ErrorAlert error={runtimeError} />}
>
<AppThemeProvider dom={dom}>
<CodeComponent {...defaultProps} />
<CodeComponent
{...defaultProps}
{...filterValues(props, (propValue) => typeof propValue !== 'undefined')}
/>
</AppThemeProvider>
</ErrorBoundary>
{compileError ? <ErrorAlert error={compileError} /> : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ function SelectPropEditor({ label, propType, value, onChange, disabled }: Editor
const items = propType.type === 'string' ? propType.enum ?? [] : [];
const handleChange = React.useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
onChange(event.target.value);
onChange(event.target.value || undefined);
},
[onChange],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { EditorProps } from '../../types';
function StringPropEditor({ label, value, onChange, disabled }: EditorProps<string>) {
const handleChange = React.useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
onChange(event.target.value);
onChange(event.target.value || undefined);
},
[onChange],
);
Expand Down
2 changes: 1 addition & 1 deletion packages/toolpad-app/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export interface EditorProps<T> {
propType: PropValueType;
disabled?: boolean;
value: T | undefined;
onChange: (newValue: T) => void;
onChange: (newValue: T | undefined) => void;
}

export type FlowDirection = 'row' | 'column' | 'row-reverse' | 'column-reverse';
Expand Down