From 41beace1fe324e9b81f4f0226dcc8dd9645bdcc8 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Tue, 27 Apr 2021 18:13:26 +0200 Subject: [PATCH] ra-no-code - Introduce Resource Configuration --- .../src/inference/inferTypeFromValues.ts | 4 +- packages/ra-no-code/src/Admin.tsx | 12 ++ .../FieldConfiguration.tsx | 45 +++++ .../FieldConfigurationTab.tsx | 40 ++++ .../ResourceConfiguration.tsx | 186 ++++++++++++++++++ .../src/ResourceConfiguration/index.ts | 1 + packages/ra-no-code/src/ui/Layout.tsx | 2 +- packages/ra-no-code/src/ui/Menu.tsx | 20 +- .../ra-no-code/src/ui/ResourceMenuItem.tsx | 57 ++++++ 9 files changed, 350 insertions(+), 17 deletions(-) create mode 100644 packages/ra-no-code/src/ResourceConfiguration/FieldConfiguration.tsx create mode 100644 packages/ra-no-code/src/ResourceConfiguration/FieldConfigurationTab.tsx create mode 100644 packages/ra-no-code/src/ResourceConfiguration/ResourceConfiguration.tsx create mode 100644 packages/ra-no-code/src/ui/ResourceMenuItem.tsx diff --git a/packages/ra-core/src/inference/inferTypeFromValues.ts b/packages/ra-core/src/inference/inferTypeFromValues.ts index 88107508946..c8fc75f4c31 100644 --- a/packages/ra-core/src/inference/inferTypeFromValues.ts +++ b/packages/ra-core/src/inference/inferTypeFromValues.ts @@ -18,7 +18,7 @@ import { valuesAreEmail, } from './assertions'; -const types = [ +export const InferenceTypes = [ 'array', 'boolean', 'date', @@ -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; diff --git a/packages/ra-no-code/src/Admin.tsx b/packages/ra-no-code/src/Admin.tsx index 803e33a0ff7..ae9431b0ac0 100644 --- a/packages/ra-no-code/src/Admin.tsx +++ b/packages/ra-no-code/src/Admin.tsx @@ -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 = [ + ( + + )} + />, +]; + export const Admin = (props: AdminProps) => ( @@ -28,6 +39,7 @@ const InnerAdmin = (props: AdminProps) => { dataProvider={dataProvider} ready={Ready} layout={Layout} + customRoutes={customRoutes} {...props} > {hasResources diff --git a/packages/ra-no-code/src/ResourceConfiguration/FieldConfiguration.tsx b/packages/ra-no-code/src/ResourceConfiguration/FieldConfiguration.tsx new file mode 100644 index 00000000000..f4a8adf86f4 --- /dev/null +++ b/packages/ra-no-code/src/ResourceConfiguration/FieldConfiguration.tsx @@ -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 ( + + + + ({ + id: type, + name: type, + }))} + /> + + ); +}; diff --git a/packages/ra-no-code/src/ResourceConfiguration/FieldConfigurationTab.tsx b/packages/ra-no-code/src/ResourceConfiguration/FieldConfigurationTab.tsx new file mode 100644 index 00000000000..7a72eb7eb7c --- /dev/null +++ b/packages/ra-no-code/src/ResourceConfiguration/FieldConfigurationTab.tsx @@ -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 ( + + ); +}; + +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', + }, +})); diff --git a/packages/ra-no-code/src/ResourceConfiguration/ResourceConfiguration.tsx b/packages/ra-no-code/src/ResourceConfiguration/ResourceConfiguration.tsx new file mode 100644 index 00000000000..00b2d1c64fb --- /dev/null +++ b/packages/ra-no-code/src/ResourceConfiguration/ResourceConfiguration.tsx @@ -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 ( + + + ( + + + { + // Here will go an icon selector + ( + resourceConfiguration.label || + resourceConfiguration.name + ).substr(0, 1) + } + + } + action={ + // Will display a menu to delete the resource maybe ? + + + + } + title={`Configuration of ${ + resourceConfiguration.label || + resourceConfiguration.name + }`} + /> + + + + + +
+ + {resourceConfiguration.fields.map(field => ( + + ))} + + {resourceConfiguration.fields.map( + (field, index) => ( + + ) + )} +
+ + + +
+ )} + /> +
+
+ ); +}; + +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, + }, +})); diff --git a/packages/ra-no-code/src/ResourceConfiguration/index.ts b/packages/ra-no-code/src/ResourceConfiguration/index.ts index 6145f4ee7f3..75fde27f687 100644 --- a/packages/ra-no-code/src/ResourceConfiguration/index.ts +++ b/packages/ra-no-code/src/ResourceConfiguration/index.ts @@ -1,4 +1,5 @@ export * from './getFieldDefinitionsFromRecords'; +export * from './ResourceConfiguration'; export * from './useResourceConfiguration'; export * from './useResourcesConfiguration'; export * from './ResourceConfigurationContext'; diff --git a/packages/ra-no-code/src/ui/Layout.tsx b/packages/ra-no-code/src/ui/Layout.tsx index ade1235d912..77851e22255 100644 --- a/packages/ra-no-code/src/ui/Layout.tsx +++ b/packages/ra-no-code/src/ui/Layout.tsx @@ -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) => ( diff --git a/packages/ra-no-code/src/ui/Menu.tsx b/packages/ra-no-code/src/ui/Menu.tsx index af30ae85bb8..5721b85bdd4 100644 --- a/packages/ra-no-code/src/ui/Menu.tsx +++ b/packages/ra-no-code/src/ui/Menu.tsx @@ -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, @@ -34,7 +34,6 @@ const Menu = (props: MenuProps) => { ); const open = useSelector((state: ReduxState) => state.admin.ui.sidebarOpen); const [resources] = useResourcesConfiguration(); - const getResourceLabel = useGetResourceLabel(); return ( <> @@ -57,14 +56,9 @@ const Menu = (props: MenuProps) => { /> )} {Object.keys(resources).map(resource => ( - } + resource={resources[resource]} onClick={onMenuClick} dense={dense} sidebarIsOpen={open} @@ -126,5 +120,3 @@ Menu.propTypes = { Menu.defaultProps = { onMenuClick: () => null, }; - -export default Menu; diff --git a/packages/ra-no-code/src/ui/ResourceMenuItem.tsx b/packages/ra-no-code/src/ui/ResourceMenuItem.tsx new file mode 100644 index 00000000000..d8816c9988b --- /dev/null +++ b/packages/ra-no-code/src/ui/ResourceMenuItem.tsx @@ -0,0 +1,57 @@ +import React, { forwardRef } from 'react'; +import { MenuItemLink, MenuItemLinkProps } from 'react-admin'; +import { IconButton } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import SettingsIcon from '@material-ui/icons/Settings'; +import DefaultIcon from '@material-ui/icons/ViewList'; +import { NavLink, NavLinkProps } from 'react-router-dom'; +import { ResourceConfiguration } from '../ResourceConfiguration'; + +export const ResourceMenuItem = ( + props: Omit & { + resource: ResourceConfiguration; + } +) => { + const { resource, ...rest } = props; + const classes = useStyles(props); + return ( +
+ } + {...rest} + /> + + + +
+ ); +}; + +const NavLinkRef = forwardRef((props, ref) => ( + +)); + +const useStyles = makeStyles(theme => ({ + root: { + display: 'flex', + }, + resource: { + flexGrow: 1, + }, + settings: { + marginLeft: 'auto', + }, +}));