Skip to content

Commit cc880f9

Browse files
committed
ra-no-code - Introduce Resource Configuration
1 parent 8672c1a commit cc880f9

File tree

9 files changed

+321
-18
lines changed

9 files changed

+321
-18
lines changed

packages/ra-core/src/inference/inferTypeFromValues.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
valuesAreEmail,
1919
} from './assertions';
2020

21-
const types = [
21+
export const InferenceTypes = [
2222
'array',
2323
'boolean',
2424
'date',
@@ -35,7 +35,7 @@ const types = [
3535
'url',
3636
] as const;
3737

38-
export type PossibleInferredElementTypes = typeof types[number];
38+
export type PossibleInferredElementTypes = typeof InferenceTypes[number];
3939

4040
export interface InferredElementDescription {
4141
type: PossibleInferredElementTypes;

packages/ra-no-code/src/Admin.tsx

+17-1
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,23 @@ import localStorageDataProvider from 'ra-data-local-storage';
44
import { CreateBuilder, EditBuilder, ListBuilder } from './builders';
55
import {
66
useResourceConfigurations,
7+
ResourceConfigurationPage,
78
ResourceConfigurationProvider,
89
} from './ResourceConfiguration';
910
import { Layout, Ready } from './ui';
11+
import { Route } from 'react-router';
1012

1113
const dataProvider = localStorageDataProvider();
1214

15+
const customRoutes = [
16+
<Route
17+
path="/configure/:resource"
18+
render={({ match }) => (
19+
<ResourceConfigurationPage resource={match.params.resource} />
20+
)}
21+
/>,
22+
];
23+
1324
export const Admin = () => (
1425
<ResourceConfigurationProvider>
1526
<InnerAdmin />
@@ -20,7 +31,12 @@ const InnerAdmin = () => {
2031
const { resources } = useResourceConfigurations();
2132
const hasResources = !!resources && Object.keys(resources).length > 0;
2233
return (
23-
<RaAdmin dataProvider={dataProvider} ready={Ready} layout={Layout}>
34+
<RaAdmin
35+
dataProvider={dataProvider}
36+
ready={Ready}
37+
layout={Layout}
38+
customRoutes={customRoutes}
39+
>
2440
{hasResources
2541
? Object.keys(resources).map(resource => (
2642
<Resource
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import * as React from 'react';
2+
import { InferenceTypes } from 'ra-core';
3+
import { SelectInput, TextInput } from 'ra-ui-materialui';
4+
import { CardContent } from '@material-ui/core';
5+
6+
export const FieldConfiguration = props => {
7+
const { index } = props;
8+
9+
return (
10+
<CardContent>
11+
<TextInput
12+
source={`fields[${index}].props.label`}
13+
label="Label"
14+
fullWidth
15+
/>
16+
<SelectInput
17+
source={`fields[${index}].type`}
18+
label="Type"
19+
fullWidth
20+
choices={InferenceTypes.map(type => ({
21+
id: type,
22+
name: type,
23+
}))}
24+
/>
25+
</CardContent>
26+
);
27+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import * as React from 'react';
2+
import { Tab } from '@material-ui/core';
3+
import { makeStyles } from '@material-ui/core/styles';
4+
5+
export const FieldConfigurationTab = ({ field, ...props }) => {
6+
const classes = useStyles();
7+
8+
return (
9+
<Tab
10+
{...props}
11+
key={field.props.source}
12+
label={field.props.label}
13+
id={`nav-tab-${field.props.source}`}
14+
aria-controls={`nav-tabpanel-${field.props.source}`}
15+
classes={classes}
16+
/>
17+
);
18+
};
19+
20+
const useStyles = makeStyles(theme => ({
21+
root: {
22+
paddingTop: theme.spacing(1),
23+
paddingBottom: theme.spacing(1),
24+
paddingLeft: theme.spacing(2),
25+
paddingRight: theme.spacing(2),
26+
textTransform: 'none',
27+
minHeight: 0,
28+
fontWeight: 'normal',
29+
},
30+
selected: {
31+
fontWeight: 'bold',
32+
},
33+
}));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import * as React from 'react';
2+
import { useEffect, useState } from 'react';
3+
import {
4+
Avatar,
5+
Card,
6+
CardActions,
7+
CardContent,
8+
CardHeader,
9+
Divider,
10+
IconButton,
11+
Tabs,
12+
} from '@material-ui/core';
13+
import { makeStyles } from '@material-ui/core/styles';
14+
import MoreVertIcon from '@material-ui/icons/MoreVert';
15+
import {
16+
FormWithRedirect,
17+
InferredElementDescription,
18+
RecordContextProvider,
19+
SaveContextProvider,
20+
} from 'ra-core';
21+
import { SaveButton, TextInput } from 'ra-ui-materialui';
22+
import { ResourceConfiguration } from './ResourceConfigurationContext';
23+
import { useResourceConfiguration } from './useResourceConfiguration';
24+
import { FieldConfiguration } from './FieldConfiguration';
25+
import { FieldConfigurationTab } from './FieldConfigurationTab';
26+
27+
export const ResourceConfigurationPage = ({
28+
resource,
29+
}: {
30+
resource: string;
31+
}) => {
32+
const [definition, actions] = useResourceConfiguration(resource);
33+
const [activeField, setActiveField] = useState<
34+
InferredElementDescription
35+
>();
36+
const classes = useStyles();
37+
38+
const save = (values: ResourceConfiguration) => {
39+
actions.update(values);
40+
};
41+
const saveContext = {
42+
save,
43+
setOnFailure: () => {},
44+
setOnSuccess: () => {},
45+
};
46+
47+
const handleTabChange = (event, newValue) => {
48+
const newField = definition.fields.find(
49+
f => f.props.source === newValue
50+
);
51+
setActiveField(newField);
52+
};
53+
54+
useEffect(() => {
55+
if (definition && definition.fields) {
56+
setActiveField(definition.fields[0]);
57+
}
58+
}, [definition]);
59+
60+
if (!definition || !activeField) {
61+
return null;
62+
}
63+
64+
return (
65+
<RecordContextProvider value={definition}>
66+
<SaveContextProvider value={saveContext}>
67+
<FormWithRedirect
68+
save={save}
69+
initialValues={definition}
70+
render={({ handleSubmitWithRedirect }) => (
71+
<Card>
72+
<CardHeader
73+
avatar={
74+
<Avatar>
75+
{
76+
// Here will go an icon selector
77+
(
78+
definition.label ||
79+
definition.name
80+
).substr(0, 1)
81+
}
82+
</Avatar>
83+
}
84+
action={
85+
// Will display a menu to delete the resource maybe ?
86+
<IconButton aria-label="settings">
87+
<MoreVertIcon />
88+
</IconButton>
89+
}
90+
title={`Configuration of ${
91+
definition.label || definition.name
92+
}`}
93+
/>
94+
95+
<CardContent>
96+
<TextInput
97+
source="label"
98+
initialValue={
99+
definition.label || definition.name
100+
}
101+
/>
102+
</CardContent>
103+
<Divider />
104+
<div className={classes.fields}>
105+
<Tabs
106+
orientation="vertical"
107+
value={activeField.props.source}
108+
onChange={handleTabChange}
109+
className={classes.fieldList}
110+
>
111+
{definition.fields.map(field => (
112+
<FieldConfigurationTab
113+
key={field.props.source}
114+
field={field}
115+
value={field.props.source}
116+
/>
117+
))}
118+
</Tabs>
119+
{definition.fields.map((field, index) => (
120+
<div
121+
role="tabpanel"
122+
hidden={
123+
activeField.props.source !==
124+
field.props.source
125+
}
126+
id={`nav-tabpanel-${field.props.source}`}
127+
aria-labelledby={`nav-tab-${field.props.source}`}
128+
>
129+
{activeField.props.source ===
130+
field.props.source ? (
131+
<FieldConfiguration
132+
key={field.props.source}
133+
field={field}
134+
index={index}
135+
className={classes.fieldPanel}
136+
/>
137+
) : null}
138+
</div>
139+
))}
140+
</div>
141+
<CardActions className={classes.actions}>
142+
<SaveButton
143+
handleSubmitWithRedirect={
144+
handleSubmitWithRedirect
145+
}
146+
/>
147+
</CardActions>
148+
</Card>
149+
)}
150+
/>
151+
</SaveContextProvider>
152+
</RecordContextProvider>
153+
);
154+
};
155+
156+
const useStyles = makeStyles(theme => ({
157+
fields: {
158+
display: 'flex',
159+
},
160+
fieldList: {
161+
backgroundColor: theme.palette.background.default,
162+
},
163+
fieldTitle: {
164+
paddingTop: theme.spacing(1),
165+
paddingBottom: theme.spacing(1),
166+
paddingLeft: theme.spacing(2),
167+
paddingRight: theme.spacing(2),
168+
textTransform: 'none',
169+
minHeight: 0,
170+
},
171+
fieldPanel: {
172+
flexGrow: 1,
173+
},
174+
actions: {
175+
backgroundColor: theme.palette.background.default,
176+
},
177+
}));

packages/ra-no-code/src/ResourceConfiguration/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './getFieldDefinitionsFromRecords';
2+
export * from './ResourceConfiguration';
23
export * from './useResourceConfiguration';
34
export * from './useResourceConfigurations';
45
export * from './ResourceConfigurationContext';

packages/ra-no-code/src/ui/Layout.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import { Layout as RaLayout, LayoutProps } from 'react-admin';
3-
import Menu from './Menu';
3+
import { Menu } from './Menu';
44

55
export const Layout = (props: LayoutProps) => (
66
<RaLayout {...props} menu={Menu} />

packages/ra-no-code/src/ui/Menu.tsx

+6-14
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,18 @@ import lodashGet from 'lodash/get';
66
// @ts-ignore
77
import { useMediaQuery, Theme } from '@material-ui/core';
88
import { makeStyles } from '@material-ui/core/styles';
9-
import DefaultIcon from '@material-ui/icons/ViewList';
109
import classnames from 'classnames';
11-
import { useGetResourceLabel, ReduxState } from 'ra-core';
10+
import { ReduxState } from 'ra-core';
1211

13-
import { DashboardMenuItem, MenuItemLink } from 'react-admin';
12+
import { DashboardMenuItem } from 'react-admin';
1413
import { NewResourceMenuItem } from './NewResourceMenuItem';
1514
import { useResourceConfigurations } from '../ResourceConfiguration';
15+
import { ResourceMenuItem } from './ResourceMenuItem';
1616

1717
export const MENU_WIDTH = 240;
1818
export const CLOSED_MENU_WIDTH = 55;
1919

20-
const Menu = (props: MenuProps) => {
20+
export const Menu = (props: MenuProps) => {
2121
const {
2222
classes: classesOverride,
2323
className,
@@ -34,7 +34,6 @@ const Menu = (props: MenuProps) => {
3434
);
3535
const open = useSelector((state: ReduxState) => state.admin.ui.sidebarOpen);
3636
const { resources } = useResourceConfigurations();
37-
const getResourceLabel = useGetResourceLabel();
3837

3938
return (
4039
<>
@@ -57,14 +56,9 @@ const Menu = (props: MenuProps) => {
5756
/>
5857
)}
5958
{Object.keys(resources).map(resource => (
60-
<MenuItemLink
59+
<ResourceMenuItem
6160
key={resource}
62-
to={{
63-
pathname: `/${resource}`,
64-
state: { _scrollToTop: true },
65-
}}
66-
primaryText={getResourceLabel(resource, 2)}
67-
leftIcon={<DefaultIcon />}
61+
resource={resources[resource]}
6862
onClick={onMenuClick}
6963
dense={dense}
7064
sidebarIsOpen={open}
@@ -126,5 +120,3 @@ Menu.propTypes = {
126120
Menu.defaultProps = {
127121
onMenuClick: () => null,
128122
};
129-
130-
export default Menu;

0 commit comments

Comments
 (0)