@mcmhomes/panorama-viewer
Version:
Provides React components to render panoramas.
925 lines (862 loc) • 30 kB
JSX
import {getVariationJsonData} from './utils/PanoramaVariationObtainingUtils.jsx';
import {getCorrectedGivenProps} from './utils/PanoramaPropsParsingUtils.jsx';
import {ARRAY, contains, each, filter, find, findIndex, findIndexValue, getEmptyImageSrc, INT_LAX, IS_ARRAY, IS_OBJECT, ISSET, map, mapToArray, OBJECT, STRING} from './utils/PanoramaUtils.jsx';
/**
* Returns the version string of the home (unix timestamp, in millis).
*
* @param {Object} params
* @param {string} params.homeId
* @param {string|null} [params.homeVersion]
* @param {string|null} [params.host]
* @returns {Promise<string>}
*/
export const getHomeVersion = async (params) =>
{
const {homeId, homeVersion, host} = params;
const {version} = await getVariationJsonData({homeId, homeVersion, host});
return STRING(version);
};
/**
* Returns the version date of the home. Will return new Date(0) if the version timestamp is invalid.
*
* The version date is the date when the home panoramas were upload.
*
* @param {Object} params
* @param {string} params.homeId
* @param {string|null} [params.homeVersion]
* @param {string|null} [params.host]
* @returns {Promise<Date>}
*/
export const getHomeVersionDate = async (params) =>
{
const {homeId, homeVersion, host} = params;
const versionInt = INT_LAX(await getHomeVersion({homeId, homeVersion, host}));
if(versionInt <= 0)
{
return new Date(0);
}
const versionDate = new Date(versionInt);
if(versionDate.toJSON() === null)
{
return new Date(0);
}
return versionDate;
};
/**
* Returns the render date of the home. Will return new Date(0) if the render timestamp is invalid.
*
* The render date is the date when the home panoramas were rendered.
*
* @param {Object} params
* @param {string} params.homeId
* @param {string|null} [params.homeVersion]
* @param {string|null} [params.host]
* @returns {Promise<Date>}
*/
export const getHomeRenderDate = async (params) =>
{
const {homeId, homeVersion, host} = params;
const {data:variationData} = await getVariationJsonData({homeId, homeVersion, host});
const renderDateInt = INT_LAX(variationData?.renderDate);
if(renderDateInt <= 0)
{
return new Date(0);
}
const renderDate = new Date(renderDateInt);
if(renderDate.toJSON() === null)
{
return new Date(0);
}
return renderDate;
};
/**
* Returns an object with sku group IDs as keys, and arrays of SKUs as values.
*
* @param {Object} params
* @param {string} params.homeId
* @param {string|null} [params.homeVersion]
* @param {string|null} [params.host]
* @param {string|null} [params.styleId]
* @param {string|null} [params.locationId]
* @returns {Promise<Object.<string|symbol,string[]>>}
*/
export const getAvailableSkusGrouped = async (params) =>
{
const {homeId, homeVersion, host, styleId:givenStyleId, locationId:givenLocationId} = params;
const {styleId, locationId} = getCorrectedGivenProps({styleId:givenStyleId, locationId:givenLocationId});
const {data:variationData} = await getVariationJsonData({homeId, homeVersion, host});
let onlyVariationStyleIds = null;
let onlyVariationGroupIds = null;
if(locationId)
{
onlyVariationStyleIds = {};
onlyVariationGroupIds = {};
each(variationData?.locations, location =>
{
if(location?.locationId === locationId)
{
each(location?.supportedStyleIds, styleId =>
{
if(styleId)
{
onlyVariationStyleIds[styleId] = true;
}
});
each(location?.variationGroups, variationGroup =>
{
if(variationGroup?.groupId)
{
onlyVariationGroupIds[variationGroup?.groupId] = true;
}
each(variationGroup?.layerDependencyGroupIds, layerDependencyGroupId =>
{
if(layerDependencyGroupId)
{
onlyVariationGroupIds[layerDependencyGroupId] = true;
}
});
});
}
});
}
const result = {};
each(variationData?.styles, style =>
{
if((!styleId || (style?.styleId === styleId)) && ((onlyVariationStyleIds === null) || (style?.styleId in onlyVariationStyleIds)))
{
each(style?.variationGroups, variationGroup =>
{
if(variationGroup?.groupId && ((onlyVariationGroupIds === null) || (variationGroup?.groupId in onlyVariationGroupIds)))
{
const variationSkus = result[variationGroup?.groupId] ?? [];
each(variationGroup?.variations, variation =>
{
if(variation?.sku && !variationSkus.includes(variation?.sku))
{
variationSkus.push(variation?.sku);
}
});
result[variationGroup?.groupId] = variationSkus;
}
});
}
});
return result;
};
/**
* Returns an array of SKUs.
*
* @param {Object} params
* @param {string} params.homeId
* @param {string|null} [params.homeVersion]
* @param {string|null} [params.host]
* @param {string|null} [params.styleId]
* @param {string|null} [params.locationId]
* @returns {Promise<string[]>}
*/
export const getAvailableSkus = async (params) =>
{
const {homeId, homeVersion, host, styleId, locationId} = params;
const skusGrouped = await getAvailableSkusGrouped({homeId, homeVersion, host, styleId, locationId});
const result = [];
each(skusGrouped, skus =>
{
result.push(...skus);
});
return result;
};
/**
* Returns an array of style IDs.
*
* @param {Object} params
* @param {string} params.homeId
* @param {string|null} [params.homeVersion]
* @param {string|null} [params.host]
* @param {string|null} [params.locationId]
* @returns {Promise<string[]>}
*/
export const getAvailableStyleIds = async (params) =>
{
const {homeId, homeVersion, host, locationId:givenLocationId} = params;
const {locationId} = getCorrectedGivenProps({locationId:givenLocationId});
const {data:variationData} = await getVariationJsonData({homeId, homeVersion, host});
const location = find(variationData?.locations, location => (location?.locationId === locationId));
return filter(mapToArray(variationData?.styles, style => (!location || contains(location?.supportedStyleIds, style?.styleId)) ? STRING(style?.styleId) : null));
};
/**
* Returns an array of location IDs.
*
* @param {Object} params
* @param {string} params.homeId
* @param {string|null} [params.homeVersion]
* @param {string|null} [params.host]
* @param {string|null} [params.styleId]
* @returns {Promise<string[]>}
*/
export const getAvailableLocationIds = async (params) =>
{
const {homeId, homeVersion, host, styleId:givenStyleId} = params;
const {styleId} = getCorrectedGivenProps({styleId:givenStyleId});
const {data:variationData} = await getVariationJsonData({homeId, homeVersion, host});
return filter(mapToArray(variationData?.locations, location => (!styleId || contains(location?.supportedStyleIds, styleId)) ? STRING(location?.locationId) : null));
};
/**
* Returns the default selected SKUs for the given home.
* Returns an object with sku group IDs as keys, and the default SKUs as values.
*
* @param {Object} params
* @param {string} params.homeId
* @param {string|null} [params.homeVersion]
* @param {string|null} [params.host]
* @param {string|null} [params.styleId]
* @param {string|null} [params.locationId]
* @returns {Promise<Object.<string|symbol,string>>}
*/
export const getDefaultSkusGrouped = async (params) =>
{
const {homeId, homeVersion, host, styleId:givenStyleId, locationId:givenLocationId} = params;
const {data:variationData} = await getVariationJsonData({homeId, homeVersion, host});
const {styleId, locationId} = await getCurrentStyleAndLocationId({homeId, homeVersion, host, styleId:givenStyleId, locationId:givenLocationId});
const result = await getAvailableSkusGrouped({homeId, homeVersion, host, styleId, locationId});
return map(result, (skus, groupId) =>
{
const style = find(variationData?.styles, style => (style?.styleId === styleId));
const variationGroup = find(style?.variationGroups, variationGroup => (variationGroup?.groupId === groupId));
const defaultVariationIndex = INT_LAX(variationGroup?.defaultVariationIndex);
return skus[defaultVariationIndex];
});
};
/**
* Returns the default selected SKUs for the given home.
* Returns an array of SKUs.
*
* @param {Object} params
* @param {string} params.homeId
* @param {string|null} [params.homeVersion]
* @param {string|null} [params.host]
* @param {string|null} [params.styleId]
* @param {string|null} [params.locationId]
* @returns {Promise<string[]>}
*/
export const getDefaultSkus = async (params) =>
{
const {homeId, homeVersion, host, styleId, locationId} = params;
const skusGrouped = await getDefaultSkusGrouped({homeId, homeVersion, host, styleId, locationId});
const result = [];
each(skusGrouped, skus =>
{
result.push(...skus);
});
return result;
};
/**
* Returns the default selected style ID for the given home.
*
* @param {Object} params
* @param {string} params.homeId
* @param {string|null} [params.homeVersion]
* @param {string|null} [params.host]
* @param {string|null} [params.locationId]
* @returns {Promise<string>}
*/
export const getDefaultStyleId = async (params) =>
{
const {homeId, homeVersion, host, locationId:givenLocationId} = params;
const {styleId, locationId} = await getCurrentStyleAndLocationId({homeId, homeVersion, host, locationId:givenLocationId});
return styleId;
};
/**
* Returns the default selected location ID for the given home.
*
* @param {Object} params
* @param {string} params.homeId
* @param {string|null} [params.homeVersion]
* @param {string|null} [params.host]
* @param {string|null} [params.styleId]
* @returns {Promise<string>}
*/
export const getDefaultLocationId = async (params) =>
{
const {homeId, homeVersion, host, styleId:givenStyleId} = params;
const {styleId, locationId} = await getCurrentStyleAndLocationId({homeId, homeVersion, host, styleId:givenStyleId});
return locationId;
};
/**
* Returns the currently selected SKUs for the given home.
* Returns an object with sku group IDs as keys, and the current SKUs as values.
*
* @param {Object} params
* @param {string} params.homeId
* @param {string|null} [params.homeVersion]
* @param {string|null} [params.host]
* @param {string|null} [params.styleId]
* @param {string|null} [params.locationId]
* @param {Object.<string|symbol,string>|null} [params.skus]
* @param {boolean|null} [params.warnings]
* @returns {Promise<Object.<string|symbol,string>>}
*/
export const getCurrentSkusGrouped = async (params) =>
{
const {homeId, homeVersion, host, styleId:givenStyleId, locationId:givenLocationId, skus, warnings} = params;
const {data:variationData} = await getVariationJsonData({homeId, homeVersion, host});
const {styleId, locationId} = await getCurrentStyleAndLocationId({homeId, homeVersion, host, styleId:givenStyleId, locationId:givenLocationId});
const skuGroups = await getAvailableSkusGrouped({homeId, homeVersion, host, styleId, locationId});
const selectedSkuGroupIndex = mapToArray(skuGroups, (skus, groupId) =>
{
const style = find(variationData?.styles, style => (style?.styleId === styleId));
const variationGroup = find(style?.variationGroups, variationGroup => (variationGroup?.groupId === groupId));
return INT_LAX(variationGroup?.defaultVariationIndex);
});
if(IS_ARRAY(skus))
{
each(skus, sku =>
{
let skuFound = false;
each(skuGroups, (skuGroup, groupId) =>
{
let skuGroupIndex = findIndex(skuGroup, skuGroupSku => (skuGroupSku === sku));
if(ISSET(skuGroupIndex))
{
selectedSkuGroupIndex[groupId] = skuGroupIndex;
skuFound = true;
}
});
if(!skuFound && warnings)
{
console.warn('SKU not found:', sku);
}
});
}
else if(IS_OBJECT(skus))
{
each(skus, (sku, groupId) =>
{
const skuGroup = skuGroups[groupId];
if(ISSET(skuGroup))
{
const skuGroupIndex = findIndex(skuGroup, skuGroupSku => (skuGroupSku === sku));
if(ISSET(skuGroupIndex))
{
selectedSkuGroupIndex[groupId] = skuGroupIndex;
}
else if(warnings)
{
console.warn('SKU not found in group:', sku, skuGroup);
}
}
else if(warnings)
{
console.warn('SKU Group ID not found:', groupId);
}
});
}
return map(skuGroups, (skus, groupId) => skus[selectedSkuGroupIndex[groupId]]);
};
/**
* Returns the currently selected SKUs for the given home.
* Returns an array of SKUs.
*
* @param {Object} params
* @param {string} params.homeId
* @param {string|null} [params.homeVersion]
* @param {string|null} [params.host]
* @param {string|null} [params.styleId]
* @param {string|null} [params.locationId]
* @param {Object.<string|symbol,string>|null} [params.skus]
* @param {boolean|null} [params.warnings]
* @returns {Promise<string[]>}
*/
export const getCurrentSkus = async (params) =>
{
const {homeId, homeVersion, host, styleId, locationId, skus, warnings} = params;
const skuGroups = await getCurrentSkusGrouped({homeId, homeVersion, host, styleId, locationId, skus, warnings});
const result = [];
each(skuGroups, skus =>
{
result.push(...skus);
});
return result;
};
/**
* Returns the currently selected style and location IDs for the given home.
*
* @internal
* @param {Object} params
* @param {string} params.homeId
* @param {string|null} [params.homeVersion]
* @param {string|null} [params.host]
* @param {string|null} [params.styleId]
* @param {string|null} [params.locationId]
* @returns {Promise<{styleId:string, locationId:string}>}
*/
const getCurrentStyleAndLocationId = async (params) =>
{
const {homeId, homeVersion, host, styleId:givenStyleId, locationId:givenLocationId} = params;
const {data:variationData} = await getVariationJsonData({homeId, homeVersion, host});
const getStyleId = ({locationId = undefined}) =>
{
const location = !locationId ? null : find(variationData?.locations, location => (location?.locationId === locationId));
let result = null;
each(variationData?.styles, style =>
{
if(!location || contains(location?.supportedStyleIds, style?.styleId))
{
result = style?.styleId;
return false;
}
});
return STRING(result);
};
const getLocationId = ({styleId = undefined}) =>
{
const style = !styleId ? null : find(variationData?.styles, style => (style?.styleId === styleId));
let result = null;
each(variationData?.locations, location =>
{
if(!style || contains(location?.supportedStyleIds, styleId))
{
result = location?.locationId;
return false;
}
});
return STRING(result);
};
let {styleId, locationId} = getCorrectedGivenProps({styleId:givenStyleId, locationId:givenLocationId});
if(!styleId || !locationId)
{
if(!styleId && !locationId)
{
styleId = getStyleId({});
}
if(styleId)
{
locationId = getLocationId({styleId});
}
else
{
styleId = getStyleId({locationId});
}
}
return {styleId, locationId};
};
/**
* Returns the currently selected style ID for the given home.
*
* @param {Object} params
* @param {string} params.homeId
* @param {string|null} [params.homeVersion]
* @param {string|null} [params.host]
* @param {string|null} [params.styleId]
* @param {string|null} [params.locationId]
* @returns {Promise<string>}
*/
export const getCurrentStyleId = async (params) =>
{
const {homeId, homeVersion, host, styleId:givenStyleId, locationId:givenLocationId} = params;
const {styleId, locationId} = await getCurrentStyleAndLocationId({homeId, homeVersion, host, styleId:givenStyleId, locationId:givenLocationId});
return styleId;
};
/**
* Returns the currently selected location ID for the given home.
*
* @param {Object} params
* @param {string} params.homeId
* @param {string|null} [params.homeVersion]
* @param {string|null} [params.host]
* @param {string|null} [params.styleId]
* @param {string|null} [params.locationId]
* @returns {Promise<string>}
*/
export const getCurrentLocationId = async (params) =>
{
const {homeId, homeVersion, host, styleId:givenStyleId, locationId:givenLocationId} = params;
const {styleId, locationId} = await getCurrentStyleAndLocationId({homeId, homeVersion, host, styleId:givenStyleId, locationId:givenLocationId});
return locationId;
};
/**
* @exports
* @typedef {Object} ThumbnailOptions
* @property {number|null} [resolution] Ensures the thumbnail is at least this resolution. When 0 or below, or not provided, the highest quality image is used.
* @property {string|string[]|null} [format] Thumbnail format preference. An array uses the first available format. If none match, falls back to the default format.
* @property {boolean|null} [srcset] If true, the thumbnail URL will be returned as a srcset string. This is useful for responsive images. If the `resolution` parameter is also provided, it will return a DevicePixelRatio srcset (1x, 2x, etc), suitable for fixed-size images. If the `resolution` parameter is NOT provided, it will return a width-based srcset (320w, 640w, etc), in that case, the corresponding `<img>` element must include a `sizes` attribute to work correctly.
*/
/**
* @exports
* @callback AvailableThumbnailFunction
* @returns {boolean} True if thumbnails are available (for this home and version), false otherwise.
*/
/**
* @exports
* @callback LocationThumbnailFunction
* @param {{locationId:string} & ThumbnailOptions} params
* @returns {string} URL to the thumbnail, or a transparent 1x1 image if not found.
*/
/**
* @exports
* @callback StyleThumbnailFunction
* @param {{styleId:string} & ThumbnailOptions} params
* @returns {string} URL to the thumbnail, or a transparent 1x1 image if not found.
*/
/**
* @exports
* @callback SkuGroupThumbnailFunction
* @param {{styleId:string, groupId:string} & ThumbnailOptions} params
* @returns {string} URL to the thumbnail, or a transparent 1x1 image if not found.
*/
/**
* @exports
* @callback SkuThumbnailFunction
* @param {{styleId:string, groupId:string, sku:string} & ThumbnailOptions} params
* @returns {string} URL to the thumbnail, or a transparent 1x1 image if not found.
*/
/**
* @exports
* @typedef {Object} ThumbnailFunctions
* @property {AvailableThumbnailFunction} available
* @property {LocationThumbnailFunction} location
* @property {StyleThumbnailFunction} style
* @property {SkuGroupThumbnailFunction} skuGroup
* @property {SkuThumbnailFunction} sku
*/
/**
* Returns an object containing functions for obtaining thumbnail URLs.
*
* @param {Object} params
* @param {string} params.homeId
* @param {string|null} [params.homeVersion]
* @param {string|null} [params.host]
* @returns {Promise<ThumbnailFunctions>}
*/
export const getThumbnails = async (params) =>
{
const {homeId, homeVersion, host} = params;
const {url:homeUrl, data:variationData} = await getVariationJsonData({homeId, homeVersion, host});
const resolutions = mapToArray(variationData?.thumbnail?.resolutions, INT_LAX).sort((a, b) => a - b);
const formats = mapToArray(variationData?.thumbnail?.formats, str => STRING(str).toLowerCase());
const redirects = OBJECT(variationData?.thumbnail?.redirects);
const containsThumbnails = (variationData?.versionUnreal && (variationData?.versionUnreal >= '20250408') && (formats.length > 0) && (resolutions.length > 0));
const getEmptyResult = (params) =>
{
return getEmptyImageSrc() + (params?.srcset ? ' 1x' : '');
};
const getThumbnail = (thumbnailId, params) =>
{
if(!containsThumbnails)
{
return getEmptyResult(params);
}
thumbnailId = STRING(thumbnailId);
const {resolution, format, srcset} = OBJECT(params);
const formatsToUse = filter(mapToArray(ARRAY(format), str => STRING(str).toLowerCase()), format => formats.includes(format));
const formatToUse = formatsToUse[0] || formats[0] || null;
if(!formatToUse)
{
return getEmptyResult(params);
}
const getUrlForResolution = (res) =>
{
let thumbId = thumbnailId;
thumbId = redirects[thumbId] ?? thumbId;
thumbId = thumbId + '/' + res;
thumbId = redirects[thumbId] ?? thumbId;
return homeUrl + 'thumb_' + thumbId + '.' + formatToUse;
};
if(srcset && !ISSET(resolution))
{
const srcsetUrls = mapToArray(resolutions, res =>
{
res = STRING(res);
return getUrlForResolution(res) + ' ' + res + 'w';
});
return srcsetUrls.join(', ');
}
const {givenResolution, resolutionToUse} = (() =>
{
let resolutionToUse = resolutions[resolutions.length - 1] ?? 2000;
const givenResolution = INT_LAX(resolution);
if(givenResolution <= 0)
{
return {givenResolution:resolutionToUse, resolutionToUse};
}
each(resolutions, validResolution =>
{
if(givenResolution <= validResolution)
{
resolutionToUse = validResolution;
return false;
}
});
return {givenResolution, resolutionToUse};
})();
if(srcset)
{
const srcsetUrls = mapToArray(resolutions, res =>
{
return getUrlForResolution(STRING(res)) + ' ' + (res / givenResolution) + 'x';
});
return srcsetUrls.join(', ');
}
return getUrlForResolution(resolutionToUse);
};
const logMissingParameters = (functionName, paramsToCheck) =>
{
const missingParams = filter(mapToArray(paramsToCheck, (param, paramName) => (!param ? STRING(paramName) : null)));
const single = (missingParams.length === 1);
console.error('Missing' + (single ? ' a' : '') + ' thumbnails.' + functionName + '() parameter' + (single ? '' : 's') + ': "' + missingParams.join('", "') + '"');
};
return {
/** @type {AvailableThumbnailFunction} */
available:
() =>
{
return containsThumbnails;
},
/** @type {LocationThumbnailFunction} */
location:
(params) =>
{
if(!params?.locationId)
{
logMissingParameters('location', {'locationId':params?.locationId});
return getEmptyResult(params);
}
const {locationId} = getCorrectedGivenProps(params);
const locationIndex = findIndex(variationData?.locations, location => (location?.locationId === locationId));
if(!ISSET(locationIndex))
{
console.warn('Location with ID not found:', params?.locationId);
return getEmptyResult(params);
}
return getThumbnail('l_' + locationIndex, params);
},
/** @type {StyleThumbnailFunction} */
style:
(params) =>
{
if(!params?.styleId)
{
logMissingParameters('style', {'styleId':params?.styleId});
return getEmptyResult(params);
}
const {styleId} = getCorrectedGivenProps(params);
const styleIndex = findIndex(variationData?.styles, style => (style?.styleId === styleId));
if(!ISSET(styleIndex))
{
console.warn('Style with ID not found:', params?.styleId);
return getEmptyResult(params);
}
return getThumbnail('s_' + styleIndex, params);
},
/** @type {SkuGroupThumbnailFunction} */
skuGroup:
(params) =>
{
if(!params?.styleId || !params?.groupId)
{
logMissingParameters('skuGroup', {'styleId':params?.styleId, 'groupId':params?.groupId});
return getEmptyResult(params);
}
const {styleId, groupId} = getCorrectedGivenProps(params);
const {index:styleIndex, value:style} = OBJECT(findIndexValue(variationData?.styles, style => (style?.styleId === styleId)));
if(!ISSET(style))
{
console.warn('Style with ID not found:', params?.styleId);
return getEmptyResult(params);
}
const groupIndex = findIndex(style?.variationGroups, variationGroup => (variationGroup?.groupId === groupId));
if(!ISSET(groupIndex))
{
console.warn('SKU Group with ID not found:', params?.groupId, ' inside style:', params?.styleId);
return getEmptyResult(params);
}
return getThumbnail('s_' + styleIndex + '_' + groupIndex, params);
},
/** @type {SkuThumbnailFunction} */
sku:
(params) =>
{
if(!params?.styleId || !params?.groupId || !params?.sku)
{
logMissingParameters('sku', {'styleId':params?.styleId, 'groupId':params?.groupId, 'sku':params?.sku});
return getEmptyResult(params);
}
const {styleId, groupId, sku:skuId} = getCorrectedGivenProps(params);
const {index:styleIndex, value:style} = OBJECT(findIndexValue(variationData?.styles, style => (style?.styleId === styleId)));
if(!ISSET(style))
{
console.warn('Style with ID not found:', params?.styleId);
return getEmptyResult(params);
}
const {index:groupIndex, value:group} = OBJECT(findIndexValue(style?.variationGroups, variationGroup => (variationGroup?.groupId === groupId)));
if(!ISSET(group))
{
console.warn('SKU Group with ID not found:', params?.groupId, ' inside style:', params?.styleId);
return getEmptyResult(params);
}
const skuIndex = findIndex(group?.variations, variation => (variation?.sku === skuId));
if(!ISSET(skuIndex))
{
console.warn('SKU with ID not found:', params?.sku, ' inside group:', params?.groupId, ' inside style:', params?.styleId);
return getEmptyResult(params);
}
return getThumbnail('s_' + styleIndex + '_' + groupIndex + '_' + skuIndex, params);
},
};
};
/**
* @exports
* @callback AvailableNamesFunction
* @returns {boolean} True if names are available (for this home and version), false otherwise.
*/
/**
* @exports
* @callback LocationNamesFunction
* @param {{locationId:string}} params
* @returns {string} The name of the location, or an empty string if not found.
*/
/**
* @exports
* @callback StyleNamesFunction
* @param {{styleId:string}} params
* @returns {string} The name of the style, or an empty string if not found.
*/
/**
* @exports
* @callback SkuGroupNamesFunction
* @param {{styleId:string, groupId:string}} params
* @returns {string} The name of the SKU group, or an empty string if not found.
*/
/**
* @exports
* @callback SkuNamesFunction
* @param {{styleId:string, groupId:string, sku:string}} params
* @returns {string} The name of the SKU, or an empty string if not found.
*/
/**
* @exports
* @typedef {Object} NamesFunctions
* @property {AvailableNamesFunction} available
* @property {LocationNamesFunction} location
* @property {StyleNamesFunction} style
* @property {SkuGroupNamesFunction} skuGroup
* @property {SkuNamesFunction} sku
*/
/**
* Returns an object containing functions for obtaining display names.
*
* @param {Object} params
* @param {string} params.homeId
* @param {string|null} [params.homeVersion]
* @param {string|null} [params.host]
* @returns {Promise<NamesFunctions>}
*/
export const getNames = async (params) =>
{
const {homeId, homeVersion, host} = params;
const {data:variationData} = await getVariationJsonData({homeId, homeVersion, host});
const containsNames = (variationData?.versionUnreal && (variationData?.versionUnreal >= '20250408'));
const logMissingParameters = (functionName, paramsToCheck) =>
{
const missingParams = filter(mapToArray(paramsToCheck, (param, paramName) => (!param ? STRING(paramName) : null)));
const single = (missingParams.length === 1);
console.error('Missing' + (single ? ' a' : '') + ' names.' + functionName + '() parameter' + (single ? '' : 's') + ': "' + missingParams.join('", "') + '"');
};
return {
/** @type {AvailableNamesFunction} */
available:
() =>
{
return containsNames;
},
/** @type {LocationNamesFunction} */
location:
(params) =>
{
if(!params?.locationId)
{
logMissingParameters('location', {'locationId':params?.locationId});
return '';
}
const {locationId} = getCorrectedGivenProps(params);
const location = find(variationData?.locations, location => (location?.locationId === locationId));
if(!location)
{
console.warn('Location with ID not found:', params?.locationId);
return '';
}
return STRING(location?.name ?? locationId);
},
/** @type {StyleNamesFunction} */
style:
(params) =>
{
if(!params?.styleId)
{
logMissingParameters('style', {'styleId':params?.styleId});
return '';
}
const {styleId} = getCorrectedGivenProps(params);
const style = find(variationData?.styles, style => (style?.styleId === styleId));
if(!style)
{
console.warn('Style with ID not found:', params?.styleId);
return '';
}
return STRING(style?.name ?? styleId);
},
/** @type {SkuGroupNamesFunction} */
skuGroup:
(params) =>
{
if(!params?.styleId || !params?.groupId)
{
logMissingParameters('skuGroup', {'styleId':params?.styleId, 'groupId':params?.groupId});
return '';
}
const {styleId, groupId} = getCorrectedGivenProps(params);
const style = find(variationData?.styles, style => (style?.styleId === styleId));
if(!style)
{
console.warn('Style with ID not found:', params?.styleId);
return '';
}
const group = find(style?.variationGroups, variationGroup => (variationGroup?.groupId === groupId));
if(!group)
{
console.warn('SKU Group with ID not found:', params?.groupId, ' inside style:', params?.styleId);
return '';
}
return STRING(group?.name ?? groupId);
},
/** @type {SkuNamesFunction} */
sku:
(params) =>
{
if(!params?.styleId || !params?.groupId || !params?.sku)
{
logMissingParameters('sku', {'styleId':params?.styleId, 'groupId':params?.groupId, 'sku':params?.sku});
return '';
}
const {styleId, groupId, sku:skuId} = getCorrectedGivenProps(params);
const style = find(variationData?.styles, style => (style?.styleId === styleId));
if(!style)
{
console.warn('Style with ID not found:', params?.styleId);
return '';
}
const group = find(style?.variationGroups, variationGroup => (variationGroup?.groupId === groupId));
if(!group)
{
console.warn('SKU Group with ID not found:', params?.groupId, ' inside style:', params?.styleId);
return '';
}
const sku = find(group?.variations, variation => (variation?.sku === skuId));
if(!sku)
{
console.warn('SKU with ID not found:', params?.sku, ' inside group:', params?.groupId, ' inside style:', params?.styleId);
return '';
}
return STRING(sku?.name ?? skuId);
},
};
};