From 078a56350de5193c17c4c7de855430a7d8c4b3fa Mon Sep 17 00:00:00 2001 From: "lkh14011424@gmail.com" Date: Mon, 26 Aug 2024 23:11:27 +0900 Subject: [PATCH] =?UTF-8?q?[feat/#28]=20=EA=B1=B0=EB=B6=81=EB=AA=A9,=20?= =?UTF-8?q?=EC=96=B4=EA=B9=A8=20=ED=8B=80=EC=96=B4=EC=A7=90=20=EC=9E=90?= =?UTF-8?q?=EC=84=B8=20=EC=9C=A0=EC=A7=80=20=EC=8B=9C=20=EC=84=9C=EB=B2=84?= =?UTF-8?q?=EC=97=90=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A0=84=EC=86=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/axiosInstance.ts | 17 ++++++- src/api/pose.ts | 26 +++++++++++ src/api/snapshot.ts | 2 +- src/components/PoseDetector.tsx | 81 +++++++++++++++++---------------- src/hooks/usePoseMutation.ts | 13 ++++++ src/pages/MonitoringPage.tsx | 2 +- src/utils/detector.ts | 16 +++---- 7 files changed, 107 insertions(+), 50 deletions(-) create mode 100644 src/api/pose.ts create mode 100644 src/hooks/usePoseMutation.ts diff --git a/src/api/axiosInstance.ts b/src/api/axiosInstance.ts index b4f7f12..7552d62 100644 --- a/src/api/axiosInstance.ts +++ b/src/api/axiosInstance.ts @@ -2,6 +2,7 @@ import axios from "axios" const API_BASE_URL = import.meta.env.VITE_API_BASE_URL +const EXCEPT_HEADER_API = ["/token", "/user/me", "/oauth"] const axiosInstance = axios.create({ baseURL: API_BASE_URL, @@ -10,6 +11,18 @@ const axiosInstance = axios.create({ }, }) +// 요청 인터셉터 설정 +axiosInstance.interceptors.request.use((config) => { + // 특정 API 경로에 대해 토큰을 제거 + if (config.url) { + // 요청 URL이 EXCEPT_HEADER_API에 포함되어 있는지 확인 + if (EXCEPT_HEADER_API.some((api) => config.url?.includes(api))) { + delete config.headers["X-HERO-AUTH-TOKEN"] + } + } + return config +}) + // localStorage에서 토큰 가져오기 const token = localStorage.getItem("accessToken") if (token) { @@ -19,13 +32,13 @@ if (token) { // 엑세스 토큰 설정 함수 export const setAccessToken = (_token: string): void => { axiosInstance.defaults.headers.common["X-HERO-AUTH-TOKEN"] = _token - // localStorage.setItem("accessToken", token) + localStorage.setItem("accessToken", _token) } // 엑세스 토큰 제거 함수 export const clearAccessToken = (): void => { delete axiosInstance.defaults.headers.common["X-HERO-AUTH-TOKEN"] - // localStorage.removeItem("accessToken") + localStorage.removeItem("accessToken") } export default axiosInstance diff --git a/src/api/pose.ts b/src/api/pose.ts new file mode 100644 index 0000000..e1b6fed --- /dev/null +++ b/src/api/pose.ts @@ -0,0 +1,26 @@ +import { pose } from "@/utils" +import axiosInstance from "./axiosInstance" + +export type poseType = "GOOD" | "TURTLE_NECK" | "SHOULDER_TWIST" | "CHIN_UTP" | "TAILBONE_SIT" + +export interface poseReq { + snapshot: pose + type: poseType + imageUrl?: string +} + +export interface poseRes { + id: number + uid: number + type: poseType + createdAt: string +} +export const sendPose = async (poseReq: poseReq): Promise => { + try { + const res = await axiosInstance.post(`/pose-snapshots`, { ...poseReq }) + const { id, uid, type, createdAt } = res.data.data + return { id, uid, type, createdAt } + } catch (e) { + throw e + } +} diff --git a/src/api/snapshot.ts b/src/api/snapshot.ts index 89e3e11..de3ae27 100644 --- a/src/api/snapshot.ts +++ b/src/api/snapshot.ts @@ -36,7 +36,7 @@ export interface createSnapshotRes { export const createSnapshot = async (snapshot: snapshot): Promise => { try { - const res = await axiosInstance.post(`/pose-layouts`, { points : snapshot.points }) + const res = await axiosInstance.post(`/pose-layouts`, { points: snapshot.points }) const { id } = res.data.data return { id } diff --git a/src/components/PoseDetector.tsx b/src/components/PoseDetector.tsx index 1642730..181f0a5 100644 --- a/src/components/PoseDetector.tsx +++ b/src/components/PoseDetector.tsx @@ -11,30 +11,36 @@ import { useCreateSnaphot } from "@/hooks/useSnapshotMutation" import { position } from "@/api" import PostureCheckIcon from "@assets/icons/good-posture-check-button-icon.svg?react" import GuideIcon from "@assets/icons/posture-guide-button-icon.svg?react" +import { useSendPose } from "@/hooks/usePoseMutation" +import { poseType } from "@/api/pose" const PoseDetector: React.FC = () => { const [isScriptLoaded, setIsScriptLoaded] = useState(false) const [isScriptError, setIsScriptError] = useState(false) - const [slope, setSlope] = useState(null) const [isTextNeck, setIsTextNeck] = useState(null) + const [isShoulderTwist, setIsShoulderTwist] = useState(null) const [isModelLoaded, setIsModelLoaded] = useState(false) const [isSnapSaved, setIsSnapSaved] = useState(false) const [isPopupVisible, setIsPopupVisible] = useState(false) const modelRef = useRef(null) const snapRef = useRef(null) const resultRef = useRef(null) - const textNeckStartTime = useRef(null) - const timer = useRef(null) + + const turtleNeckTimer = useRef(null) + const shoulderTwistTimer = useRef(null) + // const chinUtpTimer = useRef(null) + // const tailboneSit = useRef(null) + const canvasRef = useRef(null) const snapshot = useSnapshotStore((state) => state.snapshot) const createSnapMutation = useCreateSnaphot() + const sendPoseMutation = useSendPose() + const setSnap = useSnapshotStore((state) => state.setSnapshot) const { requestNotificationPermission, showNotification } = usePushNotification() - const requestApi = (delay: number): Promise => new Promise((resolve) => setTimeout(resolve, delay)) - // webgl설정 const initializeBackend = async (): Promise => { await window.ml5.setBackend("webgl") @@ -81,31 +87,14 @@ const PoseDetector: React.FC = () => { drawPose(results, canvasRef.current) } if (snapRef.current) { - const _slope = detectSlope(snapRef.current, results, false) + const _isShoulderTwist = detectSlope(snapRef.current, results, false) const _isTextNeck = detectTextNeck(snapRef.current, results, true) - if (_slope !== null) setSlope(_slope) - if (_isTextNeck !== null) setIsTextNeck(_isTextNeck) - if (_isTextNeck) { - if (!textNeckStartTime || !textNeckStartTime.current) { - textNeckStartTime.current = Date.now() - // 거북목 자세 3초 유지 시, api 요청을 보내게 (콘솔 로그에서 확인) - } else if (Date.now() - textNeckStartTime.current >= 3000) { - if (!timer.current) { - timer.current = setInterval(() => { - requestApi(1000).then(() => console.log("api request")) - showNotification() - }, 2000) - } - } - } else { - clearInterval(timer.current) - timer.current = null - textNeckStartTime.current = null - } + if (_isShoulderTwist !== null) setIsShoulderTwist(_isShoulderTwist) + if (_isTextNeck !== null) setIsTextNeck(_isTextNeck) } }, - [setSlope, setIsTextNeck, showNotification] + [setIsShoulderTwist, setIsTextNeck, showNotification] ) const detectStart = useCallback( @@ -150,11 +139,34 @@ const PoseDetector: React.FC = () => { } } - const getIsRight = (_slope: string | null, _isTextNeck: boolean | null): boolean => { - if (_slope === "적절한 자세입니다" && !_isTextNeck) return true + const getIsRight = (_isShoulderTwist: boolean | null, _isTextNeck: boolean | null): boolean => { + if (!_isShoulderTwist && !_isTextNeck) return true return false } + // 공통 타이머 관리 함수 + const usePoseTimer = (isActive: boolean | null, poseType: poseType, timerRef: React.MutableRefObject) => { + useEffect(() => { + if (isActive) { + if (!timerRef.current) { + timerRef.current = setInterval(() => { + if (resultRef.current) { + const { keypoints, score } = resultRef.current[0] + const req = { snapshot: { keypoints, score }, type: poseType } + sendPoseMutation.mutate(req) + } + }, 5000) + } + } else { + clearInterval(timerRef.current) + timerRef.current = null + } + }, [isActive, poseType]) + } + + usePoseTimer(isTextNeck, "TURTLE_NECK", turtleNeckTimer) + usePoseTimer(isShoulderTwist, "SHOULDER_TWIST", shoulderTwistTimer) + useEffect(() => { requestNotificationPermission() getScript() @@ -173,20 +185,13 @@ const PoseDetector: React.FC = () => { getUserSnap() }, [snapshot]) - // const initializePoseMonitoring = () => { - // setIsTextNeck(null) - // setSlope(null) - // snapRef.current = null - // setIsSnapSaved(false) - // } - // 팝업 열기 - const handleShowPopup = () : void => { + const handleShowPopup = (): void => { setIsPopupVisible(true) } // 팝업 닫기 - const handleClosePopup = () : void => { + const handleClosePopup = (): void => { setIsPopupVisible(false) } @@ -204,7 +209,7 @@ const PoseDetector: React.FC = () => {
{!isSnapSaved ? "바른 자세를 취한 후, 하단의 버튼을 눌러주세요." - : getIsRight(slope, isTextNeck) + : getIsRight(isShoulderTwist, isTextNeck) ? "올바른 자세입니다." : "올바르지 않은 자세입니다."}
diff --git a/src/hooks/usePoseMutation.ts b/src/hooks/usePoseMutation.ts new file mode 100644 index 0000000..67ae954 --- /dev/null +++ b/src/hooks/usePoseMutation.ts @@ -0,0 +1,13 @@ +import { poseReq, poseRes, sendPose } from "@/api/pose" +import { useMutation, UseMutationResult } from "@tanstack/react-query" + +export const useSendPose = (): UseMutationResult => { + return useMutation({ + mutationFn: (poseReq: poseReq) => { + return sendPose(poseReq) + }, + onSuccess: (data) => { + console.log(data) + }, + }) +} diff --git a/src/pages/MonitoringPage.tsx b/src/pages/MonitoringPage.tsx index 16535db..06afb74 100644 --- a/src/pages/MonitoringPage.tsx +++ b/src/pages/MonitoringPage.tsx @@ -6,7 +6,7 @@ import React, { useState } from "react" const MonitoringPage: React.FC = () => { const [isSidebarOpen, setIsSidebarOpen] = useState(false) - const toggleSidebar = () => { + const toggleSidebar = (): void => { setIsSidebarOpen((prev) => !prev) } diff --git a/src/utils/detector.ts b/src/utils/detector.ts index 8fa79dd..daef4a8 100644 --- a/src/utils/detector.ts +++ b/src/utils/detector.ts @@ -143,7 +143,7 @@ export const detectTextNeck = (refer: pose[], comp: pose[], isSnapShotMode = tru * @returns 기울기가 왼쪽으로 치우쳤으면 "left", 오른쪽으로 치우쳤으면 "right"를 반환하며, * 기울기를 계산할 수 없는 경우 null을 반환 */ -export const detectSlope = (refer: pose[], comp: pose[], isSnapShotMode = true): string | null => { +export const detectSlope = (refer: pose[], comp: pose[], isSnapShotMode = true): boolean | null => { if (!comp) return null const referLeftSoulder = getXYfromPose(refer, "left_shoulder") @@ -151,17 +151,17 @@ export const detectSlope = (refer: pose[], comp: pose[], isSnapShotMode = true): const compLeftShoulder = getXYfromPose(comp, "left_shoulder") const compRightShoulder = getXYfromPose(comp, "right_shoulder") - const SHOULDER_DIFF_THRESHOLD = 60 + const SHOULDER_DIFF_THRESHOLD = 80 if (!isSnapShotMode && compLeftShoulder && compRightShoulder) { const shoulderSlope = compLeftShoulder.y - compRightShoulder.y if (Math.abs(shoulderSlope) < SHOULDER_DIFF_THRESHOLD) { - return "적절한 자세입니다" + return false } else if (shoulderSlope > 0) { - return "오른쪽 어깨가 올라갔습니다" + return true } else { - return "왼쪽 어깨가 올라갔습니다" + return true } } @@ -177,10 +177,10 @@ export const detectSlope = (refer: pose[], comp: pose[], isSnapShotMode = true): const slopeDifference = Math.abs(referSlope - compSlope) if (slopeDifference <= tenPercentOfReferSlope) { - return "적절한 자세입니다" + return false } else if (referSlope < compSlope) { - return "왼쪽으로 치우쳐져 있습니다" + return true } else { - return "오른쪽으로 치우쳐져 있습니다" + return true } }