# V1 — Médiathèque (refonte de l'onglet Ressources) > **Statut** : **implémenté le 2026-09-02.** Backend + front livrés, `dotnet test` et > `flutter build web` verts. Reste à valider à l'œil dans le navigateur (checklist §6). > **Autonome.** Aucun appel à un modèle, aucune clé API nouvelle, aucun coût variable. > Ne dépend ni du Studio ni de la migration Postgres. **Peut se livrer seul.** > > Le Studio (V2) se greffe dessus plus tard sans le réécrire — voir > [v2/studio-plan.md §3.0](v2/studio-plan.md). Ce document est **exécutable tel quel** : > il ne suppose aucune connaissance de la conversation de conception. --- ## 0. Pourquoi ce lot existe Il est né en cherchant où loger les images générées par le futur Studio. La réponse a été « nulle part de nouveau, l'écran existant suffit » — **à condition de le refondre**, parce qu'à 312 ressources il est illisible. Les défauts sont ceux d'aujourd'hui, mesurés dans le code. Le lot les corrige, plus deux bugs. | Défaut | Où | |---|---| | Tuiles carrées de 160 px, affichées d'un bloc sans ordre ni groupement | `resource_body_grid.dart:78` — `SliverGridDelegateWithMaxCrossAxisExtent(maxCrossAxisExtent: 160, childAspectRatio: 1.0)` | | Chips de type en `Wrap` — **un seul axe de filtre** | `resource_body_grid.dart:133` | | Ni tri, ni groupement, ni sélection multiple, ni action en lot | absent | | Popup 520 px, `borderRadius: 20`, aperçu encadré d'une bordure grise de **3 px à `borderRadius: 30`** — hors de l'échelle de l'app (5/8/10) | `show_resource_popup.dart:20, 50-55` | | Champ Label **au-dessus** de l'aperçu : on nomme avant d'avoir vu | `show_resource_popup.dart:36` | | Quatre `RoundedButton` de même poids dans un `Wrap` — destructif collé au primaire, ça se stacke | `show_resource_popup.dart:67-110` | | Aucune métadonnée affichée, alors que `SizeBytes`, `FileName`, `DateCreation`, `Type` sont en base | — | | Impossible de savoir **où** une ressource est utilisée, ni si elle l'est | — | | 🐛 **Le téléchargement force `.json`** quel que soit le type : un PNG se télécharge en `label.json` | `show_resource_popup.dart:91` | | 🐛 **`Resource.FileName` n'est pas dans `ToDTO()`** — le front n'a pas de quoi nommer le fichier | `Data/Resource.cs` | --- ## 1. Périmètre ### Dans le lot 1. Renommage **Ressources → Médiathèque** (libellé de menu et titre d'écran). 2. **Rail de facettes cumulables** avec compteurs : Type · Usage · Origine · Configuration. 3. **Tri** (date, nom, poids, usages) et **groupement par mois**, bascule grille ↔ liste. 4. **Sélection multiple** et actions en lot (télécharger, supprimer). 5. **Compteur d'usages sur la vignette.** 6. Le détail passe de la modale au **panneau latéral** : aperçu d'abord, métadonnées réelles, liste « Utilisée dans » cliquable, boutons hiérarchisés, **Supprimer désactivé tant que la ressource sert**. 7. Backend : **index inverse des usages** (2 endpoints) + enrichissement de `ResourceDTO`. 8. Les deux bugs ci-dessus. ### Hors du lot — explicitement - Toute génération IA (onglet « Générer », crédits, provenance) → **V2**. - Le filtre « Origine › Générées par IA » : la facette existe dans le rail, mais **inutile en V1** puisque rien n'est généré. **La coder quand même** (elle lit `AiProvenance`, absent en V1 → 0 résultat) ou la masquer tant que `studioEnabled == false`. **Recommandé : la masquer**, pour ne pas afficher une facette vide. - Upload serveur (`IResourceBlobService.UploadAsync`) → V2, lot 0. L'upload reste navigateur → Firebase. - Thumbnails / variantes / CDN → hors périmètre, aucune des deux versions n'en a besoin. - `GuidedStep.ImageUrl` → `ImageResourceId` : **V2**. Conséquence assumée en V1, voir §3.4. --- ## 2. Backend — `manager-service` ### 2.1 Enrichir `ResourceDTO` `Data/Resource.cs`, méthode `ToDTO()`. Champs à ajouter : ```csharp fileName = FileName, // existe en base, jamais exposé — corrige le bug du .json width = Width, // nouvelle colonne, nullable height = Height, // nouvelle colonne, nullable usageCount = null, // rempli seulement par les endpoints qui le calculent (§2.2) ``` **Migration** : `Resource.Width` et `Resource.Height`, `int?`. Nullables et **non backfillées** — l'UI n'affiche les dimensions que si elles sont présentes. Elles sont renseignées à la création par manager-app, qui les connaît déjà (`ImageCompressor` décode l'image avant de la compresser). ⚠️ `FileName` peut être nul sur les ressources anciennes. Le front retombe alors sur `label` + l'extension déduite du `Type`. ### 2.2 Index inverse des usages **Le mécanisme existe déjà** : `GetReferencedResourceIds(string language = null)` est implémentée sur les **13 sous-types de `Section`**, plus `GuidedPath` et `GuidedStep`. C'est elle qui fait marcher l'export offline (`ConfigurationController.Export`). L'index inverse consiste à la parcourir dans l'autre sens. ``` GET /api/Resource/{id}/usages → [ { kind, id, label, configurationId, configurationLabel, sectionId, field, path } ] GET /api/Resource/usage-map?instanceId={id} → { usages: { "": { count, configurationIds: [] }, ... }, configurations: { "": "