# 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(&lbrace;pagesCount: number&rbrace;) +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(&lbrace;pagesCount&rbrace;) --> 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 ```