Skip to content

Commit

Permalink
[FE][Feat] Main.tsx 화면에서 4주차 데모를 위한 실시간 현재 위치 받아오는 기능 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
happyhyep committed Nov 21, 2024
1 parent 8d6f0a2 commit 2125356
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 8 deletions.
2 changes: 2 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ export default [
'no-underscore-dangle': 'warn',
'no-undef': 'off',
'max-classes-per-file': 'off',
'no-bitwise': 'off',
'no-plusplus': 'off',
'import/extensions': [
'error',
'always',
Expand Down
5 changes: 3 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"lint": "pnpm lint-staged",
"preview": "vite preview",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
"build-storybook": "storybook build",
"uuid": "^11.0.3"
},
"dependencies": {
"@fontsource/pretendard": "^5.1.0",
Expand Down Expand Up @@ -63,4 +64,4 @@
"plugin:storybook/recommended"
]
}
}
}
82 changes: 80 additions & 2 deletions frontend/src/component/maps/NaverMapSample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,33 @@ export interface INaverMapOptions {
lng: number;
zoom?: number;
}
interface INaverMapProps extends INaverMapOptions {
otherLocations?: Array<{ location: { lat: number; lng: number }; token: string }>;
}

export const NaverMap = (props: INaverMapOptions) => {
export const NaverMap = (props: INaverMapProps) => {
const mapRef = useRef<HTMLDivElement>(null);
const mapInstanceRef = useRef<naver.maps.Map | null>(null);
const markersRef = useRef<Map<string, naver.maps.Marker>>(new Map());
const userMarkerRef = useRef<naver.maps.Marker | null>(null);

// TODO: 사용자 순서 별로 색상 정하기 (util 함수로 빼기)
const getMarkerColor = (token: string) => {
// 문자열 해싱을 통해 고유 숫자 생성
let hash = 0;
for (let i = 0; i < token.length; i++) {
hash = token.charCodeAt(i) + ((hash << 5) - hash);
}

// 해시 값을 기반으로 RGB 값 생성
const r = (hash >> 16) & 0xff;
const g = (hash >> 8) & 0xff;
const b = hash & 0xff;

// RGB를 HEX 코드로 변환
return `rgb(${r}, ${g}, ${b})`;
};

const mapOptions: INaverMapOptions = {
lat: props.lat,
lng: props.lng,
Expand All @@ -17,9 +41,63 @@ export const NaverMap = (props: INaverMapOptions) => {

useEffect(() => {
if (mapRef.current) {
setNaverMapSync(mapRef.current, mapOptions);
const map = setNaverMapSync(mapRef.current, mapOptions);
mapInstanceRef.current = map;
}
}, []);

useEffect(() => {
if (mapInstanceRef.current && props.otherLocations) {
const map = mapInstanceRef.current;

// 업데이트된 마커 관리
const existingMarkers = markersRef.current;

props.otherLocations.forEach(({ location, token }) => {
const markerColor = getMarkerColor(token);
if (existingMarkers.has(token)) {
// 기존 마커 위치 업데이트
const marker = existingMarkers.get(token);
marker?.setPosition(new naver.maps.LatLng(location.lat, location.lng));
} else {
const newMarker = new naver.maps.Marker({
position: new naver.maps.LatLng(location.lat, location.lng),
map,
icon: {
content: `<div style="background-color: ${markerColor}; width: 20px; height: 20px; border-radius: 50%;"></div>`,
anchor: new naver.maps.Point(10, 10),
},
});

existingMarkers.set(token, newMarker);
}
});

// 삭제된 마커 제거
existingMarkers.forEach((marker, token) => {
if (!props.otherLocations?.some(loc => loc.token === token)) {
marker.setMap(null);
existingMarkers.delete(token);
}
});
}
}, [props.otherLocations]);

// 현재 위치 마커 추가 및 업데이트
useEffect(() => {
if (mapInstanceRef.current) {
const map = mapInstanceRef.current;
// 현재 위치 마커 생성
userMarkerRef.current = new naver.maps.Marker({
position: new naver.maps.LatLng(props.lat, props.lng),
map,
icon: {
content: `<div style="background-color: blue; width: 25px; height: 25px; border-radius: 50%; border: 2px solid white;"></div>`,
anchor: new naver.maps.Point(12.5, 12.5),
},
});
}
}, [props.lat, props.lng]);

return <section ref={mapRef} className="h-full w-full" />;
};
5 changes: 4 additions & 1 deletion frontend/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
export const KEYS = {
LOGIN_USER: 'LUT',
LOGIN_TOKEN: 'LUT_TK',
BROWSER_TOKEN: 'BRW_TK',
};

export const AppConfig = {
/**
* API SERVER
*/
API_SERVER: 'http://223.130.151.43:3001/api',
API_SERVER: 'https://ddara.kro.kr:3001/api',
// API_SERVER: 'http://localhost:3001/api',
SOCKET_SERVER: 'wss://ddara.kro.kr/ws',
// SOCKET_SERVER: 'ws://localhost:3001',

/**
* ETC
Expand Down
51 changes: 48 additions & 3 deletions frontend/src/pages/Main.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Fragment, useContext, useEffect } from 'react';
import { Fragment, useContext, useEffect, useState } from 'react';
import { getUserLocation } from '@/hooks/getUserLocation';
import { BottomSheet } from '@/component/bottomsheet/BottomSheet';
import { Content } from '@/component/content/Content';
Expand All @@ -7,6 +7,9 @@ import { FooterContext } from '@/component/layout/footer/LayoutFooterProvider';
import { useNavigate } from 'react-router-dom';
import { NaverMap } from '@/component/maps/NaverMapSample.tsx';
import { buttonActiveType } from '@/component/layout/enumTypes';
import { loadLocalData, saveLocalData } from '@/utils/common/manageLocalData.ts';
import { AppConfig } from '@/constants.ts';
import { v4 as uuidv4 } from 'uuid';

const contentData = [
{
Expand Down Expand Up @@ -37,9 +40,44 @@ export const Main = () => {
useContext(FooterContext);
const { lat, lng, error } = getUserLocation();
const navigate = useNavigate();
const MIN_HEIGHT = 0.2;
const [otherLocations, setOtherLocations] = useState<any[]>([]);
const MIN_HEIGHT = 0.5;
const MAX_HEIGHT = 0.8;

// eslint-disable-next-line consistent-return
useEffect(() => {
if (lat && lng) {
if (!loadLocalData(AppConfig.KEYS.BROWSER_TOKEN)) {
const token = uuidv4();
saveLocalData(AppConfig.KEYS.BROWSER_TOKEN, token);
}
const token = loadLocalData(AppConfig.KEYS.BROWSER_TOKEN);
const ws = new WebSocket(`${AppConfig.SOCKET_SERVER}/?token=${token}`);

// 초기 위치 전송
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'location', location: { lat, lng } }));
};

ws.onmessage = event => {
const data = JSON.parse(event.data);

if (data.type === 'init') {
// 기존 클라이언트들의 위치 초기화
setOtherLocations(data.clients);
} else if (data.type === 'location' && data.token !== token) {
// 새로 들어온 위치 업데이트
setOtherLocations(prev =>
prev.some(loc => loc.token === data.token)
? prev.map(loc => (loc.token === data.token ? data : loc))
: [...prev, data],
);
}
};
return () => ws.close();
}
}, [lat, lng]);

const goToAddChannel = () => {
navigate('/add-channel');
};
Expand All @@ -63,8 +101,15 @@ export const Main = () => {
height: `calc(100% - ${MIN_HEIGHT * 100}%)`,
}}
>
{/* eslint-disable-next-line no-nested-ternary */}
{lat && lng ? (
<NaverMap lat={lat} lng={lng} zoom={20} />
otherLocations ? (
<NaverMap otherLocations={otherLocations} lat={lat} lng={lng} />
) : (
<section className="flex h-full items-center justify-center">
Loading map data...
</section>
)
) : (
<section className="flex h-full items-center justify-center">
{error ? `Error: ${error}` : 'Loading'}
Expand Down

0 comments on commit 2125356

Please sign in to comment.