Boîte à outils

La boîte à outils est l’endroit où les utilisateurs obtiennent des blocs. Elles sont généralement affichées sur un côté de l'espace de travail. Parfois, elle comporte des catégories, et parfois non.

Cette page explique principalement comment spécifier la structure de votre boîte à outils (c'est-à-dire les catégories qu'elle contient et les blocs qu'elle contient). Pour savoir comment modifier l'interface utilisateur de votre boîte à outils, consultez l'atelier de programmation "Personnaliser une boîte à outils Blockly" et la conférence sur les API de boîte à outils 2021.

Formats

Blockly vous permet de spécifier la structure de votre boîte à outils à l'aide de différents formats. Le nouveau format recommandé utilise JSON, tandis que l'ancien format utilise XML.

Voici les différentes façons de spécifier la boîte à outils ci-dessus:

JSON

Depuis la version de septembre 2020, les boîtes à outils peuvent être définies à l'aide de JSON.

var toolbox = {
    "kind": "flyoutToolbox",
    "contents": [
      {
        "kind": "block",
        "type": "controls_if"
      },
      {
        "kind": "block",
        "type": "controls_whileUntil"
      }
    ]
  };
var workspace = Blockly.inject('blocklyDiv', {toolbox: toolbox});

XML

<xml id="toolbox" style="display: none">
  <block type="controls_if"></block>
  <block type="controls_whileUntil"></block>
</xml>
<script>
  var workspace = Blockly.inject('blocklyDiv',
      {toolbox: document.getElementById('toolbox')});
</script>

Chaîne XML

var toolbox = '<xml>' +
    '<block type="controls_if"></block>' +
    '<block type="controls_whileUntil"></block>' +
    '</xml>';
var workspace = Blockly.inject('blocklyDiv', {toolbox: toolbox});

Categories

Les blocs de la boîte à outils peuvent être organisés en catégories.

Voici comment vous pouvez définir la boîte à outils ci-dessus, qui comporte deux catégories ("Contrôle" et "Logique"), chacune contenant des blocs:

JSON

{
  "kind": "categoryToolbox",
  "contents": [
    {
      "kind": "category",
      "name": "Control",
      "contents": [
        {
          "kind": "block",
          "type": "controls_if"
        },
      ]
    },
    {
      "kind": "category",
      "name": "Logic",
      "contents": [
        {
          "kind": "block",
          "type": "logic_compare"
        },
        {
          "kind": "block",
          "type": "logic_operation"
        },
        {
          "kind": "block",
          "type": "logic_boolean"
        }
      ]
    }
  ]
}

XML

<xml id="toolbox" style="display: none">
  <category name="Control">
    <block type="controls_if"></block>
  <category name="Logic">
    <block type="logic_compare"></block>
    <block type="logic_operation"></block>
    <block type="logic_boolean"></block>
  </category>
</xml>

Catégories imbriquées

Les catégories peuvent être imbriquées dans d'autres catégories. Voici deux catégories de premier niveau ("Core" et "Custom"), la deuxième contient deux sous-catégories, chacune contenant des blocs:

Notez qu'il est possible qu'une catégorie contienne à la fois des sous-catégories et des blocs. Dans l'exemple ci-dessus, l'option "Personnalisé" comporte deux sous-catégories ("Déplacer" et "Tourner"), ainsi qu'un bloc qui lui est propre ("début").

JSON

{
  "kind": "categoryToolbox",
  "contents": [
    {
      "kind": "category",
      "name": "Core",
      "contents": [
        {
          "kind": "block",
          "type": "controls_if"
        },
        {
          "kind": "block",
          "type": "logic_compare"
        },
      ]
    },
    {
      "kind": "category",
      "name": "Custom",
      "contents": [
        {
          "kind": "block",
          "type": "start"
        },
        {
          "kind": "category",
          "name": "Move",
          "contents": [
            {
              "kind": "block",
              "type": "move_forward"
            }
          ]
        },
        {
          "kind": "category",
          "name": "Turn",
          "contents": [
            {
              "kind": "block",
              "type": "turn_left"
            }
          ]
        }
      ]
    }
  ]
}

XML

<xml id="toolbox" style="display: none">
  <category name="Core">
    <block type="controls_if"></block>
    <block type="logic_compare"></block>
  </category>
  <category name="Custom">
    <block type="start"></block>
    <category name="Move">
      <block type="move_forward"></block>
    </category>
    <category name="Turn">
      <block type="turn_left"></block>
    </category>
  </category>
</xml>

Catégories dynamiques

Les catégories dynamiques sont des catégories qui sont remplies dynamiquement en fonction d'une fonction à chaque ouverture.

Blockly prend en charge ce cas de figure en vous permettant d'associer une catégorie à une fonction via une clé de chaîne enregistrée. La fonction doit renvoyer une définition du contenu d'une catégorie (blocs, boutons, libellés, etc.). Le contenu peut être spécifié au format JSON ou XML, bien que le format JSON soit recommandé.

Notez également que la fonction fournit l'espace de travail cible en tant que paramètre. Par conséquent, les blocs de votre catégorie dynamique peuvent être basés sur l'état de l'espace de travail.

JSON

Depuis la version de septembre 2021, vous pouvez spécifier l'état des blocs sans utiliser 'blockxml'.

// Returns an array of objects.
var coloursFlyoutCallback = function(workspace) {
  // Returns an array of hex colours, e.g. ['#4286f4', '#ef0447']
  var colourList = getPalette();
  var blockList = [];
  for (var i = 0; i < colourList.length; i++) {
    blockList.push({
      'kind': 'block',
      'type': 'colour_picker',
      'fields': {
        'COLOUR': colourList[i]
      }
    });
  }
  return blockList;
};

// Associates the function with the string 'COLOUR_PALETTE'
myWorkspace.registerToolboxCategoryCallback(
    'COLOUR_PALETTE', coloursFlyoutCallback);

Ancien fichier JSON

Avant la version de septembre 2021, vous deviez utiliser la propriété 'blockxml' pour spécifier l'état des blocs.

// Returns an array of objects.
var coloursFlyoutCallback = function(workspace) {
  // Returns an array of hex colours, e.g. ['#4286f4', '#ef0447']
  var colourList = getPalette();
  var blockList = [];
  for (var i = 0; i < colourList.length; i++) {
    blockList.push({
      'kind': 'block',
      'type': 'colour_picker', // Type is optional if you provide blockxml
      'blockxml': '<block type="colour_picker">' +
          '<field name="COLOUR">' + colourList[i] + '</field>' +
          '</block>'
    });
  }
  return blockList;
};

// Associates the function with the string 'COLOUR_PALETTE'
myWorkspace.registerToolboxCategoryCallback(
    'COLOUR_PALETTE', coloursFlyoutCallback);

XML

// Returns an arry of XML nodes.
var coloursFlyoutCallback = function(workspace) {
  // Returns an array of hex colours, e.g. ['#4286f4', '#ef0447']
  var colourList = getPalette();
  var blockList = [];
  for (var i = 0; i < colourList.length; i++) {
    var block = document.createElement('block');
    block.setAttribute('type', 'colour_picker');
    var field = document.createElement('field');
    field.setAttribute('name', 'COLOUR');
    field.innerText = colourList[i];
    block.appendChild(field);
    blockList.push(block);
  }
  return blockList;
};

// Associates the function with the string 'COLOUR_PALETTE'
myWorkspace.registerToolboxCategoryCallback(
    'COLOUR_PALETTE', coloursFlyoutCallback);

Une fois que les fonctions de catégorie dynamiques sont associées à une clé de chaîne (ou enregistrée), vous pouvez attribuer cette clé de chaîne à la propriété custom de votre définition de catégorie pour la rendre dynamique.

JSON

{
  "kind": "category",
  "name": "Colours",
  "custom": "COLOUR_PALETTE"
}

XML

<category name="Colours" custom="COLOUR_PALETTE"></category>

Catégories dynamiques intégrées

Blockly propose trois catégories dynamiques intégrées.

  • 'VARIABLE' crée une catégorie pour les variables non typées.
  • 'VARIABLE_DYNAMIC' crée une catégorie pour les variables typées. Il contient des boutons pour créer des chaînes, des chiffres et des couleurs.
  • 'PROCEDURE' crée une catégorie pour les blocs de fonction.

JSON

{
  "kind": "category",
  "name": "Variables",
  "custom": "VARIABLE"
},
{
  "kind": "category",
  "name": "Variables",
  "custom": "VARIABLE_DYNAMIC"
},
{
  "kind": "category",
  "name": "Functions",
  "custom": "PROCEDURE"
}

XML

<category name="Variables" custom="VARIABLE"></category>
<category name="Variables" custom="VARIABLE_DYNAMIC"></category>
<category name="Functions" custom="PROCEDURE"></category>

Remarque: Le mot "procédure" est utilisé dans tout le codebase Blockly, mais le mot "fonction" s'est avéré plus compréhensible par les étudiants. Veuillez nous excuser pour cette différence.

Désactivation…

Une catégorie désactivée ne permet pas à un utilisateur d'ouvrir la catégorie et elle est ignorée lors de la navigation au clavier.

var category = toolbox.getToolboxItems()[0];
category.setDisabled('true');

Lorsqu'une catégorie est désactivée, une propriété 'disabled' est ajoutée à l'élément DOM, ce qui vous permet de contrôler l'apparence de cette catégorie désactivée.

.blocklyToolboxCategory[disabled="true"] {
  opacity: .5;
}

Masquage…

Les catégories masquées ne seront pas incluses dans la boîte à outils. Les catégories masquées peuvent être affichées ultérieurement via JavaScript.

JSON

{
  "kind": "category",
  "name": "...",
  "hidden": "true"
}

XML

<category name="..." hidden="true"></category>

JavaScript

var category = toolbox.getToolboxItems()[0];
category.hide();
// etc...
category.show();

Expand

Cela ne s'applique qu'aux catégories qui contiennent d'autres catégories imbriquées.

Une catégorie développée affiche ses sous-catégories. Par défaut, les catégories imbriquées sont réduites et il faut cliquer dessus pour les développer.

JSON

{
  "kind": "category",
  "name": "...",
  "expanded": "true"
}

XML

<category name="..." expanded="true"></sep>

Attribuer un style

Blockly fournit une interface utilisateur de catégories par défaut et, avec elle, des options de base de style. Si vous souhaitez obtenir des informations sur la stylisation ou la configuration plus avancées de l'interface utilisateur, consultez l'atelier de programmation sur la personnalisation d'une boîte à outils Blockly et la conférence sur les API de boîte à outils 2021.

Thèmes

Les thèmes vous permettent de spécifier toutes les couleurs de votre espace de travail en même temps, y compris celles de nos catégories.

Pour les utiliser, vous devez associer votre catégorie à un style de catégorie spécifique:

JSON

{
  "kind": "category",
  "name": "Logic",
  "categorystyle": "logic_category"
}

XML

<category name="Logic" categorystyle="logic_category"></category>

Coloris

Vous pouvez également indiquer la couleur directement, mais cela n'est pas recommandé. La couleur est un nombre concaténé (0-360) qui indique la teinte. Notez l'orthographe britannique.

JSON

{
  "contents": [
    {
      "kind": "category",
      "name": "Logic",
      "colour": "210"
    },
    {
      "kind": "category",
      "name": "Loops",
      "colour": "120"
    }
  ]
}

XML

<xml id="toolbox" style="display: none">
  <category name="Logic" colour="210">...</category>
  <category name="Loops" colour="120">...</category>
  <category name="Math" colour="230">...</category>
  <category name="Colour" colour="20">...</category>
  <category name="Variables" colour="330" custom="VARIABLE"></category>
  <category name="Functions" colour="290" custom="PROCEDURE"></category>
</xml>

Notez que nous acceptons également l'utilisation de références de couleurs localisables.

Catégorie CSS

Si vous souhaitez une personnalisation plus poussée, Blockly vous permet également de spécifier des classes CSS pour différents éléments de l'interface utilisateur par défaut. Vous pouvez ensuite utiliser du CSS pour leur appliquer un style.

Des classes CSS peuvent être appliquées aux types d'éléments suivants:

  • container - Classe du div parent de la catégorie. blocklyToolboxCategory par défaut.
  • "row" : classe du div contenant le libellé et l'icône de la catégorie. blocklyTreeRow par défaut.
  • icon : classe de l'icône de catégorie. Valeur par défaut : blocklyTreeIcon.
  • label - Classe du libellé de catégorie. Valeur par défaut : blocklyTreeLabel.
  • selected - Classe ajoutée à la catégorie lorsqu'elle est sélectionnée. blocklyTreeSelected par défaut.
  • openicon : classe ajoutée à une icône lorsqu'une catégorie est ouverte et contient des catégories imbriquées. Valeur par défaut : blocklyTreeIconOpen.
  • ferméicon : classe ajoutée à une icône lorsque la catégorie contient des catégories imbriquées et qu'elle est fermée. Valeur par défaut : blocklyTreeIconClosed.

Et voici comment spécifier les classes en utilisant l'un ou l'autre des formats:

JSON

Définissez la classe CSS d'un type d'élément particulier à l'aide de la propriété cssConfig.

{
  "kind": "category",
  "name": "...",
  "cssConfig": {
    "container": "yourClassName"
  }
}

XML

Définissez la classe CSS d'un type d'élément particulier en y ajoutant le préfixe "css-".

<category name="..." css-container="yourClassName"></category>

Accès

Il existe deux façons d'accéder par programmation à une catégorie. Vous pouvez y accéder par index (où 0 est la catégorie la plus élevée):

var category = toolbox.getToolboxItems()[0];

Ou par ID:

var category = toolbox.getToolboxItemById('categoryId');

Où l'ID est spécifié dans la définition de la boîte à outils:

JSON

{
  "kind": "category",
  "name": "...",
  "toolboxitemid": "categoryId"
}

XML

<category name="..." toolboxitemid="categoryId"></category>

Blocs prédéfinis

La définition de la boîte à outils peut contenir des blocs dont les champs sont définis sur une valeur par défaut ou qui sont déjà connectés.

Voici quatre blocs:

  1. Un bloc logic_boolean simple sans valeurs prédéfinies:
  2. Un bloc math_number qui a été modifié pour afficher le nombre 42 au lieu de la valeur par défaut de 0:
  3. Un bloc controls_for auquel trois blocs math_number sont connectés:
  4. Un bloc math_arithmetic auquel deux blocs d'ombre math_number sont connectés:

Voici une définition de la boîte à outils contenant ces quatre blocs:

JSON

À partir de la version de septembre 2021, vous pouvez spécifier l'état des blocs sans 'blockxml'.

{
  "kind": "flyoutToolbox",
  "contents": [
    {
      "kind": "block",
      "type": "logic_boolean"
    },
    {
      "kind": "block",
      "type": "math_number",
      "fields": {
        "NUM": 42
      }
    },
    {
      "kind": "block",
      "type": "controls_for",
      "inputs": {
        "FROM": {
          "block": {
            "type": "math_number",
            "fields": {
              "NUM": 1
            }
          }
        },
        "TO": {
          "block": {
            "type": "math_number",
            "fields": {
              "NUM": 10
            }
          }
        },
        "BY": {
          "block": {
            "type": "math_number",
            "fields": {
              "NUM": 1
            }
          }
        },
      }
    },
    {
      "kind": "block",
      "type": "math_arithmetic",
      "fields": {
        "OP": "ADD"
      },
      "inputs": {
        "A": {
          "shadow": {
            "type": "math_number",
            "fields": {
              "NUM": 1
            }
          }
        },
        "B": {
          "shadow": {
            "type": "math_number",
            "fields": {
              "NUM": 1
            }
          }
        }
      }
    },
  ]
}

Ancien fichier JSON

Avant la version de septembre 2021, vous deviez utiliser la propriété 'blockxml' pour spécifier l'état des blocs.

{
  "kind": "flyoutToolbox",
  "contents": [
    {
      "kind": "block",
      "type": "logic_boolean"
    },
    {
      "kind": "block",
      "blockxml":
          '<block type="math_number">' +
          '<field name="NUM">42</field>' +
          '</block>'
    },
    {
      "kind": "block",
      "blockxml":
          '<block type="controls_for">' +
            '<value name="FROM">' +
              '<block type="math_number">' +
                '<field name="NUM">1</field>' +
              '</block>' +
            '</value>' +
            '<value name="TO">' +
              '<block type="math_number">' +
                '<field name="NUM">10</field>' +
              '</block>' +
            '</value>' +
            '<value name="BY">' +
              '<block type="math_number">' +
                '<field name="NUM">1</field>' +
              '</block>' +
            '</value>' +
          '</block>'
    },
    {
      "kind": "block",
      "blockxml":
          '<block type="math_arithmetic">' +
            '<field name="OP">ADD</field>' +
            '<value name="A">' +
              '<shadow type="math_number">' +
                '<field name="NUM">1</field>' +
              '</shadow>' +
            '</value>' +
            '<value name="B">' +
              '<shadow type="math_number">' +
                '<field name="NUM">1</field>' +
              '</shadow>' +
            '</value>' +
          '</block>'
    },
  ]
}

XML

<xml id="toolbox" style="display: none">
  <block type="logic_boolean"></block>

  <block type="math_number">
    <field name="NUM">42</field>
  </block>

  <block type="controls_for">
    <value name="FROM">
      <block type="math_number">
        <field name="NUM">1</field>
      </block>
    </value>
    <value name="TO">
      <block type="math_number">
        <field name="NUM">10</field>
      </block>
    </value>
    <value name="BY">
      <block type="math_number">
        <field name="NUM">1</field>
      </block>
    </value>
  </block>

  <block type="math_arithmetic">
    <field name="OP">ADD</field>
    <value name="A">
      <shadow type="math_number">
        <field name="NUM">1</field>
      </shadow>
    </value>
    <value name="B">
      <shadow type="math_number">
        <field name="NUM">1</field>
      </shadow>
    </value>
  </block>
</xml>

Écrire ces définitions à la main peut être... un peu pénible. À la place, vous pouvez charger vos blocs dans un espace de travail, puis exécuter le code suivant pour obtenir les définitions. Ces appels fonctionnent, car la boîte à outils utilise le même format pour les blocs que le système de sérialisation.

JSON

console.log(Blockly.serialization.workspaces.save(Blockly.getMainWorkspace()));

XML

console.log(Blockly.Xml.workspaceToDom(Blockly.getMainWorkspace()));

Vous pouvez également supprimer les propriétés x, y et id, car elles sont ignorées par la boîte à outils.

Blocs d'ombres

Les ombres sont des blocs d'espace réservé qui remplissent plusieurs fonctions:

  • Ils indiquent les valeurs par défaut de leur bloc parent.
  • Elles permettent aux utilisateurs de saisir des valeurs directement sans avoir à extraire un bloc numérique ou de chaîne.
  • Contrairement à un bloc standard, ils sont remplacés si l'utilisateur y pose un bloc dessus.
  • Elles informent l'utilisateur du type de valeur attendue.

Blocs désactivés

Vous ne pouvez pas faire glisser des blocs désactivés de la boîte à outils. Les blocages peuvent être désactivés individuellement à l'aide de la propriété facultative disabled.

JSON

{
  "kind": "flyoutToolbox",
  "contents": [
    {
      "kind": "block",
      "type":"math_number"
    },
    {
      "kind": "block",
      "type": "math_arithmetic"
    },
    {
      "kind": "block",
      "type": "math_single",
      "disabled": "true"
    }
  ]
}

XML

<xml id="toolbox" style="display: none">
  <block type="math_number"></block>
  <block type="math_arithmetic"></block>
  <block type="math_single" disabled="true"></block>
</xml>

Vous pouvez également activer ou désactiver un blocage par programmation à l'aide de setEnabled.

Champs de variable

Il peut être nécessaire de spécifier les champs de variables différemment selon qu'ils se trouvent dans une boîte à outils ou lorsqu'ils sont simplement sérialisés.

En particulier, lorsque les champs de variable sont normalement sérialisés en JSON, ils ne contiennent que l'ID de la variable qu'ils représentent, car le nom et le type de la variable sont sérialisés séparément. Cependant, les boîtes à outils ne contiennent pas ces informations, elles doivent donc être incluses directement dans le champ de variable.

{
  "kind": "flyoutToolbox",
  "content": [
    {
      "type": "controls_for",
      "fields": {
        "VAR": {
          "name": "index",
          "type": "Number"
        }
      }
    }
  ]
}

Séparateurs

L'ajout d'un séparateur entre deux catégories crée une ligne et un espace supplémentaire entre les deux catégories.

Vous pouvez modifier la classe du séparateur dans la définition de votre boîte à outils JSON ou XML.

JSON

{
  "kind": "sep",
  "cssConfig": {
    "container": "yourClassName"
  }
}

XML

<sep css-container="yourClassName"></sep>

L'ajout d'un séparateur entre deux blocs crée un écart entre les blocs. Par défaut, chaque bloc est séparé de son voisin inférieur par 24 pixels. Cette séparation peut être modifiée à l'aide de l'attribut "gap", qui remplace l'écart par défaut.

Cela vous permet de créer des groupes de blocs logiques dans la boîte à outils.

JSON

{
  "kind": "flyoutToolbox",
  "contents": [
    {
      "kind": "block",
      "type":"math_number"
    },
    {
      "kind": "sep",
      "gap": "32"
    },
    {
      "kind": "block",
      "blockxml": "<block type='math_arithmetic'><field name='OP'>ADD</field></block>"
    },
    {
      "kind": "sep",
      "gap": "8"
    },
    {
      "kind": "block",
      "blockxml": "<block type='math_arithmetic'><field name='OP'>MINUS</field></block>"
    }
  ]
}

XML

<xml id="toolbox" style="display: none">
  <block type="math_number"></block>
  <sep gap="32"></sep>
  <block type="math_arithmetic">
    <field name="OP">ADD</field>
  </block>
  <sep gap="8"></sep>
  <block type="math_arithmetic">
    <field name="OP">MINUS</field>
  </block>
</xml>

Boutons et libellés

Vous pouvez placer un bouton ou un libellé partout où vous pouvez placer un bloc dans la boîte à outils.

JSON

{
  "kind": "flyoutToolbox",
  "contents": [
    {
      "kind": "block",
      "type":"logic_operation"
    },
    {
      "kind": "label",
      "text": "A label",
      "web-class": "myLabelStyle"
    },
    {
      "kind": "label",
      "text": "Another label"
    },
    {
      "kind": "block",
      "type": "logic_negate"
    },
    {
      "kind": "button",
      "text": "A button",
      "callbackKey": "myFirstButtonPressed"
    },
    {
      "kind": "block",
      "type": "logic_boolean"
    }
  ]
}

XML

<xml id="toolbox" style="display: none">
  <block type="logic_operation"></block>
  <label text="A label" web-class="myLabelStyle"></label>
  <label text="Another label"></label>
  <block type="logic_negate"></block>
  <button text="A button" callbackKey="myFirstButtonPressed"></button>
  <block type="logic_boolean"></block>
</xml>
    <style>
    .myLabelStyle>.blocklyFlyoutLabelText {
      font-style: italic;
      fill: green;
    }
    </style>

Vous pouvez spécifier un nom de classe CSS à appliquer à votre bouton ou à votre libellé. Dans l'exemple ci-dessus, le premier libellé utilise un style personnalisé, tandis que le second utilise le style par défaut.

Les boutons doivent avoir des fonctions de rappel, contrairement aux libellés. Pour définir le rappel pour un clic sur un bouton, utilisez

yourWorkspace.registerButtonCallback(yourCallbackKey, yourFunction).

Votre fonction doit accepter le bouton sur lequel l'utilisateur a cliqué en tant qu'argument. Le bouton "Créer une variable..." dans la catégorie de la variable est un bon exemple de bouton avec un rappel.

Changer la boîte à outils

L'application peut modifier à tout moment les blocs disponibles dans la boîte à outils avec un seul appel de fonction:

workspace.updateToolbox(newTree);

Comme c'était le cas lors de la configuration initiale, newTree peut être une arborescence de nœuds, une représentation sous forme de chaîne ou un objet JSON. La seule restriction est que le mode ne peut pas être modifié. En d'autres termes, s'il y avait des catégories dans la boîte à outils définie initialement, la nouvelle boîte à outils doit également comporter des catégories (bien que les catégories puissent changer). De même, si la boîte à outils définie initialement ne comportait aucune catégorie, il est possible que la nouvelle boîte à outils n'en comporte aucune.

Le contenu d'une seule catégorie peut être mis à jour comme suit:

var category = workspace.getToolbox().getToolboxItems()[0];
category.updateFlyoutContents(flyoutContents);

Où flyoutContents peut être une liste de blocs définie à l'aide de JSON, d'une arborescence de nœuds ou d'une représentation sous forme de chaîne.

Sachez qu'à ce stade, la mise à jour de la boîte à outils entraîne quelques réinitialisations mineures de l'interface utilisateur:

  • Dans une boîte à outils sans catégories, les valeurs par défaut des champs modifiés par l'utilisateur (comme une liste déroulante) seront rétablies.

Voici une démonstration en direct d'une arborescence avec des catégories et des groupes de blocs.