diff --git a/docs/pages/01_ui_react.adoc b/docs/pages/01_ui_react.adoc index 20ee00df..7b4782e0 100644 --- a/docs/pages/01_ui_react.adoc +++ b/docs/pages/01_ui_react.adoc @@ -950,6 +950,64 @@ const containerLevelHook: ViewGalaxyViewContainerHook = () => { }; ---- +=== Adding Custom buttons to container action bars + +In case we cannot model buttons on a container level which should call actions, we can add them via a component hook. + +> This feature should not be confused with the one above. In this case we can add extra visual elements, + while the above is about manually implementing the corresponding actions. + +*Locating interface keys:* + +- We should inspect the page / dialog in dev-tools, and search for the id of the container in the `src/containers` folder. +- Afterwards look for a `const` ending in `CONTAINER_EXTRA_ACTIONS_HOOK_INTERFACE_KEY` + +*Registering implementations* + +Implementations can be registered in one central location: `src/custom/application-customizer.tsx`. + +*src/custom/application-customizer.tsx:* +[source,typescriptjsx] +---- +import { type BundleContext } from '@pandino/pandino-api'; +import { + SERVICES_AUTHOR_PRODUCT_AUTHOR_PRODUCT_TABLE_CONTAINER_EXTRA_ACTIONS_HOOK_INTERFACE_KEY, + ServicesAuthorProductAuthorProduct_TableContainerExtraActionsComponentProps +} from '~/containers/Services/AuthorProduct/AuthorProduct_Table/customization'; +import { MdiIcon } from '~/components'; +import LoadingButton from '@mui/lab/LoadingButton'; +import Grid from '@mui/material/Grid'; + +export class DefaultApplicationCustomizer implements ApplicationCustomizer { + async customize(context: BundleContext): Promise { + context.registerService(SERVICES_AUTHOR_PRODUCT_AUTHOR_PRODUCT_TABLE_CONTAINER_EXTRA_ACTIONS_HOOK_INTERFACE_KEY, ExtraContainerButtons); + } +} +export function ExtraContainerButtons({ isLoading, actions, }: ServicesAuthorProductAuthorProduct_TableContainerExtraActionsComponentProps) { + // we can call other hooks here + + return ( + <> + + actions?.createProductAction?.(null as any)} + disabled={isLoading} + startIcon={} + > + Create Product + + + + ); +} +---- + +For the list of every prop available, we can check the `ServicesAuthorProductAuthorProduct_TableContainerExtraActionsComponentProps` interface +as shown above. + === How to locate actions Every action is implemented in Pages/Dialogs. The actions of Tables and Single Components which are inlined in a certain diff --git a/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/Planet/View/PlanetViewPageContainer.tsx.snapshot b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/Planet/View/PlanetViewPageContainer.tsx.snapshot index 099bd7f5..035509e2 100644 --- a/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/Planet/View/PlanetViewPageContainer.tsx.snapshot +++ b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/Planet/View/PlanetViewPageContainer.tsx.snapshot @@ -10,11 +10,14 @@ import LoadingButton from '@mui/lab/LoadingButton'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Grid from '@mui/material/Grid'; +import { OBJECTCLASS } from '@pandino/pandino-api'; +import { ComponentProxy, useTrackService } from '@pandino/react-hooks'; import { Suspense, lazy } from 'react'; import type { Dispatch, SetStateAction } from 'react'; import { useTranslation } from 'react-i18next'; import { MdiIcon, PageHeader, useJudoNavigation } from '~/components'; import { useConfirmDialog } from '~/components/dialog'; +import { CUSTOM_VISUAL_ELEMENT_INTERFACE_KEY } from '~/custom'; import type { Planet, PlanetStored } from '~/services/data-api/model/Planet'; import type { PlanetQueryCustomizer } from '~/services/data-api/rest/PlanetQueryCustomizer'; import { mainContainerPadding } from '~/theme'; @@ -23,6 +26,8 @@ import { getValue } from '~/utilities/helper'; import type { PlanetViewActionDefinitions, PlanetViewPageActions, PlanetViewPageProps } from './types'; +import { PLANET_VIEW_CONTAINER_EXTRA_ACTIONS_HOOK_INTERFACE_KEY } from './customization'; + const PlanetView = lazy(() => import('~/containers/Planet/View/PlanetView')); // Name: Planet::View @@ -138,7 +143,18 @@ export default function PlanetViewPage(props: PlanetViewPageProps) { )} -
{/* Placeholder */}
+ +
{/* Placeholder */}
+
diff --git a/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/Form/ViewGalaxyFormDialogContainer.tsx.snapshot b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/Form/ViewGalaxyFormDialogContainer.tsx.snapshot index faacbd1b..6dcd52e4 100644 --- a/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/Form/ViewGalaxyFormDialogContainer.tsx.snapshot +++ b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/Form/ViewGalaxyFormDialogContainer.tsx.snapshot @@ -20,11 +20,14 @@ import MenuItem from '@mui/material/MenuItem'; import MenuList from '@mui/material/MenuList'; import Paper from '@mui/material/Paper'; import Popper from '@mui/material/Popper'; +import { OBJECTCLASS } from '@pandino/pandino-api'; +import { ComponentProxy, useTrackService } from '@pandino/react-hooks'; import { Suspense, lazy, useCallback, useRef, useState } from 'react'; import type { Dispatch, SetStateAction } from 'react'; import { useTranslation } from 'react-i18next'; import { MdiIcon, useJudoNavigation } from '~/components'; import { useConfirmDialog } from '~/components/dialog'; +import { CUSTOM_VISUAL_ELEMENT_INTERFACE_KEY } from '~/custom'; import type { ViewGalaxy, ViewGalaxyStored } from '~/services/data-api/model/ViewGalaxy'; import type { ViewGalaxyQueryCustomizer } from '~/services/data-api/rest/ViewGalaxyQueryCustomizer'; import { processQueryCustomizer } from '~/utilities'; @@ -32,6 +35,8 @@ import { getValue } from '~/utilities/helper'; import type { ViewGalaxyFormActionDefinitions, ViewGalaxyFormDialogActions, ViewGalaxyFormDialogProps } from './types'; +import { VIEW_GALAXY_FORM_CONTAINER_EXTRA_ACTIONS_HOOK_INTERFACE_KEY } from './customization'; + const ViewGalaxyForm = lazy(() => import('~/containers/View/Galaxy/Form/ViewGalaxyForm')); // Name: View::Galaxy::Form @@ -194,6 +199,16 @@ export default function ViewGalaxyFormDialog(props: ViewGalaxyFormDialogProps) { )} + ); diff --git a/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/View/ViewGalaxyViewDialogContainer.tsx.snapshot b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/View/ViewGalaxyViewDialogContainer.tsx.snapshot index 66e86cdb..116f95e9 100644 --- a/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/View/ViewGalaxyViewDialogContainer.tsx.snapshot +++ b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/View/ViewGalaxyViewDialogContainer.tsx.snapshot @@ -13,11 +13,14 @@ import DialogContent from '@mui/material/DialogContent'; import DialogTitle from '@mui/material/DialogTitle'; import Grid from '@mui/material/Grid'; import IconButton from '@mui/material/IconButton'; +import { OBJECTCLASS } from '@pandino/pandino-api'; +import { ComponentProxy, useTrackService } from '@pandino/react-hooks'; import { Suspense, lazy } from 'react'; import type { Dispatch, SetStateAction } from 'react'; import { useTranslation } from 'react-i18next'; import { MdiIcon, useJudoNavigation } from '~/components'; import { useConfirmDialog } from '~/components/dialog'; +import { CUSTOM_VISUAL_ELEMENT_INTERFACE_KEY } from '~/custom'; import type { ViewGalaxy, ViewGalaxyStored } from '~/services/data-api/model/ViewGalaxy'; import type { ViewGalaxyQueryCustomizer } from '~/services/data-api/rest/ViewGalaxyQueryCustomizer'; import { processQueryCustomizer } from '~/utilities'; @@ -25,6 +28,8 @@ import { getValue } from '~/utilities/helper'; import type { ViewGalaxyViewActionDefinitions, ViewGalaxyViewDialogActions, ViewGalaxyViewDialogProps } from './types'; +import { VIEW_GALAXY_VIEW_CONTAINER_EXTRA_ACTIONS_HOOK_INTERFACE_KEY } from './customization'; + const ViewGalaxyView = lazy(() => import('~/containers/View/Galaxy/View/ViewGalaxyView')); // Name: View::Galaxy::View @@ -181,6 +186,16 @@ export default function ViewGalaxyViewDialog(props: ViewGalaxyViewDialogProps) { )} + ); diff --git a/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/View/ViewGalaxyViewPageContainer.tsx.snapshot b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/View/ViewGalaxyViewPageContainer.tsx.snapshot index 93139649..7b889736 100644 --- a/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/View/ViewGalaxyViewPageContainer.tsx.snapshot +++ b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/View/ViewGalaxyViewPageContainer.tsx.snapshot @@ -10,11 +10,14 @@ import LoadingButton from '@mui/lab/LoadingButton'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Grid from '@mui/material/Grid'; +import { OBJECTCLASS } from '@pandino/pandino-api'; +import { ComponentProxy, useTrackService } from '@pandino/react-hooks'; import { Suspense, lazy } from 'react'; import type { Dispatch, SetStateAction } from 'react'; import { useTranslation } from 'react-i18next'; import { MdiIcon, PageHeader, useJudoNavigation } from '~/components'; import { useConfirmDialog } from '~/components/dialog'; +import { CUSTOM_VISUAL_ELEMENT_INTERFACE_KEY } from '~/custom'; import type { ViewGalaxy, ViewGalaxyStored } from '~/services/data-api/model/ViewGalaxy'; import type { ViewGalaxyQueryCustomizer } from '~/services/data-api/rest/ViewGalaxyQueryCustomizer'; import { mainContainerPadding } from '~/theme'; @@ -23,6 +26,8 @@ import { getValue } from '~/utilities/helper'; import type { ViewGalaxyViewActionDefinitions, ViewGalaxyViewPageActions, ViewGalaxyViewPageProps } from './types'; +import { VIEW_GALAXY_VIEW_CONTAINER_EXTRA_ACTIONS_HOOK_INTERFACE_KEY } from './customization'; + const ViewGalaxyView = lazy(() => import('~/containers/View/Galaxy/View/ViewGalaxyView')); // Name: View::Galaxy::View @@ -138,7 +143,18 @@ export default function ViewGalaxyViewPage(props: ViewGalaxyViewPageProps) { )} -
{/* Placeholder */}
+ +
{/* Placeholder */}
+
diff --git a/judo-ui-react/src/main/resources/actor/src/containers/customization.ts.hbs b/judo-ui-react/src/main/resources/actor/src/containers/customization.ts.hbs index 498ef46f..a2b9341d 100644 --- a/judo-ui-react/src/main/resources/actor/src/containers/customization.ts.hbs +++ b/judo-ui-react/src/main/resources/actor/src/containers/customization.ts.hbs @@ -7,7 +7,7 @@ {{# each (getEnumDataTypesForContainer container) as |imp| }} import type { {{ restParamName imp }} } from '~/services/data-api/model/{{ restParamName imp }}'; {{/ each }} - import type { {{ pageContainerActionDefinitionTypeName container }} } from './types'; + import type { {{ pageContainerActionDefinitionTypeName container }}, {{ containerComponentName container }}PageActions } from './types'; {{/ unless }} {{# unless (containerIsEmptyDashboard container) }} @@ -25,6 +25,17 @@ {{/ unless }} ) => {{ pageContainerActionDefinitionTypeName container }}; + export const {{ camelCaseNameToInterfaceKey (containerComponentName container) }}_CONTAINER_EXTRA_ACTIONS_HOOK_INTERFACE_KEY = '{{~ camelCaseNameToInterfaceKey (containerComponentName container) }}_CONTAINER_EXTRA_ACTIONS_HOOK'; + export interface {{ containerComponentName container }}ContainerExtraActionsComponentProps { + {{# unless container.table }} + data: {{ classDataName container.dataElement 'Stored' }}, + storeDiff: (attributeName: keyof {{ classDataName container.dataElement '' }}, value: any) => void, + {{/ unless }} + editMode?: boolean, + isLoading?: boolean, + actions?: {{ containerComponentName container }}PageActions, + } + {{ else }} export const _ = 'placeholder'; {{/ unless }} diff --git a/judo-ui-react/src/main/resources/actor/src/containers/dialog.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/containers/dialog.tsx.hbs index a74c01c8..b0452a12 100644 --- a/judo-ui-react/src/main/resources/actor/src/containers/dialog.tsx.hbs +++ b/judo-ui-react/src/main/resources/actor/src/containers/dialog.tsx.hbs @@ -2,11 +2,9 @@ import { lazy, Suspense{{# if (containerHasCreateAction container) }}, useRef, useState, useCallback{{/ if }} } from 'react'; import type { Dispatch, SetStateAction } from 'react'; -{{# if (containerHasActionsWithCustomImplementation container) }} - import { OBJECTCLASS } from '@pandino/pandino-api'; - import { ComponentProxy, useTrackService } from '@pandino/react-hooks'; - import { CUSTOM_VISUAL_ELEMENT_INTERFACE_KEY } from '~/custom'; -{{/ if }} +import { OBJECTCLASS } from '@pandino/pandino-api'; +import { ComponentProxy, useTrackService } from '@pandino/react-hooks'; +import { CUSTOM_VISUAL_ELEMENT_INTERFACE_KEY } from '~/custom'; import Grid from '@mui/material/Grid'; import Button from '@mui/material/Button'; import DialogTitle from '@mui/material/DialogTitle'; @@ -52,6 +50,7 @@ import { useConfirmDialog } from '~/components/dialog'; {{/ each }} } from './customization'; {{/ if }} + import { {{ camelCaseNameToInterfaceKey (containerComponentName container) }}_CONTAINER_EXTRA_ACTIONS_HOOK_INTERFACE_KEY } from './customization'; {{/ unless }} {{# unless (containerIsEmptyDashboard container) }} @@ -290,6 +289,22 @@ export default function {{ containerComponentName container }}Dialog({{# unless )} {{/ each }} + + {{/ unless }} diff --git a/judo-ui-react/src/main/resources/actor/src/containers/page.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/containers/page.tsx.hbs index 162bc95c..88a95857 100644 --- a/judo-ui-react/src/main/resources/actor/src/containers/page.tsx.hbs +++ b/judo-ui-react/src/main/resources/actor/src/containers/page.tsx.hbs @@ -2,11 +2,9 @@ import { lazy, Suspense } from 'react'; import type { Dispatch, SetStateAction } from 'react'; -{{# if (containerHasActionsWithCustomImplementation container) }} - import { OBJECTCLASS } from '@pandino/pandino-api'; - import { ComponentProxy, useTrackService } from '@pandino/react-hooks'; - import { CUSTOM_VISUAL_ELEMENT_INTERFACE_KEY } from '~/custom'; -{{/ if }} +import { OBJECTCLASS } from '@pandino/pandino-api'; +import { ComponentProxy, useTrackService } from '@pandino/react-hooks'; +import { CUSTOM_VISUAL_ELEMENT_INTERFACE_KEY } from '~/custom'; import Box from '@mui/material/Box'; import Grid from '@mui/material/Grid'; import Button from '@mui/material/Button'; @@ -41,6 +39,7 @@ import { mainContainerPadding } from '~/theme'; {{/ each }} } from './customization'; {{/ if }} + import { {{ camelCaseNameToInterfaceKey (containerComponentName container) }}_CONTAINER_EXTRA_ACTIONS_HOOK_INTERFACE_KEY } from './customization'; {{/ unless }} {{# unless (containerIsEmptyDashboard container) }} @@ -118,7 +117,20 @@ export default function {{ containerComponentName container }}Page ({{# unless ( )} {{/ each }} -
{/* Placeholder */}
+ +
{/* Placeholder */}
+