Amélioration de l'UI : meilleur affichage des images que ce soit dans la partie lore ou la partie campagne (partie campagne : visualisation scrapbooking). Possibilité de réordonner les champs dans les templates...
Passage v0.3.0
This commit is contained in:
@@ -38,12 +38,18 @@ public class Arc {
|
||||
private List<String> relatedPageIds = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* IDs des images (Shared Kernel) servant d'illustrations a cet arc.
|
||||
* IDs des images (Shared Kernel) servant d'illustrations a cet arc (ambiance).
|
||||
* Galerie ordonnee : la 1ere image est l'illustration principale.
|
||||
*/
|
||||
@Builder.Default
|
||||
private List<String> illustrationImageIds = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* IDs des images utilisees comme cartes / plans (outil de table).
|
||||
*/
|
||||
@Builder.Default
|
||||
private List<String> mapImageIds = new ArrayList<>();
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
|
||||
@@ -34,11 +34,17 @@ public class Chapter {
|
||||
private List<String> relatedPageIds = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* IDs des images (Shared Kernel) illustrant ce chapitre.
|
||||
* IDs des images (Shared Kernel) illustrant ce chapitre (ambiance).
|
||||
*/
|
||||
@Builder.Default
|
||||
private List<String> illustrationImageIds = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* IDs des images utilisees comme cartes / plans pour ce chapitre (outil de table).
|
||||
*/
|
||||
@Builder.Default
|
||||
private List<String> mapImageIds = new ArrayList<>();
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
|
||||
@@ -48,11 +48,19 @@ public class Scene {
|
||||
|
||||
/**
|
||||
* IDs des images (Shared Kernel) illustrant cette scene.
|
||||
* Utile pour carte du lieu, portraits des PNJ principaux, ambiance.
|
||||
* Vocation "ambiance" : portraits, decors, moodboard. Rendu facon editorial.
|
||||
*/
|
||||
@Builder.Default
|
||||
private List<String> illustrationImageIds = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* IDs des images utilisees comme cartes / plans.
|
||||
* Vocation "outil de table" : plan de donjon, carte du lieu, schema tactique.
|
||||
* Rendu different des illustrations : vignettes plus grandes, ratio natif preserve.
|
||||
*/
|
||||
@Builder.Default
|
||||
private List<String> mapImageIds = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Sorties narratives possibles depuis cette scène (graphe intra-chapitre).
|
||||
* Chaque branche décrit un choix des joueurs et la scène de destination.
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.loremind.domain.lorecontext;
|
||||
|
||||
/**
|
||||
* Variante de rendu pour un champ de type IMAGE.
|
||||
* <p>
|
||||
* - GALLERY : grille de vignettes (defaut, comportement historique)
|
||||
* - HERO : premiere image en banniere pleine largeur, suivantes en petit
|
||||
* - MASONRY : mosaique hauteurs variables facon Pinterest
|
||||
* - CAROUSEL : defilement horizontal
|
||||
* <p>
|
||||
* Uniquement significatif quand {@link FieldType} = IMAGE. Ignore pour TEXT.
|
||||
*/
|
||||
public enum ImageLayout {
|
||||
GALLERY,
|
||||
HERO,
|
||||
MASONRY,
|
||||
CAROUSEL
|
||||
}
|
||||
@@ -12,10 +12,9 @@ import lombok.NoArgsConstructor;
|
||||
* Le type pilote le rendu cote front (textarea vs galerie d'images) ET
|
||||
* la logique metier (seuls les champs TEXT sont envoyes a l'IA pour generation).
|
||||
* <p>
|
||||
* Evolution de `List<String> fields` vers `List<TemplateField> fields` :
|
||||
* refactor propre (DDD Value Object polymorphism) permettant d'ajouter
|
||||
* facilement d'autres types de champs (DATE, NUMBER, RICH_TEXT...) sans
|
||||
* casser le contrat.
|
||||
* Pour les champs IMAGE, {@link #layout} precise la variante de rendu
|
||||
* (gallery/hero/masonry/carousel). Nullable : l'absence equivaut a GALLERY.
|
||||
* Ignore pour les champs TEXT.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@@ -26,14 +25,26 @@ public class TemplateField {
|
||||
private String name;
|
||||
/** Type du champ, pilote le rendu et la generation IA. */
|
||||
private FieldType type;
|
||||
/** Variante de rendu pour les champs IMAGE. Null = GALLERY. */
|
||||
private ImageLayout layout;
|
||||
|
||||
/** Constructeur de retrocompat : type seul, layout=null. */
|
||||
public TemplateField(String name, FieldType type) {
|
||||
this(name, type, null);
|
||||
}
|
||||
|
||||
/** Raccourci : construit un champ de type TEXT (cas le plus courant). */
|
||||
public static TemplateField text(String name) {
|
||||
return new TemplateField(name, FieldType.TEXT);
|
||||
return new TemplateField(name, FieldType.TEXT, null);
|
||||
}
|
||||
|
||||
/** Raccourci : construit un champ de type IMAGE. */
|
||||
/** Raccourci : construit un champ de type IMAGE avec layout GALLERY. */
|
||||
public static TemplateField image(String name) {
|
||||
return new TemplateField(name, FieldType.IMAGE);
|
||||
return new TemplateField(name, FieldType.IMAGE, ImageLayout.GALLERY);
|
||||
}
|
||||
|
||||
/** Raccourci : construit un champ IMAGE avec un layout specifique. */
|
||||
public static TemplateField image(String name, ImageLayout layout) {
|
||||
return new TemplateField(name, FieldType.IMAGE, layout);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.loremind.domain.lorecontext.FieldType;
|
||||
import com.loremind.domain.lorecontext.ImageLayout;
|
||||
import com.loremind.domain.lorecontext.TemplateField;
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
import jakarta.persistence.Converter;
|
||||
@@ -72,8 +73,20 @@ public class TemplateFieldListJsonConverter
|
||||
// Type inconnu (ajoute par une version future) : fallback TEXT.
|
||||
type = FieldType.TEXT;
|
||||
}
|
||||
ImageLayout layout = null;
|
||||
if (type == FieldType.IMAGE) {
|
||||
String layoutStr = item.path("layout").asText(null);
|
||||
if (layoutStr != null && !layoutStr.isBlank()) {
|
||||
try {
|
||||
layout = ImageLayout.valueOf(layoutStr);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// Layout inconnu : on laisse null → rendu GALLERY par defaut cote UI.
|
||||
layout = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (name != null && !name.isBlank()) {
|
||||
result.add(new TemplateField(name, type));
|
||||
result.add(new TemplateField(name, type, layout));
|
||||
}
|
||||
}
|
||||
// Autres types de noeuds (nombre, booleen...) : ignores silencieusement.
|
||||
|
||||
@@ -68,6 +68,12 @@ public class ArcJpaEntity {
|
||||
@Builder.Default
|
||||
private List<String> illustrationImageIds = new ArrayList<>();
|
||||
|
||||
/** IDs des images "cartes / plans". */
|
||||
@Column(name = "map_image_ids", columnDefinition = "TEXT")
|
||||
@Convert(converter = StringListJsonConverter.class)
|
||||
@Builder.Default
|
||||
private List<String> mapImageIds = new ArrayList<>();
|
||||
|
||||
@Column(name = "created_at", nullable = false, updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
|
||||
@@ -57,6 +57,11 @@ public class ChapterJpaEntity {
|
||||
@Builder.Default
|
||||
private List<String> illustrationImageIds = new ArrayList<>();
|
||||
|
||||
@Column(name = "map_image_ids", columnDefinition = "TEXT")
|
||||
@Convert(converter = StringListJsonConverter.class)
|
||||
@Builder.Default
|
||||
private List<String> mapImageIds = new ArrayList<>();
|
||||
|
||||
@Column(name = "created_at", nullable = false, updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
|
||||
@@ -80,6 +80,11 @@ public class SceneJpaEntity {
|
||||
@Builder.Default
|
||||
private List<String> illustrationImageIds = new ArrayList<>();
|
||||
|
||||
@Column(name = "map_image_ids", columnDefinition = "TEXT")
|
||||
@Convert(converter = StringListJsonConverter.class)
|
||||
@Builder.Default
|
||||
private List<String> mapImageIds = new ArrayList<>();
|
||||
|
||||
// Graphe narratif intra-chapitre : sorties possibles vers d'autres scènes.
|
||||
// Persisté en TEXT JSON via converter (pattern homogène avec les autres listes).
|
||||
@Column(name = "branches", columnDefinition = "TEXT")
|
||||
|
||||
@@ -83,6 +83,9 @@ public class PostgresArcRepository implements ArcRepository {
|
||||
.illustrationImageIds(jpaEntity.getIllustrationImageIds() != null
|
||||
? new ArrayList<>(jpaEntity.getIllustrationImageIds())
|
||||
: new ArrayList<>())
|
||||
.mapImageIds(jpaEntity.getMapImageIds() != null
|
||||
? new ArrayList<>(jpaEntity.getMapImageIds())
|
||||
: new ArrayList<>())
|
||||
.createdAt(jpaEntity.getCreatedAt())
|
||||
.updatedAt(jpaEntity.getUpdatedAt())
|
||||
.build();
|
||||
@@ -107,6 +110,9 @@ public class PostgresArcRepository implements ArcRepository {
|
||||
.illustrationImageIds(arc.getIllustrationImageIds() != null
|
||||
? new ArrayList<>(arc.getIllustrationImageIds())
|
||||
: new ArrayList<>())
|
||||
.mapImageIds(arc.getMapImageIds() != null
|
||||
? new ArrayList<>(arc.getMapImageIds())
|
||||
: new ArrayList<>())
|
||||
.createdAt(arc.getCreatedAt())
|
||||
.updatedAt(arc.getUpdatedAt())
|
||||
.build();
|
||||
|
||||
@@ -80,6 +80,9 @@ public class PostgresChapterRepository implements ChapterRepository {
|
||||
.illustrationImageIds(jpaEntity.getIllustrationImageIds() != null
|
||||
? new ArrayList<>(jpaEntity.getIllustrationImageIds())
|
||||
: new ArrayList<>())
|
||||
.mapImageIds(jpaEntity.getMapImageIds() != null
|
||||
? new ArrayList<>(jpaEntity.getMapImageIds())
|
||||
: new ArrayList<>())
|
||||
.createdAt(jpaEntity.getCreatedAt())
|
||||
.updatedAt(jpaEntity.getUpdatedAt())
|
||||
.build();
|
||||
@@ -102,6 +105,9 @@ public class PostgresChapterRepository implements ChapterRepository {
|
||||
.illustrationImageIds(chapter.getIllustrationImageIds() != null
|
||||
? new ArrayList<>(chapter.getIllustrationImageIds())
|
||||
: new ArrayList<>())
|
||||
.mapImageIds(chapter.getMapImageIds() != null
|
||||
? new ArrayList<>(chapter.getMapImageIds())
|
||||
: new ArrayList<>())
|
||||
.createdAt(chapter.getCreatedAt())
|
||||
.updatedAt(chapter.getUpdatedAt())
|
||||
.build();
|
||||
|
||||
@@ -85,6 +85,9 @@ public class PostgresSceneRepository implements SceneRepository {
|
||||
.illustrationImageIds(jpaEntity.getIllustrationImageIds() != null
|
||||
? new ArrayList<>(jpaEntity.getIllustrationImageIds())
|
||||
: new ArrayList<>())
|
||||
.mapImageIds(jpaEntity.getMapImageIds() != null
|
||||
? new ArrayList<>(jpaEntity.getMapImageIds())
|
||||
: new ArrayList<>())
|
||||
.branches(jpaEntity.getBranches() != null
|
||||
? new ArrayList<>(jpaEntity.getBranches())
|
||||
: new ArrayList<>())
|
||||
@@ -115,6 +118,9 @@ public class PostgresSceneRepository implements SceneRepository {
|
||||
.illustrationImageIds(scene.getIllustrationImageIds() != null
|
||||
? new ArrayList<>(scene.getIllustrationImageIds())
|
||||
: new ArrayList<>())
|
||||
.mapImageIds(scene.getMapImageIds() != null
|
||||
? new ArrayList<>(scene.getMapImageIds())
|
||||
: new ArrayList<>())
|
||||
.branches(scene.getBranches() != null
|
||||
? new ArrayList<>(scene.getBranches())
|
||||
: new ArrayList<>())
|
||||
|
||||
@@ -79,6 +79,10 @@ public class ImageController {
|
||||
.contentType(MediaType.parseMediaType(img.getContentType()))
|
||||
.contentLength(img.getSizeBytes())
|
||||
.header(HttpHeaders.CACHE_CONTROL, "public, max-age=31536000, immutable")
|
||||
// Autorise explicitement l'utilisation cross-origin du binaire dans une <img>.
|
||||
// Sans ce header, Firefox 109+ applique ORB (Opaque Response Blocking) et
|
||||
// bloque l'image quand le front (localhost:4200) la charge depuis l'API (localhost:8080).
|
||||
.header("Cross-Origin-Resource-Policy", "cross-origin")
|
||||
.body(new InputStreamResource(stream));
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,9 @@ public class ArcDTO {
|
||||
/** IDs des pages du Lore liées à cet arc (weak cross-context references). */
|
||||
private List<String> relatedPageIds = new ArrayList<>();
|
||||
|
||||
/** IDs des images (Shared Kernel) illustrant cet arc. */
|
||||
/** IDs des images (Shared Kernel) illustrant cet arc (ambiance). */
|
||||
private List<String> illustrationImageIds = new ArrayList<>();
|
||||
|
||||
/** IDs des images utilisees comme cartes / plans. */
|
||||
private List<String> mapImageIds = new ArrayList<>();
|
||||
}
|
||||
|
||||
@@ -25,6 +25,9 @@ public class ChapterDTO {
|
||||
/** IDs des pages du Lore liées (weak cross-context references). */
|
||||
private List<String> relatedPageIds = new ArrayList<>();
|
||||
|
||||
/** IDs des images (Shared Kernel) illustrant ce chapitre. */
|
||||
/** IDs des images (Shared Kernel) illustrant ce chapitre (ambiance). */
|
||||
private List<String> illustrationImageIds = new ArrayList<>();
|
||||
|
||||
/** IDs des images utilisees comme cartes / plans. */
|
||||
private List<String> mapImageIds = new ArrayList<>();
|
||||
}
|
||||
|
||||
@@ -30,9 +30,12 @@ public class SceneDTO {
|
||||
/** IDs des pages du Lore liées (weak cross-context references). */
|
||||
private List<String> relatedPageIds = new ArrayList<>();
|
||||
|
||||
/** IDs des images (Shared Kernel) illustrant cette scene. */
|
||||
/** IDs des images (Shared Kernel) illustrant cette scene (ambiance). */
|
||||
private List<String> illustrationImageIds = new ArrayList<>();
|
||||
|
||||
/** IDs des images utilisees comme cartes / plans (outil de table). */
|
||||
private List<String> mapImageIds = new ArrayList<>();
|
||||
|
||||
/** Branches narratives : sorties possibles vers d'autres scènes du même chapitre. */
|
||||
private List<SceneBranchDTO> branches = new ArrayList<>();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import lombok.NoArgsConstructor;
|
||||
* <p>
|
||||
* Miroir wire-friendly de {@link com.loremind.domain.lorecontext.TemplateField}.
|
||||
* Le type est serialise en string (TEXT/IMAGE) pour interop facile avec Angular.
|
||||
* Le layout (null pour TEXT, ou GALLERY/HERO/MASONRY/CAROUSEL pour IMAGE) pilote
|
||||
* le rendu visuel des champs image cote front.
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@@ -17,4 +19,11 @@ public class TemplateFieldDTO {
|
||||
private String name;
|
||||
/** "TEXT" ou "IMAGE" (string pour serialisation JSON transparente). */
|
||||
private String type;
|
||||
/** "GALLERY" | "HERO" | "MASONRY" | "CAROUSEL", null si type=TEXT. */
|
||||
private String layout;
|
||||
|
||||
/** Retrocompat : constructeur sans layout. */
|
||||
public TemplateFieldDTO(String name, String type) {
|
||||
this(name, type, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ public class ArcMapper {
|
||||
dto.setResolution(arc.getResolution());
|
||||
dto.setRelatedPageIds(copyList(arc.getRelatedPageIds()));
|
||||
dto.setIllustrationImageIds(copyList(arc.getIllustrationImageIds()));
|
||||
dto.setMapImageIds(copyList(arc.getMapImageIds()));
|
||||
return dto;
|
||||
}
|
||||
|
||||
@@ -52,6 +53,7 @@ public class ArcMapper {
|
||||
.resolution(dto.getResolution())
|
||||
.relatedPageIds(copyList(dto.getRelatedPageIds()))
|
||||
.illustrationImageIds(copyList(dto.getIllustrationImageIds()))
|
||||
.mapImageIds(copyList(dto.getMapImageIds()))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ public class ChapterMapper {
|
||||
dto.setNarrativeStakes(chapter.getNarrativeStakes());
|
||||
dto.setRelatedPageIds(copyList(chapter.getRelatedPageIds()));
|
||||
dto.setIllustrationImageIds(copyList(chapter.getIllustrationImageIds()));
|
||||
dto.setMapImageIds(copyList(chapter.getMapImageIds()));
|
||||
return dto;
|
||||
}
|
||||
|
||||
@@ -48,6 +49,7 @@ public class ChapterMapper {
|
||||
.narrativeStakes(dto.getNarrativeStakes())
|
||||
.relatedPageIds(copyList(dto.getRelatedPageIds()))
|
||||
.illustrationImageIds(copyList(dto.getIllustrationImageIds()))
|
||||
.mapImageIds(copyList(dto.getMapImageIds()))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,9 @@ public class SceneMapper {
|
||||
dto.setIllustrationImageIds(scene.getIllustrationImageIds() != null
|
||||
? new ArrayList<>(scene.getIllustrationImageIds())
|
||||
: new ArrayList<>());
|
||||
dto.setMapImageIds(scene.getMapImageIds() != null
|
||||
? new ArrayList<>(scene.getMapImageIds())
|
||||
: new ArrayList<>());
|
||||
dto.setBranches(toBranchDTOs(scene.getBranches()));
|
||||
return dto;
|
||||
}
|
||||
@@ -70,6 +73,9 @@ public class SceneMapper {
|
||||
.illustrationImageIds(dto.getIllustrationImageIds() != null
|
||||
? new ArrayList<>(dto.getIllustrationImageIds())
|
||||
: new ArrayList<>())
|
||||
.mapImageIds(dto.getMapImageIds() != null
|
||||
? new ArrayList<>(dto.getMapImageIds())
|
||||
: new ArrayList<>())
|
||||
.branches(toBranchDomain(dto.getBranches()))
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.loremind.infrastructure.web.mapper;
|
||||
|
||||
import com.loremind.domain.lorecontext.FieldType;
|
||||
import com.loremind.domain.lorecontext.ImageLayout;
|
||||
import com.loremind.domain.lorecontext.TemplateField;
|
||||
import com.loremind.infrastructure.web.dto.lorecontext.TemplateFieldDTO;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -11,6 +12,8 @@ import org.springframework.stereotype.Component;
|
||||
* <p>
|
||||
* Tolerance : un type inconnu recu du client est interprete comme TEXT
|
||||
* (plus safe que de rejeter la requete et d'interrompre la sauvegarde).
|
||||
* Un layout inconnu ou absent sur un champ IMAGE est interprete comme GALLERY.
|
||||
* Le layout est force a null pour les champs TEXT.
|
||||
*/
|
||||
@Component
|
||||
public class TemplateFieldMapper {
|
||||
@@ -18,7 +21,12 @@ public class TemplateFieldMapper {
|
||||
public TemplateFieldDTO toDTO(TemplateField field) {
|
||||
if (field == null) return null;
|
||||
String typeStr = field.getType() != null ? field.getType().name() : FieldType.TEXT.name();
|
||||
return new TemplateFieldDTO(field.getName(), typeStr);
|
||||
String layoutStr = null;
|
||||
if (field.getType() == FieldType.IMAGE) {
|
||||
ImageLayout layout = field.getLayout() != null ? field.getLayout() : ImageLayout.GALLERY;
|
||||
layoutStr = layout.name();
|
||||
}
|
||||
return new TemplateFieldDTO(field.getName(), typeStr, layoutStr);
|
||||
}
|
||||
|
||||
public TemplateField toDomain(TemplateFieldDTO dto) {
|
||||
@@ -29,6 +37,16 @@ public class TemplateFieldMapper {
|
||||
} catch (IllegalArgumentException ex) {
|
||||
type = FieldType.TEXT;
|
||||
}
|
||||
return new TemplateField(dto.getName(), type);
|
||||
ImageLayout layout = null;
|
||||
if (type == FieldType.IMAGE) {
|
||||
try {
|
||||
layout = dto.getLayout() != null
|
||||
? ImageLayout.valueOf(dto.getLayout())
|
||||
: ImageLayout.GALLERY;
|
||||
} catch (IllegalArgumentException ex) {
|
||||
layout = ImageLayout.GALLERY;
|
||||
}
|
||||
}
|
||||
return new TemplateField(dto.getName(), type, layout);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user