Skip to content

Commit

Permalink
[FE] Refactor/#620 모아보기 클러스터링 (#622)
Browse files Browse the repository at this point in the history
* refactor: 불필요한 인자 제거

* refactor: getDistanceOfPin 훅으로 분리

* refactor: InfoWindow 타입 추가

* refactor: 모아보기 상태에서 클러스터링 및 이벤트 핸들링 적용

* refactor: 사용자에게 노출되는 단어 중 도메인 용어인 토픽을 지도로 변환

* refactor: 혼잡한 dom 구조 및 잘못된 indent 수정

* refactor: 한 토픽에 해당하는 핀이 클러스터링 되었을 땐 무지개색 적용 하지 않기

* refactor: 스크린 사이즈 내 좌표가 아닌 초기 좌표로 모아보기 색 구별하도록 변경

* refactor: 모아보기가 비어있을 때 초기화 하는 로직 함수 분리

* refactor: type 정의 위치 수정
  • Loading branch information
semnil5202 authored Nov 17, 2023
1 parent 206fd06 commit b05ba62
Show file tree
Hide file tree
Showing 13 changed files with 202 additions and 95 deletions.
2 changes: 1 addition & 1 deletion frontend/src/components/PinPreview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function PinPreview({
const { pathname } = useLocation();
const { routePage } = useNavigator();
const { tags, setTags } = useContext(TagContext);
const [announceText, setAnnounceText] = useState<string>('토픽 핀 선택');
const [announceText, setAnnounceText] = useState<string>('지도 핀 선택');
const inputRef = useRef<HTMLInputElement | null>(null);

const onAddTagOfTopic = (e: React.ChangeEvent<HTMLInputElement>) => {
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/PullPin/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function PullPin({
tabIndex={1}
aria-label={
confirmButton === '같이보기'
? `선택된 ${tag.title} 토픽 태그`
? `선택된 ${tag.title} 지도 태그`
: `선택된 ${tag.title} 핀 태그`
}
>
Expand All @@ -64,7 +64,7 @@ function PullPin({
onClick={onClickClose}
aria-label={
confirmButton === '같이보기'
? '선택된 토픽들 같이보기 취소하기'
? '선택된 지도들 같이보기 취소하기'
: '선택된 핀들 뽑아오기 취소하기'
}
>
Expand All @@ -76,7 +76,7 @@ function PullPin({
onClick={onClickConfirm}
aria-label={
confirmButton === '같이보기'
? '선택된 토픽들 같이보기'
? '선택된 지도들 같이보기'
: '선택된 핀들 뽑아오기'
}
>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/TopicCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ function TopicCard({
color="black"
$fontSize="default"
$fontWeight="bold"
aria-label={`토픽 이름 ${name}`}
aria-label={`지도 이름 ${name}`}
>
{name}
</MediaText>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/TopicInfo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ function TopicInfo({
try {
const topicUrl = window.location.href.split('?')[0];
await navigator.clipboard.writeText(topicUrl);
showToast('info', '토픽 링크가 복사되었습니다.');
showToast('info', '지도 링크가 복사되었습니다.');
} catch (err) {
showToast('error', '토픽 링크를 복사하는데 실패했습니다.');
showToast('error', '지도 링크를 복사하는데 실패했습니다.');
}
};

Expand Down
36 changes: 29 additions & 7 deletions frontend/src/constants/pinImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,25 @@ export const USER_LOCATION_IMAGE = `<svg width="24" height="24" viewBox="0 0 24
`;

export const pinImageMap: PinImageMap = {
0: `
<svg width="40" height="59" viewBox="0 0 40 59" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.8449 50.0736C9.81988 51.3889 7.73559 50.3006 8.22893 48.7078L14.5909 28.1672C14.868 27.2727 15.8879 26.8479 16.718 27.2813L23.5743 30.8611C24.4044 31.2945 24.6388 32.3742 24.0632 33.1129L10.8449 50.0736Z" fill="#454545"/>
<circle cx="23.9766" cy="16.8848" r="16" fill="url(#paint0_linear_3559_24273)"/>
<circle cx="17.7548" cy="9.77333" r="3.55556" fill="white" fill-opacity="0.6"/>
<path d="M28.4206 24.4327C30.5005 25.3845 32.8504 24.7942 34.6186 22.798" stroke="black" stroke-width="1.8" stroke-linecap="round"/>
<circle cx="32.4193" cy="13.7738" r="1.33333" fill="black"/>
<circle cx="27.0872" cy="18.2181" r="1.33333" fill="black"/>
<defs>
<linearGradient id="paint0_linear_3559_24273" x1="23.9766" y1="0.884766" x2="23.9766" y2="32.8848" gradientUnits="userSpaceOnUse">
<stop stop-color="#E1325C"/>
<stop offset="0.25" stop-color="#F9CB55"/>
<stop offset="0.5" stop-color="#2AC1BC"/>
<stop offset="0.75" stop-color="#4B5CFA"/>
<stop offset="1" stop-color="#C340B6"/>
</linearGradient>
</defs>
</svg>
`,
1: `<svg width="60" height="60" viewBox="0 0 40 58" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.8449 48.4877C9.81988 49.803 7.73559 48.7147 8.22893 47.1219L14.5909 26.5813C14.868 25.6867 15.8879 25.262 16.718 25.6954L23.5743 29.2752C24.4044 29.7086 24.6388 30.7883 24.0632 31.5269L10.8449 48.4877Z" fill="#454545"/>
<circle cx="23.9766" cy="16" r="16" fill="#E1325C"/>
Expand Down Expand Up @@ -75,6 +94,7 @@ export const pinImageMap: PinImageMap = {
};

export const pinColors: PinImageMap = {
0: '#454545',
1: '#E1325C',
2: '#F9CB55',
3: '#4B5CFA',
Expand Down Expand Up @@ -103,20 +123,22 @@ ${
(
pin: any,
) => `<div style="border-bottom: 1px solid white; padding: 4px 12px; display:flex; border-radius: 20px; justify-content: center; align-items: center; height:32px; font-size:14px; color:#ffffff; background-color: ${backgroundColor};">
${pin.name}
</div>`,
${pin.name}
</div>`,
)
.join('')
: `<div style="padding: 4px 12px; display:flex; border-radius: 20px; justify-content: center; align-items: center; height:32px; font-size:14px; color:#ffffff; background-color: ${backgroundColor};">
${pinName}
</div>
${pinName}
</div>
${
pins.length > 1
? `
<div style="position: absolute; top: -14px; right: -12px; padding: 2px 4px; font-size: 14px; background-color: #fff; border-radius: 50%; border: 1px solid ${backgroundColor}; color: ${backgroundColor}">+${pins.length}</div>
<div style="position: absolute; top: -14px; right: -12px; padding: 2px 4px; font-size: 14px; background-color: #fff; border-radius: 50%; border: 1px solid ${backgroundColor}; color: ${backgroundColor}">
+${pins.length}
</div>
`
: ''
}
</div>`
`
}
`;
</div>`;
70 changes: 38 additions & 32 deletions frontend/src/context/MarkerContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ type MarkerContextType = {
displayClickedMarker: () => void;
};

type ElementType = 'marker' | 'infoWindow';

const defaultMarkerContext = () => {
throw new Error('MarkerContext가 제공되지 않았습니다.');
};
Expand Down Expand Up @@ -49,14 +51,36 @@ function MarkerProvider({ children }: Props): JSX.Element {
const { routePage } = useNavigator();
const { pathname } = useLocation();

const createElementsInScreenSize = () => {
const createElementsColor = (elementType: ElementType = 'marker') => {
let markerType = -1;
let currentTopicId = '-1';

const colorMap = elementType === 'marker' ? pinImageMap : pinColors;

const addedMarkerTypeCoordinates = coordinates.map((coordinate) => {
if (coordinate.topicId === 'clustered') {
markerType = -1;
} else if (coordinate.topicId && currentTopicId !== coordinate.topicId) {
markerType += 1;
currentTopicId = coordinate.topicId;
}

return { ...coordinate, elementColor: colorMap[markerType + 1] };
});

return addedMarkerTypeCoordinates;
};

const createElementsInScreenSize = (elementType: ElementType) => {
if (!mapInstance) return;

const mapBounds = mapInstance.getBounds();
const northEast = mapBounds._ne;
const southWest = mapBounds._sw;

const coordinatesInScreenSize = coordinates.filter(
const addedMarkerTypeCoordinates = createElementsColor(elementType);

const coordinatesInScreenSize = addedMarkerTypeCoordinates.filter(
(coordinate: any) =>
coordinate.latitude <= northEast._lat &&
coordinate.latitude >= southWest._lat &&
Expand All @@ -67,13 +91,6 @@ function MarkerProvider({ children }: Props): JSX.Element {
return coordinatesInScreenSize;
};

const createMarker = (coordinate: Coordinate, markerType: number) =>
new Tmapv3.Marker({
position: new Tmapv3.LatLng(coordinate.latitude, coordinate.longitude),
iconHTML: pinImageMap[markerType + 1],
map: mapInstance,
});

// 현재 클릭된 좌표의 마커 생성
const displayClickedMarker = () => {
if (clickedMarker) {
Expand All @@ -93,19 +110,16 @@ function MarkerProvider({ children }: Props): JSX.Element {

// coordinates를 받아서 marker를 생성하고, marker를 markers 배열에 추가
const createMarkers = () => {
let markerType = -1;
let currentTopicId = '-1';

const markersInScreenSize = createElementsInScreenSize();
const coordinatesInScreenSize = createElementsInScreenSize('marker');

if (!markersInScreenSize) return;
if (!coordinatesInScreenSize) return;

const newMarkers = markersInScreenSize.map((coordinate: any) => {
if (currentTopicId !== coordinate.topicId) {
markerType = (markerType + 1) % 7;
currentTopicId = coordinate.topicId;
}
const marker = createMarker(coordinate, markerType);
const newMarkers = coordinatesInScreenSize.map((coordinate: any) => {
const marker = new Tmapv3.Marker({
position: new Tmapv3.LatLng(coordinate.latitude, coordinate.longitude),
iconHTML: coordinate.elementColor,
map: mapInstance,
});
marker.id = String(coordinate.id);
return marker;
});
Expand Down Expand Up @@ -135,25 +149,17 @@ function MarkerProvider({ children }: Props): JSX.Element {
};

const createInfowindows = () => {
let markerType = -1;
let currentTopicId = '-1';

const windowsInScreenSize = createElementsInScreenSize();
const coordinatesInScreenSize = createElementsInScreenSize('infoWindow');

if (!windowsInScreenSize) return;

const newInfowindows = windowsInScreenSize.map((coordinate: any) => {
if (currentTopicId !== coordinate.topicId) {
markerType = (markerType + 1) % 7;
currentTopicId = coordinate.topicId;
}
if (!coordinatesInScreenSize) return;

const newInfowindows = coordinatesInScreenSize.map((coordinate: any) => {
const infoWindow = new Tmapv3.InfoWindow({
position: new Tmapv3.LatLng(coordinate.latitude, coordinate.longitude),
border: 0,
background: 'transparent',
content: getInfoWindowTemplate({
backgroundColor: pinColors[markerType + 1],
backgroundColor: coordinate.elementColor,
pinName: coordinate.pinName,
pins: coordinate.pins,
condition: getCondition(coordinate.pins),
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/hooks/useClickedCoordinate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default function useClickedCoordinate() {
useEffect(() => {
if (!mapInstance) return;
const currentZoom = mapInstance.getZoom();
if (clickedCoordinate.address) displayClickedMarker(mapInstance);
if (clickedCoordinate.address) displayClickedMarker();

// 선택된 좌표가 있으면 해당 좌표로 지도의 중심을 이동
if (clickedCoordinate.latitude && clickedCoordinate.longitude) {
Expand Down
26 changes: 26 additions & 0 deletions frontend/src/hooks/useRealDistanceOfPin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { PIN_SIZE } from '../constants';

const useRealDistanceOfPin = () => {
const { Tmapv3 } = window;

const getDistanceOfPin = (mapInstance: TMap) => {
const mapBounds = mapInstance.getBounds();

const leftWidth = new Tmapv3.LatLng(mapBounds._ne._lat, mapBounds._sw._lng);
const rightWidth = new Tmapv3.LatLng(
mapBounds._ne._lat,
mapBounds._ne._lng,
);

const realDistanceOfScreen = leftWidth.distanceTo(rightWidth);
const currentScreenSize =
mapInstance.realToScreen(rightWidth).x -
mapInstance.realToScreen(leftWidth).x;

return (realDistanceOfScreen / currentScreenSize) * PIN_SIZE;
};

return { getDistanceOfPin };
};

export default useRealDistanceOfPin;
2 changes: 1 addition & 1 deletion frontend/src/pages/NewPin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ function NewPin() {
<ModalContentsWrapper>
<Space size={5} />
<Text color="black" $fontSize="extraLarge" $fontWeight="bold">
토픽 리스트
지도 리스트
</Text>
<Text color="gray" $fontSize="small" $fontWeight="normal">
핀을 저장할 지도를 선택해주세요.
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/PinDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ function PinDetail({
<ModalContentsWrapper>
<Space size={5} />
<Text color="black" $fontSize="extraLarge" $fontWeight="bold">
토픽 리스트
지도 리스트
</Text>
<Text color="gray" $fontSize="small" $fontWeight="normal">
핀을 저장할 지도를 선택해주세요.
Expand Down
Loading

0 comments on commit b05ba62

Please sign in to comment.