# Lancement de Kustom Web
Depuis une page produit, `POST /api/v2/identified/{lang}/legacy/web_app/articles` puis redirection vers la page `{lang}/customizations/{id}/edit`
Peut être lancé aussi directement depuis une page produit de l'admin, qui redirigera vers `{lang}/create_customizations/{product_tag}` qui se charge de faire le create articles avant de rediriger vers `{lang}/customizations/{id}/edit?tag={product_tag}`
## Page Cheerz (CustomizationEditPage)
Elle récupère les infos venant du call customizations et différents éléments de Cheerz pour les transmettre à Kustom
- cheerz provider
- `GET /api/v2/identified/{lang}/legacy/web_app/providers`
providers de photos (session, name, token)
- paramètres vidéo (videoMaxLength, videoMaxSize, videoMinLength)
- `GET /api/identified/3.1/customizations/{customizationId}`
- config.additional_page_config
- config.addons
- config.display_format
- config.display_title
- config.display_page_name_plural
- config.display_page_name_singular
- config.is_zoom_out_enabled
- config.max_column_count
- config.min_column_count
- config.max_page_count
- config.min_page_count
- config.page_addition_order
- config.price
- config.should_start_with_gallery
- config.switch_products
- article.gift_card.price
- article.is_in_cart
- article.memory_box
- article.product
- article.selected_addons
- `GET /api/v2/identified/{lang}/legacy/user_profiles`
- fotomkey
- isGuest
- `GET /api/identified/3.0/customizations/{customizationId}/content`
- `GET /api/identified/3.0/customizations/{customizationId}/template`
## Kustom (CustomizationContainer)
Attend un certain nombre de paramètres :
- content
- finalize:
- isActive (check si les paramètres sont remplis pour activer le bouton)
- label
- onFinalize (callBack, dislay upsells ou déclenche addArticle + redirection au panier)
- getOutsideCustomizationUploadPageUrl
- getSwitchProductPrice
- giftCardAmount
- isGuest
- isInCart
- onContentSave
- onDraftSave
- onOptionsChange
- onQuit (callBack, gère historique)
- productConfiguration
- providers
- summaryComponent
- template
- track (callBack, déclenche l'événement de tracking)
- tracking
- video
//TODO : séparer infos communes ^
Au lancement, ~~le CustomizationContainer récupère le content et le template grâce aux `kustomApiToken`, `templateRevisionId` et `contentId`~~
le content est aussitôt vérifié en fonction du template et pré rempli s'il manque des pages
Tout ce qui sera affiché dans la custo ensuite sera basé sur les content & template
```mermaid
classDiagram
direction LR
CustomizationContainerProps --* Configuration
Configuration .. Price
Configuration .. FinalizeConfiguration
Configuration .. OptionsConfiguration
Configuration .. PageName
Configuration .. PagesConfiguration
Configuration .. SubscriptionConfiguration
Configuration .. ui
Configuration .. VideoConfig
class CustomizationContainerProps{
+bool autorunGallery
+String contentId
+Configuration configuration
+onQuit(CustomizationOnQuit)
+String templateRevisionId
+track(event: CustomizationEvent)?
}
class Configuration{
+Price? additionalPrice
+String? assetUrlTemplate
+List~string~? characteristics
+FinalizeConfiguration? finalize
+Price giftCardAmount
+bool? isGuest
+bool? inCart
+String? kustomApiToken
+onSave()?
+List~OptionsConfiguration~? options
+String<"addition_asc"|"addition_desc">? orderedBy
+PageName? pageName
+PagesConfiguration? pages
+Price? productPrice
+SelectionProviderConnect? selectionProviderConnect
+SelectionProviderDisconnect? selectionProviderDisconnect
+List~SelectionProviderToken~? selectionProviderTokens
+SubscriptionConfiguration? subscription
+FunctionComponent<input>? summaryComponent
+ui? ui
+FotomProvider? fotomAPIProvider
+FotomSelectionProvider? fotomSelectionProvider
+BaseUploadProvider? uploadProvider
+VideoConfig? video
+bool? withZoomOut
}
class Price{
+int cents
+String currency
}
class FinalizeConfiguration{
+String label
+isActive({pagesCount: number})
+onFinalize()
}
class OptionsConfiguration{
+onChange()
+String tag
+String value
+List~[values]~ values
}
OptionsConfiguration .. values
class values{
+String label
+String? price
+String value
}
class PageName{
+String plural
+String singular
}
class PagesConfiguration{
+int? increment
+int? max
+int? min
}
class SubscriptionConfiguration{
+int minPrintPerOrder
+int remainingPrintToOrder
+String validUntil
}
class ui{
+int maxPagesByLine
+int minPagesByLine
}
class VideoConfig{
+String helpLink
+int maxLength
+int minLength
+int maxWeight
}
```
### Intéractions
```mermaid
---
title: Passage des infos nécessaires
---
flowchart LR
subgraph Kustom
A
end
subgraph Cheerz
B
end
B(CustomizationEditPage) -- CustomizationContainerProps --> A(CustomizationContainer)
```
```mermaid
---
title: onSave
---
flowchart TB
subgraph Kustom
A
end
subgraph Cheerz: CustomizationEditPage
C
D
E
end
A(CustomizationContainer) -- props.onSave --> C{isGuest?}
C -- yes --> D(Show LoginModal)
C -- no --> E(Do Nothing)
```
```mermaid
---
title: onQuit
---
flowchart TB
subgraph Kustom
A
end
subgraph Cheerz: CustomizationEditPage
C
D
E
end
A(CustomizationContainer) -- props.onQuit --> C{isChoice?}
C -- yes --> D(Go back twice)
C -- no --> E(Go back)
```
```mermaid
---
title: onFinalize
---
flowchart TB
subgraph Kustom
A
end
subgraph Cheerz: CustomizationEditPage
C
D
E
F
end
A(CustomizationContainer) -- props.configuration.finalize.onFinalize --> C{hasUpsells?}
C -- yes --> D(UpsellsModal)
D -- choose upsell --> E
C -- no --> E(Add to cart)
E --> F(Go to cart)
```
```mermaid
---
title: finalize - isActive
---
flowchart TB
subgraph Cheerz: CustomizationEditPage
C
end
subgraph Kustom
A
end
A(CustomizationContainer) -- props.configuration.finalize.isActive({pagesCount}) --> C{hasRequiredPages?}
C -- yes --> A
C -- no --> A
```
```mermaid
---
title: track
---
flowchart TB
subgraph Kustom
A
end
subgraph Cheerz: CustomizationEditPage
B
end
subgraph Utils/tracking
C
end
A(CustomizationContainer) -- props.track --> B(trackKustomWithContext) --> C(sendTrackEvent)
```
## Association des photos au content
```mermaid
classDiagram
SelectionState .. SelectionPhoto
class SelectionState{
+List~SelectionPhoto~ photos
}
class SelectionPhoto {
+String key
+Length<"Unit.PX"> width
+Length<"Unit.PX"> height
+String? localRef
+int? lastModified
+SelectionProvider provider
+String providerRef
+String? providerThumbnailUrl
+String? providerPictureUrl
+String? fotomKey
+String<"error"|"success"|"uploading">? status
}
Content .. ContentSelectionPhoto
Content .. ContentPage
ContentPage .. ContentEditablePicture
class Content{
<<Partial>>
+List~ContentPage~ pages
+List~ContentSelectionPhoto~ selectionPhotos
+List~ContentSelectionPhoto~ galleryPhotos
...
}
class ContentSelectionPhoto {
+String key
+Length<"Unit.PX"> width
+Length<"Unit.PX"> height
+String? creationDate
+ContentSelectionProvider provider
+String providerRef
+String? localRef
+String? fotomKey
}
class ContentPage{
<<Partial>>
+String pageDefinitionKey
+ContentEditablePicture editablePictures
+ContentEditableText editableTexts
...
}
class ContentEditablePicture{
+String editablePictureKey
+String selectionPhotoKey
+Cropping cropping
+FilterTag filterTag
}
```
```mermaid
---
title: Choix et ajout d'une image
---
stateDiagram-v2
User --> SelectionState: Select photo
SelectionState --> EditablePicture: add Photo
EditablePicture --> SelectionState: get URL\nfrom fotomKey
EditablePicture --> Content
Content --> SelectionState: get saved selection\nfrom content
```
```mermaid
---
title: Sauvegarde de la sélection et du content
---
stateDiagram-v2
User --> SelectionState: Save or Add to cart
SelectionState --> Selection: add used Selection
SelectionState --> Gallery: add remaining Selection
Selection --> Content
Gallery --> Content
```
```mermaid
---
title: Display picture
---
sequenceDiagram
EditablePicture ->> utils/customization/finders.ts: findEditablePictureDefinition(editablePictureKey,pageDefinition)
utils/customization/finders.ts ->> EditablePicture: TemplateEditablePictureDefinition
EditablePicture ->> SelectionState: findSelectionPhoto(selectionPhotoKey)
SelectionState -->> EditablePicture: SelectionPhoto
EditablePicture ->> utils/selection/finders.ts: findPhoto(selectionPhoto.fotomKey)
utils/selection/finders.ts -->> EditablePicture: Url
```
```mermaid
---
title: While customizing
---
graph TB
SelectionPhoto -- key:\nref to SelectionPhoto --> selectionPhotoKey
selectionPhotoKey -- fotomKey:\nget fotomKey to construct image URL ---> SelectionPhoto
SelectionState --> editablePictures
subgraph "`**Content**`"
direction LR
pages
end
subgraph SelectionState
SelectionPhoto
end
subgraph editablePictures
direction TB
selectionPhotoKey
end
subgraph pages
direction TB
editablePictures
end
```
```mermaid
graph TB
ContentSelectionPhoto -- key:\nref to ContentSelectionPhoto --> selectionPhotoKey
selectionPhotoKey -- fotomKey:\nget fotomKey to construct image URL ---> ContentSelectionPhoto
SelectionState --> editablePictures
SelectionState -- fill when saving (only with fotom key) --> ContentSelectionPhoto
SelectionState -- fill when saving --> galleryPhotos
subgraph "`**Content**`"
direction LR
pages
selectionPhotos
galleryPhotos
end
subgraph SelectionState
SelectionPhoto
end
subgraph editablePictures
direction TB
selectionPhotoKey
end
subgraph selectionPhotos
direction TB
ContentSelectionPhoto
end
subgraph ContentSelectionPhoto
direction TB
end
subgraph pages
direction TB
editablePictures
end
```
```mermaid
graph TB
ContentSelectionPhoto -- key:\nref to ContentSelectionPhoto --> selectionPhotoKey
selectionPhotoKey -- fotomKey:\nget fotomKey to construct image URL ---> ContentSelectionPhoto
SelectionState --> editablePictures
SelectionState -- fill when saving (only with fotom key) --> ContentSelectionPhoto
SelectionState -- fill when saving --> galleryPhotos
subgraph "`**Content**`"
direction LR
pages
selectionPhotos
galleryPhotos
end
subgraph SelectionState
SelectionPhoto
end
subgraph editablePictures
direction TB
editablePictureKey
selectionPhotoKey
cropping
filterTag
end
subgraph selectionPhotos
direction TB
ContentSelectionPhoto
end
subgraph ContentSelectionPhoto
direction TB
width
height
creationDate
provider
providerRef
localRef
fotomKey
key
end
subgraph pages
direction TB
editablePictures
...
end
```
Au chargement on récupère les photos déjà présentes dans le content dans `galleryPhotos` et `selectionPhotos` pour remplir le `SelectionState` qui sert de référence pour remplir l'album tout au long de la custo.
On ajoute toutes les photos sélectionnées par l'utilisateur dans ce state tout au long de la custo et à la sauvegarde `SelectionState` est rebasculé dans le content entre `selectionPhotos` pour les photos utilisées (après vérification qu'elles ont bien toutes une clé fotom) et `galleryPhotos` pour celles qui ne le sont pas.
L'autofill via la sélection est disponible à partir du moment ou il n'y a plus d'upload en cours.
### Upload
Les photos sélectionnées en local sont envoyées dans la queue d'upload après qu'on ait récupéré les informations disponibles (lastModified, height, width)
Les photos de providers sont envoyées directement dans la queue d'upload, les infos nécessaires ont été récupérées en amont, depuis le provider
```mermaid
flowchart TD
A(file) -- Get datas --> B("`**ContentSelectionPhoto**
+key
+lastModified
+localRef
+provider
+providerPictureUrl
+providerRef
+providerThumbnailUrl
+status
+height
+width
`")
B --> C[(SelectionState)]
B --> D{Upload}
D --x|Error:\nRemove from content| E
D -->|OK:\nAdd fotom key| B
C --> E[(ContentState)]
```
## Moteur de rendu
### Source de véritée
Les informations utiles à l'ensemble de la custo sont divisée en deux parties qui peuvent alimenter les composants à n'importe qu'elle niveau, dans des contextes.
Comme ceux-ci provoque le rafraicissement de tout élément y faisant appel, il ont été divisés en deux parties:
- une partie fixe, qui n'a pas à être mise à jour suite aux modifications de l'utilisateur: Le ConfigurationState. On y place toutes les infos de configurations passées à la custo et le template.
- une partie qui sera mise à jour tout au long de la custo: le CustomizationState. On y place le content mais également les informations de ui. (quel editeur/panel est ouvert, galerie, notifications, mapping des noms assignés aux pages, etc)
### Pages spéciales
Pour l'affichage de la customisation, on commence par différenciers les types de pages:
- les pages de type `extra` seront complètement ignorées
- les pages de couverture seront affichées séparément
- types `front-cover`, `spine` --> `CoverContent`
- types `front-envelope`, `back-envelope` --> `EnvelopeContent`
- la page de type `back-cover` n'est affichée que dans la preview
- les pages de type `layflat-back-matter`, `layflat-front-matter`, `single-back-matter`, `single-front-matter` ne sont pas affichées
- le reste des pages sera géré dans le composant `PageList`
### Par typologie de produit
Suivant les types de produits ou la configuration on a besoin de mises en pages différentes. On a donc créé un utilitaire qui permet de parcourir les pages et de dispatcher le bon nombre de pages dans le bon composant de rendu.
- pages liées (`contentPageConfiguration.nextFilterTypeKeys.length > 1`)
- album double pages à spiral (`template.templateType === "book" && template.pageMode === "spiral_horizontal"`)
- album double pages (`template.templateType === "book" &&
["single_horizontal", "spiral_horizontal"].includes(template.pageMode)`)
- album layflat (`template.templateType === "book"` et pas les précédents)
- calendrier vertical (`template.templateType === "calendar" && template.pageMode === "single_vertical"`)
- page simple (tout le reste)
```mermaid
flowchart TB
CoverContent-- Add UI Layout and get cover pages-->PagePresentation
EnvelopeContent-- Add UI Layout and get cover pages-->PagePresentation
PageList-- getPagesLayoutFormat -->PageListItem
subgraph CustomizationContent
direction LR
CoverContent
EnvelopeContent
PageList
end
PagePresentation
PageListItem-- insert right number of pages into Chosen layout-->PagePresentation
subgraph PagePresentation
Page
end
subgraph PageListItem
direction TB
PageLinkedLayout-. ou .-> BookDoubleHorizontalLayout
BookDoubleHorizontalLayout-. ou .-> BookSpiralHorizontalLayout
BookSpiralHorizontalLayout-. ou .-> CalendarVerticalLayout
CalendarVerticalLayout-. ou .-> BookLayFlatLayout
BookLayFlatLayout-. ou .-> SimpleLayout
SimpleLayout
end
subgraph Page
EditablePicture["`Box
**EditablePicture**
*EditablePictureSvg*`"]
EditableText["`Box
**EditableText**
*AdvancedTextArea*`"]
ForcedImage["`Box
**ForcedImage**`"]
ForcedText["`Box
**ForcedText**`"]
Cell["`Box
**Cell**
*Cell elements*`"]
DateText["`Box
**DateText**`"]
Video["`Box
**Video**`"]
end
```
#### *PagePresentation
Elle sert à appliquer la présentation appliquée à la page (un cadre par exemple, c'est élément n'est pas imprimé).
Comme elle entoure toute la page, c'est également ici qu'on applique la shape de page, s'il y en a une.
#### *Box
Ce composant est ajouté autour de chaque élément de la page. Il permet de les positionner au sein de la page.
### Calcul de la taille des pages
Pour savoir dans quelle taille afficher chaque page et ses éléments, on crée un ratio: le PPMM (pixel par millimètre).
On calcul le PPMM en fonction de la taille d'affichage disponible et de la plus grande taille de page disponible (hors envelope).
Il y a deux cas possibles:
1. **avec le paramètre pages_by_line**
C'est le cas le plus simple.
`pages_by_line_min_web` est pris en compte pour les petits écrans (jusqu'à 960px de large)
`pages_by_line_max_web` est pris en compte pour les écrans plus grands
On prend la taille disponible (moins les marges), on divise par le nombre de pages voulu, et o n'a plus qu'à calculer le ratio entre la taille de la plus grande page (en mm) et celle qu'on vient d'obtenir (en pixel)
2. **sans pages_by_line**
On aura une limite max horizontale et verticale par rapport à la taille de la zone d'affichage (le but est de pouvoir voir une page entière).
On doit aussi déterminer si on doit calculer pour un page simple, une page double (vertical: calendriers, horizontal: album ou pages recto/verso), ou plus (pages liées)
On se base aussi sur un jeu de tailles limite par types de produits
On calcul le tout pour pouvoir mettre un maximum de pages (de la plus grande taille trouvée) dans l'espace disponible avec la taille minimum définie et on agrandi pour remplir l'espace. On prend la taille obtenue pour calculer le PPMM.
Si la taille de l'espace disponible est modifiée, on refait le calcul.
Ce PPMM est très important et on le retrouve à différents endroit car les composants servant à rendre un élément de page feront le calcul de leur propre taille à partir des dimensions fournies par le template et de ce PPMM
### Rendu Custo vs rendu des options
Le rendu de la custo dépend directement du template et du content.
Une page dans le content sera rendue par un composant Page (défini par une page_definition dans le template), et ses éléments également (élément de type editable_picture --> composant EditablePicture, editable_text --> EditableText, etc.)
Un composant Page servira donc à rendre aussi bien une page de couverture, une enveloppe, ou un print.
Tout ce qui vient du template sert à déterminer l'apparence du composant (taille, position), ce qui vient du content sert à le remplir (choix de la couleur, texte, photo).
Les options, elles, sont composées à partir de plusieurs conditions en fonction des besoin UX:
- les layouts de couverture et leurs couleurs sont donc représentés différemment des layouts de pages simples.
- on affiche une sélection de photos dans les panels d'options si le produit est un album, un calendrier, ou contient un layout de plus de 6 editablePictures.
- pour les calendriers on ajoute un onglet événements qui s'affichera au global pour toutes les pages alors que les autres onglets de panels (ou panels) sont liés à une page
## Appels d'API & échanges kustom/cheerz
```mermaid
sequenceDiagram
participant front as Cheerz front
participant frontk as Kustom front
participant back as Cheerz back
participant kustom as Kustom back
front->>back: POST /api/v2/identified/{lang}/legacy/web_app/articles
back-->>front: customizationId
Note over front, back: redirection vers<br/>{lang}/customizations/{customizationId}/edit
front->>back: GET /api/v2/identified/{lang}/legacy/web_app/providers
back-->>front: fotomUserId, fotomUserToken, providers
front->>back: GET /api/v2/identified/{lang}/legacy/web_app/customizations/{customizationId}
back-->>front: kustom_user_token, kustom_content_ref, kustom_template_revision_ref, ...
front->>back: GET /api/v2/identified/{lang}/legacy/user_profiles
back-->>front: fotomKey, isGuest, ...
front->>back: GET /api/v2/public/{lang}/{country_code3}/{currency_code3}/products/{productTag}/options?device=web
back-->>front: product_options (upsells)
rect rgb(235, 235, 235)
note over front, kustom: Kustom
frontk->>kustom: GET /template_revisions/{templateRevisionId}`<br/>body { definition_version_range: 13-13 }
kustom-->>frontk: json template
frontk->>kustom: GET /contents/{contentId}`<br/>body { definition_version_range: 13-13 }
kustom-->>frontk: json content
note right of frontk: Upload*
note right of frontk: on Switch Product
frontk->>front: onChange
front->>back: PUT `/api/v2/identified/{lang}/legacy/web_app/articles/{articleId}`<br/>body {product_tag}
front->>back: GET /api/v2/identified/{lang}/legacy/web_app/customizations/{customizationId}
note right of frontk: on Save
frontk->>front: onSave
opt User is guest
front->>frontk: login
end
frontk->>kustom: PUT /contents/{contentId}`<br/>body { content }
kustom-->>frontk: json content
note right of frontk: on Finalize
frontk->>kustom: PUT /contents/{contentId}`<br/>body { content }
kustom-->>frontk: json content
frontk->>front: onFinalize
opt has upsells
front->>back: upsells choice then<br/>PUT `/api/v2/identified/{lang}/legacy/web_app/articles/{articleId}`<br/>body {options}
end
front->>back: PUT /api/v2/identified/{lang}/legacy/web_app/article_add_to_cart/{articleId}
Note over front, kustom: redirection vers {lang}/cart
end
```
```mermaid
sequenceDiagram
box Upload
participant frontk as Kustom front
participant fotom as Fotom
participant provider as Providers
end
frontk->>fotom: GET /user_galleries<br/>body {fotom_key} headers {Fotom-Token...}
fotom-->>frontk: albums list with albumRefs
frontk->>fotom: GET /user_galleries/{albumRefs}<br/>body {fotom_key, limit, page} headers {Fotom-Token...}
fotom-->>frontk: albums contents (with selection photos data)
opt from local files
frontk->>fotom: POST /uploads body {file,provider_tag}
fotom-->>frontk: {height_px, provider_tag, sha1, taken_at, updated_at, width_px}
end
opt from google photos
frontk->>provider: POST https://photoslibrary.googleapis.com/{VERSION}/{edge} ...
provider-->>frontk: albums or photos data
frontk->>fotom: POST /remote_uploads body {file,provider_tag}
fotom-->>frontk: {height_px, provider_tag, sha1, taken_at, updated_at, width_px}
end
```