🕐 커밋 시간: 2025. 11. 14. 15:36:07 📊 변경 통계: • 총 파일: 4개 • 추가: +27줄 • 삭제: -333줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/utils/helperMethods.js ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v3.jsx ~ com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/utils/helperMethods.js (javascript): ✅ Added: getFormattingDate() 📄 com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx (javascript): 🔄 Modified: extractProductMeta() 📄 com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v3.jsx (javascript): ✅ Added: Spottable() 📄 com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx (javascript): 🔄 Modified: normalizeModalStyle() ❌ Deleted: handleEvent() 🔧 주요 변경 내용: • 공통 유틸리티 함수 최적화 Performance: 코드 최적화로 성능 개선 기대
533 lines
15 KiB
JavaScript
533 lines
15 KiB
JavaScript
import { Job } from '@enact/core/util';
|
|
|
|
import stringReSourceDe from '../../resources/de/strings.json';
|
|
import stringReSourceEn from '../../resources/en/strings.json';
|
|
import stringReSourceGb from '../../resources/gb/strings.json';
|
|
import stringReSourceRu from '../../resources/ru/strings.json';
|
|
import { ERROR_MESSAGES_GROUPS, SECRET_KEY } from './Config';
|
|
|
|
let _boundingRectCache = {};
|
|
const BOUNDING_RECT_IGNORE_TIME = 10;
|
|
|
|
const generateUUID = () => {
|
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
const r = (Math.random() * 16) | 0;
|
|
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
return v.toString(16);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* prevent multiple call
|
|
* @param {*} node
|
|
* @returns
|
|
*/
|
|
const clearBoundingRectCache = new Job(() => {
|
|
_boundingRectCache = {};
|
|
}, 5000);
|
|
|
|
export const getBoundingClientRect = (node) => {
|
|
clearBoundingRectCache.start();
|
|
if (!node.dataset.uuid) {
|
|
node.dataset.uuid = generateUUID();
|
|
}
|
|
|
|
const uuid = node.dataset.uuid;
|
|
|
|
if (_boundingRectCache[uuid]) {
|
|
if (Date.now() - _boundingRectCache[uuid].called < BOUNDING_RECT_IGNORE_TIME) {
|
|
return _boundingRectCache[uuid].boundingRect;
|
|
}
|
|
}
|
|
|
|
const boundingRect = node.getBoundingClientRect();
|
|
|
|
_boundingRectCache[uuid] = {
|
|
boundingRect: boundingRect?.toJSON(),
|
|
called: Date.now(),
|
|
};
|
|
|
|
return boundingRect;
|
|
};
|
|
|
|
const stringReSource = {
|
|
US: stringReSourceEn,
|
|
GB: stringReSourceGb,
|
|
DE: stringReSourceDe,
|
|
RU: stringReSourceRu,
|
|
};
|
|
|
|
export const $L = (str) => {
|
|
let languageSetting = 'system';
|
|
let resourceKey = '';
|
|
|
|
if (typeof window === 'object' && window.store) {
|
|
languageSetting = window.store.getState().localSettings.languageSetting;
|
|
if (languageSetting === 'system') {
|
|
resourceKey = window.store.getState().common.httpHeader?.cntry_cd;
|
|
} else {
|
|
resourceKey = languageSetting;
|
|
}
|
|
}
|
|
const resource = stringReSource[resourceKey];
|
|
if (typeof str === 'object') {
|
|
if (resource && resource[str.key]) {
|
|
return resource[str.key].replace(/{br}/g, '{br}');
|
|
} else {
|
|
return str.value;
|
|
}
|
|
} else if (resource && resource[str]) {
|
|
return resource[str].replace(/{br}/g, '{br}');
|
|
}
|
|
return str && str.replace(/{br}/g, '{br}');
|
|
};
|
|
|
|
export const createQueryString = (object) => {
|
|
const parts = [];
|
|
for (const key of Object.getOwnPropertyNames(object)) {
|
|
if (object[key] !== null && object[key] !== undefined && object[key] !== '') {
|
|
parts.push(`${key}=${encodeURIComponent(object[key])}`);
|
|
}
|
|
}
|
|
return parts.join('&');
|
|
};
|
|
|
|
export const wait = (time) => {
|
|
return new Promise((resolve) => {
|
|
setTimeout(() => {
|
|
resolve();
|
|
}, time);
|
|
});
|
|
};
|
|
|
|
export const scaleW = (value) => {
|
|
if (typeof window === 'object') {
|
|
return value * (window.innerWidth / 1920);
|
|
}
|
|
return value;
|
|
};
|
|
|
|
export const scaleH = (value) => {
|
|
if (typeof window === 'object') {
|
|
return value * (window.innerHeight / 1080);
|
|
}
|
|
return value;
|
|
};
|
|
|
|
//for test
|
|
|
|
let localLaunchParams = {
|
|
// contentTarget: "V3_8001_Tv_PD_1_A639507_0_766",
|
|
// contentTarget: "V3_8001_Tv_LS_1_USQVC_0_766",
|
|
// contentTarget: "V3_8001_Tv_VS_1_6643524e649d1d4bf0ad523f_0_766",
|
|
// contentTarget: "V3_8001_Tv_TD_11_820_3705018",
|
|
// contentTarget: "V3_8001_Tv_HD_7_527_0",
|
|
// contentTarget: "V3_8001_Tv_HP_7_527_0",
|
|
// contentTarget: "V3_8001_Tv_WE_twin",
|
|
// contentTarget: "V3_8001_Tv_OS_1006_Home",
|
|
// contentTarget: "V3_8001_Tv_BS",
|
|
// contentTarget: "V3_8001_Tv_PS",
|
|
// contentTarget: "V3_8001_Tv_SC_1000_Fashion_Item",
|
|
// contentTarget: "V3_8001_Tv_FB_1",
|
|
// contentTarget: "V3_8001_Tv_TC_824",
|
|
// contentTarget: "V3_2000_HOMEBANNER:TOP_MT_1",
|
|
// contentTarget: "V3_3000_AD:TM_TM_8",
|
|
// contentTarget: "V3_3000_AD:SR_SR_1",
|
|
// contentTarget: "V3_2006_HOMEBANNER:6241018_HP_1_1307_2",
|
|
// contentTarget: "V3_2004_HOMEBANNER:4241101_HP_9_889",
|
|
// contentTarget: "V3_2001_HOMEBANNER:1240712_TM_10",
|
|
};
|
|
|
|
export const getLaunchParams = () => {
|
|
let params = {};
|
|
|
|
if (typeof window === 'object' && window.PalmSystem && window.PalmSystem.launchParams) {
|
|
try {
|
|
params = JSON.parse(window.PalmSystem.launchParams);
|
|
if (params['x-webos-app-container-launch'] === true) {
|
|
params = params.details;
|
|
}
|
|
} catch (e) {
|
|
params = {};
|
|
}
|
|
return params;
|
|
} else {
|
|
return localLaunchParams;
|
|
}
|
|
};
|
|
|
|
export const clearLaunchParams = () => {
|
|
console.log('common.clearLaunchParams');
|
|
if (typeof window === 'object' && window.PalmSystem && window.PalmSystem.launchParams) {
|
|
window.PalmSystem.launchParams = '';
|
|
} else {
|
|
localLaunchParams = {};
|
|
}
|
|
};
|
|
|
|
export const readLocalStorage = (key, defaultValue) => {
|
|
const value = typeof window === 'object' && window.localStorage.getItem(key);
|
|
if (!value && defaultValue !== undefined) {
|
|
return defaultValue;
|
|
}
|
|
return value === 'undefined' ? null : JSON.parse(value);
|
|
};
|
|
|
|
export const writeLocalStorage = (key, value) => {
|
|
if (typeof window === 'object') {
|
|
window.localStorage.setItem(key, JSON.stringify(value));
|
|
}
|
|
};
|
|
|
|
export const convertToTimeFormat = (timeString, isIncludeDate = false) => {
|
|
const date = new Date(timeString);
|
|
|
|
let options = { hour: 'numeric', minute: '2-digit', hour12: true };
|
|
let pattern = ' ';
|
|
|
|
if (isIncludeDate) {
|
|
options = {
|
|
...options,
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
year: 'numeric',
|
|
hour: '2-digit',
|
|
};
|
|
pattern = ',';
|
|
}
|
|
|
|
return new Intl.DateTimeFormat('en-US', options).format(date).replace(pattern, '');
|
|
};
|
|
|
|
export const getTranslate3dValueByDirection = (element, isHorizontal = true) => {
|
|
try {
|
|
const transformStyle = window.getComputedStyle(element).transform;
|
|
|
|
if (!transformStyle || transformStyle === 'none') {
|
|
throw new Error('transfrom style not found');
|
|
}
|
|
|
|
const transformMatrix = transformStyle.match(/^matrix\((.+)\)$/);
|
|
|
|
let index, value;
|
|
|
|
if (transformMatrix) {
|
|
const matrixValues = transformMatrix[1].split(', ');
|
|
|
|
index = isHorizontal ? 4 : 5;
|
|
|
|
value = parseFloat(Math.abs(matrixValues[index]));
|
|
}
|
|
|
|
return value;
|
|
} catch (error) {
|
|
console.error(error.message);
|
|
}
|
|
};
|
|
|
|
export const formatGMTString = (date) => {
|
|
let string = date.toISOString().replace(/T/, ' ').replace(/\..+/, '');
|
|
|
|
return string;
|
|
};
|
|
|
|
export const getSpottableDescendants = (containerId) => {
|
|
let container = document.querySelector(`[data-spotlight-id="${containerId}"]`);
|
|
if (container) {
|
|
return container.querySelectorAll('[class*="spottable"]');
|
|
}
|
|
return [];
|
|
};
|
|
|
|
export const isElementInContainer = (element, container, fullyVisible = true) => {
|
|
// 요소와 컨테이너의 사각형 정보 가져오기
|
|
if (typeof window === 'object') {
|
|
const elementRect = getBoundingClientRect(element);
|
|
const containerRect = container
|
|
? getBoundingClientRect(container)
|
|
: {
|
|
top: 0,
|
|
left: 0,
|
|
bottom: window.innerHeight,
|
|
right: window.innerWidth,
|
|
};
|
|
|
|
// 요소가 컨테이너의 가시 영역 안에 있는지 판단
|
|
if (fullyVisible) {
|
|
return (
|
|
elementRect.top >= containerRect.top &&
|
|
elementRect.left >= containerRect.left &&
|
|
elementRect.bottom <= containerRect.bottom &&
|
|
elementRect.right <= containerRect.right
|
|
);
|
|
} else {
|
|
return (
|
|
elementRect.bottom > containerRect.top &&
|
|
elementRect.top < containerRect.bottom &&
|
|
elementRect.right > containerRect.left &&
|
|
elementRect.left < containerRect.right
|
|
);
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
export const isChildFullyShowing = (parentNode, childNode) => {
|
|
try {
|
|
const parentRect = getBoundingClientRect(parentNode);
|
|
const childRect = getBoundingClientRect(childNode);
|
|
|
|
if (
|
|
childRect.top >= parentRect.top &&
|
|
childRect.left >= parentRect.left &&
|
|
childRect.bottom <= parentRect.bottom &&
|
|
childRect.right <= parentRect.right
|
|
) {
|
|
return true;
|
|
}
|
|
} catch (e) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
export const getRectDiff = (element1, element2) => {
|
|
const element1Rect = getBoundingClientRect(element1);
|
|
const element2Rect = getBoundingClientRect(element2);
|
|
|
|
return {
|
|
right: element1Rect.right - element2Rect.right,
|
|
left: element1Rect.left - element2Rect.left,
|
|
top: element1Rect.top - element2Rect.top,
|
|
bottom: element1Rect.bottom - element2Rect.bottom,
|
|
};
|
|
};
|
|
|
|
export const getFormattingCardNo = (cardNumber) => {
|
|
return `${'*'.repeat(12)}${cardNumber.slice(-4)}`.replace(/(.{4})/g, '$1-').slice(0, -1);
|
|
};
|
|
|
|
export const getQRCodeUrl = ({
|
|
serverHOST,
|
|
serverType,
|
|
index,
|
|
patnrId,
|
|
prdtId,
|
|
dirPurcSelYn = 'Y',
|
|
prdtData,
|
|
qrType,
|
|
liveFlag = 'Y',
|
|
entryMenu,
|
|
nowMenu,
|
|
}) => {
|
|
if (!serverHOST) {
|
|
console.error('getQRCodeUrl: Not Supported, Host is missing');
|
|
return {};
|
|
}
|
|
|
|
let sdpURL = serverHOST.split('.')[0];
|
|
let countryCode = '';
|
|
|
|
if (sdpURL.indexOf('-') > 0) {
|
|
countryCode = sdpURL.split('-')[1];
|
|
} else {
|
|
countryCode = sdpURL;
|
|
}
|
|
|
|
sdpURL = sdpURL.toLowerCase();
|
|
|
|
if (serverType !== 'system') {
|
|
sdpURL = serverType;
|
|
}
|
|
|
|
let baseUrl = '';
|
|
|
|
if (sdpURL.indexOf('qt2') >= 0) {
|
|
baseUrl = 'https://qt2-m.shoptime.lgappstv.com/';
|
|
} else if (sdpURL.indexOf('qt') >= 0) {
|
|
baseUrl = 'https://qt-m.shoptime.lgappstv.com/';
|
|
} else {
|
|
baseUrl = 'https://m.shoptime.lgappstv.com/';
|
|
}
|
|
const prdtDataStr = JSON.stringify(prdtData);
|
|
const prdtDataBase64 = btoa(prdtDataStr);
|
|
|
|
const encodedNowMenu = encodeURIComponent(nowMenu);
|
|
const encodeEntryMenu = encodeURIComponent(entryMenu);
|
|
|
|
return {
|
|
detailUrl: `${baseUrl}billing?cntryCd=${countryCode}&entryMenu=${encodeEntryMenu}&nowMenu=${encodedNowMenu}&idx=${index}&qrType=${qrType}&prdtId=${prdtId}&liveFlag=${liveFlag}&patnrId=${patnrId}`,
|
|
detailBuyNowUrl: `${baseUrl}billing?cntryCd=${countryCode}&qrType=billingBuyNow&entryMenu=${encodeEntryMenu}&nowMenu=${encodedNowMenu}&idx=${index}&liveFlag=${liveFlag}&patnrId=${patnrId}&prdtData=${prdtDataBase64}&dirPurcSelYn=${dirPurcSelYn}&prdtId=${prdtId}`,
|
|
checkoutUrl: `${baseUrl}billing?cntryCd=${countryCode}&qrType=billingCheckOutAddress&entryMenu=${encodeEntryMenu}&nowMenu=${encodedNowMenu}&idx=${index}&liveFlag=${liveFlag}&patnrId=${patnrId}&prdtData=${prdtDataBase64}&dirPurcSelYn=${dirPurcSelYn}&prdtId=${prdtId}`,
|
|
billingAddressListUrl: `${baseUrl}billing?cntryCd=${countryCode}&qrType=billingAddress&entryMenu=${encodeEntryMenu}&nowMenu=${encodedNowMenu}&idx=${index}&from=billing`,
|
|
shippingAddressListUrl: `${baseUrl}billing?cntryCd=${countryCode}&qrType=billingAddress&entryMenu=${encodeEntryMenu}&nowMenu=${encodedNowMenu}&idx=${index}&from=shipping`,
|
|
orderPageUrl: `${baseUrl}my_order.jsp?cntryCd=${countryCode}`,
|
|
homeUrl: `${baseUrl}home.jsp?cntryCd=${countryCode}`,
|
|
setupPinUrl: `${baseUrl}setup_pin.jsp?cntryCd=${countryCode}`,
|
|
cardListUrl: `${baseUrl}billing?cntryCd=${countryCode}&qrType=billingPayment&entryMenu=${encodeEntryMenu}&nowMenu=${encodedNowMenu}&idx=${index}`,
|
|
};
|
|
};
|
|
|
|
// ex: JANUARY 01, 2024
|
|
export const getFormattingDate = (dateString) => {
|
|
const date = new Date(dateString.replace(' ', 'T'));
|
|
|
|
const monthNames = [
|
|
$L('JANUARY'),
|
|
$L('FEBRUARY'),
|
|
$L('MARCH'),
|
|
$L('APRIL'),
|
|
$L('MAY'),
|
|
$L('JUNE'),
|
|
$L('JULY'),
|
|
$L('AUGUST'),
|
|
$L('SEPTEMBER'),
|
|
$L('OCTOBER'),
|
|
$L('NOVEMBER'),
|
|
$L('DECEMBER'),
|
|
];
|
|
|
|
const month = monthNames[date.getMonth()];
|
|
const day = date.getDate().toString().padStart(2, '0');
|
|
const year = date.getFullYear();
|
|
|
|
return `${month} ${day}, ${year}`;
|
|
};
|
|
|
|
export const removeSpecificTags = (html) => {
|
|
// null 또는 undefined 체크
|
|
if (!html) {
|
|
return html;
|
|
}
|
|
|
|
const tagPatterns = [
|
|
/<a\b[^>]*>(.*?)<\/a>/gi,
|
|
/<script\b[^>]*>(.*?)<\/script>/gi,
|
|
/<iframe\b[^>]*>(.*?)<\/iframe>/gi,
|
|
/<img\b[^>]*\/?>/gi,
|
|
/<link\b[^>]*\/?>/gi,
|
|
];
|
|
|
|
let sanitizedHtml = html;
|
|
tagPatterns.forEach((pattern) => {
|
|
sanitizedHtml = sanitizedHtml.replace(pattern, '');
|
|
});
|
|
|
|
return sanitizedHtml;
|
|
};
|
|
|
|
export const encryptPhoneNumber = (phoneNumber) => {
|
|
if (typeof window === 'object') {
|
|
return window.CryptoJS.AES.encrypt(phoneNumber, SECRET_KEY).toString();
|
|
}
|
|
|
|
return phoneNumber;
|
|
};
|
|
|
|
export const decryptPhoneNumber = (encryptedPhoneNumber) => {
|
|
if (typeof window === 'object') {
|
|
const bytes = window.CryptoJS.AES.decrypt(encryptedPhoneNumber, SECRET_KEY);
|
|
return bytes.toString(window.CryptoJS.enc.Utf8);
|
|
}
|
|
|
|
return encryptedPhoneNumber;
|
|
};
|
|
|
|
export const formatLocalDateTime = (date) => {
|
|
const isDate = (obj) => Object.prototype.toString.call(obj) === '[object Date]';
|
|
|
|
if (typeof window === 'object' && isDate(date)) {
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
const hours = String(date.getHours()).padStart(2, '0');
|
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
|
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
}
|
|
|
|
return '';
|
|
};
|
|
|
|
export const parseLocalizedNumber = (numberString, countryCode) => {
|
|
// 유럽식: 1.499,00 -> 1499.00
|
|
if (countryCode === 'DE') {
|
|
return parseFloat(numberString.replace(/\./g, '').replace(',', '.'));
|
|
}
|
|
// 미국식: 1,499.00 -> 1499.00
|
|
if (countryCode === 'US' || countryCode === 'GB') {
|
|
return parseFloat(numberString.replace(/[^0-9.-]+/g, ''));
|
|
}
|
|
// 러시아식: 1 499,00 -> 1499.00
|
|
if (countryCode === 'RU') {
|
|
return parseFloat(numberString.replace(/\s/g, '').replace(',', '.'));
|
|
}
|
|
|
|
return parseFloat(numberString);
|
|
};
|
|
|
|
export const formatCurrencyValue = (value, currSign, currSignLoc, isDiscount = false) => {
|
|
if (value === '-' || value === 0) return '-';
|
|
|
|
const numValue = parseFloat(value);
|
|
if (isNaN(numValue)) return '-';
|
|
|
|
const sign = isDiscount && numValue > 0 ? '- ' : '';
|
|
const formattedValue = parseFloat(numValue.toFixed(2)).toLocaleString('en-US', {
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2,
|
|
});
|
|
|
|
if (!currSign || !currSignLoc) return `${sign}${formattedValue}`;
|
|
|
|
return currSignLoc === 'L'
|
|
? `${sign}${currSign} ${formattedValue}`
|
|
: `${sign}${formattedValue} ${currSign}`;
|
|
};
|
|
|
|
export const getTimeDifferenceByMilliseconds = (
|
|
startTimeString,
|
|
endTimeString,
|
|
threshold = 1000
|
|
) => {
|
|
// dateTimePattern: YYYY-MM-DD HH:MM:SS
|
|
const dateTimePattern = /(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/;
|
|
const startMatches = startTimeString.match(dateTimePattern);
|
|
const endMatches = endTimeString.match(dateTimePattern);
|
|
|
|
if (!startMatches || !endMatches) {
|
|
return false;
|
|
}
|
|
|
|
const convertedStartTime = new Date(startTimeString.replace(/ /, 'T') + 'Z');
|
|
const convertedEndTime = new Date(endTimeString.replace(/ /, 'T') + 'Z');
|
|
const timeDifference = convertedEndTime - convertedStartTime;
|
|
|
|
return timeDifference > threshold;
|
|
};
|
|
|
|
export const getErrorMessage = (errorCode, retMsg, retDetailCode, returnBindStrings) => {
|
|
const foundGroup = ERROR_MESSAGES_GROUPS.find((group) => group.codes.includes(Number(errorCode)));
|
|
|
|
const errorPrefix = errorCode
|
|
? retDetailCode
|
|
? `[${errorCode}-${retDetailCode}] `
|
|
: `[${errorCode}] `
|
|
: '';
|
|
|
|
if (foundGroup) {
|
|
if (errorCode === 1120 && returnBindStrings && typeof returnBindStrings === 'object') {
|
|
return `${errorPrefix} ${foundGroup.message} (ID: ${returnBindStrings.join(', ')})`;
|
|
}
|
|
|
|
return errorPrefix + foundGroup.message;
|
|
} else if (retMsg) {
|
|
return errorPrefix + retMsg;
|
|
} else {
|
|
return errorPrefix + 'An unknown error occurred. Please try again later.';
|
|
}
|
|
};
|