Skip to content

Commit

Permalink
ra-no-code - Introduce Resource Configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
djhi committed Apr 28, 2021
1 parent 192eb12 commit 41beace
Show file tree
Hide file tree
Showing 9 changed files with 350 additions and 17 deletions.
4 changes: 2 additions & 2 deletions packages/ra-core/src/inference/inferTypeFromValues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
valuesAreEmail,
} from './assertions';

const types = [
export const InferenceTypes = [
'array',
'boolean',
'date',
Expand All @@ -35,7 +35,7 @@ const types = [
'url',
] as const;

export type PossibleInferredElementTypes = typeof types[number];
export type PossibleInferredElementTypes = typeof InferenceTypes[number];

export interface InferredElementDescription {
type: PossibleInferredElementTypes;
Expand Down
12 changes: 12 additions & 0 deletions packages/ra-no-code/src/Admin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,23 @@ import localStorageDataProvider from 'ra-data-local-storage';
import { Create, Edit, List } from './builders';
import {
useResourcesConfiguration,
ResourceConfigurationPage,
ResourceConfigurationProvider,
} from './ResourceConfiguration';
import { Layout, Ready } from './ui';
import { Route } from 'react-router';

const dataProvider = localStorageDataProvider();

const customRoutes = [
<Route
path="/configure/:resource"
render={({ match }) => (
<ResourceConfigurationPage resource={match.params.resource} />
)}
/>,
];

export const Admin = (props: AdminProps) => (
<ResourceConfigurationProvider dataProvider={dataProvider}>
<InnerAdmin {...props} />
Expand All @@ -28,6 +39,7 @@ const InnerAdmin = (props: AdminProps) => {
dataProvider={dataProvider}
ready={Ready}
layout={Layout}
customRoutes={customRoutes}
{...props}
>
{hasResources
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import * as React from 'react';
import {
getFieldLabelTranslationArgs,
InferenceTypes,
useTranslate,
} from 'ra-core';
import { SelectInput, TextInput } from 'ra-ui-materialui';
import { CardContent } from '@material-ui/core';
import { textChangeRangeIsUnchanged } from 'typescript';

export const FieldConfiguration = props => {
const { index, field, resource } = props;
const translate = useTranslate();
const labelArgs = getFieldLabelTranslationArgs({
source: field.props.source,
resource,
label: field.props.label,
});

return (
<CardContent>
<TextInput
source={`fields[${index}].props.source`}
label="Source"
fullWidth
disabled
/>
<TextInput
source={`fields[${index}].props.label`}
label="Label"
fullWidth
initialValue={translate(...labelArgs)}
/>
<SelectInput
source={`fields[${index}].type`}
label="Type"
fullWidth
choices={InferenceTypes.map(type => ({
id: type,
name: type,
}))}
/>
</CardContent>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as React from 'react';
import { Tab } from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import { getFieldLabelTranslationArgs, useTranslate } from 'ra-core';

export const FieldConfigurationTab = ({ field, resource, ...props }) => {
const classes = useStyles();
const translate = useTranslate();
const labelArgs = getFieldLabelTranslationArgs({
source: field.props.source,
resource,
label: field.props.label,
});

return (
<Tab
{...props}
key={field.props.source}
label={translate(...labelArgs)}
id={`nav-tab-${field.props.source}`}
aria-controls={`nav-tabpanel-${field.props.source}`}
classes={classes}
/>
);
};

const useStyles = makeStyles(theme => ({
root: {
paddingTop: theme.spacing(1),
paddingBottom: theme.spacing(1),
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(2),
textTransform: 'none',
minHeight: 0,
fontWeight: 'normal',
},
selected: {
fontWeight: 'bold',
},
}));
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import * as React from 'react';
import { useEffect, useState } from 'react';
import {
Avatar,
Card,
CardActions,
CardContent,
CardHeader,
Divider,
IconButton,
Tabs,
} from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import {
FormWithRedirect,
InferredElementDescription,
RecordContextProvider,
SaveContextProvider,
} from 'ra-core';
import { SaveButton, TextInput } from 'ra-ui-materialui';
import { ResourceConfiguration } from './ResourceConfigurationContext';
import { useResourceConfiguration } from './useResourceConfiguration';
import { FieldConfiguration } from './FieldConfiguration';
import { FieldConfigurationTab } from './FieldConfigurationTab';

export const ResourceConfigurationPage = ({
resource,
}: {
resource: string;
}) => {
const [resourceConfiguration, actions] = useResourceConfiguration(resource);
const [activeField, setActiveField] = useState<
InferredElementDescription
>();
const classes = useStyles();

const save = (values: ResourceConfiguration) => {
actions.update(values);
};
const saveContext = {
save,
setOnFailure: () => {},
setOnSuccess: () => {},
};

const handleTabChange = (event, newValue) => {
const newField = resourceConfiguration.fields.find(
f => f.props.source === newValue
);
setActiveField(newField);
};

useEffect(() => {
if (resourceConfiguration && resourceConfiguration.fields) {
setActiveField(resourceConfiguration.fields[0]);
}
}, [resourceConfiguration]);

if (!resourceConfiguration || !activeField) {
return null;
}

return (
<RecordContextProvider value={resourceConfiguration}>
<SaveContextProvider value={saveContext}>
<FormWithRedirect
save={save}
initialValues={resourceConfiguration}
render={({ handleSubmitWithRedirect }) => (
<Card>
<CardHeader
avatar={
<Avatar>
{
// Here will go an icon selector
(
resourceConfiguration.label ||
resourceConfiguration.name
).substr(0, 1)
}
</Avatar>
}
action={
// Will display a menu to delete the resource maybe ?
<IconButton aria-label="settings">
<MoreVertIcon />
</IconButton>
}
title={`Configuration of ${
resourceConfiguration.label ||
resourceConfiguration.name
}`}
/>

<CardContent>
<TextInput
source="label"
initialValue={
resourceConfiguration.label ||
resourceConfiguration.name
}
/>
</CardContent>
<Divider />
<div className={classes.fields}>
<Tabs
orientation="vertical"
value={activeField.props.source}
onChange={handleTabChange}
className={classes.fieldList}
>
{resourceConfiguration.fields.map(field => (
<FieldConfigurationTab
key={`${field.props.source}_tab`}
field={field}
value={field.props.source}
resource={resource}
/>
))}
</Tabs>
{resourceConfiguration.fields.map(
(field, index) => (
<div
key={`${field.props.source}_panel`}
role="tabpanel"
hidden={
activeField.props.source !==
field.props.source
}
id={`nav-tabpanel-${field.props.source}`}
aria-labelledby={`nav-tab-${field.props.source}`}
>
{activeField.props.source ===
field.props.source ? (
<FieldConfiguration
key={field.props.source}
field={field}
index={index}
className={
classes.fieldPanel
}
resource={resource}
/>
) : null}
</div>
)
)}
</div>
<CardActions className={classes.actions}>
<SaveButton
handleSubmitWithRedirect={
handleSubmitWithRedirect
}
/>
</CardActions>
</Card>
)}
/>
</SaveContextProvider>
</RecordContextProvider>
);
};

const useStyles = makeStyles(theme => ({
fields: {
display: 'flex',
},
fieldList: {
backgroundColor: theme.palette.background.default,
},
fieldTitle: {
paddingTop: theme.spacing(1),
paddingBottom: theme.spacing(1),
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(2),
textTransform: 'none',
minHeight: 0,
},
fieldPanel: {
flexGrow: 1,
},
actions: {
backgroundColor: theme.palette.background.default,
},
}));
1 change: 1 addition & 0 deletions packages/ra-no-code/src/ResourceConfiguration/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './getFieldDefinitionsFromRecords';
export * from './ResourceConfiguration';
export * from './useResourceConfiguration';
export * from './useResourcesConfiguration';
export * from './ResourceConfigurationContext';
Expand Down
2 changes: 1 addition & 1 deletion packages/ra-no-code/src/ui/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { Layout as RaLayout, LayoutProps } from 'react-admin';
import Menu from './Menu';
import { Menu } from './Menu';

export const Layout = (props: LayoutProps) => (
<RaLayout {...props} menu={Menu} />
Expand Down
20 changes: 6 additions & 14 deletions packages/ra-no-code/src/ui/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ import lodashGet from 'lodash/get';
// @ts-ignore
import { useMediaQuery, Theme } from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import DefaultIcon from '@material-ui/icons/ViewList';
import classnames from 'classnames';
import { useGetResourceLabel, ReduxState } from 'ra-core';
import { ReduxState } from 'ra-core';

import { DashboardMenuItem, MenuItemLink } from 'react-admin';
import { DashboardMenuItem } from 'react-admin';
import { NewResourceMenuItem } from './NewResourceMenuItem';
import { useResourcesConfiguration } from '../ResourceConfiguration';
import { ResourceMenuItem } from './ResourceMenuItem';

export const MENU_WIDTH = 240;
export const CLOSED_MENU_WIDTH = 55;

const Menu = (props: MenuProps) => {
export const Menu = (props: MenuProps) => {
const {
classes: classesOverride,
className,
Expand All @@ -34,7 +34,6 @@ const Menu = (props: MenuProps) => {
);
const open = useSelector((state: ReduxState) => state.admin.ui.sidebarOpen);
const [resources] = useResourcesConfiguration();
const getResourceLabel = useGetResourceLabel();

return (
<>
Expand All @@ -57,14 +56,9 @@ const Menu = (props: MenuProps) => {
/>
)}
{Object.keys(resources).map(resource => (
<MenuItemLink
<ResourceMenuItem
key={resource}
to={{
pathname: `/${resource}`,
state: { _scrollToTop: true },
}}
primaryText={getResourceLabel(resource, 2)}
leftIcon={<DefaultIcon />}
resource={resources[resource]}
onClick={onMenuClick}
dense={dense}
sidebarIsOpen={open}
Expand Down Expand Up @@ -126,5 +120,3 @@ Menu.propTypes = {
Menu.defaultProps = {
onMenuClick: () => null,
};

export default Menu;
Loading

0 comments on commit 41beace

Please sign in to comment.