From d4f33e138162fda2de913939d878ed29e7802059 Mon Sep 17 00:00:00 2001 From: Hyein Jeong Date: Fri, 29 Nov 2024 02:10:06 +0900 Subject: [PATCH 1/4] =?UTF-8?q?[FE][Feat]=20#339=20:=20=EC=A7=80=EB=8F=84?= =?UTF-8?q?=EC=97=90=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=8B=A4=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EB=B0=A9=ED=96=A5=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - getUserLocation hook에서 위치와 alpha 값 함께 받아오기 - Main 페이지에서 hook 적용 --- frontend/src/hooks/getUserLocation.ts | 71 ++++++++++++++++++--------- frontend/src/pages/Main.tsx | 8 +-- 2 files changed, 52 insertions(+), 27 deletions(-) diff --git a/frontend/src/hooks/getUserLocation.ts b/frontend/src/hooks/getUserLocation.ts index 8307efb5..b5ef622a 100644 --- a/frontend/src/hooks/getUserLocation.ts +++ b/frontend/src/hooks/getUserLocation.ts @@ -3,6 +3,7 @@ import { useEffect, useState } from 'react'; interface IGetUserLocation { lat: number | null; lng: number | null; + alpha: number | null; error: string | null; } @@ -32,36 +33,58 @@ export const getUserLocation = (): IGetUserLocation => { const [location, setLocation] = useState({ lat: null, lng: null, + alpha: null, error: null, }); useEffect(() => { - if (navigator.geolocation) { - const watchId = navigator.geolocation.watchPosition( - position => { - setLocation({ - lat: position.coords.latitude, - lng: position.coords.longitude, - error: null, - }); - }, - error => { - setLocation({ - lat: 37.3595704, - lng: 127.105399, - error: error.message, - }); - }, - { enableHighAccuracy: true, maximumAge: 5000, timeout: 10000 }, - ); + let watchId: number; + + const handlePosition = (position: GeolocationPosition) => { + setLocation(prev => ({ + ...prev, + lat: position.coords.latitude, + lng: position.coords.longitude, + error: null, + })); + }; - return () => navigator.geolocation.clearWatch(watchId); + const handleError = (error: GeolocationPositionError) => { + setLocation({ + lat: 37.3595704, + lng: 127.105399, + alpha: 0, + error: error.message, + }); + }; + + if (navigator.geolocation) { + watchId = navigator.geolocation.watchPosition(handlePosition, handleError, { + enableHighAccuracy: true, + maximumAge: 5000, + timeout: 10000, + }); + } else { + setLocation({ + lat: 37.3595704, + lng: 127.105399, + alpha: 0, + error: '현재 위치를 불러오지 못했습니다', + }); } - setLocation({ - lat: 37.3595704, - lng: 127.105399, - error: '현재 위치를 불러오지 못했습니다', - }); + + const handleOrientation = (event: DeviceOrientationEvent) => { + if (event.alpha !== null) { + setLocation(prev => ({ ...prev, alpha: event.alpha })); + } + }; + + window.addEventListener('deviceorientation', handleOrientation); + + return () => { + if (watchId) navigator.geolocation.clearWatch(watchId); + window.removeEventListener('deviceorientation', handleOrientation); + }; }, []); return location; diff --git a/frontend/src/pages/Main.tsx b/frontend/src/pages/Main.tsx index 5ccbb926..0562fa9d 100644 --- a/frontend/src/pages/Main.tsx +++ b/frontend/src/pages/Main.tsx @@ -23,7 +23,7 @@ export const Main = () => { setFooterActive, resetFooterContext, } = useContext(FooterContext); - const { lat, lng, error } = getUserLocation(); + const { lat, lng, alpha, error } = getUserLocation(); const [otherLocations, setOtherLocations] = useState([]); const MIN_HEIGHT = 0.15; const MAX_HEIGHT = 0.9; @@ -65,7 +65,7 @@ export const Main = () => { const ws = new WebSocket(`${AppConfig.SOCKET_SERVER}/?token=${token}`); // 초기 위치 전송 ws.onopen = () => { - ws.send(JSON.stringify({ type: 'location', location: { lat, lng } })); + ws.send(JSON.stringify({ type: 'location', location: { lat, lng, alpha } })); }; ws.onmessage = event => { const data = JSON.parse(event.data); @@ -84,7 +84,8 @@ export const Main = () => { return () => ws.close(); } return undefined; - }, [lat, lng]); + }, [lat, lng, alpha]); + const goToAddChannel = () => { resetFooterContext(); resetUsers(); @@ -138,6 +139,7 @@ export const Main = () => { height="100%" lat={lat} lng={lng} + alpha={alpha} otherLocations={otherLocations} /> ) : ( From e5bb3de7179086e1be950933c7d25bdc19115aaf Mon Sep 17 00:00:00 2001 From: Hyein Jeong Date: Fri, 29 Nov 2024 02:12:23 +0900 Subject: [PATCH 2/4] =?UTF-8?q?[FE][Feat]=20#339=20:=20=EC=A7=80=EB=8F=84?= =?UTF-8?q?=EC=97=90=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=8B=A4=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EB=B0=A9=ED=96=A5=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MapCanvasForView 컴포넌트에서 hook 적용 --- .../canvasWithMap/canvasWithMapForView/MapCanvasForView.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/component/canvasWithMap/canvasWithMapForView/MapCanvasForView.tsx b/frontend/src/component/canvasWithMap/canvasWithMapForView/MapCanvasForView.tsx index 77d1bfbb..fd665a52 100644 --- a/frontend/src/component/canvasWithMap/canvasWithMapForView/MapCanvasForView.tsx +++ b/frontend/src/component/canvasWithMap/canvasWithMapForView/MapCanvasForView.tsx @@ -7,6 +7,7 @@ import { ZoomSlider } from '@/component/zoomslider/ZoomSlider'; export const MapCanvasForView = ({ lat, lng, + alpha, otherLocations, guests, width, @@ -70,6 +71,7 @@ export const MapCanvasForView = ({ guests, lat, lng, + alpha, }); const { @@ -101,7 +103,7 @@ export const MapCanvasForView = ({ useEffect(() => { redrawCanvas(); - }, [guests, otherLocations, lat, lng, map]); + }, [guests, otherLocations, lat, lng, alpha, map]); return (
Date: Fri, 29 Nov 2024 02:16:20 +0900 Subject: [PATCH 3/4] =?UTF-8?q?[FE][Feat]=20#339=20:=20=EC=A7=80=EB=8F=84?= =?UTF-8?q?=EC=97=90=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=8B=A4=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EB=B0=A9=ED=96=A5=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MapCanvasForView 컴포넌트에서 hook 적용 --- frontend/src/lib/types/canvasInterface.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/types/canvasInterface.ts b/frontend/src/lib/types/canvasInterface.ts index 15445017..4502393b 100644 --- a/frontend/src/lib/types/canvasInterface.ts +++ b/frontend/src/lib/types/canvasInterface.ts @@ -10,6 +10,12 @@ export interface IPoint { lng: number; } +export interface IPointWithAlpha { + lat: number; + lng: number; + alpha: number; +} + export interface ICanvasPoint { x: number; y: number; @@ -21,8 +27,8 @@ export interface ICanvasScreenProps { } export interface IOtherLiveLocations { - location: IPoint; - token: string; + location: IPointWithAlpha; + color: string; } export interface IMarkerStyle { @@ -41,6 +47,7 @@ export interface IGuestDataInMapProps { export interface IMapCanvasViewProps { lat: number; lng: number; + alpha?: number | null; otherLocations?: IOtherLiveLocations[] | null; guests?: IGuestDataInMapProps[] | null; width: string; From f109805e6aa80e1408fd966663a209653b81fb10 Mon Sep 17 00:00:00 2001 From: Hyein Jeong Date: Fri, 29 Nov 2024 02:20:22 +0900 Subject: [PATCH 4/4] =?UTF-8?q?[FE][Feat]=20#339=20:=20=EC=A7=80=EB=8F=84?= =?UTF-8?q?=EC=97=90=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=8B=A4=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EB=B0=A9=ED=96=A5=20=ED=91=9C=EC=8B=9C=20=EB=B0=8F?= =?UTF-8?q?=20=EC=BA=90=EB=A6=AD=ED=84=B0=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MapCanvasForView 지도에 사용자 실시간 방향 표시 - GuestView 실시간 방향 표시 - HostView 실시간 방향 표시 - guest, host들의 캐릭터 변경 --- frontend/src/hooks/useRedraw.ts | 139 ++++++++++++++++++++++++------- frontend/src/hooks/useSocket.ts | 33 ++++++++ frontend/src/pages/GuestView.tsx | 68 +++++++++++---- frontend/src/pages/HostView.tsx | 85 +++++++++++++++++-- 4 files changed, 273 insertions(+), 52 deletions(-) create mode 100644 frontend/src/hooks/useSocket.ts diff --git a/frontend/src/hooks/useRedraw.ts b/frontend/src/hooks/useRedraw.ts index 84728cb8..18f95e13 100644 --- a/frontend/src/hooks/useRedraw.ts +++ b/frontend/src/hooks/useRedraw.ts @@ -4,22 +4,31 @@ import { LINE_WIDTH, STROKE_STYLE } from '@/lib/constants/canvasConstants.ts'; import startmarker from '@/assets/startmarker.svg'; import endmarker from '@/assets/endmarker.svg'; import mylocation from '@/assets/mylocation.svg'; -import guestlocationmarker from '@/assets/guestlocationmarker.svg'; +import character1 from '@/assets/character1.png'; +import character2 from '@/assets/character2.png'; +import { IMarkerStyle } from '@/lib/types/canvasInterface.ts'; interface ILatLng { lat: number; lng: number; } +interface ILatLngAlpha { + lat: number; + lng: number; + alpha: number; +} + interface IOtherLocation { - location: ILatLng; - token: string; + location: ILatLngAlpha; + color: string; } interface IGuest { startPoint: ILatLng; endPoint: ILatLng; paths: ILatLng[]; + markerStyle: IMarkerStyle; } interface IUseRedrawCanvasProps { @@ -35,6 +44,7 @@ interface IUseRedrawCanvasProps { guests?: IGuest[] | null; lat?: number; lng?: number; + alpha?: number | null; } export const useRedrawCanvas = ({ @@ -48,11 +58,13 @@ export const useRedrawCanvas = ({ guests = [], lat, lng, + alpha = 0, }: IUseRedrawCanvasProps) => { const startImageRef = useRef(null); const endImageRef = useRef(null); const mylocationRef = useRef(null); - const guestlocationmarkerRef = useRef(null); + const character1Ref = useRef(null); + const character2Ref = useRef(null); useEffect(() => { startImageRef.current = new Image(); @@ -64,19 +76,83 @@ export const useRedrawCanvas = ({ mylocationRef.current = new Image(); mylocationRef.current.src = mylocation; - guestlocationmarkerRef.current = new Image(); - guestlocationmarkerRef.current.src = guestlocationmarker; + character1Ref.current = new Image(); + character1Ref.current.src = character1; + + character2Ref.current = new Image(); + character2Ref.current.src = character2; }, []); const drawMarker = ( ctx: CanvasRenderingContext2D, point: { x: number; y: number } | null, image: HTMLImageElement | null, + zoom: number, + rotate: number, ) => { if (point && image) { - const markerSize = 32; - ctx.drawImage(image, point.x - markerSize / 2, point.y - markerSize, markerSize, markerSize); + const markerSize = zoom * 5; + ctx.save(); + ctx.translate(point.x, point.y); + ctx.rotate(rotate); + ctx.drawImage(image, -markerSize / 2, -markerSize / 2, markerSize, markerSize); + ctx.restore(); + } + }; + + // eslint-disable-next-line no-shadow + const hexToRgba = (hex: string, alpha: number) => { + // eslint-disable-next-line no-param-reassign + hex = hex.replace(/^#/, ''); + + if (hex.length === 3) { + // eslint-disable-next-line no-param-reassign + hex = hex + .split('') + .map(char => char + char) + .join(''); } + + const bigint = parseInt(hex, 16); + const r = (bigint >> 16) & 255; + const g = (bigint >> 8) & 255; + const b = bigint & 255; + + return `rgba(${r}, ${g}, ${b}, ${alpha})`; + }; + + const drawNeonCircleAndDirection = ( + ctx: CanvasRenderingContext2D, + point: { x: number; y: number } | null, + zoom: number, + color: string, + ) => { + if (!point) return; + + const radius = zoom * 3; + const gradient = ctx.createRadialGradient( + point.x, + point.y + zoom, + 0, + point.x, + point.y + zoom, + radius, + ); + + const alphaStart = 0.75; + const alphaEnd = 0; + + gradient.addColorStop(0, hexToRgba(color || '#3498db', alphaStart)); + gradient.addColorStop(1, hexToRgba(color || '#3498db', alphaEnd)); + + ctx.beginPath(); + ctx.arc(point.x, point.y + zoom + 1, radius, 0, 2 * Math.PI); + ctx.fillStyle = gradient; + ctx.fill(); + + ctx.save(); + + ctx.restore(); }; const drawPath = (ctx: CanvasRenderingContext2D, points: ILatLng[]) => { @@ -96,20 +172,6 @@ export const useRedrawCanvas = ({ } }; - // const getMarkerColor = (token: string): 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 redrawCanvas = () => { if (!canvasRef.current || !map) return; @@ -123,14 +185,15 @@ export const useRedrawCanvas = ({ ctx.lineCap = 'round'; ctx.lineJoin = 'round'; + const zoom = map.getZoom(); if (startMarker) { const startPoint = latLngToCanvasPoint(startMarker); - drawMarker(ctx, startPoint, startImageRef.current); + drawMarker(ctx, startPoint, startImageRef.current, zoom, 0); } if (endMarker) { const endPoint = latLngToCanvasPoint(endMarker); - drawMarker(ctx, endPoint, endImageRef.current); + drawMarker(ctx, endPoint, endImageRef.current, zoom, 0); } if (pathPoints) { @@ -139,24 +202,38 @@ export const useRedrawCanvas = ({ if (lat && lng) { const currentLocation = latLngToCanvasPoint({ lat, lng }); - drawMarker(ctx, currentLocation, mylocationRef.current); + if (alpha) { + drawMarker(ctx, currentLocation, character1Ref.current, zoom, (alpha * Math.PI) / 180); + } else { + drawMarker(ctx, currentLocation, character1Ref.current, zoom, 0); + } } if (otherLocations) { - otherLocations.forEach(({ location }) => { - // const markerColor = getMarkerColor(token); - const locationPoint = latLngToCanvasPoint(location); - drawMarker(ctx, locationPoint, guestlocationmarkerRef.current); + otherLocations.forEach(({ location, color }) => { + const locationPoint = latLngToCanvasPoint({ + lat: location.lat ? location.lat : 0, + lng: location.lng ? location.lng : 0, + }); + + drawNeonCircleAndDirection(ctx, locationPoint, zoom, color); + drawMarker( + ctx, + locationPoint, + character2Ref.current, + zoom, + (location.alpha * Math.PI) / 180, + ); }); } if (guests) { guests.forEach(({ startPoint, endPoint, paths }) => { const startLocation = latLngToCanvasPoint(startPoint); - drawMarker(ctx, startLocation, startImageRef.current); + drawMarker(ctx, startLocation, startImageRef.current, zoom, 0); const endLocation = latLngToCanvasPoint(endPoint); - drawMarker(ctx, endLocation, endImageRef.current); + drawMarker(ctx, endLocation, endImageRef.current, zoom, 0); drawPath(ctx, paths); }); diff --git a/frontend/src/hooks/useSocket.ts b/frontend/src/hooks/useSocket.ts new file mode 100644 index 00000000..8857d345 --- /dev/null +++ b/frontend/src/hooks/useSocket.ts @@ -0,0 +1,33 @@ +import { useState, useEffect } from 'react'; + +export const useSocket = (url: string) => { + const [ws, setWs] = useState(null); + + useEffect(() => { + const socket = new WebSocket(url); + setWs(socket); + + socket.onopen = () => { + console.log('Socket connected'); + }; + + socket.onmessage = event => { + const data = JSON.parse(event.data); + console.log(data); + }; + + socket.onclose = () => { + console.log('Socket closed'); + }; + + socket.onerror = err => { + console.error('Socket error:', err); + }; + + return () => { + socket.close(); + }; + }, [url]); + + return ws; +}; diff --git a/frontend/src/pages/GuestView.tsx b/frontend/src/pages/GuestView.tsx index 7a48dbab..03638a4c 100644 --- a/frontend/src/pages/GuestView.tsx +++ b/frontend/src/pages/GuestView.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef } from 'react'; import { IGuest } from '@/types/channel.types.ts'; import { getGuestInfo } from '@/api/channel.api.ts'; import { useLocation } from 'react-router-dom'; @@ -8,9 +8,14 @@ import { guestEntity } from '@/api/dto/channel.dto.ts'; import { GusetMarker } from '@/component/IconGuide/GuestMarker.tsx'; import { LoadingSpinner } from '@/component/common/loadingSpinner/LoadingSpinner.tsx'; import { getUserLocation } from '@/hooks/getUserLocation.ts'; +import { loadLocalData, saveLocalData } from '@/utils/common/manageLocalData.ts'; +import { AppConfig } from '@/lib/constants/commonConstants.ts'; +import { v4 as uuidv4 } from 'uuid'; export const GuestView = () => { - const { lat, lng, error } = getUserLocation(); + const { lat, lng, alpha, error } = getUserLocation(); + const location = useLocation(); + const [guestInfo, setGuestInfo] = useState({ id: '', name: '', @@ -20,24 +25,52 @@ export const GuestView = () => { paths: [], }); - const location = useLocation(); + const wsRef = useRef(null); + + useEffect(() => { + // 소켓 연결 초기화 + const token = loadLocalData(AppConfig.KEYS.BROWSER_TOKEN) || uuidv4(); + saveLocalData(AppConfig.KEYS.BROWSER_TOKEN, token); + + const ws = new WebSocket( + `${AppConfig.SOCKET_SERVER}/?token=${token}&channelId=${location.pathname.split('/')[2]}&role=guest&guestId=${location.pathname.split('/')[4]}`, + ); + + ws.onopen = () => { + console.log('WebSocket connection established'); + }; + + wsRef.current = ws; + }, [location]); + + useEffect(() => { + // 위치 정보가 변경될 때마다 전송 + if (lat && lng && wsRef.current?.readyState === WebSocket.OPEN) { + wsRef.current.send( + JSON.stringify({ + type: 'location', + location: { lat, lng, alpha }, + }), + ); + } + }, [lat, lng, alpha]); - const transformTypeGuestEntityToIGuest = (props: guestEntity): IGuest => { + const transformTypeGuestEntityToIGuest = (props: guestEntity | undefined): IGuest => { return { - id: props.id ?? '', - name: props.name ?? '', + id: props?.id ?? '', + name: props?.name ?? '', // name: '현재 내 위치', startPoint: { - lat: props.start_location?.lat ?? 0, - lng: props.start_location?.lng ?? 0, + lat: props?.start_location?.lat ?? 0, + lng: props?.start_location?.lng ?? 0, }, endPoint: { - lat: props.end_location?.lat ?? 0, - lng: props.end_location?.lng ?? 0, + lat: props?.end_location?.lat ?? 0, + lng: props?.end_location?.lng ?? 0, }, - paths: (props.path as IPoint[]) ?? [], + paths: (props?.path as IPoint[]) ?? [], markerStyle: { - color: props.marker_style?.color ?? '', + color: props?.marker_style?.color ?? '', }, }; }; @@ -46,7 +79,7 @@ export const GuestView = () => { getGuestInfo(channelId, userId) .then(res => { if (!res.data) throw new Error('🚀 Fetch Error: responsed undefined'); - const transfromedData = transformTypeGuestEntityToIGuest(res.data); + const transfromedData = transformTypeGuestEntityToIGuest(res.data.guest); setGuestInfo(transfromedData); }) .catch((err: any) => { @@ -64,7 +97,14 @@ export const GuestView = () => { {/* eslint-disable-next-line no-nested-ternary */} {lat && lng ? ( guestInfo ? ( - + ) : ( ) diff --git a/frontend/src/pages/HostView.tsx b/frontend/src/pages/HostView.tsx index 9c5f5460..41b83ab8 100644 --- a/frontend/src/pages/HostView.tsx +++ b/frontend/src/pages/HostView.tsx @@ -4,22 +4,82 @@ import { IGuest, IChannelInfo, IGuestData } from '@/types/channel.types.ts'; import { getChannelInfo } from '@/api/channel.api.ts'; import { useLocation } from 'react-router-dom'; import { MapCanvasForView } from '@/component/canvasWithMap/canvasWithMapForView/MapCanvasForView.tsx'; -import { IGuestDataInMapProps, IPoint } from '@/lib/types/canvasInterface.ts'; +import { IGuestDataInMapProps, IPoint, IPointWithAlpha } from '@/lib/types/canvasInterface.ts'; import { getChannelResEntity, guestEntity } from '@/api/dto/channel.dto.ts'; import { HostMarker } from '@/component/IconGuide/HostMarker.tsx'; import { LoadingSpinner } from '@/component/common/loadingSpinner/LoadingSpinner.tsx'; import { getUserLocation } from '@/hooks/getUserLocation.ts'; - +import { loadLocalData, saveLocalData } from '@/utils/common/manageLocalData.ts'; +import { AppConfig } from '@/lib/constants/commonConstants.ts'; +import { v4 as uuidv4 } from 'uuid'; +import { useSocket } from '@/hooks/useSocket.ts'; + +interface IOtherLocationsInHostView { + guestId: string; + location: IPointWithAlpha; + token: string; + color: string; +} export const HostView = () => { - const { lat, lng, error } = getUserLocation(); + const { lat, lng, alpha, error } = getUserLocation(); + const location = useLocation(); + const [channelInfo, setChannelInfo] = useState(); const [guestsData, setGuestsData] = useState([]); const [mapProps, setMapProps] = useState([]); const [clickedId, setClickedId] = useState(''); + const [otherLocations, setOtherLocations] = useState([]); const headerDropdownContext = useContext(HeaderDropdownContext); + const markerDefaultColor = ['#B4D033', '#22A751', '#2722A7', '#8F22A7', '#A73D22']; - const location = useLocation(); + if (!loadLocalData(AppConfig.KEYS.BROWSER_TOKEN)) { + const token = uuidv4(); + saveLocalData(AppConfig.KEYS.BROWSER_TOKEN, token); + } + const token = loadLocalData(AppConfig.KEYS.BROWSER_TOKEN); + const url = `${AppConfig.SOCKET_SERVER}/?token=${token}&channelId=${location.pathname.split('/')[2]}&role=host`; + + const ws = useSocket(url); + + useEffect(() => { + if (ws) { + ws.onmessage = event => { + const data = JSON.parse(event.data); + if (data.type === 'init') { + // 기존 클라이언트들의 위치 초기화 + const updatedLocations = data.clients.map((client: any, index: number) => { + const matchingGuest = channelInfo?.guests?.find(guest => guest.id === client.guestId); + return { + ...client, + color: matchingGuest?.markerStyle.color ?? markerDefaultColor[index], + }; + }); + setOtherLocations(updatedLocations); + } else if (data.type === 'location') { + // 새로 들어온 위치 업데이트 + const matchingGuest = guestsData?.find(guest => guest.id === data.guestId); + const updatedLocation = { + guestId: data.guestId, + location: data.location, + token: data.token, + color: matchingGuest?.markerStyle.color ?? '#ffffff', + }; + + setOtherLocations(prev => { + const index = prev.findIndex(el => el.guestId === data.guestId); + + if (index !== -1) { + const updatedLocations = [...prev]; + updatedLocations[index] = updatedLocation; + return updatedLocations; + } + return [...prev, updatedLocation]; + }); + } + }; + } + }, [ws, guestsData]); const transformTypeGuestEntityToIGuest = (props: guestEntity): IGuest => { return { @@ -74,8 +134,6 @@ export const HostView = () => { }, []); useEffect(() => { - const markerDefaultColor = ['#B4D033', '#22A751', '#2722A7', '#8F22A7', '#A73D22']; - if (channelInfo?.guests) { const data: IGuestData[] = channelInfo.guests.map((guest, index) => ({ name: guest.name, @@ -107,8 +165,21 @@ export const HostView = () => { {/* eslint-disable-next-line no-nested-ternary */} {lat && lng ? ( + // eslint-disable-next-line no-nested-ternary mapProps ? ( - + otherLocations ? ( + + ) : ( + + ) ) : ( )