Compare commits
6 Commits
fd55c04c83
...
develop_si
| Author | SHA1 | Date | |
|---|---|---|---|
| daac18afa8 | |||
| adfede9b44 | |||
| 1fae88878f | |||
| 4ecb03002f | |||
| 27e1e3bb6a | |||
| b9d52d452c |
@@ -2467,9 +2467,9 @@ const VideoPlayerBase = class extends React.Component {
|
||||
</p>
|
||||
}
|
||||
|
||||
<div className={css.loaderWrap}>
|
||||
{/* <div className={css.loaderWrap}>
|
||||
<Loader />
|
||||
</div>
|
||||
</div> */}
|
||||
</>
|
||||
) : null}
|
||||
{this.state.mediaControlsVisible && (
|
||||
@@ -2534,7 +2534,7 @@ const VideoPlayerBase = class extends React.Component {
|
||||
videoVerticalVisible && css.videoVertical
|
||||
)}
|
||||
>
|
||||
{this.state.mediaSliderVisible && type ? (
|
||||
{this.state.mediaSliderVisible && type && !panelInfo.modal ? (
|
||||
<Times
|
||||
noCurrentTime
|
||||
total={type === 'LIVE' ? liveTotalTime : this.state.duration}
|
||||
@@ -2542,7 +2542,7 @@ const VideoPlayerBase = class extends React.Component {
|
||||
type={type}
|
||||
/>
|
||||
) : null}
|
||||
{this.state.mediaSliderVisible && type ? (
|
||||
{this.state.mediaSliderVisible && type && !panelInfo.modal ? (
|
||||
<Times
|
||||
noTotalTime
|
||||
current={type === 'LIVE' ? currentLiveTimeSeconds : this.state.currentTime}
|
||||
|
||||
@@ -718,7 +718,7 @@
|
||||
}
|
||||
|
||||
.sliderContainer {
|
||||
// display: flex;
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
margin-left: 60px;
|
||||
@@ -727,6 +727,10 @@
|
||||
bottom: -20px;
|
||||
> *:first-child {
|
||||
text-align: right;
|
||||
margin-right: 12px;
|
||||
}
|
||||
> *:nth-child(2) {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.enact-locale-rtl({
|
||||
@@ -787,79 +791,4 @@
|
||||
});
|
||||
}
|
||||
|
||||
// ========== MediaPlayer.v2 Controls ==========
|
||||
.controlsContainer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 20px 40px 30px;
|
||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.9) 0%, rgba(0, 0, 0, 0.7) 60%, transparent 100%);
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.sliderContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.times {
|
||||
min-width: 80px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.controlsButtons {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.playPauseBtn {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
font-size: 24px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: 2px solid rgba(255, 255, 255, 0.6);
|
||||
border-radius: 50%;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.backBtn {
|
||||
padding: 12px 24px;
|
||||
font-size: 18px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: 2px solid rgba(255, 255, 255, 0.6);
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
.position(@position: absolute, @top: 0, @left: 0, @right: 0, @bottom: 0);
|
||||
z-index: 19;
|
||||
border: 4px solid @PRIMARY_COLOR_RED;
|
||||
box-shadow: 0 0 @boxShadow 0 rgba(0, 0, 0, 0.5);
|
||||
border-radius: @borderRadius;
|
||||
content: "";
|
||||
}
|
||||
|
||||
@@ -183,6 +183,57 @@ const ButtonStackContainer = SpotlightContainerDecorator(
|
||||
|
||||
const SpottableComponent = Spottable('div');
|
||||
|
||||
// 🔽 [251217] ProductAllSection 스크롤 컨테이너 위치 저장/검증 함수
|
||||
// PlayerPanel의 배너 위치 검증 패턴을 ProductAllSection에 적용
|
||||
|
||||
/**
|
||||
* ProductAllSection의 스크롤 컨테이너 위치 수집 함수
|
||||
* @returns {Object|null} 스크롤 컨테이너의 위치 정보 (top, left) 또는 null
|
||||
*/
|
||||
const collectProductScrollPosition = () => {
|
||||
const scrollContainer = document.querySelector('[data-spotlight-id="main-content-scroller"]');
|
||||
|
||||
if (scrollContainer) {
|
||||
const { top, left } = scrollContainer.getBoundingClientRect();
|
||||
const position = {
|
||||
top: Math.round(top),
|
||||
left: Math.round(left)
|
||||
};
|
||||
console.log('[ProductAllSection] 스크롤 컨테이너 위치 수집:', position);
|
||||
return position;
|
||||
}
|
||||
|
||||
console.warn('[ProductAllSection] 스크롤 컨테이너를 찾을 수 없음');
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* ProductAllSection 스크롤 컨테이너 위치 검증 함수
|
||||
* @param {Object} savedPosition - 저장된 초기 위치
|
||||
* @param {Object} currentPosition - 현재 위치
|
||||
* @returns {boolean} 위치가 일치하는지 여부
|
||||
*/
|
||||
const isProductScrollPositionValid = (savedPosition, currentPosition) => {
|
||||
if (!savedPosition || !currentPosition) {
|
||||
console.warn('[ProductAllSection] 저장된 위치 또는 현재 위치가 없음');
|
||||
return false;
|
||||
}
|
||||
|
||||
const tolerance = 1; // 1px 오차 범위
|
||||
const isMatching =
|
||||
Math.abs(currentPosition.top - savedPosition.top) <= tolerance &&
|
||||
Math.abs(currentPosition.left - savedPosition.left) <= tolerance;
|
||||
|
||||
console.log('[ProductAllSection] 스크롤 위치 검증:', {
|
||||
expected: savedPosition,
|
||||
current: currentPosition,
|
||||
matching: isMatching,
|
||||
tolerance
|
||||
});
|
||||
|
||||
return isMatching;
|
||||
};
|
||||
|
||||
const getProductData = curry(
|
||||
(productType, themeProductInfo, themeProducts, selectedIndex, productInfo) =>
|
||||
pipe(
|
||||
@@ -304,6 +355,9 @@ export default function ProductAllSection({
|
||||
// handleScrollToImages의 timeout을 추적하기 위한 ref
|
||||
const scrollToImagesTimeoutRef = useRef(null);
|
||||
|
||||
// 🔽 [251217] 스크롤 컨테이너 초기 위치 저장 ref (VideoPlayer modal 전환 시 위치 검증용)
|
||||
const scrollPositionOnMountRef = useRef(null);
|
||||
|
||||
// ProductAllSection 초기 로딩 시 Skeleton 표시를 위한 상태
|
||||
const [isInitialLoading, setIsInitialLoading] = useState(true);
|
||||
|
||||
@@ -344,6 +398,19 @@ export default function ProductAllSection({
|
||||
);
|
||||
}, [selectedPatnrId, selectedPrdtId, userNumber, dispatch]);
|
||||
|
||||
// 🔽 [251217] ProductAllSection 마운트 시 스크롤 컨테이너 위치 저장
|
||||
useEffect(() => {
|
||||
// 초기 렌더링 후 스크롤 컨테이너의 위치를 저장 (1회만 실행)
|
||||
// 이 위치는 나중에 VideoPlayer의 modal 전환 시 유효성 검증에 사용됨
|
||||
const savedPosition = collectProductScrollPosition();
|
||||
if (savedPosition) {
|
||||
scrollPositionOnMountRef.current = savedPosition;
|
||||
// 🔽 window 객체에도 저장하여 MediaPanel.v3.jsx에서 접근 가능하게 함
|
||||
window.productScrollPositionOnMount = savedPosition;
|
||||
console.log('[ProductAllSection] ✅ 초기 스크롤 위치 저장 완료:', savedPosition);
|
||||
}
|
||||
}, []); // 마운트 시 1회만 실행
|
||||
|
||||
useEffect(() => {
|
||||
// 필수 값이 모두 있을 때만 호출
|
||||
if (selectedPatnrId && selectedPrdtId) {
|
||||
|
||||
@@ -93,6 +93,36 @@ const findSelector = (selector, maxAttempts = 5, currentAttempts = 0) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 🔽 [251217] ProductAllSection 스크롤 컨테이너 위치 수집 함수
|
||||
const collectProductScrollPosition = () => {
|
||||
const scrollContainer = document.querySelector('[data-spotlight-id="main-content-scroller"]');
|
||||
|
||||
if (scrollContainer) {
|
||||
const { top, left } = scrollContainer.getBoundingClientRect();
|
||||
const position = {
|
||||
top: Math.round(top),
|
||||
left: Math.round(left)
|
||||
};
|
||||
return position;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// 🔽 [251217] ProductAllSection 스크롤 컨테이너 위치 검증 함수
|
||||
const isProductScrollPositionValid = (savedPosition, currentPosition) => {
|
||||
if (!savedPosition || !currentPosition) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tolerance = 1; // 1px 오차 범위
|
||||
const isMatching =
|
||||
Math.abs(currentPosition.top - savedPosition.top) <= tolerance &&
|
||||
Math.abs(currentPosition.left - savedPosition.left) <= tolerance;
|
||||
|
||||
return isMatching;
|
||||
};
|
||||
|
||||
const getLogTpNo = (type, nowMenu) => {
|
||||
if (type === 'LIVE') {
|
||||
switch (nowMenu) {
|
||||
@@ -1107,6 +1137,26 @@ const MediaPanel = React.forwardRef(
|
||||
}, [dispatch]);
|
||||
|
||||
const enterFullscreen = useCallback(() => {
|
||||
// 🔽 [251217] ProductAllSection 스크롤 위치 검증
|
||||
// DetailPanel에서 ProductAllSection의 초기 위치와 현재 위치를 비교하여 검증
|
||||
const savedPosition = window.productScrollPositionOnMount;
|
||||
const currentPosition = collectProductScrollPosition();
|
||||
|
||||
if (savedPosition && currentPosition) {
|
||||
const isValid = isProductScrollPositionValid(savedPosition, currentPosition);
|
||||
if (!isValid) {
|
||||
console.warn('[MediaPanel] ⚠️ ProductAllSection 스크롤 위치 검증 실패 - 전체화면 재생 가능하지만 경고 표시', {
|
||||
savedPosition,
|
||||
currentPosition,
|
||||
});
|
||||
// 사용자 피드백: 위치가 일치하지 않음을 콘솔에 기록 (비디오는 계속 재생)
|
||||
} else {
|
||||
console.log('[MediaPanel] ✅ ProductAllSection 스크롤 위치 검증 성공');
|
||||
}
|
||||
} else {
|
||||
console.warn('[MediaPanel] ⚠️ ProductAllSection 위치 정보 없음 - 검증 스킵');
|
||||
}
|
||||
|
||||
isTransitioningToFullscreen.current = true;
|
||||
dispatch(switchMediaToFullscreen());
|
||||
}, [dispatch]);
|
||||
|
||||
@@ -167,8 +167,8 @@
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 4px solid @PRIMARY_COLOR_RED;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 0 22px @PRIMARY_COLOR_RED;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
@@ -298,8 +298,8 @@
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 4px solid @PRIMARY_COLOR_RED;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 0 22px @PRIMARY_COLOR_RED;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ export default function LiveChannelContents({
|
||||
// currentVideoShowId와 일치하는 배너의 인덱스 찾기
|
||||
const index = liveInfos.findIndex((item) => item.showId === currentVideoShowId);
|
||||
if (index !== -1) {
|
||||
scrollToRef.current({ index, animate: true, focus: false });
|
||||
scrollToRef.current({ index, animate: true, focus: false, alignToStart: true });
|
||||
}
|
||||
}
|
||||
}, [currentVideoShowId, liveInfos]);
|
||||
|
||||
@@ -82,28 +82,34 @@ export default function TScrollerLiveChannel({
|
||||
if (!container || !itemsRef.current[index]) return;
|
||||
|
||||
const item = itemsRef.current[index];
|
||||
const { animate = true } = options;
|
||||
const { animate = true, alignToStart = false } = options;
|
||||
|
||||
if (direction === 'horizontal') {
|
||||
// 수평 스크롤: 현재 아이템 + 다음 아이템까지 보이도록
|
||||
// 수평 스크롤: 스크롤 가능 여부 판단
|
||||
const isScrollable = container.scrollWidth > container.clientWidth;
|
||||
|
||||
let scrollLeft = 0;
|
||||
|
||||
if (alignToStart && isScrollable) {
|
||||
// 첫 번째 위치로 스크롤
|
||||
scrollLeft = item.offsetLeft - scaleW(spacing);
|
||||
} else if (!alignToStart) {
|
||||
// 기존 로직: 현재 아이템 + 다음 아이템까지 보이도록
|
||||
const itemLeft = item.offsetLeft;
|
||||
const itemWidth = item.offsetWidth;
|
||||
const containerWidth = container.clientWidth;
|
||||
|
||||
// 다음 아이템도 일부 보일 수 있도록 스크롤
|
||||
// 현재 아이템 + 다음 아이템의 일부가 보이는 위치로 스크롤
|
||||
const nextItem = itemsRef.current[index + 1];
|
||||
let scrollLeft = itemLeft - scaleW(spacing);
|
||||
scrollLeft = itemLeft - scaleW(spacing);
|
||||
|
||||
if (nextItem) {
|
||||
// 다음 아이템의 왼쪽 끝이 컨테이너의 오른쪽 끝과 같은 위치가 되도록
|
||||
const nextItemLeft = nextItem.offsetLeft;
|
||||
const nextItemWidth = nextItem.offsetWidth;
|
||||
const targetScrollLeft = nextItemLeft + nextItemWidth - containerWidth + scaleW(spacing);
|
||||
|
||||
// 두 가지 중에서 현재 아이템이 더 잘 보이는 쪽 선택
|
||||
scrollLeft = Math.min(scrollLeft, targetScrollLeft);
|
||||
}
|
||||
}
|
||||
|
||||
// 음수 스크롤 방지
|
||||
scrollLeft = Math.max(0, scrollLeft);
|
||||
@@ -117,24 +123,31 @@ export default function TScrollerLiveChannel({
|
||||
container.scrollLeft = scrollLeft;
|
||||
}
|
||||
} else {
|
||||
// 수직 스크롤: 현재 아이템 + 다음 아이템까지 보이도록
|
||||
// 수직 스크롤: 스크롤 가능 여부 판단
|
||||
const isScrollable = container.scrollHeight > container.clientHeight;
|
||||
|
||||
let scrollTop = 0;
|
||||
|
||||
if (alignToStart && isScrollable) {
|
||||
// 첫 번째 위치로 스크롤
|
||||
scrollTop = item.offsetTop - scaleH(spacing);
|
||||
} else if (!alignToStart) {
|
||||
// 기존 로직: 현재 아이템 + 다음 아이템까지 보이도록
|
||||
const itemTop = item.offsetTop;
|
||||
const itemHeight = item.offsetHeight;
|
||||
const containerHeight = container.clientHeight;
|
||||
|
||||
// 다음 아이템도 일부 보일 수 있도록 스크롤
|
||||
const nextItem = itemsRef.current[index + 1];
|
||||
let scrollTop = itemTop - scaleH(spacing);
|
||||
scrollTop = itemTop - scaleH(spacing);
|
||||
|
||||
if (nextItem) {
|
||||
// 다음 아이템의 위쪽 끝이 컨테이너의 아래쪽 끝과 같은 위치가 되도록
|
||||
const nextItemTop = nextItem.offsetTop;
|
||||
const nextItemHeight = nextItem.offsetHeight;
|
||||
const targetScrollTop = nextItemTop + nextItemHeight - containerHeight + scaleH(spacing);
|
||||
|
||||
// 두 가지 중에서 현재 아이템이 더 잘 보이는 쪽 선택
|
||||
scrollTop = Math.min(scrollTop, targetScrollTop);
|
||||
}
|
||||
}
|
||||
|
||||
// 음수 스크롤 방지
|
||||
scrollTop = Math.max(0, scrollTop);
|
||||
@@ -156,9 +169,9 @@ export default function TScrollerLiveChannel({
|
||||
useEffect(() => {
|
||||
if (cbScrollTo) {
|
||||
cbScrollTo((options) => {
|
||||
const { index, animate = true, focus = true } = options;
|
||||
const { index, animate = true, focus = true, alignToStart = false } = options;
|
||||
if (typeof index === 'number' && index >= 0 && index < dataSize) {
|
||||
scrollToIndex(index, { animate });
|
||||
scrollToIndex(index, { animate, alignToStart });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user