From 6ba37cf6553b8b851d4256e6cd6d8bcf66e9653a Mon Sep 17 00:00:00 2001 From: Gildas Garcia Date: Sun, 24 Feb 2019 09:25:19 +0100 Subject: [PATCH 01/12] [WIP] MIgrate Core components - [x] Resource - [ ] RoutesWithLayout - [ ] CoreAdminRouter - [ ] CoreAdmin - [ ] helpers --- .../{Resource.spec.js => Resource.spec.tsx} | 0 .../ra-core/src/{Resource.js => Resource.tsx} | 76 ++++++++++--------- 2 files changed, 41 insertions(+), 35 deletions(-) rename packages/ra-core/src/{Resource.spec.js => Resource.spec.tsx} (100%) rename packages/ra-core/src/{Resource.js => Resource.tsx} (77%) diff --git a/packages/ra-core/src/Resource.spec.js b/packages/ra-core/src/Resource.spec.tsx similarity index 100% rename from packages/ra-core/src/Resource.spec.js rename to packages/ra-core/src/Resource.spec.tsx diff --git a/packages/ra-core/src/Resource.js b/packages/ra-core/src/Resource.tsx similarity index 77% rename from packages/ra-core/src/Resource.js rename to packages/ra-core/src/Resource.tsx index 2c7c0bd3cd..f7b32417a7 100644 --- a/packages/ra-core/src/Resource.js +++ b/packages/ra-core/src/Resource.tsx @@ -1,17 +1,38 @@ -import React, { createElement, Component } from 'react'; -import PropTypes from 'prop-types'; +import React, { createElement, Component, ComponentType } from 'react'; import { connect } from 'react-redux'; import { Route, Switch } from 'react-router-dom'; import WithPermissions from './auth/WithPermissions'; -import { registerResource, unregisterResource } from './actions'; +import { + registerResource as registerResourceAction, + unregisterResource as unregisterResourceAction, +} from './actions'; +import { match as Match } from 'react-router'; +import { Dispatch } from './types'; -const componentPropType = PropTypes.oneOfType([ - PropTypes.func, - PropTypes.string, -]); +interface Props { + context: 'route' | 'registration'; + match: Match; + name: string; + list?: ComponentType; + create?: ComponentType; + edit?: ComponentType; + show?: ComponentType; + icon?: ComponentType; + options?: object; +} + +interface ConnectedProps { + registerResource: Dispatch; + unregisterResource: Dispatch; +} + +export class Resource extends Component { + static defaultProps = { + context: 'route', + options: {}, + }; -export class Resource extends Component { componentWillMount() { const { context, @@ -155,31 +176,16 @@ export class Resource extends Component { } } -Resource.propTypes = { - context: PropTypes.oneOf(['route', 'registration']).isRequired, - match: PropTypes.shape({ - isExact: PropTypes.bool, - params: PropTypes.object, - path: PropTypes.string, - url: PropTypes.string, - }), - name: PropTypes.string.isRequired, - list: componentPropType, - create: componentPropType, - edit: componentPropType, - show: componentPropType, - icon: componentPropType, - options: PropTypes.object, - registerResource: PropTypes.func.isRequired, - unregisterResource: PropTypes.func.isRequired, -}; - -Resource.defaultProps = { - context: 'route', - options: {}, -}; - -export default connect( +const ConnectedResource = connect( null, - { registerResource, unregisterResource } -)(Resource); + { + registerResource: registerResourceAction, + unregisterResource: unregisterResourceAction, + } +)( + // Necessary casting because of https://github.com/DefinitelyTyped/DefinitelyTyped/issues/19989#issuecomment-432752918 + Resource as ComponentType +); + +// Necessary casting because of https://github.com/DefinitelyTyped/DefinitelyTyped/issues/19989#issuecomment-432752918 +export default ConnectedResource as ComponentType; From 15f317972b72ba5e54821f33384ecee38e3a6380 Mon Sep 17 00:00:00 2001 From: Gildas Garcia Date: Sun, 24 Feb 2019 10:20:45 +0100 Subject: [PATCH 02/12] RoutesWithLayout --- packages/ra-core/package.json | 2 + ...yout.spec.js => RoutesWithLayout.spec.tsx} | 19 ++++---- ...utesWithLayout.js => RoutesWithLayout.tsx} | 45 +++++++++++-------- packages/ra-core/src/auth/WithPermissions.tsx | 23 ++++++---- packages/ra-core/src/types.ts | 15 +++++++ yarn.lock | 17 +++++-- 6 files changed, 80 insertions(+), 41 deletions(-) rename packages/ra-core/src/{RoutesWithLayout.spec.js => RoutesWithLayout.spec.tsx} (83%) rename packages/ra-core/src/{RoutesWithLayout.js => RoutesWithLayout.tsx} (73%) diff --git a/packages/ra-core/package.json b/packages/ra-core/package.json index 85ccdfc0fe..fbe124a1e5 100644 --- a/packages/ra-core/package.json +++ b/packages/ra-core/package.json @@ -31,6 +31,8 @@ "@types/react-router": "^4.4.1", "@types/recompose": "^0.27.0", "@types/redux-form": "^7.5.2", + "@types/react-router": "^4.4.4", + "@types/react-router-dom": "^4.3.1", "cross-env": "^5.2.0", "enzyme": "~3.7.0", "enzyme-adapter-react-16": "~1.6.0", diff --git a/packages/ra-core/src/RoutesWithLayout.spec.js b/packages/ra-core/src/RoutesWithLayout.spec.tsx similarity index 83% rename from packages/ra-core/src/RoutesWithLayout.spec.js rename to packages/ra-core/src/RoutesWithLayout.spec.tsx index 67f6bb1f17..4ab1b985db 100644 --- a/packages/ra-core/src/RoutesWithLayout.spec.js +++ b/packages/ra-core/src/RoutesWithLayout.spec.tsx @@ -9,9 +9,9 @@ import RoutesWithLayout from './RoutesWithLayout'; describe('', () => { const Dashboard = () =>
Dashboard
; - const Custom = () =>
Custom
; - const FirstResource = () =>
Default
; - const Resource = () =>
Resource
; + const Custom = ({ name }) =>
Custom
; + const FirstResource = ({ name }) =>
Default
; + const Resource = ({ name }) =>
Resource
; // the Provider is required because the dashboard is wrapped by , which is a connected component const store = createStore(() => ({ @@ -22,10 +22,7 @@ describe('', () => { const wrapper = mount( - true} - > + @@ -40,7 +37,7 @@ describe('', () => { const wrapper = mount( - true}> + @@ -54,7 +51,7 @@ describe('', () => { const wrapper = mount( - true}> + @@ -68,7 +65,9 @@ describe('', () => { }); it('should accept custom routes', () => { - const customRoutes = []; // eslint-disable-line react/jsx-key + const customRoutes = [ + , + ]; // eslint-disable-line react/jsx-key const wrapper = mount( diff --git a/packages/ra-core/src/RoutesWithLayout.js b/packages/ra-core/src/RoutesWithLayout.tsx similarity index 73% rename from packages/ra-core/src/RoutesWithLayout.js rename to packages/ra-core/src/RoutesWithLayout.tsx index 76b5a4fbf3..59f40c77a2 100644 --- a/packages/ra-core/src/RoutesWithLayout.js +++ b/packages/ra-core/src/RoutesWithLayout.tsx @@ -1,10 +1,29 @@ -import React, { Children, cloneElement, createElement } from 'react'; -import PropTypes from 'prop-types'; +import React, { + Children, + cloneElement, + createElement, + ComponentType, + SFC, +} from 'react'; import { Redirect, Route, Switch } from 'react-router-dom'; import WithPermissions from './auth/WithPermissions'; +import { + AdminChildren, + CustomRoutes, + CatchAllComponent, + TitleComponent, +} from './types'; -const RoutesWithLayout = ({ +interface Props { + catchAll?: CatchAllComponent; + children: AdminChildren; + customRoutes?: CustomRoutes; + dashboard?: ComponentType; + title?: TitleComponent; +} + +const RoutesWithLayout: SFC = ({ catchAll, children, customRoutes, @@ -12,13 +31,16 @@ const RoutesWithLayout = ({ title, }) => { const childrenAsArray = React.Children.toArray(children); - const firstChild = childrenAsArray.length > 0 ? childrenAsArray[0] : null; + const firstChild: React.ReactElement | null = + childrenAsArray.length > 0 + ? (childrenAsArray[0] as React.ReactElement) + : null; return ( {customRoutes && customRoutes.map((route, key) => cloneElement(route, { key }))} - {Children.map(children, child => ( + {Children.map(children, (child: React.ReactElement) => ( ReactNode; interface Props { - authProvider: AuthProvider; authParams: object; - children: WithPermissionsChildren; + children?: WithPermissionsChildren; location: Location; match: Match; - render: WithPermissionsChildren; + render?: WithPermissionsChildren; + staticContext?: object; +} + +interface EnhancedProps { + authProvider: AuthProvider; isLoggedIn: boolean; - staticContext: object; userCheck: UserCheck; } @@ -72,7 +75,7 @@ const isEmptyChildren = children => Children.count(children) === 0; * * ); */ -export class WithPermissions extends Component { +export class WithPermissions extends Component { cancelled = false; state = { permissions: null }; @@ -106,12 +109,12 @@ export class WithPermissions extends Component { } } - checkAuthentication(params: Props) { + checkAuthentication(params: Props & EnhancedProps) { const { userCheck, authParams, location } = params; userCheck(authParams, location && location.pathname); } - async checkPermissions(params: Props) { + async checkPermissions(params: Props & EnhancedProps) { const { authProvider, authParams, location, match } = params; try { const permissions = await authProvider(AUTH_GET_PERMISSIONS, { @@ -157,7 +160,7 @@ const mapStateToProps = state => ({ isLoggedIn: getIsLoggedIn(state), }); -export default compose( +const EnhancedWithPermissions = compose( getContext({ authProvider: PropTypes.func, }), @@ -166,3 +169,5 @@ export default compose( { userCheck: userCheckAction } ) )(WithPermissions); + +export default EnhancedWithPermissions as ComponentType; diff --git a/packages/ra-core/src/types.ts b/packages/ra-core/src/types.ts index c2a065727d..72aee0f928 100644 --- a/packages/ra-core/src/types.ts +++ b/packages/ra-core/src/types.ts @@ -1,3 +1,6 @@ +import { ReactNode, ReactElement, ComponentType } from 'react'; +import { RouteProps } from 'react-router'; + export type Identifier = string | number; export interface Record { id: Identifier; @@ -62,3 +65,15 @@ export interface ReduxState { export type Dispatch = T extends (...args: infer A) => any ? (...args: A) => void : never; + +type RenderResourcesFunction = (permissions: any) => any[]; +export type AdminChildren = RenderResourcesFunction | ReactNode; + +export interface CustomRoute extends RouteProps { + noLayout: boolean; +} + +export type CustomRoutes = Array>; + +export type TitleComponent = string | ReactElement; +export type CatchAllComponent = ComponentType<{ title?: TitleComponent }>; diff --git a/yarn.lock b/yarn.lock index de758336d5..225ee8775d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1115,10 +1115,19 @@ "@types/react" "*" redux "^4.0.0" -"@types/react-router@^4.4.1": - version "4.4.1" - resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-4.4.1.tgz#c875dfd0b6fe00efa463eb8e5de8b5f74644b3f5" - integrity sha512-CtQfdcXyMye3vflnQQ2sHU832iDJRoAr4P+7f964KlLYupXU1I5crP1+d/WnCMo6mmtjBjqQvxrtbAbodqerMA== +"@types/react-router-dom@^4.3.1": + version "4.3.1" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.3.1.tgz#71fe2918f8f60474a891520def40a63997dafe04" + integrity sha512-GbztJAScOmQ/7RsQfO4cd55RuH1W4g6V1gDW3j4riLlt+8yxYLqqsiMzmyuXBLzdFmDtX/uU2Bpcm0cmudv44A== + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*", "@types/react-router@^4.4.4": + version "4.4.4" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-4.4.4.tgz#4dbd5588ea6024e0c04519bd8aabe74c0a2b77e5" + integrity sha512-TZVfpT6nvUv/lbho/nRtckEtgkhspOQr3qxrnpXixwgQRKKyg5PvDfNKc8Uend/p/Pi70614VCmC0NPAKWF+0g== dependencies: "@types/history" "*" "@types/react" "*" From 71cb5992fe3b481081381bd9375f96d43838ea46 Mon Sep 17 00:00:00 2001 From: Gildas Garcia Date: Sun, 24 Feb 2019 10:30:27 +0100 Subject: [PATCH 03/12] Fixes --- packages/ra-core/src/Resource.tsx | 34 +++++++++++++------ packages/ra-core/src/RoutesWithLayout.tsx | 6 ++-- packages/ra-core/src/auth/WithPermissions.tsx | 6 ++-- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/packages/ra-core/src/Resource.tsx b/packages/ra-core/src/Resource.tsx index f7b32417a7..9dabdf3af3 100644 --- a/packages/ra-core/src/Resource.tsx +++ b/packages/ra-core/src/Resource.tsx @@ -8,17 +8,29 @@ import { unregisterResource as unregisterResourceAction, } from './actions'; import { match as Match } from 'react-router'; -import { Dispatch } from './types'; +import { Dispatch, Identifier } from './types'; + +interface ReactAdminComponentProps { + basePath: string; +} +interface ReactAdminComponentPropsWithId { + id: Identifier; + basePath: string; +} + +type ResourceMatch = Match<{ + id?: string; +}>; interface Props { - context: 'route' | 'registration'; - match: Match; + context?: 'route' | 'registration'; + match?: ResourceMatch; name: string; - list?: ComponentType; - create?: ComponentType; - edit?: ComponentType; - show?: ComponentType; - icon?: ComponentType; + list?: ComponentType; + create?: ComponentType; + edit?: ComponentType; + show?: ComponentType; + icon?: ComponentType; options?: object; } @@ -123,7 +135,8 @@ export class Resource extends Component { createElement(show, { basePath, id: decodeURIComponent( - props.match.params.id + (props.match as ResourceMatch) + .params.id ), ...props, }) @@ -143,7 +156,8 @@ export class Resource extends Component { createElement(edit, { basePath, id: decodeURIComponent( - props.match.params.id + (props.match as ResourceMatch) + .params.id ), ...props, }) diff --git a/packages/ra-core/src/RoutesWithLayout.tsx b/packages/ra-core/src/RoutesWithLayout.tsx index 59f40c77a2..7de6f5fa75 100644 --- a/packages/ra-core/src/RoutesWithLayout.tsx +++ b/packages/ra-core/src/RoutesWithLayout.tsx @@ -7,7 +7,9 @@ import React, { } from 'react'; import { Redirect, Route, Switch } from 'react-router-dom'; -import WithPermissions from './auth/WithPermissions'; +import WithPermissions, { + WithPermissionsChildrenParams, +} from './auth/WithPermissions'; import { AdminChildren, CustomRoutes, @@ -19,7 +21,7 @@ interface Props { catchAll?: CatchAllComponent; children: AdminChildren; customRoutes?: CustomRoutes; - dashboard?: ComponentType; + dashboard?: ComponentType; title?: TitleComponent; } diff --git a/packages/ra-core/src/auth/WithPermissions.tsx b/packages/ra-core/src/auth/WithPermissions.tsx index 2d5a593bea..5878201235 100644 --- a/packages/ra-core/src/auth/WithPermissions.tsx +++ b/packages/ra-core/src/auth/WithPermissions.tsx @@ -13,8 +13,8 @@ import { UserCheck } from './types'; import { Location } from 'history'; import { match as Match } from 'react-router'; -interface WithPermissionsChildrenParams { - authParams: object; +export interface WithPermissionsChildrenParams { + authParams?: object; location?: Location; match: Match; permissions: any; @@ -25,7 +25,7 @@ type WithPermissionsChildren = ( ) => ReactNode; interface Props { - authParams: object; + authParams?: object; children?: WithPermissionsChildren; location: Location; match: Match; From 819f90b36993951bc8f759b170c72bec4048b179 Mon Sep 17 00:00:00 2001 From: Gildas Garcia Date: Sun, 24 Feb 2019 10:31:27 +0100 Subject: [PATCH 04/12] CoreAdminRouter --- ...outer.spec.js => CoreAdminRouter.spec.tsx} | 0 ...CoreAdminRouter.js => CoreAdminRouter.tsx} | 131 ++++++++++-------- 2 files changed, 77 insertions(+), 54 deletions(-) rename packages/ra-core/src/{CoreAdminRouter.spec.js => CoreAdminRouter.spec.tsx} (100%) rename packages/ra-core/src/{CoreAdminRouter.js => CoreAdminRouter.tsx} (64%) diff --git a/packages/ra-core/src/CoreAdminRouter.spec.js b/packages/ra-core/src/CoreAdminRouter.spec.tsx similarity index 100% rename from packages/ra-core/src/CoreAdminRouter.spec.js rename to packages/ra-core/src/CoreAdminRouter.spec.tsx diff --git a/packages/ra-core/src/CoreAdminRouter.js b/packages/ra-core/src/CoreAdminRouter.tsx similarity index 64% rename from packages/ra-core/src/CoreAdminRouter.js rename to packages/ra-core/src/CoreAdminRouter.tsx index 04670f7bec..2ec47068b6 100644 --- a/packages/ra-core/src/CoreAdminRouter.js +++ b/packages/ra-core/src/CoreAdminRouter.tsx @@ -1,4 +1,12 @@ -import React, { Children, Component, cloneElement, createElement } from 'react'; +import React, { + Children, + Component, + cloneElement, + createElement, + ReactNode, + ComponentType, + CSSProperties, +} from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { Route, Switch } from 'react-router-dom'; @@ -7,17 +15,49 @@ import getContext from 'recompose/getContext'; import { AUTH_GET_PERMISSIONS } from './auth/types'; import { isLoggedIn } from './reducer'; -import { userLogout } from './actions/authActions'; +import { userLogout as userLogoutAction } from './actions/authActions'; import RoutesWithLayout from './RoutesWithLayout'; - -const welcomeStyles = { +import { + Dispatch, + AuthProvider, + AdminChildren, + CustomRoutes, + CatchAllComponent, + TitleComponent, +} from './types'; +import { WithPermissionsChildrenParams } from './auth/WithPermissions'; + +const welcomeStyles: CSSProperties = { width: '50%', margin: '40vh', textAlign: 'center', }; -export class CoreAdminRouter extends Component { - state = { children: [] }; +interface LayoutProps { + dashboard?: ComponentType; + logout: ReactNode; + menu: ComponentType; + theme: object; + title?: TitleComponent; +} + +interface Props extends LayoutProps { + appLayout: ComponentType; + authProvider?: AuthProvider; + catchAll?: CatchAllComponent; + userLogout: Dispatch; + children: AdminChildren; + customRoutes?: CustomRoutes; + isLoggedIn?: boolean; + loading?: ComponentType; +} + +interface State { + children: any[]; +} + +export class CoreAdminRouter extends Component { + state: State = { children: [] }; componentWillMount() { this.initializeResources(this.props); @@ -122,20 +162,22 @@ export class CoreAdminRouter extends Component { return ; } - let childrenToRender = + const childrenToRender = typeof children === 'function' ? this.state.children : children; return (
{// Render every resources children outside the React Router Switch // as we need all of them and not just the one rendered - Children.map(childrenToRender, child => - cloneElement(child, { - key: child.props.name, - // The context prop instructs the Resource component to not render anything - // but simply to register itself as a known resource - context: 'registration', - }) + Children.map( + childrenToRender, + (child: React.ReactElement) => + cloneElement(child, { + key: child.props.name, + // The context prop instructs the Resource component to not render anything + // but simply to register itself as a known resource + context: 'registration', + }) )} {customRoutes @@ -153,24 +195,26 @@ export class CoreAdminRouter extends Component { - createElement(appLayout, { - children: ( - !route.props.noLayout - )} - dashboard={dashboard} - title={title} - /> - ), - dashboard, - logout, - menu, - theme, - title, - }) + createElement( + appLayout, + { + dashboard, + logout, + menu, + theme, + title, + }, + !route.props.noLayout + )} + dashboard={dashboard} + title={title} + > + {childrenToRender} + + ) } /> @@ -179,27 +223,6 @@ export class CoreAdminRouter extends Component { } } -const componentPropType = PropTypes.oneOfType([ - PropTypes.func, - PropTypes.string, -]); - -CoreAdminRouter.propTypes = { - appLayout: componentPropType, - authProvider: PropTypes.func, - catchAll: componentPropType, - children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), - customRoutes: PropTypes.array, - dashboard: componentPropType, - isLoggedIn: PropTypes.bool, - loading: componentPropType, - logout: PropTypes.node, - menu: componentPropType, - theme: PropTypes.object, - title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), - userLogout: PropTypes.func, -}; - const mapStateToProps = state => ({ isLoggedIn: isLoggedIn(state), }); @@ -210,6 +233,6 @@ export default compose( }), connect( mapStateToProps, - { userLogout } + { userLogout: userLogoutAction } ) )(CoreAdminRouter); From ae025b60fa31aad51e362bccd6bf41647a1d4c15 Mon Sep 17 00:00:00 2001 From: Gildas Garcia Date: Sun, 24 Feb 2019 10:44:10 +0100 Subject: [PATCH 05/12] CoreAdmin --- packages/ra-core/src/CoreAdmin.tsx | 59 +++++++++++--------- packages/ra-core/src/CoreAdminRouter.tsx | 67 +++++++++++------------ packages/ra-core/src/RoutesWithLayout.tsx | 3 +- packages/ra-core/src/types.ts | 20 ++++++- 4 files changed, 85 insertions(+), 64 deletions(-) diff --git a/packages/ra-core/src/CoreAdmin.tsx b/packages/ra-core/src/CoreAdmin.tsx index f6518c113a..373e2cf53d 100644 --- a/packages/ra-core/src/CoreAdmin.tsx +++ b/packages/ra-core/src/CoreAdmin.tsx @@ -1,9 +1,4 @@ -import React, { - createElement, - Component, - ReactNode, - ComponentType, -} from 'react'; +import React, { createElement, Component, ComponentType } from 'react'; import PropTypes from 'prop-types'; import { Provider } from 'react-redux'; import { History } from 'history'; @@ -15,30 +10,41 @@ import withContext from 'recompose/withContext'; import createAdminStore from './createAdminStore'; import TranslationProvider from './i18n/TranslationProvider'; import CoreAdminRouter from './CoreAdminRouter'; -import { AuthProvider, I18nProvider, DataProvider } from './types'; +import { + AuthProvider, + I18nProvider, + DataProvider, + TitleComponent, + LoginComponent, + LayoutComponent, + AdminChildren, + CatchAllComponent, + CustomRoutes, + DashboardComponent, +} from './types'; export type ChildrenFunction = () => ComponentType[]; interface Props { - appLayout: ComponentType; - authProvider: AuthProvider; - children: ReactNode | ChildrenFunction; - catchAll: ComponentType; - customSagas: any[]; - customReducers: object; - customRoutes: any[]; - dashboard: ComponentType; + appLayout?: LayoutComponent; + authProvider?: AuthProvider; + children: AdminChildren; + catchAll?: CatchAllComponent; + customSagas?: any[]; + customReducers?: object; + customRoutes?: CustomRoutes; + dashboard?: DashboardComponent; dataProvider: DataProvider; history: History; - i18nProvider: I18nProvider; - initialState: object; - loading: ComponentType; - locale: string; - loginPage: ComponentType | boolean; - logoutButton: ComponentType; - menu: ComponentType; - theme: object; - title: ReactNode; + i18nProvider?: I18nProvider; + initialState?: object; + loading?: ComponentType; + locale?: string; + loginPage?: LoginComponent | boolean; + logoutButton?: ComponentType; + menu?: ComponentType; + theme?: object; + title?: TitleComponent; } class CoreAdmin extends Component { @@ -96,12 +102,12 @@ React-admin requires a valid dataProvider function to work.`); - {loginPage !== false ? ( + {loginPage !== false && loginPage !== true ? ( - createElement(loginPage as ComponentType, { + createElement(loginPage, { ...props, title, theme, @@ -118,7 +124,6 @@ React-admin requires a valid dataProvider function to work.`); customRoutes={customRoutes} dashboard={dashboard} loading={loading} - loginPage={loginPage} logout={logout} menu={menu} theme={theme} diff --git a/packages/ra-core/src/CoreAdminRouter.tsx b/packages/ra-core/src/CoreAdminRouter.tsx index 2ec47068b6..2c0a2ed768 100644 --- a/packages/ra-core/src/CoreAdminRouter.tsx +++ b/packages/ra-core/src/CoreAdminRouter.tsx @@ -23,9 +23,9 @@ import { AdminChildren, CustomRoutes, CatchAllComponent, - TitleComponent, + LayoutComponent, + LayoutProps, } from './types'; -import { WithPermissionsChildrenParams } from './auth/WithPermissions'; const welcomeStyles: CSSProperties = { width: '50%', @@ -33,67 +33,64 @@ const welcomeStyles: CSSProperties = { textAlign: 'center', }; -interface LayoutProps { - dashboard?: ComponentType; - logout: ReactNode; - menu: ComponentType; - theme: object; - title?: TitleComponent; -} - interface Props extends LayoutProps { - appLayout: ComponentType; - authProvider?: AuthProvider; + appLayout?: LayoutComponent; catchAll?: CatchAllComponent; - userLogout: Dispatch; children: AdminChildren; customRoutes?: CustomRoutes; - isLoggedIn?: boolean; loading?: ComponentType; } +interface EnhancedProps { + authProvider?: AuthProvider; + isLoggedIn?: boolean; + userLogout: Dispatch; +} + interface State { children: any[]; } -export class CoreAdminRouter extends Component { +export class CoreAdminRouter extends Component { state: State = { children: [] }; componentWillMount() { this.initializeResources(this.props); } - initializeResources = nextProps => { + initializeResources = (nextProps: Props & EnhancedProps) => { if (typeof nextProps.children === 'function') { this.initializeResourcesAsync(nextProps); } }; - initializeResourcesAsync = async props => { + initializeResourcesAsync = async (props: Props & EnhancedProps) => { const { authProvider } = props; try { const permissions = await authProvider(AUTH_GET_PERMISSIONS); const { children } = props; - const childrenFuncResult = children(permissions); - if (childrenFuncResult.then) { - childrenFuncResult.then(resolvedChildren => { + if (typeof children === 'function') { + const childrenFuncResult = children(permissions); + if (childrenFuncResult.then) { + childrenFuncResult.then(resolvedChildren => { + this.setState({ + children: resolvedChildren + .filter(child => child) + .map(child => ({ + ...child, + props: { + ...child.props, + key: child.props.name, + }, + })), + }); + }); + } else { this.setState({ - children: resolvedChildren - .filter(child => child) - .map(child => ({ - ...child, - props: { - ...child.props, - key: child.props.name, - }, - })), + children: childrenFuncResult.filter(child => child), }); - }); - } else { - this.setState({ - children: childrenFuncResult.filter(child => child), - }); + } } } catch (error) { this.props.userLogout(); @@ -235,4 +232,4 @@ export default compose( mapStateToProps, { userLogout: userLogoutAction } ) -)(CoreAdminRouter); +)(CoreAdminRouter) as ComponentType; diff --git a/packages/ra-core/src/RoutesWithLayout.tsx b/packages/ra-core/src/RoutesWithLayout.tsx index 7de6f5fa75..5e75477a95 100644 --- a/packages/ra-core/src/RoutesWithLayout.tsx +++ b/packages/ra-core/src/RoutesWithLayout.tsx @@ -15,13 +15,14 @@ import { CustomRoutes, CatchAllComponent, TitleComponent, + DashboardComponent, } from './types'; interface Props { catchAll?: CatchAllComponent; children: AdminChildren; customRoutes?: CustomRoutes; - dashboard?: ComponentType; + dashboard?: DashboardComponent; title?: TitleComponent; } diff --git a/packages/ra-core/src/types.ts b/packages/ra-core/src/types.ts index 72aee0f928..48f8198100 100644 --- a/packages/ra-core/src/types.ts +++ b/packages/ra-core/src/types.ts @@ -1,5 +1,6 @@ import { ReactNode, ReactElement, ComponentType } from 'react'; -import { RouteProps } from 'react-router'; +import { RouteProps, RouteComponentProps } from 'react-router'; +import { WithPermissionsChildrenParams } from './auth/WithPermissions'; export type Identifier = string | number; export interface Record { @@ -77,3 +78,20 @@ export type CustomRoutes = Array>; export type TitleComponent = string | ReactElement; export type CatchAllComponent = ComponentType<{ title?: TitleComponent }>; + +interface LoginComponentProps extends RouteComponentProps { + title?: TitleComponent; + theme?: object; +} +export type LoginComponent = ComponentType; +export type DashboardComponent = ComponentType; + +export interface LayoutProps { + dashboard?: DashboardComponent; + logout: ReactNode; + menu: ComponentType; + theme: object; + title?: TitleComponent; +} + +export type LayoutComponent = ComponentType; From 0372879a1a26330cb508365a17d218f64a8da3db Mon Sep 17 00:00:00 2001 From: Gildas Garcia Date: Sun, 24 Feb 2019 10:44:54 +0100 Subject: [PATCH 06/12] Misc --- packages/ra-core/src/{dataFetchActions.js => dataFetchActions.ts} | 0 packages/ra-core/src/{index.js => index.ts} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename packages/ra-core/src/{dataFetchActions.js => dataFetchActions.ts} (100%) rename packages/ra-core/src/{index.js => index.ts} (100%) diff --git a/packages/ra-core/src/dataFetchActions.js b/packages/ra-core/src/dataFetchActions.ts similarity index 100% rename from packages/ra-core/src/dataFetchActions.js rename to packages/ra-core/src/dataFetchActions.ts diff --git a/packages/ra-core/src/index.js b/packages/ra-core/src/index.ts similarity index 100% rename from packages/ra-core/src/index.js rename to packages/ra-core/src/index.ts From c44c6953cc313fd167dc57b5525db58dd9ba2038 Mon Sep 17 00:00:00 2001 From: Gildas Garcia Date: Sun, 24 Feb 2019 11:25:23 +0100 Subject: [PATCH 07/12] Output declarations - TS complained about some types so added the required tweaks --- .../src/controller/CreateController.tsx | 15 +++++--- .../ra-core/src/controller/EditController.tsx | 23 ++++++----- .../ra-core/src/controller/ListController.tsx | 38 ++++++++++++------- .../ra-core/src/controller/ShowController.tsx | 19 ++++++---- .../input/ReferenceArrayInputController.tsx | 27 +++++++------ .../input/ReferenceInputController.tsx | 29 +++++++------- .../ra-core/src/form/withDefaultValue.tsx | 4 +- packages/ra-core/tsconfig.json | 4 +- 8 files changed, 97 insertions(+), 62 deletions(-) diff --git a/packages/ra-core/src/controller/CreateController.tsx b/packages/ra-core/src/controller/CreateController.tsx index e686847ec1..ee360f7ea3 100644 --- a/packages/ra-core/src/controller/CreateController.tsx +++ b/packages/ra-core/src/controller/CreateController.tsx @@ -1,4 +1,4 @@ -import { Component, ReactNode } from 'react'; +import { Component, ReactNode, ComponentType } from 'react'; import { connect } from 'react-redux'; import compose from 'recompose/compose'; import inflection from 'inflection'; @@ -26,16 +26,19 @@ interface ChildrenFuncParams { interface Props { basePath: string; children: (params: ChildrenFuncParams) => ReactNode; - crudCreate: Dispatch; hasCreate?: boolean; hasEdit?: boolean; hasList?: boolean; hasShow?: boolean; - isLoading: boolean; location: Location; match: Match; record?: Partial; resource: string; +} + +interface EnhancedProps { + crudCreate: Dispatch; + isLoading: boolean; translate: Translate; } @@ -80,7 +83,9 @@ interface Props { * ); * export default App; */ -export class UnconnectedCreateController extends Component { +export class UnconnectedCreateController extends Component< + Props & EnhancedProps +> { public static defaultProps: Partial = { record: {}, }; @@ -169,4 +174,4 @@ const CreateController = compose( withTranslate )(UnconnectedCreateController); -export default CreateController; +export default CreateController as ComponentType; diff --git a/packages/ra-core/src/controller/EditController.tsx b/packages/ra-core/src/controller/EditController.tsx index 66f22d0929..37b631dc34 100644 --- a/packages/ra-core/src/controller/EditController.tsx +++ b/packages/ra-core/src/controller/EditController.tsx @@ -1,4 +1,4 @@ -import { Component, ReactNode } from 'react'; +import { Component, ReactNode, ComponentType } from 'react'; import { connect } from 'react-redux'; import compose from 'recompose/compose'; import inflection from 'inflection'; @@ -29,20 +29,23 @@ interface ChildrenFuncParams { interface Props { basePath: string; children: (params: ChildrenFuncParams) => ReactNode; - crudGetOne: Dispatch; - dispatchCrudUpdate: Dispatch; - record?: Record; hasCreate?: boolean; hasEdit?: boolean; hasShow?: boolean; hasList?: boolean; id: Identifier; isLoading: boolean; - resetForm: (form: string) => void; resource: string; + undoable?: boolean; +} + +interface EnhancedProps { + crudGetOne: Dispatch; + dispatchCrudUpdate: Dispatch; + record?: Record; + resetForm: (form: string) => void; startUndoable: Dispatch; translate: Translate; - undoable?: boolean; version: number; } @@ -88,12 +91,14 @@ interface Props { * ); * export default App; */ -export class UnconnectedEditController extends Component { +export class UnconnectedEditController extends Component< + Props & EnhancedProps +> { componentDidMount() { this.updateData(); } - componentWillReceiveProps(nextProps) { + componentWillReceiveProps(nextProps: Props & EnhancedProps) { if ( this.props.id !== nextProps.id || nextProps.version !== this.props.version @@ -205,4 +210,4 @@ const EditController = compose( withTranslate )(UnconnectedEditController); -export default EditController; +export default EditController as ComponentType; diff --git a/packages/ra-core/src/controller/ListController.tsx b/packages/ra-core/src/controller/ListController.tsx index 3aa9f5ab49..96b87d008b 100644 --- a/packages/ra-core/src/controller/ListController.tsx +++ b/packages/ra-core/src/controller/ListController.tsx @@ -1,5 +1,11 @@ /* eslint no-console: ["error", { allow: ["warn", "error"] }] */ -import { Component, isValidElement, ReactNode, ReactElement } from 'react'; +import { + Component, + isValidElement, + ReactNode, + ReactElement, + ComponentType, +} from 'react'; import { connect } from 'react-redux'; import { parse, stringify } from 'query-string'; import { push as pushAction } from 'react-router-redux'; @@ -79,24 +85,26 @@ interface Props { // the props managed by react-admin authProvider?: AuthProvider; basePath: string; - changeListParams: Dispatch; - crudGetList: Dispatch; - data?: RecordMap; debounce?: number; hasCreate?: boolean; hasEdit?: boolean; hasList?: boolean; hasShow?: boolean; - ids?: Identifier[]; - loadedOnce?: boolean; - selectedIds?: Identifier[]; - isLoading: boolean; location: Location; path?: string; - params: ListParams; - push: (location: LocationDescriptorObject) => void; query: ListParams; resource: string; +} +interface EnhancedProps { + changeListParams: Dispatch; + crudGetList: Dispatch; + data?: RecordMap; + ids?: Identifier[]; + isLoading: boolean; + loadedOnce?: boolean; + params: ListParams; + push: (location: LocationDescriptorObject) => void; + selectedIds?: Identifier[]; setSelectedIds: (resource: string, ids: Identifier[]) => void; toggleItem: (resource: string, id: Identifier) => void; total: number; @@ -145,7 +153,9 @@ interface Props { * * ); */ -export class UnconnectedListController extends Component { +export class UnconnectedListController extends Component< + Props & EnhancedProps +> { public static defaultProps: Partial = { debounce: 500, filter: {}, @@ -194,7 +204,7 @@ export class UnconnectedListController extends Component { this.setFilters.cancel(); } - componentWillReceiveProps(nextProps: Props) { + componentWillReceiveProps(nextProps: Props & EnhancedProps) { if ( nextProps.resource !== this.props.resource || nextProps.query.sort !== this.props.query.sort || @@ -217,7 +227,7 @@ export class UnconnectedListController extends Component { } } - shouldComponentUpdate(nextProps: Props, nextState) { + shouldComponentUpdate(nextProps: Props & EnhancedProps, nextState) { if ( nextProps.translate === this.props.translate && nextProps.isLoading === this.props.isLoading && @@ -518,4 +528,4 @@ const ListController = compose( withTranslate )(UnconnectedListController); -export default ListController; +export default ListController as ComponentType; diff --git a/packages/ra-core/src/controller/ShowController.tsx b/packages/ra-core/src/controller/ShowController.tsx index 7310cb8430..8d01e41b32 100644 --- a/packages/ra-core/src/controller/ShowController.tsx +++ b/packages/ra-core/src/controller/ShowController.tsx @@ -1,4 +1,4 @@ -import { Component, ReactNode } from 'react'; +import { Component, ReactNode, ComponentType } from 'react'; import { connect } from 'react-redux'; import compose from 'recompose/compose'; import inflection from 'inflection'; @@ -20,15 +20,18 @@ interface ChildrenFuncParams { interface Props { basePath: string; children: (params: ChildrenFuncParams) => ReactNode; - crudGetOne: Dispatch; - record?: Record; hasCreate?: boolean; hasEdit?: boolean; hasShow?: boolean; hasList?: boolean; - id: Identifier; isLoading: boolean; resource: string; +} + +interface EnhancedProps { + crudGetOne: Dispatch; + id: Identifier; + record?: Record; translate: Translate; version: number; } @@ -75,12 +78,14 @@ interface Props { * ); * export default App; */ -export class UnconnectedShowController extends Component { +export class UnconnectedShowController extends Component< + Props & EnhancedProps +> { componentDidMount() { this.updateData(); } - componentWillReceiveProps(nextProps) { + componentWillReceiveProps(nextProps: Props & EnhancedProps) { if ( this.props.id !== nextProps.id || nextProps.version !== this.props.version @@ -150,4 +155,4 @@ const ShowController = compose( withTranslate )(UnconnectedShowController); -export default ShowController; +export default ShowController as ComponentType; diff --git a/packages/ra-core/src/controller/input/ReferenceArrayInputController.tsx b/packages/ra-core/src/controller/input/ReferenceArrayInputController.tsx index 32dbf6a17b..d65b538ba5 100644 --- a/packages/ra-core/src/controller/input/ReferenceArrayInputController.tsx +++ b/packages/ra-core/src/controller/input/ReferenceArrayInputController.tsx @@ -1,4 +1,4 @@ -import { Component, ReactNode } from 'react'; +import { Component, ReactNode, ComponentType } from 'react'; import { connect } from 'react-redux'; import debounce from 'lodash/debounce'; import compose from 'recompose/compose'; @@ -38,22 +38,25 @@ interface Props { allowEmpty?: boolean; basePath: string; children: (params: ChildrenFuncParams) => ReactNode; - crudGetMatching: Dispatch; - crudGetMany: Dispatch; filter?: object; filterToQuery: (filter: {}) => any; input?: WrappedFieldInputProps; - matchingReferences?: Record[] | MatchingReferencesError; meta?: object; - onChange?: () => void; perPage?: number; record?: Record; reference: string; - referenceRecords?: Record[]; referenceSource: typeof defaultReferenceSource; resource: string; sort?: Sort; source: string; +} + +interface EnhancedProps { + crudGetMatching: Dispatch; + crudGetMany: Dispatch; + matchingReferences?: Record[] | MatchingReferencesError; + onChange?: () => void; + referenceRecords?: Record[]; translate: Translate; } @@ -135,7 +138,9 @@ interface Props { * * */ -export class UnconnectedReferenceArrayInputController extends Component { +export class UnconnectedReferenceArrayInputController extends Component< + Props & EnhancedProps +> { public static defaultProps = { allowEmpty: false, filter: {}, @@ -150,7 +155,7 @@ export class UnconnectedReferenceArrayInputController extends Component { private params; private debouncedSetFilter; - constructor(props) { + constructor(props: Props & EnhancedProps) { super(props); const { perPage, sort, filter } = props; // stored as a property rather than state because we don't want redraw of async updates @@ -162,12 +167,12 @@ export class UnconnectedReferenceArrayInputController extends Component { this.fetchReferencesAndOptions(this.props); } - componentWillReceiveProps(nextProps) { + componentWillReceiveProps(nextProps: Props & EnhancedProps) { let shouldFetchOptions = false; if ( (this.props.record || { id: undefined }).id !== - (nextProps.record || {}).id + (nextProps.record || { id: undefined }).id ) { this.fetchReferencesAndOptions(nextProps); } else if (this.props.input.value !== nextProps.input.value) { @@ -324,4 +329,4 @@ ReferenceArrayInputController.defaultProps = { referenceSource: defaultReferenceSource, // used in makeMapStateToProps }; -export default ReferenceArrayInputController; +export default ReferenceArrayInputController as ComponentType; diff --git a/packages/ra-core/src/controller/input/ReferenceInputController.tsx b/packages/ra-core/src/controller/input/ReferenceInputController.tsx index f4c19940e0..2e6c47239c 100644 --- a/packages/ra-core/src/controller/input/ReferenceInputController.tsx +++ b/packages/ra-core/src/controller/input/ReferenceInputController.tsx @@ -1,4 +1,4 @@ -import { Component, ReactNode } from 'react'; +import { Component, ReactNode, ComponentType } from 'react'; import { connect } from 'react-redux'; import debounce from 'lodash/debounce'; import compose from 'recompose/compose'; @@ -41,21 +41,24 @@ interface Props { allowEmpty?: boolean; basePath: string; children: (params: ChildrenFuncParams) => ReactNode; - crudGetMatchingAccumulate: Dispatch; - crudGetManyAccumulate: Dispatch; filter?: object; filterToQuery: (filter: {}) => any; input?: WrappedFieldInputProps; - matchingReferences?: Record[] | MatchingReferencesError; - onChange: () => void; perPage: number; record?: Record; reference: string; - referenceRecord?: Record; referenceSource: typeof defaultReferenceSource; resource: string; sort?: Sort; source: string; +} + +interface EnhancedProps { + crudGetMatchingAccumulate: Dispatch; + crudGetManyAccumulate: Dispatch; + matchingReferences?: Record[] | MatchingReferencesError; + onChange: () => void; + referenceRecord?: Record; translate: Translate; } @@ -145,7 +148,7 @@ interface State { * */ export class UnconnectedReferenceInputController extends Component< - Props, + Props & EnhancedProps, State > { public static defaultProps = { @@ -173,10 +176,10 @@ export class UnconnectedReferenceInputController extends Component< this.fetchReferenceAndOptions(this.props); } - componentWillReceiveProps(nextProps) { + componentWillReceiveProps(nextProps: Props & EnhancedProps) { if ( (this.props.record || { id: undefined }).id !== - (nextProps.record || {}).id + (nextProps.record || { id: undefined }).id ) { this.fetchReferenceAndOptions(nextProps); } else if (this.props.input.value !== nextProps.input.value) { @@ -200,7 +203,7 @@ export class UnconnectedReferenceInputController extends Component< } } - setFilter = filter => { + setFilter = (filter: any) => { if (filter !== this.state.filter) { this.setState( { filter: this.props.filterToQuery(filter) }, @@ -209,13 +212,13 @@ export class UnconnectedReferenceInputController extends Component< } }; - setPagination = pagination => { + setPagination = (pagination: Pagination) => { if (pagination !== this.state.pagination) { this.setState({ pagination }, this.fetchOptions); } }; - setSort = sort => { + setSort = (sort: Sort) => { if (sort !== this.state.sort) { this.setState({ sort }, this.fetchOptions); } @@ -320,4 +323,4 @@ ReferenceInputController.defaultProps = { referenceSource: defaultReferenceSource, // used in makeMapStateToProps }; -export default ReferenceInputController; +export default ReferenceInputController as ComponentType; diff --git a/packages/ra-core/src/form/withDefaultValue.tsx b/packages/ra-core/src/form/withDefaultValue.tsx index ac6aa37021..3d4e098c3a 100644 --- a/packages/ra-core/src/form/withDefaultValue.tsx +++ b/packages/ra-core/src/form/withDefaultValue.tsx @@ -5,12 +5,12 @@ import { connect } from 'react-redux'; import { initializeForm as initializeFormAction } from '../actions/formActions'; import { InputProps } from './types'; -interface Props extends InputProps { +export interface DefaultValueProps extends InputProps { decoratedComponent: ComponentType; initializeForm: typeof initializeFormAction; } -export class DefaultValueView extends Component { +export class DefaultValueView extends Component { static propTypes = { decoratedComponent: PropTypes.oneOfType([ PropTypes.element, diff --git a/packages/ra-core/tsconfig.json b/packages/ra-core/tsconfig.json index 8be9b27649..e8a2155510 100644 --- a/packages/ra-core/tsconfig.json +++ b/packages/ra-core/tsconfig.json @@ -2,7 +2,9 @@ "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "lib", - "rootDir": "src" + "rootDir": "src", + "declaration": true, + "allowJs": false }, "exclude": ["**/*.spec.ts", "**/*.spec.tsx", "**/*.spec.js"], "include": ["src"] From 7b88838eec4ca5e5b0f39ccb3268477bb82b6104 Mon Sep 17 00:00:00 2001 From: Gildas Garcia Date: Sun, 24 Feb 2019 11:29:36 +0100 Subject: [PATCH 08/12] Declare types --- packages/ra-core/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ra-core/package.json b/packages/ra-core/package.json index fbe124a1e5..577f1e6b80 100644 --- a/packages/ra-core/package.json +++ b/packages/ra-core/package.json @@ -10,6 +10,7 @@ ], "main": "lib/index", "module": "esm/index.js", + "types": "esm/index.d.ts", "sideEffects": false, "authors": [ "François Zaninotto", From 0e0bcb139f8096e17bd99ba58e97f47d28c9351c Mon Sep 17 00:00:00 2001 From: Gildas Garcia Date: Tue, 26 Feb 2019 19:49:02 +0100 Subject: [PATCH 09/12] Review --- packages/ra-core/src/CoreAdmin.tsx | 24 ++++++++++++++++++------ packages/ra-core/src/CoreAdminRouter.tsx | 15 ++++++++++----- packages/ra-core/src/Resource.tsx | 8 ++++---- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/packages/ra-core/src/CoreAdmin.tsx b/packages/ra-core/src/CoreAdmin.tsx index 373e2cf53d..094ba9959b 100644 --- a/packages/ra-core/src/CoreAdmin.tsx +++ b/packages/ra-core/src/CoreAdmin.tsx @@ -1,4 +1,10 @@ -import React, { createElement, Component, ComponentType } from 'react'; +import React, { + createElement, + Component, + ComponentType, + Children, + isValidElement, +} from 'react'; import PropTypes from 'prop-types'; import { Provider } from 'react-redux'; import { History } from 'history'; @@ -26,10 +32,10 @@ import { export type ChildrenFunction = () => ComponentType[]; interface Props { - appLayout?: LayoutComponent; + appLayout: LayoutComponent; authProvider?: AuthProvider; - children: AdminChildren; - catchAll?: CatchAllComponent; + children?: AdminChildren; + catchAll: CatchAllComponent; customSagas?: any[]; customReducers?: object; customRoutes?: CustomRoutes; @@ -38,7 +44,7 @@ interface Props { history: History; i18nProvider?: I18nProvider; initialState?: object; - loading?: ComponentType; + loading: ComponentType; locale?: string; loginPage?: LoginComponent | boolean; logoutButton?: ComponentType; @@ -52,6 +58,10 @@ class CoreAdmin extends Component { store: PropTypes.object, }; + static defaultProps: Partial = { + loginPage: false, + }; + reduxIsAlreadyInitialized = false; history = null; @@ -102,7 +112,9 @@ React-admin requires a valid dataProvider function to work.`); - {loginPage !== false && loginPage !== true ? ( + {loginPage !== false && + loginPage !== true && + typeof loginPage !== undefined ? ( { + static defaultProps: Partial = { + customRoutes: [], + }; + state: State = { children: [] }; componentWillMount() { @@ -168,7 +173,7 @@ export class CoreAdminRouter extends Component { // as we need all of them and not just the one rendered Children.map( childrenToRender, - (child: React.ReactElement) => + (child: React.ReactElement) => cloneElement(child, { key: child.props.name, // The context prop instructs the Resource component to not render anything diff --git a/packages/ra-core/src/Resource.tsx b/packages/ra-core/src/Resource.tsx index 9dabdf3af3..6e39bd802d 100644 --- a/packages/ra-core/src/Resource.tsx +++ b/packages/ra-core/src/Resource.tsx @@ -22,7 +22,7 @@ type ResourceMatch = Match<{ id?: string; }>; -interface Props { +export interface ResourceProps { context?: 'route' | 'registration'; match?: ResourceMatch; name: string; @@ -39,7 +39,7 @@ interface ConnectedProps { unregisterResource: Dispatch; } -export class Resource extends Component { +export class Resource extends Component { static defaultProps = { context: 'route', options: {}, @@ -198,8 +198,8 @@ const ConnectedResource = connect( } )( // Necessary casting because of https://github.com/DefinitelyTyped/DefinitelyTyped/issues/19989#issuecomment-432752918 - Resource as ComponentType + Resource as ComponentType ); // Necessary casting because of https://github.com/DefinitelyTyped/DefinitelyTyped/issues/19989#issuecomment-432752918 -export default ConnectedResource as ComponentType; +export default ConnectedResource as ComponentType; From d24b4be4729107b95b9ba0c020ab2f9df951f746 Mon Sep 17 00:00:00 2001 From: Gildas Garcia Date: Wed, 27 Feb 2019 17:14:38 +0100 Subject: [PATCH 10/12] Migrate react-admin package to TS with declarations export enabled. --- packages/ra-core/src/CoreAdmin.tsx | 24 +++++++++---------- packages/ra-core/src/CoreAdminRouter.tsx | 17 ++++++++----- packages/react-admin/package.json | 1 + .../react-admin/src/{Admin.js => Admin.ts} | 0 .../src/{AdminRouter.js => AdminRouter.ts} | 0 .../react-admin/src/{index.js => index.ts} | 0 packages/react-admin/tsconfig.json | 4 +++- 7 files changed, 27 insertions(+), 19 deletions(-) rename packages/react-admin/src/{Admin.js => Admin.ts} (100%) rename packages/react-admin/src/{AdminRouter.js => AdminRouter.ts} (100%) rename packages/react-admin/src/{index.js => index.ts} (100%) diff --git a/packages/ra-core/src/CoreAdmin.tsx b/packages/ra-core/src/CoreAdmin.tsx index 094ba9959b..f43479f513 100644 --- a/packages/ra-core/src/CoreAdmin.tsx +++ b/packages/ra-core/src/CoreAdmin.tsx @@ -1,10 +1,4 @@ -import React, { - createElement, - Component, - ComponentType, - Children, - isValidElement, -} from 'react'; +import React, { createElement, Component, ComponentType } from 'react'; import PropTypes from 'prop-types'; import { Provider } from 'react-redux'; import { History } from 'history'; @@ -31,7 +25,7 @@ import { export type ChildrenFunction = () => ComponentType[]; -interface Props { +export interface AdminProps { appLayout: LayoutComponent; authProvider?: AuthProvider; children?: AdminChildren; @@ -53,12 +47,16 @@ interface Props { title?: TitleComponent; } -class CoreAdmin extends Component { +interface AdminContext { + authProvider: AuthProvider; +} + +class CoreAdminBase extends Component { static contextTypes = { store: PropTypes.object, }; - static defaultProps: Partial = { + static defaultProps: Partial = { loginPage: false, }; @@ -184,9 +182,11 @@ React-admin requires a valid dataProvider function to work.`); } } -export default withContext( +const CoreAdmin = withContext( { authProvider: PropTypes.func, }, ({ authProvider }) => ({ authProvider }) -)(CoreAdmin); +)(CoreAdminBase) as ComponentType; + +export default CoreAdmin; diff --git a/packages/ra-core/src/CoreAdminRouter.tsx b/packages/ra-core/src/CoreAdminRouter.tsx index 944fd198d9..26269f9b46 100644 --- a/packages/ra-core/src/CoreAdminRouter.tsx +++ b/packages/ra-core/src/CoreAdminRouter.tsx @@ -34,7 +34,7 @@ const welcomeStyles: CSSProperties = { textAlign: 'center', }; -interface Props extends LayoutProps { +export interface AdminRouterProps extends LayoutProps { appLayout: LayoutComponent; catchAll: CatchAllComponent; children?: AdminChildren; @@ -52,8 +52,11 @@ interface State { children: any[]; } -export class CoreAdminRouter extends Component { - static defaultProps: Partial = { +export class CoreAdminRouter extends Component< + AdminRouterProps & EnhancedProps, + State +> { + static defaultProps: Partial = { customRoutes: [], }; @@ -63,13 +66,15 @@ export class CoreAdminRouter extends Component { this.initializeResources(this.props); } - initializeResources = (nextProps: Props & EnhancedProps) => { + initializeResources = (nextProps: AdminRouterProps & EnhancedProps) => { if (typeof nextProps.children === 'function') { this.initializeResourcesAsync(nextProps); } }; - initializeResourcesAsync = async (props: Props & EnhancedProps) => { + initializeResourcesAsync = async ( + props: AdminRouterProps & EnhancedProps + ) => { const { authProvider } = props; try { const permissions = await authProvider(AUTH_GET_PERMISSIONS); @@ -237,4 +242,4 @@ export default compose( mapStateToProps, { userLogout: userLogoutAction } ) -)(CoreAdminRouter) as ComponentType; +)(CoreAdminRouter) as ComponentType; diff --git a/packages/react-admin/package.json b/packages/react-admin/package.json index 0b0ad22006..c7247f42a8 100644 --- a/packages/react-admin/package.json +++ b/packages/react-admin/package.json @@ -11,6 +11,7 @@ ], "main": "lib/index.js", "module": "esm/index.js", + "types": "esm/index.d.ts", "sideEffects": false, "authors": [ "François Zaninotto" diff --git a/packages/react-admin/src/Admin.js b/packages/react-admin/src/Admin.ts similarity index 100% rename from packages/react-admin/src/Admin.js rename to packages/react-admin/src/Admin.ts diff --git a/packages/react-admin/src/AdminRouter.js b/packages/react-admin/src/AdminRouter.ts similarity index 100% rename from packages/react-admin/src/AdminRouter.js rename to packages/react-admin/src/AdminRouter.ts diff --git a/packages/react-admin/src/index.js b/packages/react-admin/src/index.ts similarity index 100% rename from packages/react-admin/src/index.js rename to packages/react-admin/src/index.ts diff --git a/packages/react-admin/tsconfig.json b/packages/react-admin/tsconfig.json index 8be9b27649..e8a2155510 100644 --- a/packages/react-admin/tsconfig.json +++ b/packages/react-admin/tsconfig.json @@ -2,7 +2,9 @@ "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "lib", - "rootDir": "src" + "rootDir": "src", + "declaration": true, + "allowJs": false }, "exclude": ["**/*.spec.ts", "**/*.spec.tsx", "**/*.spec.js"], "include": ["src"] From 94ecdc9cb573e799426f0a223ec93e8e1ad988de Mon Sep 17 00:00:00 2001 From: Gildas Garcia Date: Fri, 1 Mar 2019 08:26:20 +0100 Subject: [PATCH 11/12] Review --- packages/ra-core/src/CoreAdmin.tsx | 8 +++--- packages/ra-core/src/CoreAdminRouter.tsx | 31 ++++++++++++---------- packages/ra-core/src/Resource.tsx | 27 +------------------ packages/ra-core/src/RoutesWithLayout.tsx | 2 +- packages/ra-core/src/types.ts | 32 +++++++++++++++++++++-- 5 files changed, 53 insertions(+), 47 deletions(-) diff --git a/packages/ra-core/src/CoreAdmin.tsx b/packages/ra-core/src/CoreAdmin.tsx index f43479f513..20355bc17a 100644 --- a/packages/ra-core/src/CoreAdmin.tsx +++ b/packages/ra-core/src/CoreAdmin.tsx @@ -40,7 +40,7 @@ export interface AdminProps { initialState?: object; loading: ComponentType; locale?: string; - loginPage?: LoginComponent | boolean; + loginPage: LoginComponent | boolean; logoutButton?: ComponentType; menu?: ComponentType; theme?: object; @@ -57,6 +57,8 @@ class CoreAdminBase extends Component { }; static defaultProps: Partial = { + catchAll: () => null, + loading: () => null, loginPage: false, }; @@ -110,9 +112,7 @@ React-admin requires a valid dataProvider function to work.`); - {loginPage !== false && - loginPage !== true && - typeof loginPage !== undefined ? ( + {loginPage !== false && loginPage !== true ? ( { + const childrenFuncResult = resolveChildren(permissions); + if ((childrenFuncResult as Promise).then) { + (childrenFuncResult as Promise).then( + resolvedChildren => { this.setState({ children: resolvedChildren .filter(child => child) @@ -95,12 +96,14 @@ export class CoreAdminRouter extends Component< }, })), }); - }); - } else { - this.setState({ - children: childrenFuncResult.filter(child => child), - }); - } + } + ); + } else { + this.setState({ + children: (childrenFuncResult as ResourceElement[]).filter( + child => child + ), + }); } } catch (error) { this.props.userLogout(); diff --git a/packages/ra-core/src/Resource.tsx b/packages/ra-core/src/Resource.tsx index 6e39bd802d..0eb3213bc6 100644 --- a/packages/ra-core/src/Resource.tsx +++ b/packages/ra-core/src/Resource.tsx @@ -7,32 +7,7 @@ import { registerResource as registerResourceAction, unregisterResource as unregisterResourceAction, } from './actions'; -import { match as Match } from 'react-router'; -import { Dispatch, Identifier } from './types'; - -interface ReactAdminComponentProps { - basePath: string; -} -interface ReactAdminComponentPropsWithId { - id: Identifier; - basePath: string; -} - -type ResourceMatch = Match<{ - id?: string; -}>; - -export interface ResourceProps { - context?: 'route' | 'registration'; - match?: ResourceMatch; - name: string; - list?: ComponentType; - create?: ComponentType; - edit?: ComponentType; - show?: ComponentType; - icon?: ComponentType; - options?: object; -} +import { Dispatch, ResourceProps, ResourceMatch } from './types'; interface ConnectedProps { registerResource: Dispatch; diff --git a/packages/ra-core/src/RoutesWithLayout.tsx b/packages/ra-core/src/RoutesWithLayout.tsx index 5e75477a95..43824b89b0 100644 --- a/packages/ra-core/src/RoutesWithLayout.tsx +++ b/packages/ra-core/src/RoutesWithLayout.tsx @@ -19,7 +19,7 @@ import { } from './types'; interface Props { - catchAll?: CatchAllComponent; + catchAll: CatchAllComponent; children: AdminChildren; customRoutes?: CustomRoutes; dashboard?: DashboardComponent; diff --git a/packages/ra-core/src/types.ts b/packages/ra-core/src/types.ts index 48f8198100..b6cd11fd76 100644 --- a/packages/ra-core/src/types.ts +++ b/packages/ra-core/src/types.ts @@ -1,5 +1,6 @@ import { ReactNode, ReactElement, ComponentType } from 'react'; -import { RouteProps, RouteComponentProps } from 'react-router'; +import { RouteProps, RouteComponentProps, match as Match } from 'react-router'; + import { WithPermissionsChildrenParams } from './auth/WithPermissions'; export type Identifier = string | number; @@ -67,7 +68,10 @@ export type Dispatch = T extends (...args: infer A) => any ? (...args: A) => void : never; -type RenderResourcesFunction = (permissions: any) => any[]; +export type ResourceElement = ReactElement; +export type RenderResourcesFunction = ( + permissions: any +) => ResourceElement[] | Promise; export type AdminChildren = RenderResourcesFunction | ReactNode; export interface CustomRoute extends RouteProps { @@ -95,3 +99,27 @@ export interface LayoutProps { } export type LayoutComponent = ComponentType; + +interface ReactAdminComponentProps { + basePath: string; +} +interface ReactAdminComponentPropsWithId { + id: Identifier; + basePath: string; +} + +export type ResourceMatch = Match<{ + id?: string; +}>; + +export interface ResourceProps { + context: 'route' | 'registration'; + match?: ResourceMatch; + name: string; + list?: ComponentType; + create?: ComponentType; + edit?: ComponentType; + show?: ComponentType; + icon?: ComponentType; + options: object; +} From 46d5f64a2106ed9e4f5d52777a80861b52cd9702 Mon Sep 17 00:00:00 2001 From: Gildas Garcia Date: Fri, 1 Mar 2019 08:28:19 +0100 Subject: [PATCH 12/12] Linting --- packages/ra-core/src/RoutesWithLayout.tsx | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/ra-core/src/RoutesWithLayout.tsx b/packages/ra-core/src/RoutesWithLayout.tsx index 43824b89b0..b5a0287338 100644 --- a/packages/ra-core/src/RoutesWithLayout.tsx +++ b/packages/ra-core/src/RoutesWithLayout.tsx @@ -1,15 +1,7 @@ -import React, { - Children, - cloneElement, - createElement, - ComponentType, - SFC, -} from 'react'; +import React, { Children, cloneElement, createElement, SFC } from 'react'; import { Redirect, Route, Switch } from 'react-router-dom'; -import WithPermissions, { - WithPermissionsChildrenParams, -} from './auth/WithPermissions'; +import WithPermissions from './auth/WithPermissions'; import { AdminChildren, CustomRoutes,