diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..e399b34 --- /dev/null +++ b/.env.sample @@ -0,0 +1 @@ +VITE_HOST_NAME= \ No newline at end of file diff --git a/cspell.json b/cspell.json index 2bd1743..b5d7a79 100644 --- a/cspell.json +++ b/cspell.json @@ -2,10 +2,5 @@ "ignorePaths": ["node_modules/**", "*.svg"], "version": "0.2", "language": "en", - "words": [ - "NATSUMATSURI", - "Dela", - "Yuji", - "Syuku" - ] + "words": ["NATSUMATSURI", "Dela", "Yuji", "Syuku", "zustand"] } diff --git a/package.json b/package.json index 1fc0c98..0088bbd 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "dependencies": { "react": "^18.3.1", "react-dom": "^18.3.1", - "react-router-dom": "^6.26.0" + "react-router-dom": "^6.26.0", + "zustand": "^4.5.4" }, "devDependencies": { "@biomejs/biome": "1.8.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 47af117..8246df7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ dependencies: react-router-dom: specifier: ^6.26.0 version: 6.26.0(react-dom@18.3.1)(react@18.3.1) + zustand: + specifier: ^4.5.4 + version: 4.5.4(@types/react@18.3.3)(react@18.3.1) devDependencies: '@biomejs/biome': @@ -1015,7 +1018,6 @@ packages: /@types/prop-types@15.7.12: resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} - dev: true /@types/react-dom@18.3.0: resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} @@ -1028,7 +1030,6 @@ packages: dependencies: '@types/prop-types': 15.7.12 csstype: 3.1.3 - dev: true /@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0)(eslint@8.57.0)(typescript@5.5.4): resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==} @@ -1456,7 +1457,6 @@ packages: /csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - dev: true /debug@4.3.6: resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} @@ -2365,6 +2365,14 @@ packages: punycode: 2.3.1 dev: true + /use-sync-external-store@1.2.0(react@18.3.1): + resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.3.1 + dev: false + /vite@5.4.0: resolution: {integrity: sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==} engines: {node: ^18.0.0 || >=20.0.0} @@ -2475,3 +2483,23 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: true + + /zustand@4.5.4(@types/react@18.3.3)(react@18.3.1): + resolution: {integrity: sha512-/BPMyLKJPtFEvVL0E9E9BTUM63MNyhPGlvxk1XjrfWTUlV+BR8jufjsovHzrtR6YNcBEcL7cMHovL1n9xHawEg==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + dependencies: + '@types/react': 18.3.3 + react: 18.3.1 + use-sync-external-store: 1.2.0(react@18.3.1) + dev: false diff --git a/src/hooks/useOrientation.ts b/src/hooks/useOrientation.ts new file mode 100644 index 0000000..ef30dc3 --- /dev/null +++ b/src/hooks/useOrientation.ts @@ -0,0 +1,49 @@ +import { useCallback, useEffect, useState } from "react"; + +export interface Orientation { + alpha: number; + beta: number; + gamma: number; +} + +export const useOrientation = () => { + const [initialOrientation, setInitialOrientation] = + useState(null); + const [orientationDiff, setOrientationDiff] = useState({ + alpha: 0, + beta: 0, + gamma: 0, + }); + + const handleOrientation = useCallback( + (event: DeviceOrientationEvent) => { + if (!initialOrientation) { + setInitialOrientation({ + alpha: event.alpha || 0, + beta: event.beta || 0, + gamma: event.gamma || 0, + }); + } else { + setOrientationDiff({ + alpha: (event.alpha || 0) - initialOrientation.alpha, + beta: (event.beta || 0) - initialOrientation.beta, + gamma: (event.gamma || 0) - initialOrientation.gamma, + }); + } + }, + [initialOrientation], + ); + + const reset = useCallback(() => { + setInitialOrientation(null); + }, []); + + useEffect(() => { + window.addEventListener("deviceorientation", handleOrientation); + return () => { + window.removeEventListener("deviceorientation", handleOrientation); + }; + }, [handleOrientation]); + + return { orientationDiff, reset }; +}; diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..d527464 --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,2 @@ +export { useSocketRefStore } from "./useSocketRefStore"; +export { useUUIDStore } from "./useUUIDStore"; diff --git a/src/store/useSocketRefStore.ts b/src/store/useSocketRefStore.ts new file mode 100644 index 0000000..a18baa4 --- /dev/null +++ b/src/store/useSocketRefStore.ts @@ -0,0 +1,15 @@ +import type { MutableRefObject } from "react"; +import { create } from "zustand"; + +type State = { + socketRef: MutableRefObject | null; +}; + +type Action = { + setRef: (ref: MutableRefObject) => void; +}; + +export const useSocketRefStore = create()((set) => ({ + socketRef: null, + setRef: (ref) => set(() => ({ socketRef: ref })), +})); diff --git a/src/store/useUUIDStore.ts b/src/store/useUUIDStore.ts new file mode 100644 index 0000000..09475b4 --- /dev/null +++ b/src/store/useUUIDStore.ts @@ -0,0 +1,23 @@ +import { create } from "zustand"; +import { persist } from "zustand/middleware"; +import { generateUUID } from "../utils/uuid"; + +type State = { + uuid: string; +}; + +type Action = { + updateUUID: () => void; +}; + +export const useUUIDStore = create()( + persist( + (set) => ({ + uuid: generateUUID(), + updateUUID: () => set(() => ({ uuid: generateUUID() })), + }), + { + name: "user-uuid", + }, + ), +); diff --git a/src/type/schema.ts b/src/type/schema.ts new file mode 100644 index 0000000..b2ca2f0 --- /dev/null +++ b/src/type/schema.ts @@ -0,0 +1,30 @@ +export interface Schema { + id: string; + interval: number; + angle: angle; + acceleration: acceleration; + distance: distance; + message_type: message_type; +} + +interface angle { + x: number; + y: number; +} + +interface acceleration { + x: number; + y: number; + z: number; +} + +interface distance { + x: number; + y: number; + z: number; +} + +export enum message_type { + pointer = "pointer", + action = "action", +} diff --git a/src/utils/parmission.ts b/src/utils/parmission.ts new file mode 100644 index 0000000..2fb8ece --- /dev/null +++ b/src/utils/parmission.ts @@ -0,0 +1,31 @@ +/** + * 角度・加速度センサーのパーミッションをリクエストする関数 + */ +export const requestPermission = async () => { + // @ts-expect-error only safari property + if (typeof DeviceMotionEvent.requestPermission === "function") { + try { + // @ts-expect-error only safari property + const permissionState = await DeviceMotionEvent.requestPermission(); + if (permissionState !== "granted") { + console.warn("Device Motion permission denied."); + } + } catch (error) { + console.error("Error requesting Device Motion permission:", error); + } + } + + // @ts-expect-error only safari property + if (typeof DeviceOrientationEvent.requestPermission === "function") { + try { + const permissionState = + // @ts-expect-error only safari property + await DeviceOrientationEvent.requestPermission(); + if (permissionState !== "granted") { + console.warn("Device Orientation permission denied."); + } + } catch (error) { + console.error("Error requesting Device Orientation permission:", error); + } + } +}; diff --git a/src/utils/uuid.ts b/src/utils/uuid.ts new file mode 100644 index 0000000..6bfb90f --- /dev/null +++ b/src/utils/uuid.ts @@ -0,0 +1,5 @@ +export const generateUUID = (): string => { + const timestamp = Date.now().toString(16); + const randomHex = Math.floor(Math.random() * 0xffffff).toString(16); + return `${timestamp}-${randomHex}`; +};