UNPKG

@megaads/wm

Version:

To install the library, use npm:

527 lines (414 loc) 16.5 kB
# Customization TeeInBlue Tài liệu này dành cho lập trình viên mobile cần tích hợp module `customization-teeinblue` từ package `@megaads/wm`. Module nhận campaign data của TeeInBlue, chuẩn hóa dữ liệu thành các nhóm dễ dùng cho app mobile: - Danh sách template để user chọn mẫu thiết kế. - Danh sách option/layer để user nhập text, chọn option, chọn clipart hoặc upload ảnh. - Dữ liệu preview gồm mockup, print area và design layer. - Dữ liệu `config` để gửi khi add to cart. - State để lưu và restore màn hình customization. ## Cài đặt ```sh npm install @megaads/wm ``` ## Khởi tạo service ```ts import WM from "@megaads/wm"; const service = WM.initCustomizationTeeinblue(campaignData, { campaignProductIndex: 0, campaignMockupId: undefined, mockupId: undefined, printareaId: undefined, templateId: savedState?.templateId, state: savedState, }); const snapshot = service.getSnapshot(); ``` `campaignData` có thể truyền toàn bộ response hoặc phần `result`. Constructor tự xử lý cả hai dạng: ```ts WM.initCustomizationTeeinblue(apiResponse); WM.initCustomizationTeeinblue(apiResponse.result); ``` ## Init options | Field | Type | Ý nghĩa | | --- | --- | --- | | `campaignProductIndex` | `number` | Index của product trong `campaign_products`. Mặc định là `0`. | | `campaignMockupId` | `string \| number` | Chọn campaign mockup theo `campaign_mockup.id`. | | `mockupId` | `string \| number` | Chọn mockup theo `campaign_mockup.mockup_id`. Nên truyền cùng `printareaId` khi dùng variant mockup. | | `printareaId` | `string \| number` | Chọn artwork theo print area tương ứng. | | `templateId` | `string \| number` | Template được active ban đầu. | | `state` | `object` | State đã lưu từ `service.getState()` để restore lựa chọn trước đó. | Nếu không truyền mockup, service tự chọn theo thứ tự: variant có `campaign_mockup_id`, variant có `mockup_id + printarea_id`, mockup duy nhất, mockup có `mockup_id` hoặc `old_campaign_mockup_id`, mockup có print area, rồi fallback về item đầu tiên. Về template: nếu campaign chỉ có 1 artwork, service tự chọn template đầu tiên. Nếu có nhiều artwork và app không truyền `templateId`, service **không auto-select** — user phải tự chọn template, và `validation.errors` sẽ có entry `type: 'template'` cho đến khi user chọn. > Để biết cách render `layerOptions``templateOptions` cùng các method update, xem [customization-teeinblue-options.md](./customization-teeinblue-options.md). ## Lung tích hp khuyến nghị 1. Fetch campaign data tAPI backend/app. 2. Khi to service bng `WM.initCustomizationTeeinblue(campaignData, options)`. 3. Gi `getSnapshot()` để render màn hình ln đầu. 4. Khi user thao tác, gi method thay đổi tươngng. 5. Sau mi thao tác, ly snapshot mi treturn value hoc `getSnapshot()` ri cp nht UI. 6. Trước khi add to cart, kim tra `snapshot.validation.isValid`. 7. Gi `snapshot.config` trong payload add to cart. 8. Lưu `service.getState()` nếu cn restore khi quay li màn hình. Ví dtng quát: ```ts let snapshot = service.getSnapshot(); function refresh(nextSnapshot?: any) { snapshot = nextSnapshot ?? service.getSnapshot(); renderCustomization(snapshot); } function onSelectTemplate(templateId: string | number) { refresh(service.selectTemplate(templateId)); } function onSelectOption(layer: any, optionItem: any) { refresh(service.changeLayerOptionValue(optionItem, layer)); } function onChangeText(layer: any, text: string) { refresh(service.changeInputValue(layer, text)); } function onUploadPhoto(layer: any, imageUrl: string, file?: unknown) { refresh(service.changeUploadValue(layer, imageUrl, { file })); } function onToggleLayer(layer: any, checked: boolean) { refresh(service.changeLayerVisibility(layer, checked)); } function buildCartPayload() { const latest = service.getSnapshot(); if (!latest.validation.isValid) { return { ok: false, errors: latest.validation.errors, }; } return { ok: true, customization: latest.config, customizationState: service.getState(), }; } ``` ## Snapshot `getSnapshot()` là API chính để mobile render toàn bộ màn hình. ```ts const snapshot = service.getSnapshot(); ``` Snapshot trả về: | Field | Ý nghĩa | | --- | --- | | `campaign` | Campaign data đã clone và normalize. | | `campaignProduct` | Product đang active trong campaign. | | `campaignMockup` | Campaign mockup đang active. | | `mockup` | Dữ liệu preview mockup đã rút gọn cho renderer. | | `printAreas` | Danh sách vùng in kèm design layers. | | `artwork` | Artwork đang active. | | `template` | Template đang active, có `layers` đã được apply value hiện tại. | | `templateOptions` | Danh sách nhóm template cho UI chọn template. | | `layerOptions` | Danh sách field personalization để render form. | | `designLayers` | Danh sách layer text/image để render trong print area. | | `fonts` | Font cần load cho text layer. | | `config` | Payload customization để gửi add to cart. | | `validation` | Kết quả validate required fields. | ## Render template options `snapshot.templateOptions` là danh sách nhóm template theo artwork. ```ts type TemplateOptionGroup = { artworkId: string | number; label: string; optionValues: TemplateOptionValue[]; values: TemplateOptionValue[]; }; type TemplateOptionValue = { id: string | number; name: string; title: string; url: string | null; thumbnail: string | null; active: boolean; artworkId: string | number; }; ``` Render mỗi group như một selector. Khi user chọn template: ```ts const nextSnapshot = service.selectTemplate(template.id); ``` Lưu ý: chọn template sẽ thay toàn bộ `templateLayers`, vì vậy UI nên refresh từ snapshot mới thay vì giữ reference layer cũ. ## Render layer options `snapshot.layerOptions` là danh sách control mà user có thể thao tác. Các layer đã được lọc theo `visible`, dependency, `form_type`, `linked`, `group`. Field quan trọng: | Field | Ý nghĩa | | --- | --- | | `id` | ID layer, dùng lại khi gọi method update. | | `form_label` | Label hiển thị cho user. | | `input_type` | Loại control đã normalize. | | `option_items` | Danh sách item cho option/clipart/grouped clipart. | | `options` | Alias của `option_items`. | | `value` | Giá trị logic hiện tại. | | `show_value` | Giá trị hiển thị/render hiện tại. | | `active` | Với option item, đánh dấu item đang chọn. | | `required` | Field bắt buộc nếu `true`. | | `max_length` | Giới hạn text nếu có. | | `form_visibility_value` | Trạng thái bật/tắt với layer có visibility control. | Các `input_type` phổ biến: | `input_type` | UI mobile nên render | | --- | --- | | `text` | Text input. Dùng `max_length` nếu có. | | `photo` | Upload/select photo. Sau khi upload xong truyền URL vào `changeUploadValue`. | | `option` | Single choice list/grid. | | `clipart` | Grid ảnh, chọn trực tiếp `option_items`. | | `grouped_clipart` | Chọn group trước, sau đó chọn item bên trong `group.options`. | ### Text ```ts const textLayer = snapshot.layerOptions.find((layer: any) => layer.input_type === "text"); const nextSnapshot = service.changeInputValue(textLayer, "Alex"); ``` Với layer có `form_input_textcase: "uppercase"`, service sẽ tự uppercase khi tạo `designLayers`. ### Photo upload ```ts const photoLayer = snapshot.layerOptions.find((layer: any) => layer.input_type === "photo"); // imageUrl là URL đã upload lên server/storage của app. const nextSnapshot = service.changeUploadValue(photoLayer, imageUrl, { file: localFileMetadata, }); ``` Service không upload file hộ mobile app. Mobile cần upload ảnh trước, nhận URL public hoặc URL mà renderer đọc được, rồi truyền URL đó vào `changeUploadValue`. ### Option và clipart ```ts const optionLayer = snapshot.layerOptions.find((layer: any) => layer.input_type === "option"); const item = optionLayer.option_items[0]; const nextSnapshot = service.changeLayerOptionValue(item, optionLayer); ``` Với `clipart`, cách gọi tương tự: ```ts const clipartLayer = snapshot.layerOptions.find((layer: any) => layer.input_type === "clipart"); const item = clipartLayer.option_items[0]; service.changeLayerOptionValue(item, clipartLayer); ``` ### Grouped clipart `grouped_clipart` có cấu trúc 2 cấp: ```ts const groupedLayer = snapshot.layerOptions.find( (layer: any) => layer.input_type === "grouped_clipart", ); const group = groupedLayer.option_items[0]; const childItem = group.options[0]; service.changeLayerOptionValue(childItem, group, groupedLayer); ``` Nếu UI chỉ cần chọn group mặc định, có thể dùng: ```ts service.changeOptionGroupClipartValue(group, groupedLayer); ``` ## Render preview Mobile có 2 hướng render preview: - Dùng `snapshot.mockup` để render mockup hoàn chỉnh gồm background/mockup image và print area. - Dùng `snapshot.printAreas` nếu renderer của app tách vùng in riêng. ### Mockup preview ```ts type MockupPreview = { id: string | number; width: number; height: number; preview_url: string | null; preview_thumbnail: string | null; layers: MockupPreviewLayer[]; }; ``` `mockup.layers` gồm: | `type` | Ý nghĩa | | --- | --- | | `image` | Layer ảnh của mockup. Render bằng `src`/`url`. | | `printarea` | Vùng đặt design. Render `layers` bên trong theo kích thước vùng này. | Các field tọa độ chính: `top`, `left`, `width`, `height`, `rotate`, `opacity`. ### Design layers `snapshot.designLayers` và `printarea.layers` chứa layer thật cần vẽ trong vùng in. Layer ảnh: ```ts type ImageDesignLayer = { id: string | number; type: "image"; src: string; url: string; top: number; left: number; width: number; height: number; rotate: number; opacity: number; masked_enable?: boolean; masked_image?: string | null; }; ``` Layer text: ```ts type TextDesignLayer = { id: string | number; type: "text"; text: string; defaultText?: string; top: number; left: number; width: number; height: number; rotate: number; color?: string; align?: string; typography?: { family?: string; variant?: string; size?: number; }; typography_type?: "google" | "custom"; custom_font?: { family?: string; url?: string; } | null; stroke_enabled?: boolean; stroke_color?: string; stroke_width?: number; autoscale_enabled?: boolean; autoscale_max_width?: number; }; ``` Renderer nên sort layer theo `order` tăng dần nếu cần kiểm soát z-index. ## Load font `snapshot.fonts` cho biết font nào cần load trước khi render text: ```ts { google: { "Roboto": ["regular", "700"] }, custom: { "My Font": { family: "My Font", url: "https://cdn.teeinblue.com/path/font.woff2" } } } ``` Với custom font, service đổi đuôi `.otf`/`.ttf` sang `.woff2` nếu layer có `custom_font.url`. ## Validate trước add to cart ```ts const { validation } = service.getSnapshot(); if (!validation.isValid) { showErrors(validation.errors); return; } ``` Error format: ```ts type ValidationError = | { type: "template"; artworkId: string | number; message: string; } | { type: "layer"; layerId: string | number; label: string; message: string; }; ``` Service validate: - Template required khi artwork có `template_settings` và có nhiều hơn 1 template. - Layer required khi layer đang visible, đang thỏa dependency, không phải static/linked, có `form_label`, và chưa có `value` hoặc `upload_image_url`. ## Payload add to cart Dùng `snapshot.config` hoặc `service.getCustomizationConfig()`. ```ts const snapshot = service.getSnapshot(); const payload = { product_id: productId, variant_id: variantId, quantity, customization: snapshot.config, }; ``` `config` có dạng: ```ts { disable_make_change: true, custom_type: { name: "teeinblue", }, options: [ { name: "Pet", value: "https://cdn.teeinblue.com/...", optionItem: { id: "option-id", name: "Dog", url: "https://cdn.teeinblue.com/..." } } ], layers: [ { name: "Name", type: "text", value: "Alex", // Các transform config còn lại của layer được giữ lại. } ] } ``` `options` dùng để hiển thị tóm tắt lựa chọn của user. `layers` là dữ liệu chi tiết để backend/order system tái dựng personalization. ## Lưu và restore state Khi user rời màn hình hoặc cần cache customization: ```ts const state = service.getState(); ``` Restore: ```ts const service = WM.initCustomizationTeeinblue(campaignData, { templateId: state.templateId, state, }); ``` State gồm: ```ts { templateId: "template-391321", layers: { "layer-1": { value: "Alex", show_value: "Alex", form_visibility_value: true, selected_name: "Dog", optionItem: {} } } } ``` Chỉ nên persist object từ `getState()`. Không nên tự build state thủ công nếu không cần thiết. ## Public methods | Method | Mục đích | | --- | --- | | `getSnapshot()` | Lấy toàn bộ data để render UI, preview, config, validation. | | `getData()` | Alias của `getSnapshot()`. | | `selectTemplate(templateId)` | Chọn template, rebuild layer/options/preview. | | `changeLayerOptionValue(optionItem, option, parentLayer?)` | Chọn option, clipart hoặc child item của grouped clipart. | | `changeOptionValue(optionItem, option, parentLayer?)` | Method gốc cho chọn option. | | `changeOptionGroupClipartValue(optionItem, option)` | Chọn grouped clipart theo group. | | `changeInputValue(layerOption, value)` | Update text layer và các linked layer. | | `changeUploadValue(layerOption, imageUrl, metadata?)` | Update photo/upload layer và các linked layer. | | `changeLayerVisibility(layerOption, value)` | Bật/tắt layer có `form_visibility`. | | `validate()` | Validate required template/layer. | | `getCustomizationConfig()` | Lấy payload add to cart. | | `getState()` | Lấy state để persist. | | `getFonts()` | Lấy danh sách Google/custom font cần load. | | `getPreview()` | Alias của `getMockupPreview()`. | | `getMockupLayers()` | Lấy raw mockup layers đang active. | | `getDesignLayers()` | Lấy design layers đang active. | | `getLayerOptions()` | Lấy personalization controls. | | `getTemplateOptions()` | Lấy template controls. | | `getPrintAreas()` | Lấy print areas. | | `getTemplate()` | Lấy template active kèm layers đã update. | | `getTemplateLayers()` | Lấy raw template layers đang active. | | `setTemplateLayers(layers)` | Set lại template layers rồi rebuild. Chỉ dùng cho case nâng cao/debug. | | `setTemplateOptions(options)` | Override template options. Chỉ dùng cho case nâng cao/debug. | ## Lưu ý tích hợp mobile - Sau mọi thao tác thay đổi, dùng snapshot mới để render lại vì service có thể rebuild dependency, active state, design layers và config. - Không giữ reference layer/option quá lâu sau khi gọi `selectTemplate`, vì template layer đã được clone lại. - URL asset tương đối được chuẩn hóa sang `https://cdn.teeinblue.com/...`. URL `http`, `https`, và `blob` được giữ nguyên. - Với upload ảnh, app chịu trách nhiệm upload và truyền URL sau upload. Package không xử lý upload file trong public flow này. - Dependency giữa các layer được xử lý trong service. UI chỉ cần render `snapshot.layerOptions` hiện tại. - `snapshot.config` nên được lấy lại ngay trước add to cart để tránh gửi payload cũ. - Nếu render preview bằng canvas/native view, cần quy đổi tọa độ theo ratio giữa kích thước thiết kế (`mockup.width/height` hoặc `printarea.width/height`) và kích thước hiển thị thực tế.