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

Refator: 소켓 최적화(소켓새로고침 최적화, 디바운싱, 쓰로틀링), 커서공유 구현 #276

Merged
merged 16 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@
"@types/react-beautiful-dnd": "^13.1.8",
"axios": "^1.6.2",
"date-fns": "^3.1.0",
"lodash": "^4.17.21",
"msw": "0.36.3",
"path": "^0.12.7",
"react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1",
"react-dom": "^18.2.0",
"react-hook-form": "^7.49.2",
"react-icons": "^5.0.1",
"react-infinite-scroller": "^1.2.6",
"react-kakao-maps-sdk": "^1.1.24",
"react-modal": "^3.16.1",
Expand All @@ -52,6 +54,7 @@
"websocket": "^1.0.34"
},
"devDependencies": {
"@types/lodash": "^4.14.202",
"@types/react": "^18.2.43",
"@types/react-date-range": "^1.4.9",
"@types/react-dom": "^18.2.17",
Expand Down
21 changes: 21 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions src/@types/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,27 @@ export type subBudgetRes = {
} | null;
};

export type subCursorRes = {
status: number;
message: string;
data: {
color: string;
tripId: string;
visitDate: string;
memberId: number;
name: string;
x: number;
y: number;
} | null;
};

export type SocketContextType = {
tripInfo: subInfoRes | null;
tripItem: subItemRes | null;
tripPath: subPathRes | null;
tripMember: subMemberRes | null;
tripBudget: subBudgetRes | null;
tripCursor: subCursorRes | null;
tripId: string;
callBackPub: (callback: () => void) => void;
};
Expand Down
21 changes: 21 additions & 0 deletions src/@types/socket.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,20 @@ type subBudgetMessage = (response: {
};
}) => void;

type subCursorMessage = (response: {
status: number;
message: string;
data: {
color: string;
tripId: string;
visitDate: string;
memberId: number;
name: string;
x: number;
y: number;
};
}) => void;

interface pubInfo {
startDate: string;
endDate: string;
Expand Down Expand Up @@ -138,3 +152,10 @@ interface pubGetPathAndItems {
interface pubUpdateBudget {
budget: number;
}

interface pubCursor {
token: string;
visitDate: string;
x: number;
y: number;
}
21 changes: 21 additions & 0 deletions src/api/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,18 @@ export const subBudget = (
});
};

// 커서 공유
export const subCursor = (
tripId: string,
visitDate: string,
subCursorMessage: subCursorMessage,
) => {
socketClient.subscribe(`/sub/${tripId}/cursor/${visitDate}`, (message) => {
const res = JSON.parse(message.body);
subCursorMessage(res);
});
};

// 소켓 전송
// 여정 기본 정보 변경 이벤트 발생시
export const pubInfo = (pubInfo: pubInfo, tripId: string) => {
Expand Down Expand Up @@ -101,6 +113,7 @@ export const pubUpdateTripItem = (
destination: `/pub/trips/${tripId}/updateTripItemOrder`,
body: JSON.stringify(pubUpdateTripItem),
});
console.log('실행');
};

// 여행 날짜별 교통 수단 변경 이벤트 발생시 (01/16 업데이트)
Expand Down Expand Up @@ -187,3 +200,11 @@ export const pubUpdateBudget = (
body: JSON.stringify(pubUpdateBudget),
});
};

// 커서공유
export const pubCursor = (pubCursor: pubCursor, tripId: string) => {
socketClient.publish({
destination: `/pub/trips/${tripId}/cursor`,
body: JSON.stringify(pubCursor),
});
};
2 changes: 1 addition & 1 deletion src/components/DetailSectionTop/DetailAddSchedule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ const DetailAddSchedule = () => {
key={index}
className="flex w-[99px] items-start">
<button
className={`relative flex h-10 flex-shrink-0 flex-grow-0 items-center justify-center gap-1 rounded-[168px] border-[1.25px] border-solid ${
className={`relative flex h-10 w-[99px] flex-shrink-0 flex-grow-0 items-center justify-center gap-1 rounded-[168px] border-[1.25px] border-solid ${
index === selectedButton
? 'border-main2'
: 'border-gray3'
Expand Down
114 changes: 114 additions & 0 deletions src/components/Plan/PlanCursor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { useEffect, useState, useContext, RefObject } from 'react';
import { BsFillCursorFill } from 'react-icons/bs';
import { pubCursor } from '@api/socket';
import { socketContext } from '@hooks/useSocket';
import { useGetTripsAuthority } from '@hooks/useGetTripsAuthority';
import { useRecoilValue } from 'recoil';
import { visitDateState } from '@recoil/socket';
import { throttle } from 'lodash';

type PlanCursorProps = {
props: RefObject<HTMLDivElement>;
};

const PlanCursor = ({ props }: PlanCursorProps) => {
const visitDate = useRecoilValue(visitDateState);
const token = localStorage.getItem('accessToken');
const { memberId } = useGetTripsAuthority();
const { tripId, tripMember } = useContext(socketContext);
const [position, setPosition] = useState({ myX: 0, myY: 0 });

const myName = tripMember?.data?.tripMembers.find(
(member) => member.memberId === memberId,
);

const throttledPubCursor = throttle((x, y) => {
if (token && visitDate) {
pubCursor(
{
token: token,
visitDate: visitDate.visitDate,
x,
y,
},
tripId,
);
}
}, 50);

useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
const myX = e.clientX;
const myY = e.clientY;
setPosition({ myX, myY });

const x = (e.clientX + window.scrollX) / window.innerWidth;
const y = (e.clientY + window.scrollY) / window.innerHeight;

if (token && myName && visitDate && tripId) {
throttledPubCursor(x, y);
}
};

const handleMouseEnter = () => {
cursorStyle('none');
};

const handleMouseLeave = () => {
cursorStyle('auto');
};

const cursorArea = props.current;

const cursorStyle = (style: string): void => {
document.querySelectorAll('*').forEach((el) => {
const element = el as HTMLElement;
element.style.cursor = style;
});
};

if (cursorArea) {
cursorArea.addEventListener('mousemove', handleMouseMove);
cursorArea.addEventListener('mouseenter', handleMouseEnter);
cursorArea.addEventListener('mouseleave', handleMouseLeave);
}

return () => {
if (cursorArea) {
cursorStyle('auto');
cursorArea.removeEventListener('mousemove', handleMouseMove);
cursorArea.removeEventListener('mouseenter', handleMouseEnter);
cursorArea.removeEventListener('mouseleave', handleMouseLeave);
}
};
}, [token, myName, visitDate, tripId]);

const getColorByMemberId = (memberId: number | null) => {
if (memberId == null) {
return 'black';
}

const colors = ['#FF2167', '#7932FF', '#29DDF6', '#FFAC16', '#16E7A9'];
const remainder = memberId % 5;
return colors[remainder];
};

return (
<div
className="pointer-events-none fixed z-50 w-full -translate-x-2 -translate-y-2 transform"
style={{ left: `${position.myX}px`, top: `${position.myY}px` }}>
<BsFillCursorFill
size={15}
color={getColorByMemberId(memberId)}
className="scale-x-[-1]"
/>
<div
className="text-bold absolute left-1 top-2 p-1 text-center text-xs"
style={{ color: getColorByMemberId(memberId) }}>
{myName?.name}
</div>
</div>
);
};

export default PlanCursor;
26 changes: 14 additions & 12 deletions src/components/Plan/PlanEditItemBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@ import {
} from 'react-beautiful-dnd';
import { useState, useEffect } from 'react';
import { pubUpdateTripItem, pubDeleteItem } from '@api/socket';
import { useContext } from 'react';
import { socketContext } from '@hooks/useSocket';
import { pubUpdateTripItemReq } from '@/@types/service';
import Alert from '@components/common/alert/Alert';
import ToastPopUp from '@components/common/toastpopup/ToastPopUp';
import PlanMoveItem from './PlanMoveItem';
import { useRecoilState } from 'recoil';
import { isEditState } from '@recoil/socket';
import { debounce } from 'lodash';

type PlanItemBoxProps = {
item: TripItem[];
Expand All @@ -34,7 +33,6 @@ const PlanEditItemBox = ({
return <div>Missing data</div>;
}

const { callBackPub } = useContext(socketContext);
const [, setIsEdit] = useRecoilState(isEditState);
const [items, setItems] = useState(item);
const [newData, setNewData] = useState<pubUpdateTripItemReq | null>(null);
Expand Down Expand Up @@ -63,17 +61,19 @@ const PlanEditItemBox = ({
});
};

const debouncedPubUpdateTripItem = debounce((newData, tripId) => {
pubUpdateTripItem(newData, tripId);
}, 1000);

useEffect(() => {
if (newData && tripId) {
callBackPub(() => pubUpdateTripItem(newData, tripId));
debouncedPubUpdateTripItem(newData, tripId);
}
}, [newData]);

const handleConfirm = () => {
if (tripId && visitDate && selectedItemId) {
callBackPub(() =>
pubDeleteItem({ tripId: tripId, visitDate: visitDate }, selectedItemId),
);
pubDeleteItem({ tripId: tripId, visitDate: visitDate }, selectedItemId);
}
setToastPopUp(() => ({
isPopUp: true,
Expand Down Expand Up @@ -132,22 +132,24 @@ const PlanEditItemBox = ({
checked={selectedItemId === item.tripItemId}></input>
</div>
<div className="flex w-full flex-col">
<div className="mb-[8px] flex h-[87.5px] rounded-lg border border-solid border-[#ededed] bg-white">
<div className="mb-[8px] flex h-[88.5px] rounded-lg border border-solid border-[#ededed] bg-white">
<img
className="h-[87px] w-[93px] rounded-bl-lg rounded-tl-lg "
src={item.thumbnailUrl}
alt="img"
/>
<div className="flex w-full flex-col p-[10px]">
<div className="flex h-[88px] w-full flex-col px-[10px] py-[8px]">
<div className="flex text-left text-[14px] font-medium text-black">
{item.name.length > 17
? item.name.slice(0, 17) + '...'
: item.name}
</div>
<div className="mt-[3px] flex h-fit w-fit items-center justify-center gap-2 rounded-[3px] bg-[#ededed] p-[4px] text-center text-[11px] text-black">
{item.category}
<div className="mb-[11px] mt-[4px] flex h-[16px] w-fit items-center justify-center rounded-[3px] bg-[#ededed] px-[4px] py-[8px] text-center text-[11px] text-black">
<div className="flex h-[13px] items-center justify-center text-center">
{item.category}
</div>
</div>
<div className="mt-[15px] text-sm font-bold text-black">
<div className="flex justify-between text-sm font-bold text-black">
{item.price} 원
</div>
</div>
Expand Down
Loading