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

Allow typing event arguments for autocomplete #1180

Merged
merged 11 commits into from
Oct 20, 2022
Merged
Show file tree
Hide file tree
Changes from 9 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
10 changes: 2 additions & 8 deletions packages/toolpad-app/src/components/Console.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { darken, lighten, styled, SxProps } from '@mui/material';
import clsx from 'clsx';
import * as React from 'react';
import Inspector, { InspectorProps } from 'react-inspector';
import useInspectorTheme from '../inspectorTheme';
import { interleave } from '../utils/react';
import ObjectInspector from './ObjectInspector';

export interface LogEntry {
timestamp: number;
Expand Down Expand Up @@ -83,11 +82,6 @@ const ConsoleRoot = styled('div')(({ theme }) => {
};
});

function ConsoleInpector(props: InspectorProps) {
const inspectorTheme = useInspectorTheme();
return <Inspector {...props} theme={inspectorTheme} />;
}

interface ConsoleEntryProps {
entry: LogEntry;
}
Expand All @@ -98,7 +92,7 @@ function ConsoleEntry({ entry }: ConsoleEntryProps) {
<div className={classes.logEntryText}>
{interleave(
entry.args.map((arg, i) =>
typeof arg === 'string' ? arg : <ConsoleInpector key={i} data={arg} />,
typeof arg === 'string' ? arg : <ObjectInspector key={i} data={arg} />,
),
' ',
)}
Expand Down
27 changes: 2 additions & 25 deletions packages/toolpad-app/src/components/JsonView.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,15 @@
import * as React from 'react';
import { SxProps, styled, IconButton, Tooltip, Snackbar } from '@mui/material';
import { ObjectInspector, ObjectInspectorProps, ObjectValue, ObjectLabel } from 'react-inspector';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import clsx from 'clsx';
import useInspectorTheme from '../inspectorTheme';
import ObjectInspector, { ObjectInspectorProps } from './ObjectInspector';

const classes = {
viewport: 'Toolpad_ObjectInspectorViewport',
copyToClipboardButton: 'Toolpad_CopyToClipboardButton',
disabled: 'Toolpad_ObjectInspectorDisabled',
};

const nodeRenderer: ObjectInspectorProps['nodeRenderer'] = ({
depth,
name,
data,
isNonenumerable,
}) => {
return depth === 0 ? (
<ObjectValue object={data} />
) : (
<ObjectLabel name={name} data={data} isNonenumerable={isNonenumerable} />
);
};

const JsonViewRoot = styled('div')(({ theme }) => ({
whiteSpace: 'nowrap',
position: 'relative',
Expand Down Expand Up @@ -75,21 +61,12 @@ export default function JsonView({ src, sx, copyToClipboard, disabled, ...props

const handleCopySnackbarClose = React.useCallback(() => setConfirmSnackbarOpen(false), []);

const inspectorTheme = useInspectorTheme();

return (
<JsonViewRoot sx={sx} className={clsx({ [classes.disabled]: disabled })}>
{typeof src === 'undefined' ? null : (
<React.Fragment>
<div className={classes.viewport}>
<ObjectInspector
nodeRenderer={nodeRenderer}
expandLevel={1}
expandPaths={expandPaths}
data={src}
theme={inspectorTheme}
{...props}
/>
<ObjectInspector expandLevel={1} expandPaths={expandPaths} data={src} {...props} />
</div>

{copyToClipboard ? (
Expand Down
1 change: 1 addition & 0 deletions packages/toolpad-app/src/components/MonacoEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ const TYPESCRIPT_DEFAULT_COMPILER_OPTIONS: monaco.languages.typescript.CompilerO
jsx: monaco.languages.typescript.JsxEmit.React,
reactNamespace: 'React',
allowJs: true,
lib: ['es2020'],
typeRoots: ['node_modules/@types'],
};

Expand Down
30 changes: 30 additions & 0 deletions packages/toolpad-app/src/components/ObjectInspector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as React from 'react';
import {
ObjectInspector as ReactObjectInspector,
ObjectInspectorProps,
chromeDark,
chromeLight,
InspectorTheme,
} from 'react-inspector';
import { useTheme } from '@mui/material/styles';

const useInspectorTheme = (): InspectorTheme => {
const theme = useTheme();
return React.useMemo(() => {
return {
...(theme.palette.mode === 'dark' ? chromeDark : chromeLight),
BASE_BACKGROUND_COLOR: 'inherit',
TREENODE_FONT_FAMILY: 'inherit',
TREENODE_FONT_SIZE: 'inherit',
ARROW_FONT_SIZE: 'inherit',
TREENODE_LINE_HEIGHT: 'inherit',
};
}, [theme]);
};

export type { ObjectInspectorProps };

export default (function ObjectInspector(props: ObjectInspectorProps) {
const inspectorTheme = useInspectorTheme();
return <ReactObjectInspector theme={inspectorTheme} {...props} />;
});
16 changes: 0 additions & 16 deletions packages/toolpad-app/src/inspectorTheme.ts

This file was deleted.

43 changes: 32 additions & 11 deletions packages/toolpad-app/src/toolpad/AppEditor/BindingEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export function JsBindingEditor({ value, onChange }: JsBindingEditorProps) {
const { label, globalScope, globalScopeMeta = {}, server, propType } = useBindingEditorContext();
return (
<Stack direction="row" sx={{ height: 400, gap: 2 }}>
<GlobalScopeExplorer value={globalScope} />
<GlobalScopeExplorer value={globalScope} meta={globalScopeMeta} />

<Box
sx={{
Expand Down Expand Up @@ -162,13 +162,14 @@ function JsExpressionActionEditor({ value, onChange }: JsExpressionActionEditorP
(newValue: string) => onChange({ type: 'jsExpressionAction', value: newValue }),
[onChange],
);

return (
<Box sx={{ my: 1 }}>
<Typography>Run code when this event fires</Typography>
<Box
sx={{ my: 3, display: 'flex', flexDirection: 'row', maxHeight: 250, alignItems: 'stretch' }}
>
<GlobalScopeExplorer value={globalScope} />
<GlobalScopeExplorer value={globalScope} meta={globalScopeMeta} />

<JsExpressionEditor
sx={{ flex: 1 }}
Expand Down Expand Up @@ -412,15 +413,35 @@ export function BindingEditor<V>({
</TooltipComponent>
);

const bindingEditorContext: BindingEditorContext = {
label,
globalScope,
globalScopeMeta,
server,
disabled,
propType,
liveBinding,
};
const resolvedMeta = React.useMemo(() => {
const meta: GlobalScopeMeta = { ...globalScopeMeta };
if (propType?.type === 'event' && propType.arguments) {
for (const { name, tsType } of propType.arguments) {
meta[name] ??= {};
meta[name].tsType = tsType;
}
}

for (const [name, globalValue] of Object.entries(globalScope)) {
meta[name] ??= {};
meta[name].value = globalValue;
}

return meta;
}, [propType, globalScopeMeta, globalScope]);

const bindingEditorContext: BindingEditorContext = React.useMemo(
() => ({
label,
globalScope,
globalScopeMeta: resolvedMeta,
server,
disabled,
propType,
liveBinding,
}),
[disabled, globalScope, label, liveBinding, propType, resolvedMeta, server],
);

return (
<BindingEditorContextProvider value={bindingEditorContext}>
Expand Down
4 changes: 4 additions & 0 deletions packages/toolpad-app/src/toolpad/AppEditor/ElementContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import * as React from 'react';
import * as appDom from '../../appDom';

export default React.createContext<appDom.ElementNode | undefined>(undefined);
26 changes: 23 additions & 3 deletions packages/toolpad-app/src/toolpad/AppEditor/GlobalScopeExplorer.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,40 @@
import { Typography } from '@mui/material';
import { Box, SxProps } from '@mui/system';
import * as React from 'react';
import JsonView from '../../components/JsonView';
import ObjectInspector from '../../components/ObjectInspector';
import { GlobalScopeMeta } from '../../utils/types';

export interface GlobalScopeExplorerProps {
value?: Record<string, unknown>;
meta?: GlobalScopeMeta;
sx?: SxProps;
}

export default function GlobalScopeExplorer({ value, sx }: GlobalScopeExplorerProps) {
export default function GlobalScopeExplorer({
meta = {},
value = {},
sx,
}: GlobalScopeExplorerProps) {
const valueKeys = new Set(Object.keys(value));
const extraGlobalKeys = Object.keys(meta).filter((key) => !valueKeys.has(key));
return (
<Box sx={{ ...sx, width: 200, display: 'flex', flexDirection: 'column' }}>
<Typography sx={{ mb: 1 }} variant="subtitle2">
Scope
</Typography>
<JsonView sx={{ flex: 1, width: '100%' }} src={value} />
<Box sx={{ overflow: 'auto', whiteSpace: 'nowrap' }}>
{Object.entries(value).map(([key, content]) => {
return (
<Box key={key} sx={{ display: 'flex', flexDirection: 'row', gap: 1 }}>
{key}
<ObjectInspector expandLevel={0} data={content} />
</Box>
);
})}
{extraGlobalKeys.map((key) => (
<Box key={key}>{key}</Box>
))}
</Box>
</Box>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
LAYOUT_DIRECTION_HORIZONTAL,
LAYOUT_DIRECTION_VERTICAL,
} from '../../../toolpadComponents/layoutBox';
import ElementContext from '../ElementContext';

const classes = {
control: 'Toolpad_Control',
Expand Down Expand Up @@ -117,22 +118,24 @@ function SelectedNodeEditor({ node }: SelectedNodeEditorProps) {
const component = useToolpadComponent(dom, getElementNodeComponentId(node));

return (
<Stack direction="column" gap={1}>
<Typography variant="subtitle1">
Component: {component?.displayName || '<unknown>'}
</Typography>
<Typography variant="subtitle2" sx={{ mb: 1 }}>
ID: {node.id}
</Typography>
<NodeNameEditor node={node} />
{nodeError ? <ErrorAlert error={nodeError} /> : null}
<Divider sx={{ mt: 1 }} />
{node ? (
<React.Fragment>
<ComponentPropsEditor componentConfig={componentConfig} node={node} />
</React.Fragment>
) : null}
</Stack>
<ElementContext.Provider value={node}>
<Stack direction="column" gap={1}>
<Typography variant="subtitle1">
Component: {component?.displayName || '<unknown>'}
</Typography>
<Typography variant="subtitle2" sx={{ mb: 1 }}>
ID: {node.id}
</Typography>
<NodeNameEditor node={node} />
{nodeError ? <ErrorAlert error={nodeError} /> : null}
<Divider sx={{ mt: 1 }} />
{node ? (
<React.Fragment>
<ComponentPropsEditor componentConfig={componentConfig} node={node} />
</React.Fragment>
) : null}
</Stack>
</ElementContext.Provider>
);
}

Expand Down
Loading