From b4bde090909f93375d570219a005e8dff185a223 Mon Sep 17 00:00:00 2001 From: pbareja Date: Sun, 7 Aug 2022 15:44:20 +0200 Subject: [PATCH 1/2] feat(small): allow adding buttons to Action header --- src/backend/actions/action.interface.ts | 126 +++++++++--------- .../app/action-header/action-header.tsx | 51 ++++--- 2 files changed, 97 insertions(+), 80 deletions(-) diff --git a/src/backend/actions/action.interface.ts b/src/backend/actions/action.interface.ts index fe8892a60..edb2de4ab 100644 --- a/src/backend/actions/action.interface.ts +++ b/src/backend/actions/action.interface.ts @@ -29,35 +29,35 @@ export type ActionContext = TranslateFunctions & { /** * current instance of AdminJS. You may use it to fetch other Resources by their names: */ - _admin: AdminJS; + _admin: AdminJS /** * Resource on which action has been invoked. Null for dashboard handler. */ - resource: BaseResource; + resource: BaseResource /** * Record on which action has been invoked (only for {@link actionType} === 'record') */ - record?: BaseRecord; + record?: BaseRecord /** * Records on which action has been invoked (only for {@link actionType} === 'bulk') */ - records?: Array; + records?: Array /** * view helpers */ - h: ViewHelpers; + h: ViewHelpers /** * Object of currently invoked function. Not present for dashboard action */ - action: ActionDecorator; + action: ActionDecorator /** * Currently logged in admin */ - currentAdmin?: CurrentAdmin; + currentAdmin?: CurrentAdmin /** * Any custom property which you can add to context */ - [key: string]: any; + [key: string]: any } /** @@ -70,15 +70,15 @@ export type PageContext = { /** * current instance of AdminJS. You may use it to fetch other Resources by their names: */ - _admin: AdminJS; - /** + _admin: AdminJS + /** * Currently logged in admin */ - currentAdmin?: CurrentAdmin; - /** + currentAdmin?: CurrentAdmin + /** * view helpers */ - h: ViewHelpers; + h: ViewHelpers } /** @@ -94,38 +94,38 @@ export type ActionRequest = { /** * Id of current resource */ - resourceId: string; + resourceId: string /** * Id of current record (in case of record action) */ - recordId?: string; + recordId?: string /** * Id of selected records (in case of bulk action) divided by commas */ - recordIds?: string; + recordIds?: string /** * Name of an action */ - action: string; + action: string /** * an optional search query string (for `search` resource action) */ - query?: string; + query?: string - [key: string]: any; - }; + [key: string]: any + } /** * POST data passed to the backend */ - payload?: Record; + payload?: Record /** * Elements of query string */ - query?: Record; + query?: Record /** * HTTP method */ - method: 'post' | 'get'; + method: 'post' | 'get' } /** @@ -137,15 +137,15 @@ export type ActionResponse = { /** * Notice message which should be presented to the end user after showing the action */ - notice?: NoticeMessage; + notice?: NoticeMessage /** * redirect path */ - redirectUrl?: string; + redirectUrl?: string /** * Any other custom parameter */ - [key: string]: any; + [key: string]: any } /** @@ -166,7 +166,7 @@ export type RecordActionResponse = ActionResponse & { /** * Record object. */ - record: RecordJSON; + record: RecordJSON } /** @@ -179,7 +179,7 @@ export type BulkActionResponse = ActionResponse & { /** * Array of RecordJSON objects. */ - records: Array; + records: Array } /** @@ -191,11 +191,7 @@ export type BulkActionResponse = ActionResponse & { * @memberof Action * @returns {Promise} */ -export type ActionHandler = ( - request: ActionRequest, - response: any, - context: ActionContext -) => Promise +export type ActionHandler = (request: ActionRequest, response: any, context: ActionContext) => Promise /** * Before action hook. When it is given - it is performed before the {@link ActionHandler} @@ -210,10 +206,10 @@ export type Before = ( * Request object */ request: ActionRequest, - /** + /** * Invocation context */ - context: ActionContext, + context: ActionContext ) => Promise /** @@ -235,17 +231,10 @@ export type After = ( /** * Invocation context */ - context: ActionContext, + context: ActionContext ) => Promise -export type BuildInActions = - 'show' | - 'edit' | - 'list' | - 'delete' | - 'bulkDelete' | - 'new' | - 'search' +export type BuildInActions = 'show' | 'edit' | 'list' | 'delete' | 'bulkDelete' | 'new' | 'search' /** * @classdesc @@ -300,6 +289,15 @@ export type BuildInActions = * actionType: 'resource', * handler: async (request, response, context) => {...} * } + * // Example of adding a link button to Action header + * // for User model + * someLinkAction: { + * actionType: "resource", + * name: "link #1", + * custom: { Link: "https://google.com" }, + * variant: "info", + * icon: "SettingsAdjust", + * }, * } * } * }] @@ -310,14 +308,14 @@ export type BuildInActions = * ACTIONS.show.after = async () => {...} * ``` */ -export interface Action { +export interface Action { /** * Name of an action which is its uniq key. * If you use one of _list_, _search_, _edit_, _new_, _show_, _delete_ or * _bulkDelete_ you override existing actions. * For all other keys you create a new action. */ - name: BuildInActions | string; + name: BuildInActions | string /** * indicates if action should be visible for given invocation context. * It also can be a simple boolean value. @@ -351,7 +349,7 @@ export interface Action { * @see {@link ActionContext} parameter passed to isAccessible * @see {@link IsFunction} exact type of the function */ - isVisible?: boolean | IsFunction; + isVisible?: boolean | IsFunction /** * Indicates if the action can be invoked for given invocation context. * You can pass a boolean or function of type {@link IsFunction}, which @@ -379,7 +377,7 @@ export interface Action { * @see {@link ActionContext} parameter passed to isAccessible * @see {@link IsFunction} exact type of the function */ - isAccessible?: boolean | IsFunction; + isAccessible?: boolean | IsFunction /** * If filter should be visible on the sidebar. Only for _resource_ actions * @@ -397,7 +395,7 @@ export interface Action { * }]}) * ``` */ - showFilter?: boolean; + showFilter?: boolean /** * If action should have resource actions buttons displayed above action header. * @@ -405,7 +403,7 @@ export interface Action { * * @new in version v5.8.1 */ - showResourceActions?: boolean; + showResourceActions?: boolean /** * Type of an action - could be either _resource_, _record_ or _bulk_. * @@ -413,7 +411,7 @@ export interface Action { * * When you define a new action - it is required. */ - actionType: ActionType; + actionType: ActionType /** * icon name for the action. Take a look {@link Icon} component, * because what you put here is passed down to it. @@ -425,7 +423,7 @@ export interface Action { * }]}) * ``` */ - icon?: string; + icon?: string /** * guard message - user will have to confirm it before executing an action. * @@ -444,7 +442,7 @@ export interface Action { * so in order to define the actual message you will have to specify its * translation in {@link AdminJSOptions.Locale} */ - guard?: string; + guard?: string /** * Component which will be used to render the action. To pass the component * use {@link AdminJS.bundle} method. @@ -456,7 +454,7 @@ export interface Action { * Instead after clicking button it is immediately performed. Example of * an action without a view is {@link module:DeleteAction}. */ - component?: string | false; + component?: string | false /** * handler function which will be invoked by either: * - {@link ApiController#resourceAction} @@ -484,7 +482,7 @@ export interface Action { * Required for new actions. For modifying already defined actions * like new and edit we suggest using {@link Action#before} and {@link Action#after} hooks. */ - handler: ActionHandler | Array> | null; + handler: ActionHandler | Array> | null /** * Before action hook. When it is given - it is performed before the {@link Action#handler} * method. @@ -508,7 +506,7 @@ export interface Action { * } * ``` */ - before?: Before | Array; + before?: Before | Array /** * After action hook. When it is given - it is performed on the returned, * by {@link Action#handler handler} function response. @@ -559,12 +557,12 @@ export interface Action { * ``` * */ - after?: After | Array>; + after?: After | Array> /** * Indicates if given action should be seen in a drawer or in a full screen. Default to false */ - showInDrawer?: boolean; + showInDrawer?: boolean /** * Indicates if Action Header should be hidden. @@ -573,7 +571,7 @@ export interface Action { * - action buttons * - action title */ - hideActionHeader?: boolean; + hideActionHeader?: boolean /** * The max width of action HTML container. @@ -594,7 +592,7 @@ export interface Action { * containerWidth: [1, 1/2, 1/3] * ``` */ - containerWidth?: string | number | Array; + containerWidth?: string | number | Array /** * Definition for the layout. Works with the edit and show actions. * @@ -628,25 +626,25 @@ export interface Action { * @see LayoutElement * @see LayoutElementFunction */ - layout?: LayoutElementFunction | Array; + layout?: LayoutElementFunction | Array /** * Defines the variant of the action. based on that it will receive given color. * @new in version v3.3 */ - variant?: VariantType; + variant?: VariantType /** * Action can be nested. If you give here another action name - it will be nested under it. * If parent action doesn't exists - it will be nested under name in the parent. * @new in version v3.3 */ - parent?: string; + parent?: string /** * Any custom properties you want to pass down to {@link ActionJSON}. They have to * be stringified. * @new in version v3.3 */ - custom?: Record; + custom?: Record } diff --git a/src/frontend/components/app/action-header/action-header.tsx b/src/frontend/components/app/action-header/action-header.tsx index 863ea24df..857b2bdb7 100644 --- a/src/frontend/components/app/action-header/action-header.tsx +++ b/src/frontend/components/app/action-header/action-header.tsx @@ -24,9 +24,7 @@ import { ActionJSON, buildActionClickHandler } from '../../../interfaces/action' * @subcategory Application */ export const ActionHeader: React.FC = (props) => { - const { - resource, toggleFilter, actionPerformed, record, action, tag, omitActions, - } = props + const { resource, toggleFilter, actionPerformed, record, action, tag, omitActions } = props const { translateButton } = useTranslation() const history = useHistory() @@ -39,14 +37,13 @@ export const ActionHeader: React.FC = (props) => { const resourceId = resource.id const params = { resourceId, recordId: record?.id } - const handleActionClick = (event, sourceAction: ActionJSON): any | Promise => ( - buildActionClickHandler({ - action: sourceAction, - params, - actionResponseHandler, - push: history.push, - })(event) - ) + // eslint-disable-next-line max-len + const handleActionClick = (event, sourceAction: ActionJSON): any | Promise => buildActionClickHandler({ + action: sourceAction, + params, + actionResponseHandler, + push: history.push, + })(event) const actionButtons = actionsToButtonGroup({ actions: record @@ -65,10 +62,20 @@ export const ActionHeader: React.FC = (props) => { }) } + const addCustomLinks = resource?.resourceActions + ?.filter(ra => ra.custom.Link) + .map((ra) => { + actionButtons.unshift({ + label: ra.label || 'link', + icon: ra.icon || 'Link', + href: ra.custom.Link, + }) + }) + // list and new actions are special and are are always const customResourceButtons = actionsToButtonGroup({ actions: action.showResourceActions - ? resource.resourceActions.filter(ra => !['list', 'new'].includes(ra.name)) + ? resource.resourceActions.filter(ra => !['list', 'new'].includes(ra.name) && !ra.custom.Link) : [], params: { resourceId }, handleClick: handleActionClick, @@ -86,7 +93,9 @@ export const ActionHeader: React.FC = (props) => { return ( - {action.showInDrawer ? '' : ( + {action.showInDrawer ? ( + '' + ) : ( @@ -99,12 +108,22 @@ export const ActionHeader: React.FC = (props) => { {!isList && listAction ? ( - ) : ''} + ) : ( + '' + )} {title} - {tag ? ({tag}) : ''} + {tag ? ( + + {tag} + + ) : ( + '' + )} - {omitActions ? '' : ( + {omitActions ? ( + '' + ) : ( From 744f1b79e86e813a54fec4e93f5822a04642bf32 Mon Sep 17 00:00:00 2001 From: pbareja Date: Sun, 7 Aug 2022 15:52:49 +0200 Subject: [PATCH 2/2] chore: fix lint errors --- src/backend/actions/action.interface.ts | 91 +++++++++++++------------ 1 file changed, 46 insertions(+), 45 deletions(-) diff --git a/src/backend/actions/action.interface.ts b/src/backend/actions/action.interface.ts index edb2de4ab..8c858670e 100644 --- a/src/backend/actions/action.interface.ts +++ b/src/backend/actions/action.interface.ts @@ -29,35 +29,35 @@ export type ActionContext = TranslateFunctions & { /** * current instance of AdminJS. You may use it to fetch other Resources by their names: */ - _admin: AdminJS + _admin: AdminJS; /** * Resource on which action has been invoked. Null for dashboard handler. */ - resource: BaseResource + resource: BaseResource; /** * Record on which action has been invoked (only for {@link actionType} === 'record') */ - record?: BaseRecord + record?: BaseRecord; /** * Records on which action has been invoked (only for {@link actionType} === 'bulk') */ - records?: Array + records?: Array; /** * view helpers */ - h: ViewHelpers + h: ViewHelpers; /** * Object of currently invoked function. Not present for dashboard action */ - action: ActionDecorator + action: ActionDecorator; /** * Currently logged in admin */ - currentAdmin?: CurrentAdmin + currentAdmin?: CurrentAdmin; /** * Any custom property which you can add to context */ - [key: string]: any + [key: string]: any; } /** @@ -70,15 +70,15 @@ export type PageContext = { /** * current instance of AdminJS. You may use it to fetch other Resources by their names: */ - _admin: AdminJS + _admin: AdminJS; /** * Currently logged in admin */ - currentAdmin?: CurrentAdmin + currentAdmin?: CurrentAdmin; /** * view helpers */ - h: ViewHelpers + h: ViewHelpers; } /** @@ -94,38 +94,38 @@ export type ActionRequest = { /** * Id of current resource */ - resourceId: string + resourceId: string; /** * Id of current record (in case of record action) */ - recordId?: string + recordId?: string; /** * Id of selected records (in case of bulk action) divided by commas */ - recordIds?: string + recordIds?: string; /** * Name of an action */ - action: string + action: string; /** * an optional search query string (for `search` resource action) */ - query?: string + query?: string; - [key: string]: any - } + [key: string]: any; + }; /** * POST data passed to the backend */ - payload?: Record + payload?: Record; /** * Elements of query string */ - query?: Record + query?: Record; /** * HTTP method */ - method: 'post' | 'get' + method: 'post' | 'get'; } /** @@ -137,15 +137,15 @@ export type ActionResponse = { /** * Notice message which should be presented to the end user after showing the action */ - notice?: NoticeMessage + notice?: NoticeMessage; /** * redirect path */ - redirectUrl?: string + redirectUrl?: string; /** * Any other custom parameter */ - [key: string]: any + [key: string]: any; } /** @@ -166,7 +166,7 @@ export type RecordActionResponse = ActionResponse & { /** * Record object. */ - record: RecordJSON + record: RecordJSON; } /** @@ -179,7 +179,7 @@ export type BulkActionResponse = ActionResponse & { /** * Array of RecordJSON objects. */ - records: Array + records: Array; } /** @@ -191,6 +191,7 @@ export type BulkActionResponse = ActionResponse & { * @memberof Action * @returns {Promise} */ +// eslint-disable-next-line max-len export type ActionHandler = (request: ActionRequest, response: any, context: ActionContext) => Promise /** @@ -315,7 +316,7 @@ export interface Action { * _bulkDelete_ you override existing actions. * For all other keys you create a new action. */ - name: BuildInActions | string + name: BuildInActions | string; /** * indicates if action should be visible for given invocation context. * It also can be a simple boolean value. @@ -349,7 +350,7 @@ export interface Action { * @see {@link ActionContext} parameter passed to isAccessible * @see {@link IsFunction} exact type of the function */ - isVisible?: boolean | IsFunction + isVisible?: boolean | IsFunction; /** * Indicates if the action can be invoked for given invocation context. * You can pass a boolean or function of type {@link IsFunction}, which @@ -377,7 +378,7 @@ export interface Action { * @see {@link ActionContext} parameter passed to isAccessible * @see {@link IsFunction} exact type of the function */ - isAccessible?: boolean | IsFunction + isAccessible?: boolean | IsFunction; /** * If filter should be visible on the sidebar. Only for _resource_ actions * @@ -395,7 +396,7 @@ export interface Action { * }]}) * ``` */ - showFilter?: boolean + showFilter?: boolean; /** * If action should have resource actions buttons displayed above action header. * @@ -403,7 +404,7 @@ export interface Action { * * @new in version v5.8.1 */ - showResourceActions?: boolean + showResourceActions?: boolean; /** * Type of an action - could be either _resource_, _record_ or _bulk_. * @@ -411,7 +412,7 @@ export interface Action { * * When you define a new action - it is required. */ - actionType: ActionType + actionType: ActionType; /** * icon name for the action. Take a look {@link Icon} component, * because what you put here is passed down to it. @@ -423,7 +424,7 @@ export interface Action { * }]}) * ``` */ - icon?: string + icon?: string; /** * guard message - user will have to confirm it before executing an action. * @@ -442,7 +443,7 @@ export interface Action { * so in order to define the actual message you will have to specify its * translation in {@link AdminJSOptions.Locale} */ - guard?: string + guard?: string; /** * Component which will be used to render the action. To pass the component * use {@link AdminJS.bundle} method. @@ -454,7 +455,7 @@ export interface Action { * Instead after clicking button it is immediately performed. Example of * an action without a view is {@link module:DeleteAction}. */ - component?: string | false + component?: string | false; /** * handler function which will be invoked by either: * - {@link ApiController#resourceAction} @@ -482,7 +483,7 @@ export interface Action { * Required for new actions. For modifying already defined actions * like new and edit we suggest using {@link Action#before} and {@link Action#after} hooks. */ - handler: ActionHandler | Array> | null + handler: ActionHandler | Array> | null; /** * Before action hook. When it is given - it is performed before the {@link Action#handler} * method. @@ -506,7 +507,7 @@ export interface Action { * } * ``` */ - before?: Before | Array + before?: Before | Array; /** * After action hook. When it is given - it is performed on the returned, * by {@link Action#handler handler} function response. @@ -557,12 +558,12 @@ export interface Action { * ``` * */ - after?: After | Array> + after?: After | Array>; /** * Indicates if given action should be seen in a drawer or in a full screen. Default to false */ - showInDrawer?: boolean + showInDrawer?: boolean; /** * Indicates if Action Header should be hidden. @@ -571,7 +572,7 @@ export interface Action { * - action buttons * - action title */ - hideActionHeader?: boolean + hideActionHeader?: boolean; /** * The max width of action HTML container. @@ -592,7 +593,7 @@ export interface Action { * containerWidth: [1, 1/2, 1/3] * ``` */ - containerWidth?: string | number | Array + containerWidth?: string | number | Array; /** * Definition for the layout. Works with the edit and show actions. * @@ -626,25 +627,25 @@ export interface Action { * @see LayoutElement * @see LayoutElementFunction */ - layout?: LayoutElementFunction | Array + layout?: LayoutElementFunction | Array; /** * Defines the variant of the action. based on that it will receive given color. * @new in version v3.3 */ - variant?: VariantType + variant?: VariantType; /** * Action can be nested. If you give here another action name - it will be nested under it. * If parent action doesn't exists - it will be nested under name in the parent. * @new in version v3.3 */ - parent?: string + parent?: string; /** * Any custom properties you want to pass down to {@link ActionJSON}. They have to * be stringified. * @new in version v3.3 */ - custom?: Record + custom?: Record; }