UNPKG

@megaads/wm

Version:

To install the library, use npm:

909 lines (787 loc) 38.8 kB
import { CircularTextInputElement, Condition, CustomizationOptions, DropdownOption, DropdownValue, DynamicImageElement, ImagePlaceHolderElement, ImageUploadOption, SwatchOption, SwatchValue, Template, TextInputElement, TextInputOption, VectorElement } from "./types"; import axios from 'axios'; import { getRenderConfigImage, getRenderConfigTextBox, getRenderConfigTextBoxCircular, getRenderConfigVectorEps, isNumeric, } from "./helper"; type IOption = SwatchOption | DropdownOption | ImageUploadOption | TextInputOption; type IElement = DynamicImageElement | TextInputElement | CircularTextInputElement | ImagePlaceHolderElement | VectorElement; class Customization { private template: Template; private options: IOption[]; private readonly breadcrumbs: { id: number; name: string; slug: string, _lft: number, _rgt: number, url: string }[]; private readonly baseUrl: string; private LibCaching = {} as { [key: string]: string }; private groupId: string | number | undefined = undefined; private showElementIds: (string | number)[] = []; private hideElementIds: (string | number)[] = []; private selectedGroups = [] as { id: string; uuid: string, position: number }[]; constructor(customizationOptions: CustomizationOptions) { this.template = customizationOptions.template; this.options = customizationOptions.options; this.baseUrl = customizationOptions.baseUrl; this.breadcrumbs = customizationOptions.breadcrumbs; this.buildOptionSet(); } getTemplate(): Template { return this.template; } getOptions(): IOption[] { return this.options; } setTemplate(template: Template): void { this.template = template; } setOptions(options: IOption[]): void { this.options = options; } async selectOptionValue(option: IOption, value: SwatchValue | DropdownValue): Promise<void> { this.buildOptionSet(); if (this.isChangeTemplateOption(option as SwatchOption | DropdownOption)) { // fetch and set new template const templateId = value.templateId; const newTemplate = await fetch(`${this.baseUrl}/template/${templateId}`); const templateData = await newTemplate.json(); if (templateData) { this.template = templateData.result; } else { throw new Error('Template not found'); } } this.options.forEach((option) => { if (option.isShow) { if (option.type == 'swatch' || option.type == 'dropdown') { this.changeSwatchOrDropdownValue(option as SwatchOption | DropdownOption); } else if (option.type == 'text-input') { this.changeTextInputValue(option as TextInputOption); } else if (option.type == 'image-upload') { this.changeImageUploadValue(option as ImageUploadOption); } } }); // fetch customily library await this.fetchCustomilyLibrary(); let elements = this.template.elements; for (const element of elements) { if ("images" in element.config && element.config.images.length > 0 && element.config.imageId && !element.config.libraryId) { const image = element.config.images.find(item => item.order == element.config.imageId); if (image) { element.config.imageUrl = image.value; } } if ("fonts" in element.config && element.config.fonts.length > 0 && element.config.fontId && !element.config.colorLibraryId) { const font = element.config.fonts.find(item => item.order == element.config.fontId); if (font) { element.config.textConfig.font = font.value; } } if ("colors" in element.config && element.config.colors.length > 0 && element.config.colorId && !element.config.fontLibraryId) { const color = element.config.colors.find(item => item.order == element.config.colorId); if (color) { element.config.textConfig.fill = color.value; } } if ("vectors" in element.config && element.config.vectors.length > 0 && element.config.vectorId && !element.config.libraryId) { const vector = element.config.vectors.find(item => item.order == element.config.vectorId); if (vector) { element.config.imageUrl = vector.value; } } } this.updateVisibleElement(); } private async fetchCustomilyLibrary() { let elements = this.template.elements; let promises = []; for (const element of elements) { if ("images" in element.config && !("library" in element.config) && element.config.imageId && element.config.libraryId) { const libraryId = element.config.libraryId; const position = element.config.imageId; const libCacheKey = `${libraryId}-${position}`; const endpoint = `https://app.customily.com/api/Libraries/${libraryId}/Elements/Position/${position}`; const promise = new Promise<void>((resolve) => { if (this.LibCaching[libCacheKey]) { element.config.imageUrl = this.LibCaching[libCacheKey]; return resolve(); } axios.get(endpoint).then((response) => { const baseURL = "https://cdn.customily.com"; let pathProperty = 'Path'; if (response.data && typeof response.data[pathProperty] != 'undefined' && response.data[pathProperty]) { let contentReplace = response.data[pathProperty].startsWith('/') ? '/Content' : 'Content'; this.LibCaching[libCacheKey] = `${baseURL}${response.data[pathProperty].replace(contentReplace, '')}`; element.config.imageUrl = this.LibCaching[libCacheKey]; return resolve(); } return resolve(); }) }); promises.push(promise); } if ("colors" in element.config && !("color_library" in element.config) && element.config.colorId && element.config.colorLibraryId) { const libraryId = element.config.colorLibraryId; const position = element.config.colorId; const libCacheKey = `${libraryId}-${position}`; const endpoint = `https://app.customily.com/api/Libraries/${libraryId}/Elements/Position/${position}`; const promise = new Promise<void>((resolve) => { if (this.LibCaching[libCacheKey]) { if ("textConfig" in element.config) { element.config.textConfig.fill = this.LibCaching[libCacheKey]; } return resolve(); } axios.get(endpoint).then((response) => { let pathProperty = 'hex'; if (response.data && typeof response.data[pathProperty] != 'undefined' && response.data[pathProperty]) { this.LibCaching[libCacheKey] = response.data[pathProperty]; if ("textConfig" in element.config) { element.config.textConfig.fill = this.LibCaching[libCacheKey]; } return resolve(); } return resolve(); }); }); promises.push(promise); } if ("fonts" in element.config && !("font_library" in element.config) && element.config.fontId && element.config.fontLibraryId) { const libraryId = element.config.fontLibraryId; const position = element.config.fontId; const libCacheKey = `${libraryId}-${position}`; const endpoint = `https://app.customily.com/api/Libraries/${libraryId}/Elements/Position/${position}`; const promise = new Promise<void>((resolve) => { if (this.LibCaching[libCacheKey]) { if ("textConfig" in element.config) { element.config.textConfig.font = this.LibCaching[libCacheKey]; } return resolve(); } axios.get(endpoint).then((response) => { const baseURL = "https://cdn.customily.com"; let pathProperty = 'Path'; if (response.data && typeof response.data[pathProperty] != 'undefined' && response.data[pathProperty]) { let contentReplace = response.data[pathProperty].startsWith('/') ? '/Content' : 'Content'; this.LibCaching[libCacheKey] = `${baseURL}${response.data[pathProperty].replace(contentReplace, '')}`; if ("textConfig" in element.config) { element.config.textConfig.font = this.LibCaching[libCacheKey]; element.config.textConfig.fontFamily = 'customFont-' + response.data.fontId; element.config.textConfig.fontFamilyDownload = this.LibCaching[libCacheKey]; } return resolve(); } return resolve(); }); }); promises.push(promise); } if ("vectors" in element.config && !("vector_library" in element.config) && element.config.vectorId && element.config.libraryId) { const libraryId = element.config.libraryId; const position = element.config.vectorId; const libCacheKey = `${libraryId}-${position}`; const endpoint = `https://app.customily.com/api/Libraries/${libraryId}/Elements/Position/${position}`; const promise = new Promise<void>((resolve) => { if (this.LibCaching[libCacheKey]) { element.config.imageUrl = this.LibCaching[libCacheKey]; return resolve(); } axios.get(endpoint).then((response) => { const baseURL = "https://cdn.customily.com"; let pathProperty = 'svgPath'; if (response.data && typeof response.data[pathProperty] != 'undefined' && response.data[pathProperty]) { let contentReplace = response.data[pathProperty].startsWith('/') ? '/Content' : 'Content'; this.LibCaching[libCacheKey] = `${baseURL}${response.data[pathProperty].replace(contentReplace, '')}`; element.config.imageUrl = this.LibCaching[libCacheKey]; return resolve(); } return resolve(); }); }); promises.push(promise); } } await Promise.all(promises); } private buildOptionSet() { // set currentValue for selected value this.options.forEach((option: IOption) => { if (option.type == 'swatch' || option.type == 'dropdown') { let selectedValue; if ("dropdownValues" in option) { selectedValue = option.dropdownValues?.find((value: DropdownValue) => value.selected); } else if ("swatchValues" in option) { selectedValue = option.swatchValues?.find((value: SwatchValue) => value.selected); } // @ts-ignore if (selectedValue && !Object.hasOwnProperty.call(option, 'currentValue')) { option.currentValue = selectedValue.id; } if (selectedValue && Object.hasOwnProperty.call(selectedValue, 'groupId')) { this.groupId = selectedValue.groupId; } } }); // build isShow for option this.options.forEach((option: IOption) => { option.isShow = this.isShowOption(this.options, option); if (option.isShow) { if (this.isExcessiveSizeOption(option) || this.isExcessiveColorOption(option)) { option.hideVisually = true; } } }); // re-check isShow option this.options.forEach((optionItem: IOption) => { if (optionItem.isShow) { let conditionValues = [] as { value: boolean; combinationOperator: string }[]; if (optionItem.conditions && optionItem.conditions.length > 0) { optionItem.conditions.forEach((condition: Condition) => { const watchOptionId = condition.watchOptionId; const watchOption = this.options.find(item => item.id == watchOptionId); if (watchOption && watchOption.isShow || !watchOption) { conditionValues.push({ value: true, combinationOperator: condition.combinationOperator }) } else { conditionValues.push({ value: false, combinationOperator: condition.combinationOperator }) } }); let finalCondition = true; if (conditionValues.length) { finalCondition = conditionValues.reduce(function(preCondition, condition) { if (condition.combinationOperator === 'or') { return preCondition || condition.value; } else { return preCondition && condition.value; } }, false); if (conditionValues.length == 1) finalCondition = conditionValues[0].value; } optionItem.isShow = finalCondition; } } }); } private isShowOption(options: IOption[], option: IOption): boolean { let conditions = option.conditions; if (conditions && conditions.length > 0) { let conditionValues = []; for (const condition of conditions) { if (condition.watchOptionId) { let watchOptionIndex = options.findIndex(item => item.id === condition.watchOptionId); if (watchOptionIndex >= 0) { let watchOption = options[watchOptionIndex]; // @ts-ignore if ((Array.isArray(condition.desiredValue) && condition.desiredValue.includes(watchOption.currentValue)) || condition.desiredValue == watchOption.currentValue || // @ts-ignore (Array.isArray(condition.desiredValue) && condition.desiredValue.includes(-1)) ) { conditionValues.push({ value: true, combinationOperator: condition.combinationOperator, }); } else { conditionValues.push({ value: false, combinationOperator: condition.combinationOperator, }) } } else { // true if watchOptionId not found conditionValues.push({ value: true, combinationOperator: condition.combinationOperator, }); } } } let finalCondition = true; if (conditionValues.length) { finalCondition = conditionValues.reduce(function(preCondition, condition) { if (condition.combinationOperator === 'or') { return preCondition || condition.value; } else { return preCondition && condition.value; } }, false); if (conditionValues.length == 1) finalCondition = conditionValues[0].value; } return finalCondition; } return true; } private isExcessiveSizeOption(obj: IOption) { const isLabelSize = obj.label && obj.label.toLowerCase().includes('size'); const sizeValues = ['S', 'M', 'L', 'XL', '2XL', '3XL', '4XL']; let valuesToCheck; if ('dropdownValues' in obj) { valuesToCheck = obj.dropdownValues; } else if ('swatchValues' in obj) { valuesToCheck = obj.swatchValues; } const areValuesSizes = valuesToCheck && valuesToCheck.some(value => sizeValues.includes(value.valueName) ); return isLabelSize && areValuesSizes; } private isExcessiveColorOption(obj: IOption) { const isLabelColor = obj.label && obj.label.toLowerCase().includes('color'); const validateLabelColors = [ 'Shirt\'s Color', 'Hoodie\'s Color', 'Hoodie Color', 'Sweatshirt\'s Color', 'Sweatshirt Color', 'Tank Top\'s Color', 'Tank Top Color', 'T-Shirt\'s Color', 'T-Shirt Color', 'Polo\'s Color', 'Polo Color', 'Tee Color' ] let isTShirt = false; if (this.breadcrumbs && this.breadcrumbs.length > 2 && this.breadcrumbs[2].id == 7) { isTShirt = true; } let isValidLabelColor: boolean; if (isTShirt) { isValidLabelColor = validateLabelColors.some(label => obj.label.toLowerCase().includes(label.toLowerCase())); } else { isValidLabelColor = validateLabelColors.includes(obj.label); } return isLabelColor && isValidLabelColor; } private changeSwatchOrDropdownValue(option: SwatchOption | DropdownOption): void { let values; if ("dropdownValues" in option) { values = option.dropdownValues; } if ("swatchValues" in option) { values = option.swatchValues; } if (!values) { return; } let value = values.find((item: SwatchValue | DropdownValue) => item.id === option.currentValue); if (!value) { value = values.find((item: SwatchValue | DropdownValue) => item.selected); } if (!value) { return; } if (value.groupId) { this.groupId = value.groupId; } let functionItems = option.functionItems; if (!functionItems) { return; } for (let functionItem of functionItems) { let elementId = functionItem.elementId; // find element in template let element = this.template.elements.find( item => item.elementId == elementId ); if (!element) { continue; } if (functionItem.type == 'dynamic-image') { let valueId = this.getValueId(value); let dynamicImageElement: DynamicImageElement = element as DynamicImageElement; if (dynamicImageElement.config.images?.length && ("library" in dynamicImageElement.config)) { let image = dynamicImageElement.config.images?.find( item => item.order == valueId ); if (image) { dynamicImageElement.config.imageUrl = image.value; dynamicImageElement.config.imageId = valueId; } } else { dynamicImageElement.config.imageId = valueId; } // check if element has child elements if (dynamicImageElement.config.childElementIds?.length) { for (let childElementId of dynamicImageElement.config.childElementIds) { let childElement = this.template.elements.find( item => item.elementId == childElementId ) as DynamicImageElement; if (childElement) { childElement.config.imageId = valueId; if (dynamicImageElement.config.imageUrl) { childElement.config.imageUrl = dynamicImageElement.config.imageUrl; this.setElement(childElement, childElementId) } } } } this.setElement(dynamicImageElement, elementId); } else if (functionItem.type == 'font-type') { let valueId = this.getValueId(value); let textInputElement = element as TextInputElement; if (textInputElement.config.fonts) { let font = textInputElement.config.fonts.find( item => item.order == valueId ); if (font) { textInputElement.config.textConfig.font = font.value; textInputElement.config.fontId = valueId; } } else { textInputElement.config.fontId = valueId; } // check if element has child elements if (textInputElement.config.childElementIds?.length) { for (let childElementId of textInputElement.config.childElementIds) { let childElement = this.template.elements.find( item => item.elementId == childElementId ) as TextInputElement; if (childElement) { childElement.config.fontId = valueId; if (textInputElement.config.textConfig.font) { childElement.config.textConfig.font = textInputElement.config.textConfig.font; this.setElement(childElement, childElementId) } } } } this.setElement(textInputElement, elementId); } else if (functionItem.type == 'text-color') { let valueId = this.getValueId(value); let textInputElement = element as TextInputElement; if (textInputElement.config.colors) { let color = textInputElement.config.colors.find( item => item.order == valueId ); if (color) { textInputElement.config.textConfig.fill = color.value; textInputElement.config.colorId = valueId; } } else { textInputElement.config.colorId = valueId; } // check if element has child elements if (textInputElement.config.childElementIds?.length) { for (let childElementId of textInputElement.config.childElementIds) { let childElement = this.template.elements.find( item => item.elementId == childElementId ) as TextInputElement; if (childElement) { if (textInputElement.config.textConfig.fill) { childElement.config.textConfig.fill = textInputElement.config.textConfig.fill } childElement.config.colorId = valueId; this.setElement(childElement, childElementId) } } } this.setElement(textInputElement, elementId); } else if (functionItem.type === 'vector') { let valueId = this.getValueId(value); let vectorElement: VectorElement = element as VectorElement; if (vectorElement.config.vectors?.length) { let image = vectorElement.config.vectors?.find( item => item.order == valueId ); if (image) { vectorElement.config.imageUrl = image.value; vectorElement.config.vectorId = valueId; } } else { vectorElement.config.vectorId = valueId; } // check if element has child elements if (vectorElement.config.childElementIds?.length) { for (let childElementId of vectorElement.config.childElementIds) { let childElement = this.template.elements.find( item => item.elementId == childElementId ) as DynamicImageElement; if (childElement) { childElement.config.imageId = valueId; if (vectorElement.config.imageUrl) { childElement.config.imageUrl = vectorElement.config.imageUrl; this.setElement(childElement, childElementId); } } } } this.setElement(element, elementId); } } } private changeImageUploadValue(option: ImageUploadOption) :void { if (!option.fileUploadImageId || typeof option.currentValue === 'undefined') { return; } const imageUrl = option.currentValue.toString(); let uploadImageElementId = option.fileUploadImageId; let uploadImageElement = this.template.elements.find( item => item.elementId == uploadImageElementId ) as DynamicImageElement | ImagePlaceHolderElement; if (uploadImageElement) { uploadImageElement.config.imageUrl = imageUrl; // check if element has child elements if (uploadImageElement.config.childElementIds?.length) { for (let childElementId of uploadImageElement.config.childElementIds) { let childElement = this.template.elements.find( item => item.elementId == childElementId ) as DynamicImageElement | ImagePlaceHolderElement; if (childElement) { childElement.config.imageUrl = imageUrl; // set element to template.elements let index = this.template.elements.findIndex( item => item.elementId == childElementId ); if (index >= 0) { this.template.elements[index] = childElement; } } } } // set element to template.elements let index = this.template.elements.findIndex( item => item.elementId == uploadImageElementId ); if (index >= 0) { this.template.elements[index] = uploadImageElement; } } } private changeTextInputValue(option: TextInputOption): void { if (typeof option.currentValue === 'undefined') { return; } let text = option.currentValue.toString(); let functionItems = option.functionItems; if (!functionItems) { return; } for (let functionItem of functionItems) { let elementId = functionItem.elementId; // find element in template let element = this.template.elements.find( item => item.elementId == elementId ); if (!element) { continue; } if (functionItem.type == 'text-input') { let textInputElement = element as TextInputElement; textInputElement.config.textConfig.text = text; // check if element has child elements if (textInputElement.config.childElementIds?.length) { for (let childElementId of textInputElement.config.childElementIds) { let childElement = this.template.elements.find( item => item.elementId == childElementId ) as TextInputElement; if (childElement) { childElement.config.textConfig.text = text; // set element to template.elements let index = this.template.elements.findIndex( item => item.elementId == childElementId ); if (index >= 0) { this.template.elements[index] = childElement; } } } } // set element to template.elements let index = this.template.elements.findIndex( item => item.elementId == elementId ); if (index >= 0) { this.template.elements[index] = element; } } } } private isChangeTemplateOption(option: SwatchOption | DropdownOption): boolean { let functionItems = option.functionItems; if (!functionItems) { return false; } for (let functionItem of functionItems) { if (functionItem.type == 'change-template') { return true; } } return false; } private getValueId(value: SwatchValue | DropdownValue): number | string { let valueId: number | string = -1; if (value.imageId) { valueId = value.imageId; } else if (value.id) { if (typeof value.id === "string") { valueId = parseInt(value.id) + 1; } else { valueId = value.id + 1; } } else if (typeof value.value != 'undefined' && isNumeric(value.value)) { if (typeof value.value === "string") { valueId = parseInt(value.value) + 1; } else { valueId = value.value + 1; } } return valueId; } private setElement (element: IElement, elementId: string | number | undefined) { // set element to template.elements if (typeof elementId !== 'undefined') { let index = this.template.elements.findIndex( item => item.elementId == elementId ); if (index >= 0) { this.template.elements[index] = element; } } } private updateVisibleElement() { if (!this.template) { return; } this.buildShowHideElements(); const VISIBILITY = {show: 1, hide: 0}; for (const element of this.template.elements) { let opacity = VISIBILITY.hide; element.isShow = false; if (this.showElementIds.includes(element.elementId) || this.showElementIds.includes(element.elementId.toString())) { opacity = VISIBILITY.show; element.isShow = true; } else if (!this.hideElementIds.includes(element.elementId) && !this.hideElementIds.includes(element.elementId.toString())) { opacity = VISIBILITY.show; } // check show hide by group if (element.groupId && typeof this.selectedGroups != 'undefined') { const groupIds = this.selectedGroups.map((g) => { return g.id }); if (!groupIds.includes(element.groupId)) { opacity = VISIBILITY.hide; element.isShow = false; } } element.opacity = opacity; } } private buildShowHideElements() { let showElementIds: (string | number)[] = []; let hideElementIds: (string | number)[] = []; this.setSelectedGroups(); for (const thisOption of this.options) { // check show / hide on canvas // upload image id if ("fileUploadImageId" in thisOption && thisOption.fileUploadImageId) { const element = this.template.elements.find(function(element: IElement) { return element.elementId == thisOption.fileUploadImageId; }); if (element) { this.addShowConditionArray(element, thisOption, showElementIds, hideElementIds); } } // function items if (thisOption.functionItems && thisOption.functionItems.length) { // function items for (let j = 0; j < thisOption.functionItems.length; j++) { const elementId = thisOption.functionItems[j].elementId; const element = this.template.elements.find(function(element) { return element.elementId == elementId; }); if (element) { if (thisOption.functionItems[j].type == 'text' && element.type == 'dynamic_image') continue; this.addShowConditionArray(element, thisOption, showElementIds, hideElementIds); } } } } // convert to unique array showElementIds = showElementIds.filter(function (value, index, self) { return self.indexOf(value) === index; }); hideElementIds = hideElementIds.filter(function (value, index, self) { return self.indexOf(value) === index; }); this.showElementIds = showElementIds; this.hideElementIds = hideElementIds; } private setSelectedGroups() { const groups = []; if (typeof this.template.groups != 'undefined' && this.template.groups) { this.template.groups = JSON.parse(this.template.groups); // @ts-ignore for (let i = 0; i < this.template.groups.length; i++) { // @ts-ignore const group = this.template.groups[i]; // @ts-ignore if (this.groupId && parseInt(this.groupId) == parseInt(group.position)) { groups.push(group); } } } if (groups.length) { // @ts-ignore this.selectedGroups = groups; } } private addShowConditionArray = (element: IElement, option: IOption, showElementIds: (string | number)[], hideElementIds: (string | number)[]) => { const childElementIds = element.childElementIds ?? []; let emptyValues = true; if ("swatchValues" in option) { emptyValues = (option.swatchValues && !option.swatchValues.length); } if ("dropdownValues" in option) { emptyValues = (option.dropdownValues && !option.dropdownValues.length); } if ((option.isShow || typeof option.hideVisually == 'undefined') && !showElementIds.includes(element.elementId) && !emptyValues) { showElementIds.push(element.elementId); if (childElementIds && childElementIds.length) { for (let k = 0; k < childElementIds.length; k++) { showElementIds.push(childElementIds[k]); } } } else { hideElementIds.push(element.elementId); if (childElementIds && childElementIds.length) { for (let k = 0; k < childElementIds.length; k++) { hideElementIds.push(childElementIds[k]); } } } } getFabricConfig(element: IElement) { switch (element.type) { case 'text_box_circular': return getRenderConfigTextBoxCircular(element as CircularTextInputElement); case 'text_box': return getRenderConfigTextBox(element as TextInputElement); case 'dynamic_image': case 'image_placeholder': return getRenderConfigImage(element as DynamicImageElement); case 'vector_eps': return getRenderConfigVectorEps(element as VectorElement); } } } export default Customization;