import {
AirCleanerButtonWrap,
ArrowIconImg,
Button,
ButtonWrap,
ButtonWrapper,
Container,
DeviceIcon,
DeviceName,
DeviceOptions,
Map,
MapWrapper,
RefreshButton,
RefreshIconImg,
RefrigeratorButtonWrap,
SpanText,
SpeechBubbleState,
SpeecheBubbleTitle,
SpeechBubble,
TvButtonWrap,
MiniMapContainer,
MiniMapWrapper,
MiniMap,
VisibleArea,
LivingRoom,
DownArrowIcon,
} from "./styled/viewer";
import mapImg from "../assets/image/map_viewer.png";
import upArrowIcon from "../assets/icon/up_arrow.png";
import downArrowIcon from "../assets/icon/down_arrow_black.png";
import refreshIcon from "../assets/icon/refresh.png";
import tvIcon from "../assets/icon/tv.png";
import airCleanerIcon from "../assets/icon/air_cleaner.png";
import kimchiRefrigeratorIcon from "../assets/icon/kimchi_refrigerator.png";
import { useEffect, useRef, useState } from "react";
import { Link } from "react-router-dom";
import { Img } from "../components/Nav/styled/globalNav";
const Viewer = ({ isMobile }: { isMobile: boolean }) => {
const scale = useRef(1);
const clicked = useRef(false);
const transformOriginX = useRef(0);
const transformOriginY = useRef(0);
const xAxis = useRef(0);
const yAxis = useRef(0);
const x = useRef(0);
const y = useRef(0);
const [widthPercent, setWidthPercent] = useState(100);
const [heightPercent, setHeightPercent] = useState(100);
const [animation, setAnimation] = useState(false);
const [showSpeechBubble, setShowSpeechBubble] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
const mapWrapperRef = useRef<HTMLDivElement>(null);
const visibleRef = useRef<HTMLDivElement>(null);
const miniMapRef = useRef<HTMLDivElement>(null);
const currentMapHeight = useRef(0);
const linkStyle = {
textDecoration: "none",
color: "inherit",
};
const initMouseZoomEvent = () => {
const zoomTarget = mapWrapperRef.current;
if (zoomTarget === null) return;
window.addEventListener("mouseup", end);
zoomTarget.addEventListener("mousedown", start);
zoomTarget.addEventListener("mousemove", move);
zoomTarget.addEventListener("mouseup", end);
zoomTarget.addEventListener("mouseleave", end);
zoomTarget.addEventListener("wheel", handleMouseWheel);
};
const initTouchZoomEvent = () => {
alert("웨않뒈");
};
const handleMouseWheel = (event: WheelEvent) => {
const zoomTarget = mapWrapperRef.current;
const containerOut = containerRef.current;
if (containerOut === null || zoomTarget === null) return;
const container = containerOut.getBoundingClientRect();
const Imgin = zoomTarget.getBoundingClientRect();
transformOriginX.current = event.offsetX;
transformOriginY.current = event.offsetY;
const scaleChange = event.deltaY > 0 ? -0.5 : 0.5;
const newZoom = scale.current + scaleChange;
const test = Math.min(Math.max(1.0, newZoom), 5.0);
// 마우스가 바라보는 현재위치를 넣어야할때 => 무관해보임
// zoomTarget.style.transformOrigin = `${event.offsetX}px ${event.offsetY}px`;
zoomTarget.style.transform = `scale(${test})`;
scale.current = test;
if (currentMapHeight.current * scale.current > container.height) {
const percentageDifference = Math.floor(
((currentMapHeight.current * scale.current - container.height) /
container.height) *
100
);
setHeightPercent(100 - percentageDifference);
}
setWidthPercent(100 / scale.current);
if (scale.current >= 3) setAnimation(true);
else setAnimation(false);
checkTopSize();
checkLeftSize();
};
const start = (event: MouseEvent) => {
clicked.current = true;
const zoomTarget = mapWrapperRef.current;
if (zoomTarget === null) return;
xAxis.current = (event as MouseEvent).clientX - zoomTarget.offsetLeft;
yAxis.current = (event as MouseEvent).clientY - zoomTarget.offsetTop;
};
const move = (event: MouseEvent) => {
if (!clicked.current) return;
const containerOut = containerRef.current;
const zoomTarget = mapWrapperRef.current;
const miniMap = miniMapRef.current;
const visibleArea = visibleRef.current;
if (
containerOut === null ||
zoomTarget === null ||
miniMap === null ||
visibleArea === null
)
return;
x.current = (event as MouseEvent).clientX;
y.current = (event as MouseEvent).clientY;
const container = containerOut.getBoundingClientRect();
const Imgin = zoomTarget.getBoundingClientRect();
const miniMapRect = miniMap.getBoundingClientRect();
if (container.height >= Imgin.height && container.width >= Imgin.width) {
return;
}
const moveX = x.current - xAxis.current;
const moveY = y.current - yAxis.current;
zoomTarget.style.cursor = `grab`;
if (container.height <= Imgin.height) {
zoomTarget.style.top = `${moveY}px`;
const visibleAreaY = (moveY / Imgin.height) * miniMapRect.height;
visibleArea.style.top = `calc(75% - ${visibleAreaY}px)`;
checkTopSize();
}
if (container.width <= Imgin.width) {
zoomTarget.style.left = `${moveX}px`;
const visibleAreaX = (moveX / Imgin.width) * miniMapRect.width;
visibleArea.style.left = `calc(50% - ${visibleAreaX}px)`;
checkLeftSize();
}
};
const checkTopSize = () => {
const containerOut = containerRef.current;
const zoomTarget = mapWrapperRef.current;
const visibleArea = visibleRef.current;
const miniMap = miniMapRef.current;
if (
containerOut === null ||
zoomTarget === null ||
miniMap === null ||
visibleArea === null
)
return;
const container = containerOut.getBoundingClientRect();
const Imgin = zoomTarget.getBoundingClientRect();
const miniMapRect = miniMap.getBoundingClientRect();
if (Imgin.top > container.top) {
const topEnd = zoomTarget.offsetTop - Imgin.y + containerOut.offsetTop;
zoomTarget.style.top = `${topEnd}px`;
// visibleArea.style.top = `0px`;
}
if (Imgin.bottom < container.bottom) {
zoomTarget.style.top = `${
Number(zoomTarget.style.top.replace("px", "")) +
Math.abs(Imgin.bottom - container.bottom)
}px`;
}
};
const checkLeftSize = () => {
const containerOut = containerRef.current;
const zoomTarget = mapWrapperRef.current;
const visibleArea = visibleRef.current;
const miniMap = miniMapRef.current;
if (
containerOut === null ||
zoomTarget === null ||
miniMap === null ||
visibleArea === null
)
return;
const container = containerOut.getBoundingClientRect();
const Imgin = zoomTarget.getBoundingClientRect();
const miniMapRect = miniMap.getBoundingClientRect();
if (Imgin.left > container.left) {
const leftEnd = (Imgin.width - container.width) / 2;
const visibleAreaX = (leftEnd / Imgin.width) * miniMapRect.width;
zoomTarget.style.left = `${leftEnd}px`;
visibleArea.style.left = `calc(50% - ${visibleAreaX}px)`;
} else if (Imgin.right < container.right) {
const leftEnd = (Imgin.width - container.width) / 2;
const visibleAreaX = (leftEnd / Imgin.width) * miniMapRect.width;
zoomTarget.style.left = `-${(Imgin.width - container.width) / 2}px`;
visibleArea.style.left = `calc(50% + ${visibleAreaX}px)`;
}
};
const end = (e: any) => {
clicked.current = false;
};
useEffect(() => {
const mapWrapper = mapWrapperRef.current;
if (mapWrapper === null) return;
scale.current = 2;
const newHeight = mapWrapper.getBoundingClientRect().height;
currentMapHeight.current = newHeight;
setWidthPercent(100 / scale.current);
if (mapWrapperRef) {
mapWrapper.style.transform = `scale(2)`;
mapWrapper.style.transition = `transform 0.8s ease`;
}
setTimeout(() => {
mapWrapper.style.removeProperty("transition");
if (isMobile) initTouchZoomEvent();
else initMouseZoomEvent();
}, 900);
return () => {
if (mapWrapperRef) {
mapWrapper.style.transform = `scale(1)`;
}
};
}, []);
return (
<Container ref={containerRef} isMobile={isMobile}>
<MapWrapper ref={mapWrapperRef}>
<Map src={mapImg} alt="map_alt" />
<TvButtonWrap>
<Button animation={animation}>
<DeviceIcon src={tvIcon} animation={animation} />
</Button>
<DeviceName animation={animation}>TV</DeviceName>
</TvButtonWrap>
<RefrigeratorButtonWrap>
<Button
animation={animation}
onClick={() => {
setShowSpeechBubble(true);
}}
>
<Link to="/device/김치냉장고" style={linkStyle}>
<SpeechBubble showSpeechBubble={showSpeechBubble}>
<div style={{ position: "relative" }}>
<SpeecheBubbleTitle>김치냉장고</SpeecheBubbleTitle>
<SpeechBubbleState>문닫힘</SpeechBubbleState>
<DownArrowIcon src={downArrowIcon} />
</div>
</SpeechBubble>
</Link>
<DeviceIcon src={kimchiRefrigeratorIcon} animation={animation} />
</Button>
<DeviceName animation={animation}>김치냉장고</DeviceName>
</RefrigeratorButtonWrap>
<AirCleanerButtonWrap>
<Button animation={animation}>
<DeviceIcon src={airCleanerIcon} animation={animation} />
</Button>
<DeviceName animation={animation}>공기청정기</DeviceName>
</AirCleanerButtonWrap>
<LivingRoom>거실</LivingRoom>
</MapWrapper>
<ButtonWrapper>
<DeviceOptions>
<SpanText>모든기기</SpanText>
<ArrowIconImg src={upArrowIcon} alt="up_arrow_icon" />
</DeviceOptions>
<RefreshButton>
<RefreshIconImg src={refreshIcon} alt="refresh_icon" />
</RefreshButton>
</ButtonWrapper>
<MiniMapContainer>
<MiniMapWrapper ref={miniMapRef}>
<MiniMap src={mapImg} alt="map_alt" />
<VisibleArea
ref={visibleRef}
widthPercent={`${widthPercent}%`}
heightPercent={`${heightPercent}%`}
></VisibleArea>
</MiniMapWrapper>
</MiniMapContainer>
</Container>
);
};
export default Viewer;