Skip to content

Commit

Permalink
feat: add module draggable tools component (#732)
Browse files Browse the repository at this point in the history
* RM#82774
  • Loading branch information
lme-axelor committed Oct 3, 2024
1 parent 9ba063e commit da28374
Show file tree
Hide file tree
Showing 14 changed files with 516 additions and 3 deletions.
5 changes: 5 additions & 0 deletions changelogs/unreleased/82774.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"title": "Module: add new system to manage quick actions with a global toolbox",
"type": "feat",
"packages": "core"
}
2 changes: 2 additions & 0 deletions docs/doc/en/Application/Creation_d_un_module.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface Module {
};
requiredConfig?: string[];
moduleRegister?: Function;
globalTools?: Tool[];
}
```

Expand All @@ -68,6 +69,7 @@ A module therefore has :
- a configuration of templates for API calls (_models_).
- a list of web application names to retrieve the associated configuration (_requiredConfig_), such as 'AppBase' or 'AppMobileSettings'. Each configuration will then be retrieved using the application's router. The associated routes must therefore be specified to the router. New routes can be set in the application configuration file via the _additionalRoutes_ attribute.
- a function for dynamically registering modules (_moduleRegister_). This function will be executed once when the user logs in, to enable the creation of menus and screens from ERP data such as dashboards or customized web views.
- a list of tools to display globally on the application (_globalTools_).

# Dynamic module creation

Expand Down
100 changes: 100 additions & 0 deletions docs/doc/en/Outil/toolManagenent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
id: toolManagement
sidebar_position: 10
sidebar_class_name: icon tools
---

# Quick actions management

It is sometimes necessary to add quick actions on a number of screens, but it is very costly to overload each screen to add the various tools. To simplify these additions, the application's base contains a toolbox that each module can enhance with quick actions. These actions can then be displayed on all screens according to the given configuration.

When exported, each module can enter a list of tools in its _globalTools_ attribute:

```tsx
interface ToolData {
dispatch: Dispatch<any>;
storeState: any;
screenContext: any;
}

interface ActionToolData extends ToolData {
navigation: any;
}

export interface Tool {
key: string;
order?: number;
title?: string;
iconName: string;
color?: string;
hideIf?: (data: ToolData) => boolean;
disabledIf?: (data: ToolData) => boolean;
onPress: (data: ActionToolData) => void;
}

export interface Module {
name: string;
...
globalTools?: Tool[];
}
```

A tool is defined with the following properties:

- _key_: action identifier to enable overloading between modules.
- _order_: action order in the list. The action with the lowest order will appear at the top of the toolbox.
- _title_: translation key to be used for the title displayed in the toolbox next to the action.
- _iconName_ : name of the [Bootstrap](https://icons.getbootstrap.com/) icon to be displayed on the button.
- _color_ : color key to be displayed. The default color is `'primaryColor'`.
- _hideIf_ : function used to define the tool's display condition. This function takes as arguments the state of the application's store and the current screen context.
- _disabledIf_ : function for defining the tool's activation condition. This function takes as arguments the state of the application's store and the current screen context.
- _onPress_: function for defining tool behavior. This function takes as arguments the state of the store, the screen context and the dispatch & navigation tools for making an API call or navigating to a specific screen.

Here's an example of how to define a tool in the Sales module to add a product to the user's cart:

```tsx
export const SaleModule: Module = {
name: 'app-sale',
...
globalTools: [
{
key: 'sale_activeCart_addProduct',
iconName: 'cart-plus-fill',
hideIf: ({screenContext, storeState}) => {
return (
!storeState.appConfig?.sale?.isCartManagementEnabled ||
screenContext?.productId == null
);
},
onPress: ({dispatch, screenContext, storeState}) => {
dispatch(
addProductToUserCart({
productId: screenContext.productId,
userId: storeState.auth?.userId,
}),
);
},
},
],
};
```

To define the context of a screen, simply use the `useContextRegister` hook and pass the associated data as arguments:

```tsx
import React from 'react';
import {Screen} from '@axelor/aos-mobile-ui';
import {useContextRegister} from '@axelor/aos-mobile-core';

const ProductDetailsScreen = ({route}) => {
const {productId} = route.params.productId;

useContextRegister({productId});

...

return (...);
};

export default ProductDetailsScreen;
```
2 changes: 2 additions & 0 deletions docs/doc/fr/Application/Creation_d_un_module.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface Module {
};
requiredConfig?: string[];
moduleRegister?: Function;
globalTools?: Tool[];
}
```

Expand All @@ -68,6 +69,7 @@ Un module possède donc :
- une configuration de modèles pour les appels API (_models_).
- une liste de noms d'application web pour récupérer la configuration associée (_requiredConfig_), comme par exemple 'AppBase" ou 'AppMobileSettings'. Chaque configuration sera ensuite récupérée avec le router de l'application. Il faut donc que les routes associées soient renseignées auprès du router. Il est possible de renseigner des nouvelles routes dans le fichier de configuration de l'application à travers l'attribut _additionalRoutes_.
- une fonction pour enregistrer des modules dynamiquement (_moduleRegister_). Cette fonction sera éxécuté une seule fois à la connexion de l'utilisateur pour permettre la création de menus et écrans à partir de données de l'ERP comme les tableaux de bord ou les vues web personnalisées.
- une liste d'outils à afficher globalement sur l'application (_globalTools_).

# Création dynamique de modules

Expand Down
100 changes: 100 additions & 0 deletions docs/doc/fr/Outil/toolManagenent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
id: toolManagement
sidebar_position: 10
sidebar_class_name: icon tools
---

# Gestion des actions rapides

Il est parfois nécessaire d'ajouter des actions rapides sur un certain nombre d'écrans mais cela est très coûteux de surcharger chaque écran pour ajouter les différents outils. Pour simplifier ces ajouts, la base de l'application contient donc une boîte à outils que chaque module peut venir agrémenter d'actions rapides. Ces actions pourront alors être affichées sur tous les écrans en fonction de la configuration donnée.

Chaque module lors de son export peut venir renseigner une liste d'outils dans son attribut _globalTools_ :

```tsx
interface ToolData {
dispatch: Dispatch<any>;
storeState: any;
screenContext: any;
}

interface ActionToolData extends ToolData {
navigation: any;
}

export interface Tool {
key: string;
order?: number;
title?: string;
iconName: string;
color?: string;
hideIf?: (data: ToolData) => boolean;
disabledIf?: (data: ToolData) => boolean;
onPress: (data: ActionToolData) => void;
}

export interface Module {
name: string;
...
globalTools?: Tool[];
}
```

Un outil est défini avec les propriétés suivantes :

- _key_ : identifiant de l'action pour permettre la surcharge entre les modules.
- _order_ : ordre de l'action dans la liste. L'action avec l'ordre le plus bas apparaîtra au plus haut dans la boîte à outils.
- _title_ : clé de traduction à utiliser pour le titre affiché dans la boîte à outils à côté de l'action.
- _iconName_ : nom de l'icon [Bootstrap](https://icons.getbootstrap.com/) à afficher sur le bouton.
- _color_ : clé de la couleur à afficher. La couleur par défaut est `'primaryColor'`.
- _hideIf_ : fonction permettant de définir la condition d'affichage de l'outil. Cette fonction prend en arguments l'état du store de l'application et le contexte de l'écran actuel.
- _disabledIf_ : fonction permettant de définir la condition d'activation de l'outil. Cette fonction prend en arguments l'état du store de l'application et le contexte de l'écran actuel.
- _onPress_ : fonction permettant de définir le comportement de l'outil. Cette fonction prend en arguments l'état du store, le contexte de l'écran et les outils dispatch & navigation pour réaliser un appel API ou une navigation vers un écran spécifique.

Voici un exemple de définition d'un outil dans le module Ventes pour ajouter un produit dans le panier de l'utilisateur :

```tsx
export const SaleModule: Module = {
name: 'app-sale',
...
globalTools: [
{
key: 'sale_activeCart_addProduct',
iconName: 'cart-plus-fill',
hideIf: ({screenContext, storeState}) => {
return (
!storeState.appConfig?.sale?.isCartManagementEnabled ||
screenContext?.productId == null
);
},
onPress: ({dispatch, screenContext, storeState}) => {
dispatch(
addProductToUserCart({
productId: screenContext.productId,
userId: storeState.auth?.userId,
}),
);
},
},
],
};
```

Pour définir le contexte d'un écran, il suffit d'utiliser le hook `useContextRegister` et de transmettre les données associées en arguments :

```tsx
import React from 'react';
import {Screen} from '@axelor/aos-mobile-ui';
import {useContextRegister} from '@axelor/aos-mobile-core';

const ProductDetailsScreen = ({route}) => {
const {productId} = route.params.productId;

useContextRegister({productId});

...

return (...);
};

export default ProductDetailsScreen;
```
9 changes: 8 additions & 1 deletion packages/core/src/app/ContextedApplication.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ import RootNavigator from './RootNavigator';
import Translator from '../i18n/component/Translator';
import {getActiveUserInfo} from '../api/login-api';
import ErrorScreen from '../screens/ErrorScreen';
import {Camera, HeaderBandList, Scanner, Toast} from '../components';
import {
Camera,
GlobalToolBox,
HeaderBandList,
Scanner,
Toast,
} from '../components';
import {RouterProvider} from '../config';
import {proxy, releaseConfig, versionCheckConfig} from './types';
import {useDispatch} from '../redux/hooks';
Expand Down Expand Up @@ -89,6 +95,7 @@ const ContextedApplication = ({
<HeaderBandList />
<LoadingIndicator />
<BlockInteractionMessage />
<GlobalToolBox />
<RootNavigator
modules={modules}
mainMenu={mainMenu}
Expand Down
23 changes: 23 additions & 0 deletions packages/core/src/app/modules/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import {Dispatch} from 'react';
import {Reducer} from '@reduxjs/toolkit';
import {Schema} from 'yup';
import {FormConfigs} from '../../forms/types';
Expand Down Expand Up @@ -89,6 +90,27 @@ export interface Models {
typeObjects?: ModuleSelections;
}

interface ToolData {
dispatch: Dispatch<any>;
storeState: any;
screenContext: any;
}

interface ActionToolData extends ToolData {
navigation: any;
}

export interface Tool {
key: string;
order?: number;
title?: string;
iconName: string;
color?: string;
hideIf?: (data: ToolData) => boolean;
disabledIf?: (data: ToolData) => boolean;
onPress: (data: ActionToolData) => void;
}

type version = `${number}.${number}.${number}` | '-';

export interface Compatibility {
Expand Down Expand Up @@ -128,4 +150,5 @@ export interface Module {
requiredConfig?: string[];
/** Function which will be executed once after user login to create modules/menus based on data */
moduleRegister?: Function;
globalTools?: Tool[];
}
Loading

0 comments on commit da28374

Please sign in to comment.