diff --git a/examples/no-code/src/main.tsx b/examples/no-code/src/main.tsx index 72e4f0ceed0..77c0ef213ef 100644 --- a/examples/no-code/src/main.tsx +++ b/examples/no-code/src/main.tsx @@ -1,10 +1,21 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Root } from 'ra-no-code'; +import { defaultTheme } from 'react-admin'; +import { + unstable_createMuiStrictModeTheme, + createMuiTheme, +} from '@material-ui/core/styles'; + +// FIXME MUI bug https://github.com/mui-org/material-ui/issues/13394 +const theme = + process.env.NODE_ENV !== 'production' + ? unstable_createMuiStrictModeTheme(defaultTheme) + : createMuiTheme(defaultTheme); ReactDOM.render( - + , document.getElementById('root') ); diff --git a/packages/ra-no-code/src/ApplicationsDashboard/ApplicationsDashboard.tsx b/packages/ra-no-code/src/ApplicationsDashboard/ApplicationsDashboard.tsx index 0bd8bcd0574..0422b62d5ce 100644 --- a/packages/ra-no-code/src/ApplicationsDashboard/ApplicationsDashboard.tsx +++ b/packages/ra-no-code/src/ApplicationsDashboard/ApplicationsDashboard.tsx @@ -14,8 +14,12 @@ import { createMuiTheme, makeStyles, ThemeProvider, + unstable_createMuiStrictModeTheme, } from '@material-ui/core/styles'; -import { defaultTheme } from 'ra-ui-materialui'; +import { + defaultTheme as RaDefaultTheme, + RaThemeOptions, +} from 'ra-ui-materialui'; import FolderIcon from '@material-ui/icons/Folder'; import { Application } from './types'; import { NewApplicationForm } from './NewApplicationForm'; @@ -24,8 +28,19 @@ import { storeApplicationsInStorage, } from './applicationStorage'; -export const ApplicationsDashboard = ({ onApplicationSelected }) => ( - +const defaultTheme = + process.env.NODE_ENV !== 'production' + ? unstable_createMuiStrictModeTheme(RaDefaultTheme) + : createMuiTheme(RaDefaultTheme); + +export const ApplicationsDashboard = ({ + onApplicationSelected, + theme = defaultTheme, +}: { + onApplicationSelected: any; + theme: RaThemeOptions; +}) => ( + ); diff --git a/packages/ra-no-code/src/ResourceConfiguration/ConfigurationInputsFromFieldDefinition.tsx b/packages/ra-no-code/src/ResourceConfiguration/ConfigurationInputsFromFieldDefinition.tsx new file mode 100644 index 00000000000..549ec698ed7 --- /dev/null +++ b/packages/ra-no-code/src/ResourceConfiguration/ConfigurationInputsFromFieldDefinition.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { FormDataConsumer, InferredElementDescription } from 'ra-core'; +import { SelectInput } from 'ra-ui-materialui'; +import get from 'lodash/get'; +import { useResourcesConfiguration } from './useResourcesConfiguration'; + +export const ConfigurationInputsFromFieldDefinition = ({ + definition, + sourcePrefix, +}: { + definition: InferredElementDescription; + sourcePrefix?: string; +}) => { + const [resources] = useResourcesConfiguration(); + + switch (definition.type) { + case 'reference': + return ( + <> + ({ + id: name, + name: resources[name].label || resources[name].name, + }))} + /> + + + {({ formData, ...rest }) => { + const resourceName = get( + formData, + `${sourcePrefix}.props.reference` + ); + if (!resourceName) return null; + + const resource = resources[resourceName]; + return ( + ({ + id: field.props.source, + name: + field.props.label || + field.props.source, + }))} + {...rest} + /> + ); + }} + + + ); + default: + return null; + } +}; + +const ReferenceSelectionChoice = [ + { id: 'select', name: 'Simple list' }, + { id: 'autocomplete', name: 'Searchable list' }, + { id: 'radio', name: 'Radio buttons' }, +]; diff --git a/packages/ra-no-code/src/ResourceConfiguration/FieldConfigurationFormSection.tsx b/packages/ra-no-code/src/ResourceConfiguration/FieldConfigurationFormSection.tsx index d23e25299f6..828b9916bbf 100644 --- a/packages/ra-no-code/src/ResourceConfiguration/FieldConfigurationFormSection.tsx +++ b/packages/ra-no-code/src/ResourceConfiguration/FieldConfigurationFormSection.tsx @@ -4,6 +4,7 @@ import { TextInput } from 'ra-ui-materialui'; import { CardContent } from '@material-ui/core'; import { FieldTypeInput } from './FieldConfiguration/FieldTypeInput'; import { FieldViewsInput } from './FieldConfiguration/FieldViewsInput'; +import { ConfigurationInputsFromFieldDefinition } from './ConfigurationInputsFromFieldDefinition'; export const FieldConfigurationFormSection = props => { const { sourcePrefix, field, resource } = props; @@ -38,6 +39,10 @@ export const FieldConfigurationFormSection = props => { label="Views" fullWidth /> + ); }; diff --git a/packages/ra-no-code/src/ResourceConfiguration/ResourceConfigurationContext.ts b/packages/ra-no-code/src/ResourceConfiguration/ResourceConfigurationContext.ts index 726430ba0f7..aeb1a0d798c 100644 --- a/packages/ra-no-code/src/ResourceConfiguration/ResourceConfigurationContext.ts +++ b/packages/ra-no-code/src/ResourceConfiguration/ResourceConfigurationContext.ts @@ -32,10 +32,25 @@ export type ResourceConfiguration = { fields?: FieldConfiguration[]; }; -export interface FieldConfiguration extends InferredElementDescription { +export interface ReferenceFieldConfiguration extends BaseFieldConfiguration { + type: 'reference'; + options: { + selectionType: 'select' | 'autocomplete' | 'radio'; + referenceField: 'string'; + }; +} + +export interface BaseFieldConfiguration extends InferredElementDescription { views: FieldView[]; + options?: { + [key: string]: any; + }; } +export type FieldConfiguration = + | BaseFieldConfiguration + | ReferenceFieldConfiguration; + export type FieldView = 'list' | 'create' | 'edit' | 'show'; export type ResourceConfigurationMap = diff --git a/packages/ra-no-code/src/ResourceConfiguration/getFieldDefinitionsFromRecords.ts b/packages/ra-no-code/src/ResourceConfiguration/getFieldDefinitionsFromRecords.ts index 99517c76699..1242bde30e4 100644 --- a/packages/ra-no-code/src/ResourceConfiguration/getFieldDefinitionsFromRecords.ts +++ b/packages/ra-no-code/src/ResourceConfiguration/getFieldDefinitionsFromRecords.ts @@ -6,8 +6,19 @@ export const getFieldDefinitionsFromRecords = ( ): FieldConfiguration[] => { const values = getValuesFromRecords(records); - return Object.keys(values).map(key => ({ - ...inferTypeFromValues(key, values[key]), - views: ['list', 'create', 'edit', 'show'], - })); + return Object.keys(values).map(key => { + const inferedDefinition = inferTypeFromValues(key, values[key]); + + return { + ...inferedDefinition, + options: + inferedDefinition.type === 'reference' + ? { + referenceField: 'id', + selectionType: 'select', + } + : undefined, + views: ['list', 'create', 'edit', 'show'], + }; + }); }; diff --git a/packages/ra-no-code/src/Root.tsx b/packages/ra-no-code/src/Root.tsx index df464f18428..ec7d42a924c 100644 --- a/packages/ra-no-code/src/Root.tsx +++ b/packages/ra-no-code/src/Root.tsx @@ -1,10 +1,11 @@ +import { RaThemeOptions } from 'ra-ui-materialui'; import * as React from 'react'; import { useMemo, useState } from 'react'; import { Admin } from './Admin'; import { ApplicationContext } from './ApplicationContext'; import { ApplicationsDashboard } from './ApplicationsDashboard'; -export const Root = () => { +export const Root = ({ theme }: { theme: RaThemeOptions }) => { const [application, setApplication] = useState(); const handleExitApplication = () => { @@ -26,7 +27,7 @@ export const Root = () => { if (context.application) { return ( - + ); } @@ -34,6 +35,7 @@ export const Root = () => { return ( ); }; diff --git a/packages/ra-no-code/src/builders/Create.tsx b/packages/ra-no-code/src/builders/Create.tsx index dd87f57dd84..ebbd0fcd27c 100644 --- a/packages/ra-no-code/src/builders/Create.tsx +++ b/packages/ra-no-code/src/builders/Create.tsx @@ -7,7 +7,10 @@ import { SimpleFormProps, } from 'ra-ui-materialui'; import { getInputFromFieldDefinition } from './getInputFromFieldDefinition'; -import { useResourceConfiguration } from '../ResourceConfiguration'; +import { + useResourceConfiguration, + useResourcesConfiguration, +} from '../ResourceConfiguration'; export const Create = (props: CreateProps) => ( @@ -17,13 +20,16 @@ export const Create = (props: CreateProps) => ( export const CreateForm = (props: Omit) => { const resource = useResourceContext(props); + const [resources] = useResourcesConfiguration(); const [resourceConfiguration] = useResourceConfiguration(resource); return ( {resourceConfiguration.fields .filter(definition => definition.views.includes('create')) - .map(definition => getInputFromFieldDefinition(definition))} + .map(definition => + getInputFromFieldDefinition(definition, resources) + )} ); }; diff --git a/packages/ra-no-code/src/builders/Edit.tsx b/packages/ra-no-code/src/builders/Edit.tsx index b3550c3ae65..5dbae398f94 100644 --- a/packages/ra-no-code/src/builders/Edit.tsx +++ b/packages/ra-no-code/src/builders/Edit.tsx @@ -6,7 +6,10 @@ import { SimpleForm, SimpleFormProps, } from 'ra-ui-materialui'; -import { useResourceConfiguration } from '../ResourceConfiguration'; +import { + useResourceConfiguration, + useResourcesConfiguration, +} from '../ResourceConfiguration'; import { getInputFromFieldDefinition } from './getInputFromFieldDefinition'; export const Edit = (props: EditProps) => ( @@ -17,13 +20,16 @@ export const Edit = (props: EditProps) => ( export const EditForm = (props: Omit) => { const resource = useResourceContext(props); + const [resources] = useResourcesConfiguration(); const [resourceConfiguration] = useResourceConfiguration(resource); return ( {resourceConfiguration.fields .filter(definition => definition.views.includes('edit')) - .map(definition => getInputFromFieldDefinition(definition))} + .map(definition => + getInputFromFieldDefinition(definition, resources) + )} ); }; diff --git a/packages/ra-no-code/src/builders/List.tsx b/packages/ra-no-code/src/builders/List.tsx index 9d066c49d82..2356c07dde6 100644 --- a/packages/ra-no-code/src/builders/List.tsx +++ b/packages/ra-no-code/src/builders/List.tsx @@ -7,7 +7,10 @@ import { ListProps, } from 'ra-ui-materialui'; -import { useResourceConfiguration } from '../ResourceConfiguration'; +import { + useResourceConfiguration, + useResourcesConfiguration, +} from '../ResourceConfiguration'; import { getFieldFromFieldDefinition } from './getFieldFromFieldDefinition'; export const List = (props: ListProps) => ( @@ -18,13 +21,16 @@ export const List = (props: ListProps) => ( export const Datagrid = (props: Omit) => { const resource = useResourceContext(props); + const [resources] = useResourcesConfiguration(); const [resourceConfiguration] = useResourceConfiguration(resource); return ( {resourceConfiguration.fields .filter(definition => definition.views.includes('list')) - .map(definition => getFieldFromFieldDefinition(definition))} + .map(definition => + getFieldFromFieldDefinition(definition, resources) + )} ); }; diff --git a/packages/ra-no-code/src/builders/ReferenceInputChildFromDefinition.tsx b/packages/ra-no-code/src/builders/ReferenceInputChildFromDefinition.tsx new file mode 100644 index 00000000000..9b743ec6918 --- /dev/null +++ b/packages/ra-no-code/src/builders/ReferenceInputChildFromDefinition.tsx @@ -0,0 +1,43 @@ +import * as React from 'react'; +import { + AutocompleteInput, + RadioButtonGroupInput, + SelectInput, +} from 'ra-ui-materialui'; +import { ReferenceFieldConfiguration } from '../ResourceConfiguration'; + +export const ReferenceInputChildFromDefinition = ({ + definition, + ...props +}: ReferenceInputChildFromDefinitionProps) => { + if (definition.options.selectionType === 'select') { + return ( + + ); + } + + if (definition.options.selectionType === 'autocomplete') { + return ( + + ); + } + + if (definition.options.selectionType === 'radio') { + return ( + + ); + } +}; + +interface ReferenceInputChildFromDefinitionProps { + definition: ReferenceFieldConfiguration; +} diff --git a/packages/ra-no-code/src/builders/Show.tsx b/packages/ra-no-code/src/builders/Show.tsx index 4f07246d063..217805ed504 100644 --- a/packages/ra-no-code/src/builders/Show.tsx +++ b/packages/ra-no-code/src/builders/Show.tsx @@ -6,7 +6,10 @@ import { SimpleShowLayout, SimpleShowLayoutProps, } from 'ra-ui-materialui'; -import { useResourceConfiguration } from '../ResourceConfiguration'; +import { + useResourceConfiguration, + useResourcesConfiguration, +} from '../ResourceConfiguration'; import { getFieldFromFieldDefinition } from './getFieldFromFieldDefinition'; export const Show = (props: ShowProps) => ( @@ -17,13 +20,16 @@ export const Show = (props: ShowProps) => ( export const ShowForm = (props: Omit) => { const resource = useResourceContext(props); + const [resources] = useResourcesConfiguration(); const [resourceConfiguration] = useResourceConfiguration(resource); return ( {resourceConfiguration.fields .filter(definition => definition.views.includes('show')) - .map(definition => getFieldFromFieldDefinition(definition))} + .map(definition => + getFieldFromFieldDefinition(definition, resources) + )} ); }; diff --git a/packages/ra-no-code/src/builders/getFieldFromFieldDefinition.tsx b/packages/ra-no-code/src/builders/getFieldFromFieldDefinition.tsx index c7626c4a5dd..9eb7852e003 100644 --- a/packages/ra-no-code/src/builders/getFieldFromFieldDefinition.tsx +++ b/packages/ra-no-code/src/builders/getFieldFromFieldDefinition.tsx @@ -6,12 +6,18 @@ import { EmailField, ImageField, NumberField, + ReferenceField, TextField, UrlField, } from 'ra-ui-materialui'; +import { + FieldConfiguration, + ResourceConfigurationMap, +} from '../ResourceConfiguration'; export const getFieldFromFieldDefinition = ( - definition: InferredElementDescription + definition: FieldConfiguration, + resources: ResourceConfigurationMap ) => { switch (definition.type) { case 'date': @@ -53,6 +59,30 @@ export const getFieldFromFieldDefinition = ( return ( ); + case 'reference': + const reference = resources[definition.props.reference]; + + if (reference) { + const field = reference.fields.find( + field => + field.props.source === definition.options.referenceField + ); + return ( + + {getFieldFromFieldDefinition(field, resources)} + + ); + } + + return ( + + ); default: return ( { switch (definition.type) { @@ -59,10 +67,46 @@ export const getInputFromFieldDefinition = ( case 'object': if (Array.isArray(definition.children)) { return definition.children.map((child, index) => - getInputFromFieldDefinition(child, index.toString()) + getInputFromFieldDefinition( + child, + resources, + index.toString() + ) ); } - return <>{getInputFromFieldDefinition(definition.children)}; + return ( + <> + {getInputFromFieldDefinition( + definition.children, + resources, + undefined + )} + + ); + case 'reference': + const referenceDefinition = definition as ReferenceFieldConfiguration; + const reference = resources[definition.props.reference]; + + if (reference) { + return ( + + + + ); + } + + return ( + + ); + default: return (