From 7d5813b1c257e2bce63503ee746c24e9f400edd7 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Fri, 28 Feb 2025 18:25:12 +0100 Subject: [PATCH] Migrate to components --- client/src/App.jsx | 11 +- client/src/common/styles/main.sass | 47 ++++- client/src/pages/Game/Game.jsx | 64 +++++++ .../components/MusicSlider/MusicSlider.jsx | 108 +++++++++++ .../Game/components/MusicSlider/index.js | 1 + .../Game/components/MusicSlider/styles.sass | 82 ++++++++ client/src/pages/Game/index.js | 1 + client/src/pages/{Home => Game}/styles.sass | 126 +----------- client/src/pages/Home/Home.jsx | 179 ------------------ client/src/pages/Home/index.js | 1 - 10 files changed, 309 insertions(+), 311 deletions(-) create mode 100644 client/src/pages/Game/Game.jsx create mode 100644 client/src/pages/Game/components/MusicSlider/MusicSlider.jsx create mode 100644 client/src/pages/Game/components/MusicSlider/index.js create mode 100644 client/src/pages/Game/components/MusicSlider/styles.sass create mode 100644 client/src/pages/Game/index.js rename client/src/pages/{Home => Game}/styles.sass (54%) delete mode 100644 client/src/pages/Home/Home.jsx delete mode 100644 client/src/pages/Home/index.js diff --git a/client/src/App.jsx b/client/src/App.jsx index 079b4dc..1426719 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -1,13 +1,20 @@ -import Home from "./pages/Home"; +import Game from "./pages/Game"; import {useContext} from "react"; import {StateContext} from "@/common/contexts/StateContext"; +import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; +import {faMusic} from "@fortawesome/free-solid-svg-icons"; const App = () => { const {currentState} = useContext(StateContext); return ( <> - {currentState === "Home" && } +
+ + + +
+ {currentState === "Home" && } ) } diff --git a/client/src/common/styles/main.sass b/client/src/common/styles/main.sass index fc69bb1..a996833 100644 --- a/client/src/common/styles/main.sass +++ b/client/src/common/styles/main.sass @@ -3,15 +3,52 @@ * box-sizing: border-box -body, html +body margin: 0 padding: 0 - background-color: #232529 letter-spacing: 0.2rem color: #E0E0E0 - height: 100% font-family: 'Bangers', sans-serif - + height: 100vh + background: linear-gradient(135deg, #0f2027, #203a43, #2c5364) + user-select: none + overflow: hidden + position: relative ::-webkit-scrollbar - width: 10px \ No newline at end of file + width: 10px + +.background-elements + position: absolute + top: 0 + left: 0 + width: 100% + height: 100% + overflow: hidden + z-index: 0 + + .music-note + position: absolute + font-size: 24pt + color: rgba(255, 255, 255, 0.5) + animation: float-notes 5s infinite ease-in-out + + &.note-1 + top: 20% + left: 30% + + &.note-2 + top: 60% + left: 80% + + &.note-3 + top: 90% + left: 50% + +@keyframes float-notes + 0% + transform: translateY(0) + 50% + transform: translateY(-10px) + 100% + transform: translateY(0) \ No newline at end of file diff --git a/client/src/pages/Game/Game.jsx b/client/src/pages/Game/Game.jsx new file mode 100644 index 0000000..4971922 --- /dev/null +++ b/client/src/pages/Game/Game.jsx @@ -0,0 +1,64 @@ +import "./styles.sass"; +import {StateContext} from "@/common/contexts/StateContext"; +import {useContext, useState, useEffect, useRef} from "react"; +import MusicSlider from "@/pages/Game/components/MusicSlider"; + +export const Game = () => { + const {setCurrentState} = useContext(StateContext); + const [messages, setMessages] = useState([{sender: "Marco", text: "Hallo!"},]); + const [inputValue, setInputValue] = useState(""); + const messageEndRef = useRef(null); + + useEffect(() => { + messageEndRef.current?.scrollIntoView({behavior: "smooth"}); + }, [messages]); + + const handleSendMessage = () => { + if (inputValue.trim()) { + setMessages([...messages, {sender: "User", text: inputValue}]); + setInputValue(""); + } + }; + + return ( +
+
+
+

ToneGuessr

+
+ Song +
+
Black Steam
+
von Carrot Quest GmbH
+
+
+
+
+
+
+
Chat
+
+
+ {messages.map((message, index) => ( +
+ {message.sender}: + {message.text} +
+ ))} +
+
+
+ setInputValue(e.target.value)} + onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()} + placeholder="Gib eine Nachricht ein..." + /> + +
+
+
+ +
+ ); +} \ No newline at end of file diff --git a/client/src/pages/Game/components/MusicSlider/MusicSlider.jsx b/client/src/pages/Game/components/MusicSlider/MusicSlider.jsx new file mode 100644 index 0000000..e377b1c --- /dev/null +++ b/client/src/pages/Game/components/MusicSlider/MusicSlider.jsx @@ -0,0 +1,108 @@ +import {useEffect, useRef, useState} from "react"; +import "./styles.sass"; + +export const MusicSlider = () => { + const [frequency, setFrequency] = useState(440); + const [isPlaying, setIsPlaying] = useState(false); + const [dragging, setDragging] = useState(false); + const audioContextRef = useRef(null); + const oscillatorRef = useRef(null); + const gainNodeRef = useRef(null); + const sliderRef = useRef(null); + + const handleMouseDown = (e) => { + setDragging(true); + startAudio(); + handleFrequencyChange(e); + }; + + const handleMouseUp = () => { + setDragging(false); + stopAudio(); + }; + + const handleFrequencyChange = (e) => { + const rect = sliderRef.current.getBoundingClientRect(); + const x = e.clientX - rect.left; + const clampedX = Math.max(0, Math.min(x, rect.width)); + const newFrequency = 20 + (clampedX / rect.width) * 1980; + setFrequency(newFrequency); + }; + + useEffect(() => { + const handleMouseMove = (e) => dragging && handleFrequencyChange(e); + const handleMouseUpGlobal = () => dragging && handleMouseUp(); + + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUpGlobal); + + return () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUpGlobal); + }; + }, [dragging]); + + useEffect(() => { + if (isPlaying) { + if (oscillatorRef.current) { + oscillatorRef.current.frequency.setValueAtTime(frequency, audioContextRef.current.currentTime); + } + } + }, [frequency, isPlaying]); + + const startAudio = () => { + if (!audioContextRef.current) { + audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)(); + } + if (!oscillatorRef.current) { + oscillatorRef.current = audioContextRef.current.createOscillator(); + oscillatorRef.current.type = 'sine'; + oscillatorRef.current.frequency.setValueAtTime(frequency, audioContextRef.current.currentTime); + + gainNodeRef.current = audioContextRef.current.createGain(); + gainNodeRef.current.gain.setValueAtTime(0.5, audioContextRef.current.currentTime); + + oscillatorRef.current.connect(gainNodeRef.current); + gainNodeRef.current.connect(audioContextRef.current.destination); + oscillatorRef.current.start(); + } else { + oscillatorRef.current.frequency.setValueAtTime(frequency, audioContextRef.current.currentTime); + } + setIsPlaying(true); + }; + + const stopAudio = () => { + if (oscillatorRef.current) { + oscillatorRef.current.stop(); + oscillatorRef.current.disconnect(); + oscillatorRef.current = null; + } + setIsPlaying(false); + }; + + return ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ) +} \ No newline at end of file diff --git a/client/src/pages/Game/components/MusicSlider/index.js b/client/src/pages/Game/components/MusicSlider/index.js new file mode 100644 index 0000000..7acfde8 --- /dev/null +++ b/client/src/pages/Game/components/MusicSlider/index.js @@ -0,0 +1 @@ +export {MusicSlider as default} from "./MusicSlider.jsx"; \ No newline at end of file diff --git a/client/src/pages/Game/components/MusicSlider/styles.sass b/client/src/pages/Game/components/MusicSlider/styles.sass new file mode 100644 index 0000000..4fc4069 --- /dev/null +++ b/client/src/pages/Game/components/MusicSlider/styles.sass @@ -0,0 +1,82 @@ +.otamatone-container + display: flex + justify-content: center + align-items: center + position: fixed + left: 0 + right: 0 + bottom: 0 + padding: 20px + background-color: rgba(30, 30, 30, 0.5) + backdrop-filter: blur(10px) + border-radius: 20px + margin: 20px + z-index: 1 + +.otamatone + display: flex + flex-direction: row + align-items: center + cursor: pointer + padding: 10px + width: 100% + +.otamatone-neck + flex: 1 + height: 20px + background: linear-gradient(135deg, #000, #444) + border-radius: 10px + position: relative + +.frequency-indicator + position: absolute + top: -10px + width: 20px + height: 20px + background-color: #ff0000 + border-radius: 50% + transform: translateX(-50%) + +.note-marker + position: absolute + top: -30px + font-size: 24pt + color: #fff + +.otamatone-face + width: 100px + height: 100px + background: radial-gradient(circle, #fff, #ddd) + border-radius: 50% + display: flex + flex-direction: column + align-items: center + justify-content: center + position: absolute + left: 0 + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2) + +.otamatone-eyes + display: flex + justify-content: space-between + width: 60% + position: absolute + top: 30px + +.eye + width: 15px + height: 15px + background-color: #000 + border-radius: 50% + +.left-eye + margin-right: 10px + +.right-eye + margin-left: 10px + +.otamatone-mouth + background-color: #000 + border-radius: 50% + position: absolute + bottom: 10px \ No newline at end of file diff --git a/client/src/pages/Game/index.js b/client/src/pages/Game/index.js new file mode 100644 index 0000000..d46fed5 --- /dev/null +++ b/client/src/pages/Game/index.js @@ -0,0 +1 @@ +export {Game as default} from "./Game.jsx"; \ No newline at end of file diff --git a/client/src/pages/Home/styles.sass b/client/src/pages/Game/styles.sass similarity index 54% rename from client/src/pages/Home/styles.sass rename to client/src/pages/Game/styles.sass index edc0050..f099e5e 100644 --- a/client/src/pages/Home/styles.sass +++ b/client/src/pages/Game/styles.sass @@ -4,46 +4,7 @@ align-items: center justify-content: center height: 100vh - background: linear-gradient(135deg, #0f2027, #203a43, #2c5364) padding: 20px - user-select: none - overflow: hidden - position: relative - - .background-elements - position: absolute - top: 0 - left: 0 - width: 100% - height: 100% - overflow: hidden - z-index: 0 - - .music-note - position: absolute - font-size: 24pt - color: rgba(255, 255, 255, 0.5) - animation: float-notes 5s infinite ease-in-out - - &.note-1 - top: 20% - left: 30% - - &.note-2 - top: 60% - left: 80% - - &.note-3 - top: 90% - left: 50% - - @keyframes float-notes - 0% - transform: translateY(0) - 50% - transform: translateY(-10px) - 100% - transform: translateY(0) .main-content display: flex @@ -92,7 +53,7 @@ flex-direction: column align-items: flex-start - .song-name + .song-names font-size: 24pt color: #fff margin-bottom: 10px @@ -172,87 +133,4 @@ cursor: pointer &:hover - background-color: #1e1e1e - - .otamatone-container - display: flex - justify-content: center - align-items: center - position: fixed - left: 0 - right: 0 - bottom: 0 - padding: 20px - background-color: rgba(30, 30, 30, 0.5) - backdrop-filter: blur(10px) - border-radius: 20px - margin: 20px - z-index: 1 - - .otamatone - display: flex - flex-direction: row - align-items: center - cursor: pointer - padding: 10px - width: 100% - - .otamatone-neck - flex: 1 - height: 20px - background: linear-gradient(135deg, #000, #444) - border-radius: 10px - position: relative - - .frequency-indicator - position: absolute - top: -10px - width: 20px - height: 20px - background-color: #ff0000 - border-radius: 50% - transform: translateX(-50%) - - .note-marker - position: absolute - top: -30px - font-size: 24pt - color: #fff - - .otamatone-face - width: 100px - height: 100px - background: radial-gradient(circle, #fff, #ddd) - border-radius: 50% - display: flex - flex-direction: column - align-items: center - justify-content: center - position: absolute - left: 0 - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2) - - .otamatone-eyes - display: flex - justify-content: space-between - width: 60% - position: absolute - top: 30px - - .eye - width: 15px - height: 15px - background-color: #000 - border-radius: 50% - - .left-eye - margin-right: 10px - - .right-eye - margin-left: 10px - - .otamatone-mouth - background-color: #000 - border-radius: 50% - position: absolute - bottom: 10px \ No newline at end of file + background-color: #1e1e1e \ No newline at end of file diff --git a/client/src/pages/Home/Home.jsx b/client/src/pages/Home/Home.jsx deleted file mode 100644 index 65bd04d..0000000 --- a/client/src/pages/Home/Home.jsx +++ /dev/null @@ -1,179 +0,0 @@ -import "./styles.sass"; -import {StateContext} from "@/common/contexts/StateContext"; -import {useContext, useState, useEffect, useRef} from "react"; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faMusic } from '@fortawesome/free-solid-svg-icons'; - -export const Home = () => { - const {setCurrentState} = useContext(StateContext); - const [frequency, setFrequency] = useState(440); - const [isPlaying, setIsPlaying] = useState(false); - const [dragging, setDragging] = useState(false); - const audioContextRef = useRef(null); - const oscillatorRef = useRef(null); - const gainNodeRef = useRef(null); - const sliderRef = useRef(null); - const [messages, setMessages] = useState([{ sender: "Marco", text: "Hallo!" },]); - const [inputValue, setInputValue] = useState(""); - const messageEndRef = useRef(null); - - const startAudio = () => { - if (!audioContextRef.current) { - audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)(); - } - if (!oscillatorRef.current) { - oscillatorRef.current = audioContextRef.current.createOscillator(); - oscillatorRef.current.type = 'sine'; - oscillatorRef.current.frequency.setValueAtTime(frequency, audioContextRef.current.currentTime); - - gainNodeRef.current = audioContextRef.current.createGain(); - gainNodeRef.current.gain.setValueAtTime(0.5, audioContextRef.current.currentTime); - - oscillatorRef.current.connect(gainNodeRef.current); - gainNodeRef.current.connect(audioContextRef.current.destination); - oscillatorRef.current.start(); - } else { - oscillatorRef.current.frequency.setValueAtTime(frequency, audioContextRef.current.currentTime); - } - setIsPlaying(true); - }; - - const stopAudio = () => { - if (oscillatorRef.current) { - oscillatorRef.current.stop(); - oscillatorRef.current.disconnect(); - oscillatorRef.current = null; - } - setIsPlaying(false); - }; - - const handleMouseDown = (e) => { - setDragging(true); - startAudio(); - handleFrequencyChange(e); - }; - - const handleMouseUp = () => { - setDragging(false); - stopAudio(); - }; - - const handleFrequencyChange = (e) => { - const rect = sliderRef.current.getBoundingClientRect(); - const x = e.clientX - rect.left; - const clampedX = Math.max(0, Math.min(x, rect.width)); - const newFrequency = 20 + (clampedX / rect.width) * 1980; - setFrequency(newFrequency); - }; - - useEffect(() => { - const handleMouseMove = (e) => { - if (dragging) { - handleFrequencyChange(e); - } - }; - - const handleMouseUpGlobal = () => { - if (dragging) { - handleMouseUp(); - } - }; - - document.addEventListener('mousemove', handleMouseMove); - document.addEventListener('mouseup', handleMouseUpGlobal); - - return () => { - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('mouseup', handleMouseUpGlobal); - }; - }, [dragging]); - - useEffect(() => { - if (isPlaying) { - if (oscillatorRef.current) { - oscillatorRef.current.frequency.setValueAtTime(frequency, audioContextRef.current.currentTime); - } - } - }, [frequency, isPlaying]); - - useEffect(() => { - messageEndRef.current?.scrollIntoView({ behavior: "smooth" }); - }, [messages]); - - const handleSendMessage = () => { - if (inputValue.trim()) { - setMessages([...messages, { sender: "User", text: inputValue }]); - setInputValue(""); - } - }; - - return ( -
-
- - - -
-
-
-

ToneGuessr

-
- Song -
-
Black Steam
-
von Carrot Quest GmbH
-
-
-
-
-
-
-
Chat
-
-
- {messages.map((message, index) => ( -
- {message.sender}: - {message.text} -
- ))} -
-
-
- setInputValue(e.target.value)} - onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()} - placeholder="Gib eine Nachricht ein..." - /> - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ); -} \ No newline at end of file diff --git a/client/src/pages/Home/index.js b/client/src/pages/Home/index.js deleted file mode 100644 index 3a3f41e..0000000 --- a/client/src/pages/Home/index.js +++ /dev/null @@ -1 +0,0 @@ -export {Home as default} from "./Home"; \ No newline at end of file