Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 특정 줌 레벨까지 마커의 크기를 줄여서 보여주는 기능을 구현한다 #830

Merged
merged 20 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b55749d
chore: @googlemaps/markerclusterer 패키지 설치
kyw0716 Oct 2, 2023
98ae805
feat: 마커 클러스터링 기능 추가
kyw0716 Oct 2, 2023
2d0f621
refactor: 불필요한 console.log 삭제
kyw0716 Oct 2, 2023
86c51d9
refactor: 클러스터링 제거하고 점 마커 도입
kyw0716 Oct 3, 2023
be63245
refactor: 작은 마커 디자인 추가
kyw0716 Oct 4, 2023
917c9a6
refactor: 줌 레벨 16에서의 마커를 점 형태에서 기본 마커 형식을 유지한 작은 마커로 수정
kyw0716 Oct 5, 2023
794b12a
comment: 사용하지 않는 주석 제거
kyw0716 Oct 5, 2023
5d0bf55
refactor: 줌 레벨 17 이상일 경우 CarffeineMarker가 렌더링 되도록 수정
kyw0716 Oct 5, 2023
99cd8fd
refactor: 불필요한 코드 삭제
kyw0716 Oct 5, 2023
963e62e
refactor: 줌레벨, 마커 크기 비율 상수화
kyw0716 Oct 5, 2023
6ba6679
refactor: zoom 상태에 따른 부수적인 동작 제거
kyw0716 Oct 5, 2023
3cefd3c
refactor: 줌 레벨이 17 이상일 경우 카페인 마커가 렌더링 되도록 수정
kyw0716 Oct 5, 2023
701632f
Merge remote-tracking branch 'origin/develop' into feat/157
kyw0716 Oct 5, 2023
41fdf18
refactor: 중복되는 로직 함수 분리
kyw0716 Oct 5, 2023
c8adb00
chore: 사용하지 않는 패키지 제거
kyw0716 Oct 5, 2023
c42c649
refactor: 사용하지 않는 store 제거
kyw0716 Oct 5, 2023
59192b4
refactor: 하드코딩 된 마커 색상 값 상수 활용하도록 수정
kyw0716 Oct 5, 2023
f14e97e
refactor: 마커 인스턴스 네이밍 변경
kyw0716 Oct 6, 2023
648c030
fix: 메서드명 변경에 따른 오류 수정
kyw0716 Oct 6, 2023
3b75ebe
fix: 빌드 오류 수정
kyw0716 Oct 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 1 addition & 21 deletions frontend/src/components/google-maps/map/CarFfeineListener.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,23 @@ import { useEffect } from 'react';

import { useQueryClient } from '@tanstack/react-query';

import { useRenderStationMarker } from '@marker/HighZoomMarkerContainer/hooks/useRenderStationMarker';

import { debounce } from '@utils/debounce';
import { useExternalValue, useSetExternalState } from '@utils/external-state';
import { getDisplayPosition } from '@utils/google-maps';
import { isCachedRegion } from '@utils/google-maps/isCachedRegion';
import { setLocalStorage } from '@utils/storage';

import { getGoogleMapStore } from '@stores/google-maps/googleMapStore';
import { markerInstanceStore } from '@stores/google-maps/markerInstanceStore';
import { zoomActions, zoomStore } from '@stores/google-maps/zoomStore';
import { warningModalActions } from '@stores/layout/warningModalStore';
import { profileMenuOpenStore } from '@stores/profileMenuOpenStore';

import ZoomWarningModal from '@ui/WarningModal';

import { QUERY_KEY_STATION_MARKERS } from '@constants/queryKeys';
import { LOCAL_KEY_LAST_POSITION } from '@constants/storageKeys';

const CarFfeineMapListener = () => {
const googleMap = useExternalValue(getGoogleMapStore());
const queryClient = useQueryClient();
const setIsProfileMenuOpen = useSetExternalState(profileMenuOpenStore);
const { removeAllMarkers } = useRenderStationMarker();
const zoom = useExternalValue(zoomStore);

const debouncedHighZoomHandler = debounce(() => {
Expand All @@ -47,6 +40,7 @@ const CarFfeineMapListener = () => {
if (zoom.state === 'high') {
debouncedHighZoomHandler();
}

zoomActions.setZoom(googleMap.getZoom());
});

Expand All @@ -57,20 +51,6 @@ const CarFfeineMapListener = () => {
});
}, []);

/**
* zoom.state가 바뀌었을 때만 1번 실행된다.
*/
useEffect(() => {
removeAllMarkers(markerInstanceStore.getState());
queryClient.setQueryData([QUERY_KEY_STATION_MARKERS], () => []);

if (zoom.state === 'middle') {
warningModalActions.openModal(<ZoomWarningModal />);
} else {
warningModalActions.closeModal();
}
}, [zoom.state]);

return <></>;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { useEffect } from 'react';

import { useStationMarkers } from '@marker/HighZoomMarkerContainer/hooks/useStationMarkers';

import { useExternalValue } from '@utils/external-state';

import type { StationMarkerInstance } from '@stores/google-maps/markerInstanceStore';
import { markerInstanceStore } from '@stores/google-maps/markerInstanceStore';
import { zoomStore } from '@stores/google-maps/zoomStore';
import type { ZoomBreakpoints } from '@stores/google-maps/zoomStore/types';

import { useRenderStationMarker } from './hooks/useRenderStationMarker';

Expand All @@ -10,10 +17,38 @@ const HighZoomMarkerContainer = () => {
createNewMarkerInstances,
getRemainedMarkerInstances,
removeMarkersOutsideBounds,
renderMarkerInstances,
removeAllMarkers,
renderDefaultMarkers,
renderCarffeineMarkers,
} = useRenderStationMarker();
const { state: zoomState } = useExternalValue(zoomStore);

const renderMarkerByZoomState = (
zoomState: keyof ZoomBreakpoints,
markerInstances: StationMarkerInstance[]
) => {
if (zoomState === 'max') {
renderCarffeineMarkers(markerInstances, stationMarkers);
}
if (zoomState === 'high') {
renderDefaultMarkers(markerInstances, stationMarkers);
}
};

useEffect(() => {
if (stationMarkers !== undefined) {
renderMarkerByZoomState(zoomState, markerInstanceStore.getState());
}
}, [zoomState]);

useEffect(() => {
return () => {
// MarkerContainers 컴포넌트에서 HighZoomMarkerContainer 컴포넌트가 unmount될 때 모든 마커를 지워준다.
removeAllMarkers(markerInstanceStore.getState());
};
}, []);

if (!stationMarkers || !isSuccess) {
if (stationMarkers === undefined || !isSuccess) {
return <></>;
}

Expand All @@ -28,7 +63,7 @@ const HighZoomMarkerContainer = () => {
);

removeMarkersOutsideBounds(markerInstanceStore.getState(), stationMarkers);
renderMarkerInstances(newMarkerInstances, stationMarkers);
renderMarkerByZoomState(zoomState, newMarkerInstances);

markerInstanceStore.setState([...remainedMarkerInstances, ...newMarkerInstances]);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import styled from 'styled-components';

import type { StationMarker } from '@type';

import type { MARKER_COLORS } from './CarFfeineMarker.style';
Expand All @@ -11,15 +13,23 @@ const CarFfeineMarker = (station: StationMarker) => {
const state: StationAvailability = availableCount === 0 ? 'noAvailable' : 'available';

return (
<Marker
data-testid="carFfeineMarker"
data-marker-id={`marker-${station.stationId}`}
title={stationName}
state={state}
>
{availableCount}
</Marker>
<Container>
<Marker
data-testid="carFfeineMarker"
data-marker-id={`marker-${station.stationId}`}
title={stationName}
state={state}
>
{availableCount}
</Marker>
</Container>
);
};

const Container = styled.div`
position: absolute;
left: -13.5px;
top: -35px;
`;

export default CarFfeineMarker;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const DEFAULT_MARKER_SIZE_RATIO = 0.5;
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import StationDetailsWindow from '@ui/StationDetailsWindow';
import type { StationDetails, StationMarker, StationSummary } from '@type';

import CarFfeineMarker from '../components/CarFfeineMarker';
import { MARKER_COLORS } from '../components/CarFfeineMarker/CarFfeineMarker.style';
import { DEFAULT_MARKER_SIZE_RATIO } from '../constants';

export const useRenderStationMarker = () => {
const googleMap = getStoreSnapshot(getGoogleMapStore());
Expand All @@ -30,7 +32,7 @@ export const useRenderStationMarker = () => {
title: stationName,
});

bindMarkerClickHandler([{ stationId, markerInstance }]);
bindMarkerClickHandler([{ stationId, instance: markerInstance }]);

return markerInstance;
};
Expand All @@ -53,7 +55,7 @@ export const useRenderStationMarker = () => {

return {
stationId,
markerInstance,
instance: markerInstance,
};
});

Expand All @@ -66,19 +68,18 @@ export const useRenderStationMarker = () => {
prevMarkerInstances: StationMarkerInstance[],
currentMarkers: StationMarker[]
) => {
const markersOutOfBounds = prevMarkerInstances.filter(
(prevMarker) =>
!currentMarkers.some((currentMarker) => currentMarker.stationId === prevMarker.stationId)
const markersOutOfBounds = prevMarkerInstances.filter((prevMarker) =>
currentMarkers.every((currentMarker) => currentMarker.stationId !== prevMarker.stationId)
);

markersOutOfBounds.forEach((marker) => {
marker.markerInstance.map = null;
marker.instance.map = null;
});
};

const removeAllMarkers = (prevMarkerInstances: StationMarkerInstance[]) => {
prevMarkerInstances.forEach((marker) => {
marker.markerInstance.map = null;
marker.instance.map = null;
});
};

Expand All @@ -91,11 +92,40 @@ export const useRenderStationMarker = () => {
);
};

const renderMarkerInstances = (
newMarkerInstances: StationMarkerInstance[],
const renderDefaultMarkers = (
markerInstances: StationMarkerInstance[],
markers: StationMarker[] | StationSummary[]
) => {
newMarkerInstances.forEach(({ markerInstance, stationId }) => {
markers.forEach((marker) => {
const markerInstance = markerInstances.find(
(markerInstance) => markerInstance.stationId === marker.stationId
)?.instance;

if (markerInstance) {
const defaultMarkerDesign = new google.maps.marker.PinElement({
scale: DEFAULT_MARKER_SIZE_RATIO,
background:
marker.availableCount > 0
? MARKER_COLORS.available.background
: MARKER_COLORS.noAvailable.background,
borderColor:
marker.availableCount > 0
? MARKER_COLORS.available.border
: MARKER_COLORS.noAvailable.border,
glyph: '',
});

markerInstance.map = googleMap;
markerInstance.content = defaultMarkerDesign.element;
}
});
};

const renderCarffeineMarkers = (
markerInstances: StationMarkerInstance[],
markers: StationMarker[] | StationSummary[]
) => {
markerInstances.forEach(({ instance: markerInstance, stationId }) => {
const container = document.createElement('div');

markerInstance.content = container;
Expand All @@ -104,12 +134,13 @@ export const useRenderStationMarker = () => {
const markerInformation = markers.find(
(stationMarker) => stationMarker.stationId === stationId
);

createRoot(container).render(<CarFfeineMarker {...markerInformation} />);
});
};

const bindMarkerClickHandler = (markerInstances: StationMarkerInstance[]) => {
markerInstances.forEach(({ markerInstance, stationId }) => {
markerInstances.forEach(({ instance: markerInstance, stationId }) => {
markerInstance.addListener('click', () => {
openStationInfoWindow(stationId, markerInstance);

Expand All @@ -125,7 +156,8 @@ export const useRenderStationMarker = () => {
createNewMarkerInstances,
removeMarkersOutsideBounds,
getRemainedMarkerInstances,
renderMarkerInstances,
renderDefaultMarkers,
renderCarffeineMarkers,
removeAllMarkers,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ const MarkersContainers = () => {

return (
<>
{markerMode.state === 'high' && <MemoizedHighZoomMarkerContainer />}
{(markerMode.state === 'high' || markerMode.state === 'max') && (
<MemoizedHighZoomMarkerContainer />
)}
{/* 이 아래는 앞으로 추가될 기능을 미리 대응하는 컴포넌트 */}
{markerMode.state === 'middle' && <MemoizedMiddleZoomMarkerContainer />}
{markerMode.state === 'low' && <MemoizedLowZoomMarkerContainer />}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
import { useEffect } from 'react';

import { warningModalActions } from '@stores/layout/warningModalStore';

import ZoomWarningModal from '@ui/WarningModal';

const MiddleZoomMarkerContainer = () => {
useEffect(() => {
warningModalActions.openModal(<ZoomWarningModal />);

return () => {
warningModalActions.closeModal();
};
}, []);
return <></>;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ const meta = {
title: 'UI/StationInfoWindow/Buttons',
component: SummaryButtons,
args: {
handleCloseStationSummary: () => {
handleCloseStationWindow: () => {
alert('충전소 간단 정보창이 닫혔습니다.');
},
handleOpenStationDetail: () => {
alert('충전소 상세 정보창이 열렸습니다.');
},
},
argTypes: {
handleCloseStationSummary: {
handleCloseStationWindow: {
description: '충전소 간단 정보창을 닫을 수 있습니다.',
},
handleOpenStationDetail: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const useStationSearchWindow = () => {

const { openLastPanel } = useNavigationBar();
const { openStationInfoWindow } = useStationInfoWindow();
const { createNewMarkerInstance, renderMarkerInstances } = useRenderStationMarker();
const { createNewMarkerInstance, renderDefaultMarkers } = useRenderStationMarker();

const screen = useMediaQueries();

Expand Down Expand Up @@ -87,11 +87,13 @@ export const useStationSearchWindow = () => {

const markerInstance = createNewMarkerInstance(stationDetails);

setMarkerInstances((prev) => [...prev, { stationId, markerInstance }]);
renderMarkerInstances(
[{ stationId, markerInstance }],
setMarkerInstances((prev) => [...prev, { stationId, instance: markerInstance }]);

renderDefaultMarkers(
[{ stationId, instance: markerInstance }],
[convertStationDetailsToSummary(stationDetails)]
);

openStationInfoWindow(stationId, markerInstance);

queryClient.setQueryData([QUERY_KEY_STATION_DETAILS, stationId], stationDetails);
Expand Down
1 change: 1 addition & 0 deletions frontend/src/constants/googleMaps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const ZOOM_BREAKPOINTS: ZoomBreakpoints = {
low: MIN_ZOOM_LEVEL,
middle: 12,
high: INITIAL_ZOOM_LEVEL, // 기존 코드와 호환을 위해 일단 이렇게 처리했습니다.
max: 17,
};

export const DELTA_MULTIPLE = 2;
2 changes: 1 addition & 1 deletion frontend/src/hooks/google-maps/useStationInfoWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const useStationInfoWindow = () => {
const stationMarker = markerInstanceStore
.getState()
.find((stationMarker) => stationMarker.stationId === stationId);
const markerInstance = stationMarkerInstance ?? stationMarker.markerInstance;
const markerInstance = stationMarkerInstance ?? stationMarker.instance;

moveMapToStationMarker(markerInstance);

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/stores/google-maps/markerInstanceStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { store } from '@utils/external-state';

export interface StationMarkerInstance {
stationId: string;
markerInstance: google.maps.marker.AdvancedMarkerElement;
instance: google.maps.marker.AdvancedMarkerElement;
}

export const markerInstanceStore = store<StationMarkerInstance[]>([]);
1 change: 1 addition & 0 deletions frontend/src/stores/google-maps/zoomStore/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export interface ZoomBreakpoints {
low: number;
middle: number;
high: number;
max: number;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mini ,기본, plus, pro, pro max

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ultra

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

장난입니다. 바꾸면 안됩니다

}

export type ZoomState = keyof ZoomBreakpoints;
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ describe('markerModeStore', () => {
[12, 'middle'],
[15, 'middle'],
[16, 'high'],
[20, 'high'],
[20, 'max'],
])('getZoomState(%s) returns %s', (zoom, expected) => {
expect(getZoomState(zoom)).toBe(expected);
});
Expand Down
Loading
Loading