mirror of
https://github.com/strapi/strapi.git
synced 2025-09-19 21:38:05 +00:00
Add documentation
Signed-off-by: soupette <cyril.lpz@gmail.com>
This commit is contained in:
parent
5c677e70f1
commit
dfe7780b90
@ -0,0 +1,377 @@
|
|||||||
|
# Technical documentation
|
||||||
|
|
||||||
|
Here we will cover how the `<Permissions />` component works.
|
||||||
|
This component is in charge of managing the permissions of the admin panel, it has 4 children one child for each tab.
|
||||||
|
The collection types & single types tabs uses the same component: `<ContentTypes>` similarly, the settings & plugins tab uses the `<Plugins>` component.
|
||||||
|
|
||||||
|
The UI uses the layout received from the back-end in order to build the UI.
|
||||||
|
|
||||||
|
## Layout shape
|
||||||
|
|
||||||
|
```js
|
||||||
|
const layout = {
|
||||||
|
conditions: [{ id: 'string', displayName: 'string', category: 'string'}], // Array of conditions that can be applied on a permission
|
||||||
|
sections: {
|
||||||
|
plugins: [
|
||||||
|
{ displayName: 'string', action: 'string', subCategory: 'string', plugin: 'string'}
|
||||||
|
],
|
||||||
|
settings: [], // Same shape as the plugins
|
||||||
|
collectionTypes: {
|
||||||
|
subjects: [ // Array of subjects
|
||||||
|
{
|
||||||
|
uid: 'string', // Collection type uid,
|
||||||
|
label: 'string', // Collection type label,
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
label: 'string', // Property label ex: Fields,
|
||||||
|
value: 'string', // Value of the property ex: fields,
|
||||||
|
children: [ // This corresponds to the fields that we will display
|
||||||
|
{
|
||||||
|
label: 'string',
|
||||||
|
value: 'string',
|
||||||
|
required: 'boolean', // This key is optional,
|
||||||
|
children: [], // This key is optional, if it exists it means that a field has nested fields
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
actions: [ // Array of actions
|
||||||
|
{
|
||||||
|
label: 'string', // Label of the action ex: Create,
|
||||||
|
actionId: 'string', // id of the action ex: content-manager.explorer.create
|
||||||
|
subjects: [<string>], // Array of subjects (collection type uid) on which the action can be applied
|
||||||
|
applyToProperties: [<string>] // Array of properties (ex: fields or locales) on which an action can be applied
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Concepts
|
||||||
|
|
||||||
|
- Checkbox: has only 1 `checked=true` or `checked=false`.
|
||||||
|
|
||||||
|
- Parent checkbox: A **parent checkbox** is a checkbox which the state value depends on the state of its children ones. It means that we cannot directly access its value from the `modifiedData` object.
|
||||||
|
Such checkbox can have multiple states:
|
||||||
|
- someChecked: `true` or `false`,
|
||||||
|
- checked: `true` of `false`
|
||||||
|
> Both states are coupled: if `someChecked=true` then `checked=false`.
|
||||||
|
> In terms of user's interaction when cliking a parent checkbox is will toggle the value of its children.
|
||||||
|
|
||||||
|
Ex: given the following data:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const modifiedData = {
|
||||||
|
address: {
|
||||||
|
create: {
|
||||||
|
fields: { f1: true, f2: true,}
|
||||||
|
locales: { en: false, fr: false}
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
enabled: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
From the `modifiedData` object we can identify 4 parent checkboxes:
|
||||||
|
|
||||||
|
1. `address` which value depends on the values of `address.create` & `address.create.update` here the state will be: `someChecked=true`
|
||||||
|
2. `create` which value depends on the values of `address.create.fields` & `address.create.locles` here the state will be: `someChecked=true, checked=true`
|
||||||
|
3. `fields` which value depends on the values of `address.create.fields.f1` & `address.create.fields.f2` here the state will be: `checked=true`
|
||||||
|
4. `locales` which value depends on the values of `address.create.locales.en` & `address.create.locales.fr` here the state will be `checked=false, someChecked=falses`
|
||||||
|
|
||||||
|
> `address.update` is not a parent checkbox since we can access its value directly `address.update.enabled`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Components architecture
|
||||||
|
|
||||||
|
### `<Permissions />` archictecture
|
||||||
|
|
||||||
|
```js
|
||||||
|
<PermissionsDataManagerProvider>
|
||||||
|
<Tabs>
|
||||||
|
<ContentTypes /> // Used with the `layout.sections.collectionTypes` data
|
||||||
|
<ContentTypes /> // Used with the `layout.sections.singleTypes` data
|
||||||
|
<PluginsAndSettings /> // Used with the `layout.sections.settings` data
|
||||||
|
<PluginsAndSettings /> // Used with the `layout.sections.plugins` data
|
||||||
|
</Tabs>
|
||||||
|
</PermissionsDataManagerProvider>
|
||||||
|
```
|
||||||
|
|
||||||
|
Below, is the architecture of the `<Permissions />` component:
|
||||||
|
|
||||||
|
```js
|
||||||
|
<ContentTypes>
|
||||||
|
<GlobalActions /> // Component in charge of displaying the global action checkboxes (parent
|
||||||
|
checkboxes), they are used to toggle all the checkboxes of the associated column
|
||||||
|
<ContentTypeCollapses>
|
||||||
|
{' '}
|
||||||
|
// Wrapper or the collapse and the matrix
|
||||||
|
<ContentTypeCollapse>
|
||||||
|
<Collapse /> // Main row of a content type => Displays the main actions of a content type
|
||||||
|
(parent checkboxes)
|
||||||
|
<CollapsePropertyMatrix>
|
||||||
|
{' '}
|
||||||
|
// Matrix of the actions subject property
|
||||||
|
<Header /> // Row that displays the actions labels inside a property (ex: create, read,
|
||||||
|
update)
|
||||||
|
<ActionRow>
|
||||||
|
{' '}
|
||||||
|
// Displays a subject's property values
|
||||||
|
<SubActionRow /> // Recursive component if a property has a children key, the component
|
||||||
|
will return itself
|
||||||
|
</ActionRow>
|
||||||
|
</CollapsePropetyMatrix>
|
||||||
|
</ContentTypeCollapse>
|
||||||
|
</ContentTypeCollapses>
|
||||||
|
</ContentTypes>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building the matrix layout
|
||||||
|
|
||||||
|
In order to build the layout, the components uses the `sections.collectionTypes.subjects` value received from the API.
|
||||||
|
|
||||||
|
#### Retrieving the actions to display in the `<GlobalActions />`
|
||||||
|
|
||||||
|
Pratical example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// layout.sections.collectionTypes.actions
|
||||||
|
const actions = [
|
||||||
|
{
|
||||||
|
label: 'Create',
|
||||||
|
actionId: 'content-manager.explorer.A1',
|
||||||
|
subjects: ['address', 'restaurant'],
|
||||||
|
applyToProperties: ['fields', 'locales',],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Read',
|
||||||
|
actionId: 'content-manager.explorer.read',
|
||||||
|
subjects: ['address'],
|
||||||
|
applyToProperties: ['fields'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Delete',
|
||||||
|
actionId: 'content-manager.explorer.delete',
|
||||||
|
subjects: ['restaurant'],
|
||||||
|
}
|
||||||
|
{
|
||||||
|
label: 'Publish',
|
||||||
|
actionId: 'content-manager.explorer.publish',
|
||||||
|
subjects: [],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Here we will display only 3 actions: `create`, `read` and `delete` since they are the only actions that can be applied to a `subject`.
|
||||||
|
|
||||||
|
#### Building the content type's matrix
|
||||||
|
|
||||||
|
We will use the actions defined above and the following data:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// layout.sections.collectionTypes.subjects
|
||||||
|
const subjects = [
|
||||||
|
{
|
||||||
|
uid: 'address',
|
||||||
|
label: 'Address'
|
||||||
|
properties: {
|
||||||
|
{
|
||||||
|
label: 'Fields',
|
||||||
|
value: 'fields',
|
||||||
|
children: [
|
||||||
|
{value: 'f1', label: 'F1'},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uid: 'restaurant',
|
||||||
|
label: 'Restaurant',
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
label: 'Fields',
|
||||||
|
value: 'fields',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: 'F1',
|
||||||
|
value: 'f1',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: 'F11',
|
||||||
|
value: 'f11',
|
||||||
|
children: [
|
||||||
|
{ label: 'F111', value: 'f111' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ label: 'F2', value: 'f2' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Locales',
|
||||||
|
value: 'locales',
|
||||||
|
children: [{ label: 'en', value: 'en'}, { label: 'fr', value: 'fr' }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
With this layout the ui will look like the following: (`[]` represents a checkbox )
|
||||||
|
|
||||||
|
| | [] Create | [] Read | [] Delete | `<GlobalActions />` | Parent Wrapper: `<ContentTypes />` |
|
||||||
|
| ------------- | ---------- | -------- | --------- | ------------------- | -------------------------------------------- |
|
||||||
|
| [] Address | [] | [] | | `<Collapse />` | Parent Wrapper: `<ContentTypeCollapse />` |
|
||||||
|
| **Fields** | **Create** | **Read** | | `<Header />` | Parent Wrapper: `<CollapsePropertyMatrix />` |
|
||||||
|
| [] F1 | [] | [] | | `<ActionRow />` | Parent Wrapper: `<CollapsePropertyMatrix />` |
|
||||||
|
| | | | | | |
|
||||||
|
| [] Restaurant | [] Create | [] Read | [] Delete | `<Collapse />` | Parent Wrapper: `<CollapsePropertyMatrix />` |
|
||||||
|
| **Fields** | **Create** | | | `<Header />` | Parent Wrapper: `<CollapsePropertyMatrix />` |
|
||||||
|
| [] F1 | [] | | | `<ActionRow />` | Parent Wrapper: `<CollapsePropertyMatrix />` |
|
||||||
|
| F1.F11 | [] | | | `<SubActionRow />` | Parent Wrapper: `<ActionRow />` |
|
||||||
|
| F1.F11.F111 | [] | | | `<SubActionRow />` | Parent Wrapper: `<SubActionRow />` |
|
||||||
|
| [ ] F2 | [] | | | `<ActionRow />` | Parent Wrapper: `<CollapsePropertyMatrix />` |
|
||||||
|
| **Locales** | **Create** | **Read** | | `<Header />` | Parent Wrapper: `<CollapsePropertyMatrix />` |
|
||||||
|
| [ ] EN | [] | [] | | `<ActionRow />` | Parent Wrapper: `<CollapsePropertyMatrix />` |
|
||||||
|
| [ ] FR | [] | [] | | `<ActionRow />` | Parent Wrapper: `<CollapsePropertyMatrix />` |
|
||||||
|
|
||||||
|
#### Shape of the `modifiedData.collectionTypes` object:
|
||||||
|
|
||||||
|
In order to easily know the state of a checkbox, we build the `modifiedData` using the `layout.sections.collectionTypes` to generate the following shape:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const conditions = [
|
||||||
|
{
|
||||||
|
id: 'admin::is-creator',
|
||||||
|
displayName: 'Is creator',
|
||||||
|
category: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'admin::has-same-role-as-creator',
|
||||||
|
displayName: 'Has same role as creator',
|
||||||
|
category: 'default',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const collectionTypesDefaultForm = createDefaultCTFormFromLayout(layout.sections.collectionTypes, action, conditions)
|
||||||
|
// createDefaultCTFormFromLayout returns an object with all the values set to false.
|
||||||
|
// Using the data from above it will return
|
||||||
|
|
||||||
|
console.log(collectionTypesDefaultForm)
|
||||||
|
|
||||||
|
{
|
||||||
|
address: {
|
||||||
|
'content-manager.explorer.create': {
|
||||||
|
fields: {
|
||||||
|
f1: false,
|
||||||
|
},
|
||||||
|
conditions: {
|
||||||
|
'admin::is-creator': false,
|
||||||
|
'admin::has-same-role-as-creator': false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'content-manager.explorer.read': {
|
||||||
|
fields: {
|
||||||
|
f1: false,
|
||||||
|
},
|
||||||
|
conditions: {
|
||||||
|
'admin::is-creator': false,
|
||||||
|
'admin::has-same-role-as-creator': false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
restaurant: {
|
||||||
|
'content-manager.explorer.create': {
|
||||||
|
fields: {
|
||||||
|
f1: {
|
||||||
|
f11: {
|
||||||
|
f111: false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
f2: false
|
||||||
|
},
|
||||||
|
locales: { en: false, fr: false},
|
||||||
|
conditions: {
|
||||||
|
'admin::is-creator': false,
|
||||||
|
'admin::has-same-role-as-creator': false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'content-manager.explorer.delete: {
|
||||||
|
enabled: false,
|
||||||
|
conditions: {
|
||||||
|
'admin::is-creator': false,
|
||||||
|
'admin::has-same-role-as-creator': false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Getting the state of a checkbox using the `modifiedData` object
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
- The `create` checkbox located in the `<GlobalActions />` component is a **parent checkbox** therefore, it's value depends on its children ones. Since, this checkbox is related the `content-manager.explorer.create` action we need to know the values of:
|
||||||
|
- `address['content-manager.explorer.create'].fields.f1`
|
||||||
|
- `restaurant['content-manager.explorer.create'].fields.f1.f11.f111`
|
||||||
|
- `restaurant['content-manager.explorer.create'].fields.f2`
|
||||||
|
- `restaurant['content-manager.explorer.create'].locales.en`
|
||||||
|
- `restaurant['content-manager.explorer.create'].locales.fr`
|
||||||
|
|
||||||
|
> The `conditions` key is not a property of an action so the value of `create` does not depend on it
|
||||||
|
|
||||||
|
A way to dynamically retrieve the state of the `create` checkbox, is to create the following object:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const objectToRetrieveTheStateOfTheCreateCheckbox = {
|
||||||
|
address: {
|
||||||
|
fields: { f1: false },
|
||||||
|
},
|
||||||
|
restaurant: {
|
||||||
|
fields: {
|
||||||
|
f1: { f11: { f111: false } },
|
||||||
|
f2: false,
|
||||||
|
},
|
||||||
|
locales: { en: false, fr: false },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Know we need to know, if all the properties are `false` or if some of them `true`, since we are only dealing with `Boolean` values we can create the following array using the object:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const arrayOfBooleanValues = [
|
||||||
|
false, // address.fields.f1
|
||||||
|
false, // restaurant.field.f1.f11.f111,
|
||||||
|
false, // restaurant.fields.f2,
|
||||||
|
false, // restaurant.locales.en
|
||||||
|
false, // restaurant.locales.fr
|
||||||
|
];
|
||||||
|
|
||||||
|
const checkboxCreateState = { someChecked: false, allChecked: false };
|
||||||
|
```
|
||||||
|
|
||||||
|
- The state of the checkbox located on the left of F1 field (`address.fields.f1`) depends on the values of `address.create.fields.f1` & `address.read.fields.f2`
|
||||||
|
|
||||||
|
```js
|
||||||
|
const objectToRetrieveTheStateOfTheCheckbox = {
|
||||||
|
create: {
|
||||||
|
f1: false,
|
||||||
|
},
|
||||||
|
read: {
|
||||||
|
f1: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const arrayOfBooleanVales = [false, false];
|
||||||
|
|
||||||
|
const checkboxState = { someChecked: false, allChecked: false };
|
||||||
|
```
|
Loading…
x
Reference in New Issue
Block a user