diff --git a/components/Multiplayer/ModalInvitePlayer.tsx b/components/Multiplayer/ModalInvitePlayer.tsx index 2c5fab893542021363ecce5ddc114fa2e68baeaf..de1e14c9a6b0bf469f19a9e37e74e695260e6870 100644 --- a/components/Multiplayer/ModalInvitePlayer.tsx +++ b/components/Multiplayer/ModalInvitePlayer.tsx @@ -4,7 +4,7 @@ import WhiteButton from "../buttons/WhiteButton"; interface Props { showModal: boolean; onClosePressed: () => void; - roomId: string; + roomId?: string; } export default function ModalInvitePlayer({showModal, onClosePressed, roomId}: Props) { diff --git a/components/PlayQuiz/RunStartedList.tsx b/components/PlayQuiz/RunStartedList.tsx index 6a7da51526cce14fe3a88eb1099da4e461dc5cae..34da38583da6b151ca2c3c9db5536301a31968d4 100644 --- a/components/PlayQuiz/RunStartedList.tsx +++ b/components/PlayQuiz/RunStartedList.tsx @@ -1,8 +1,6 @@ import React from "react"; import { FlatList, View, Text, StyleSheet, TouchableOpacity, ActivityIndicator } from "react-native"; -import {Quiz} from "../../models/Quiz"; -import { NavigationProp } from "@react-navigation/native"; -import {RunInformations} from "../../models/RunInformations"; +import {RunsInformationsAllRuns} from "../../models/RunsInformationsAllRuns"; @@ -36,17 +34,15 @@ import {RunInformations} from "../../models/RunInformations"; * @returns Un composant React contenant une liste interactive de quiz ou des messages en fonction de l'état. */ interface Props { - runList: RunInformations[] | undefined; - onRunPressed:(runInfo: RunInformations) => void; + runList: RunsInformationsAllRuns[] | undefined; + onRunPressed:(runInfo: RunsInformationsAllRuns) => void; isLoadingData: boolean; } export default function RunStartedList({ runList, isLoadingData, onRunPressed }: Props) { - const renderQuizItem = ({ item }: { item: RunInformations }) => ( + const renderQuizItem = ({ item }: { item: RunsInformationsAllRuns }) => ( <TouchableOpacity style={styles.quizItem} onPress={() => onRunPressed(item)}> - <Text style={styles.quizTitle}>{item.id}</Text> - <Text style={styles.quizDetails}> - {item.score} - </Text> + <Text style={styles.quizTitle}>{item.quiz.name ? item.quiz.name : item.id}</Text> + <Text style={styles.quizDetails}>{item.quiz.category ? item.quiz.category.name : "Community quiz"}</Text> </TouchableOpacity> ); diff --git a/models/Response/AllRunsResponse.ts b/models/Response/AllRunsResponse.ts new file mode 100644 index 0000000000000000000000000000000000000000..8e7c71ceff2654461f8b9d7449932ac94daa7bfc --- /dev/null +++ b/models/Response/AllRunsResponse.ts @@ -0,0 +1,5 @@ +import {RunsInformationsAllRuns} from "../RunsInformationsAllRuns"; + +export interface AllRunsResponse{ + runs: RunsInformationsAllRuns[]; +} \ No newline at end of file diff --git a/models/RunsInformationsAllRuns.tsx b/models/RunsInformationsAllRuns.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f70c778cbe5dc9d81b93efe636eb4f8b2c6ae048 --- /dev/null +++ b/models/RunsInformationsAllRuns.tsx @@ -0,0 +1,20 @@ +import {Category, Difficulty} from "./Quiz"; + +export interface QuizInfoRun{ + category: Category; + difficulty: Difficulty; + name?: string; + questionCount: string; +} +export interface RunsInformationsAllRuns{ + id: string; + quizId: string; + originalQuizId: string; + score: number; + questionIndex: number; + startDate: string; + lastChange: string; + quizUserId: number; + roomId: number; + quiz: QuizInfoRun; +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d1035b2d312864a4ed1e1c4a5fb7fb3507d78a26..2a605a2226b077d10f3560a61b36c7d1b97e4f1d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "react-native-progress": "^5.0.1", "react-native-safe-area-context": "^4.12.0", "react-native-screens": "~4.1.0", + "react-native-sse": "^1.2.1", "react-native-vector-icons": "^10.2.0", "react-query": "^3.39.3", "rxjs": "^7.8.1", @@ -9775,6 +9776,12 @@ "react-native": "*" } }, + "node_modules/react-native-sse": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/react-native-sse/-/react-native-sse-1.2.1.tgz", + "integrity": "sha512-zejanlScF+IB9tYnbdry0MT34qjBXbiV/E72qGz33W/tX1bx8MXsbB4lxiuPETc9v/008vYZ60yjIstW22VlVg==", + "license": "MIT" + }, "node_modules/react-native-svg": { "version": "15.10.1", "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.10.1.tgz", diff --git a/package.json b/package.json index 7a0c0d2a5091698546c423616dd9e0da9f2ef965..fd3a2bf9088f4150929498d11e1f5854e6b360de 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "react-native-progress": "^5.0.1", "react-native-safe-area-context": "^4.12.0", "react-native-screens": "~4.1.0", + "react-native-sse": "^1.2.1", "react-native-vector-icons": "^10.2.0", "react-query": "^3.39.3", "rxjs": "^7.8.1", diff --git a/screens/Home/MyQuizzes/InformationsOfRuns.tsx b/screens/Home/MyQuizzes/InformationsOfRuns.tsx index 5ca934b2e3f42056b9adb165fd5a4fa378371deb..367b9426b866591b06df68d910e190eabc65a834 100644 --- a/screens/Home/MyQuizzes/InformationsOfRuns.tsx +++ b/screens/Home/MyQuizzes/InformationsOfRuns.tsx @@ -1,16 +1,12 @@ -import React, {useEffect, useState} from "react"; +import React from "react"; import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; import TemplateMenu from "../../../templates/TemplateMenu"; import {NavigationProp, RouteProp} from "@react-navigation/native"; -import {Quiz} from "../../../models/Quiz"; -import {RunInformations} from "../../../models/RunInformations"; -import {QuizInformations} from "../../../models/QuizInformations"; -import HttpError from "../../../services/HttpError"; -import {useQuizService} from "../../../services/QuizService"; +import {RunsInformationsAllRuns} from "../../../models/RunsInformationsAllRuns"; type RoutePropsType = { InformationsOfRuns: { - runInformations: RunInformations; + runInformations: RunsInformationsAllRuns; }; }; interface Props { @@ -20,20 +16,6 @@ interface Props { export default function InformationsOfRuns({ navigation, route }: Props) { const { runInformations, } = route.params; - const {getQuizInformations} = useQuizService(); - const [quizInformations, setQuizInformations] = useState<QuizInformations>(); - - const fetchQuizInformations = async () => { - const quizInformations = await getQuizInformations(runInformations.quizId); - if (quizInformations instanceof HttpError) { - return; - } - setQuizInformations(quizInformations); - } - - useEffect(() => { - fetchQuizInformations(); - }, []); const onPlay = () => { navigation.reset({ @@ -50,13 +32,15 @@ export default function InformationsOfRuns({ navigation, route }: Props) { return ( <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={true}> <View style={styles.container}> - <Text style={styles.quizTitle}>{quizInformations ? quizInformations.name : "Loading..."}</Text> - <Text style={styles.quizDescription}>{quizInformations ? quizInformations.description :"Loading..."}</Text> + <Text style={styles.quizTitle}>{runInformations.quiz.name ? runInformations.quiz.name : "Loading..."}</Text> + {/*<Text style={styles.quizDescription}>{runInformations ? runInformations.description :"Loading..."}</Text>*/} <View style={styles.aboutContainer}> <Text style={styles.aboutTitle}>About this quiz :</Text> - <Text style={styles.aboutDetails}>{runInformations.questionIndex}/{quizInformations ? quizInformations.questionCount : "Loading..."} questions, {quizInformations ? quizInformations.difficulty.name : "Loading..."}</Text> - <Text style={styles.aboutDetails}>By : {quizInformations?.authorId && "Unknown"}</Text> + <Text style={styles.aboutDetails}>Current score : {runInformations ? runInformations.score : "Loading..."}</Text> + <Text style={styles.aboutDetails}>Question : {runInformations.questionIndex}/{runInformations ? runInformations.quiz.questionCount : "Loading..."}</Text> + <Text style={styles.aboutDetails}>Difficulty : {runInformations ? runInformations.quiz.difficulty.name : "Loading..."}</Text> + {/*<Text style={styles.aboutDetails}>By : {runInformations.authorId && "Unknown"}</Text>*/} </View> <TouchableOpacity style={styles.playButton} onPress={onPlay}> diff --git a/screens/Home/MyQuizzes/MyQuizzes.tsx b/screens/Home/MyQuizzes/MyQuizzes.tsx index 1e5ce513e4dd2b37b75df84eaa5e0f299b8d2b91..d7d94bcb74781ea7bdc5d780285f2546da061fb8 100644 --- a/screens/Home/MyQuizzes/MyQuizzes.tsx +++ b/screens/Home/MyQuizzes/MyQuizzes.tsx @@ -1,15 +1,16 @@ import {NavigationProp} from "@react-navigation/native"; import React, {useEffect, useState} from "react"; import TemplateRunList from "../../../templates/TemplateRunList"; -import {RunInformations} from "../../../models/RunInformations"; import {useQuizService} from "../../../services/QuizService"; import HttpError from "../../../services/HttpError"; +import {AllRunsResponse} from "../../../models/Response/AllRunsResponse"; +import {RunsInformationsAllRuns} from "../../../models/RunsInformationsAllRuns"; interface Props { navigation: NavigationProp<any> } export default function MyQuizzes({navigation}: Props) { - const [runList, setRunList] = useState<RunInformations[] | undefined>(undefined) + const [runList, setRunList] = useState<AllRunsResponse | undefined>(undefined) const [input, setInput] = useState<string>("") const {getAllRuns} = useQuizService(); @@ -26,11 +27,11 @@ export default function MyQuizzes({navigation}: Props) { getRuns(); },[]); - const onQuizPressed = (runInfo: RunInformations) => { + const onQuizPressed = (runInfo: RunsInformationsAllRuns) => { navigation.navigate("InformationsOfRuns", { runInformations: runInfo }); }; return ( - <TemplateRunList title={"My quizzes"} setTextInInput={setInput} runList={runList} navigation={navigation} buttonGoBack={true} isLoadingData={false} onRunPressed={onQuizPressed}/> + <TemplateRunList title={"My quizzes"} setTextInInput={setInput} runList={runList?.runs} navigation={navigation} buttonGoBack={true} isLoadingData={false} onRunPressed={onQuizPressed}/> ); } \ No newline at end of file diff --git a/screens/Multiplayer/Lobby/Lobby.tsx b/screens/Multiplayer/Lobby/Lobby.tsx index fee0d0d9dc6a153ff4aa3bb27b9fc1e9f983b0ec..c20d61a63cec762014f19fc76667cf8146927bbe 100644 --- a/screens/Multiplayer/Lobby/Lobby.tsx +++ b/screens/Multiplayer/Lobby/Lobby.tsx @@ -1,16 +1,17 @@ -import { View, Text, StyleSheet, Button, Image, ScrollView, FlatList } from "react-native"; -import TemplateMenu from "../../../templates/TemplateMenu"; +import {View, Text, StyleSheet, Image, FlatList} from "react-native"; import {NavigationProp, RouteProp} from "@react-navigation/native"; import React, {useEffect, useState} from "react"; -import WhiteButton from "../../../components/buttons/WhiteButton"; -import BlueButton from "../../../components/buttons/BlueButton"; import { TouchableOpacity } from "react-native"; -import ModalInvitePlayer from "../../../components/Multiplayer/ModalInvitePlayer"; import {User} from "../../../models/User"; -import {QuizGenerateAnswer} from "../../../models/QuizGenerateAnswer"; import {io, Socket} from "socket.io-client"; import useCookie from "../../../hooks/UseCookie"; - +import {useMultiService} from "../../../services/MultiService"; +import HttpError from "../../../services/HttpError"; +import TemplateMenu from "../../../templates/TemplateMenu"; +import BlueButton from "../../../components/buttons/BlueButton"; +import WhiteButton from "../../../components/buttons/WhiteButton"; +import ModalInvitePlayer from "../../../components/Multiplayer/ModalInvitePlayer"; +import EventSource from "react-native-sse"; interface Props { navigation: NavigationProp<any>; route: RouteProp<RoutePropsType, "Lobby">; @@ -18,98 +19,70 @@ interface Props { type RoutePropsType = { Lobby: { - quizIdOrRoomId: string; + quizId?: string; + roomId?: string; nbPlayers: number; isHost: boolean; }; }; +type CustomEventType = "userJoined" | "nextQuestion" export default function Lobby({navigation, route}: Props) { - const { quizIdOrRoomId, nbPlayers, isHost} = route.params; + const { quizId, roomId, nbPlayers, isHost} = route.params; - // const [isHost, setIsHost] = useState<boolean>(true); - const [showModal, setShowModal] = useState<boolean>(false); const [players, setPlayers] = useState<User[]>([{ id: 0, username: 'Invite', email: 'invite@email.com'}]); - const {getCookie} = useCookie('access_token'); const [socket, setSocket] = useState<Socket | null>(null); - const [roomId, setRoomId] = useState<string>(""); + const [roomIdCreated, setRoomIdCreated] = useState<string | undefined>(roomId); const [runId, setRunId] = useState<string>(""); + const [showModal, setShowModal] = useState<boolean>(false); - const userInvite = { id: 0, username: 'Invite', email: 'invite@email.com'} - const initSocket = async () => { - const cookie = await getCookie(); - const newSocket = io("http://klebert-host.com:33053/party", { - transports: ["websocket"], // Utilise WebSocket uniquement - extraHeaders: { - Cookie: `access_token=${cookie}`, - }, - }); + const {createParty} = useMultiService(); - setSocket(newSocket); + const userInvite = { id: 0, username: 'Invite', email: 'invite@email.com'} + let goodId = roomId; + const initSSE = async () => { + console.log("Init SSE for quiz ID:", quizId); + if(isHost && quizId) { + const response = await createParty(quizId); + if (HttpError.isHttpError(response)) { + console.error("Failed to create party:", response.message); + return; + } + console.log("Created party with ID:", response); + goodId = response; + setRoomIdCreated(response); + } + console.log("User try to joined party with ID:", goodId); + console.log(`https://klebert-host.com:33037/party/join/${goodId}`) - if(isHost) { - newSocket.on("roomCreated", (data) => { - console.log("Room created : ", data.id); - console.log("Connected to the server"); + const es = new EventSource<CustomEventType>(`https://klebert-host.com:33037/party/join/${goodId}`); - setRoomId(data.id); - console.log("Host id room : ", data.id); - newSocket.emit("joinRoom", { roomId: data.id }); + es.addEventListener("userJoined", (event) => { + console.log("User joined event data:", event.data); + if(!event.data) return; - setTimeout(() => { - console.log("Sending generateRun"); - newSocket.emit("generateRun"); - },1000); - }); + const newUsers: User[] = JSON.parse(event.data); - newSocket.on("connect", () => { - newSocket.emit("createRoom", { quizId: quizIdOrRoomId }); + setPlayers((prevPlayers) => { + const ids = new Set(prevPlayers.map((player) => player.id)); + const filteredNewUsers = newUsers.filter((user) => !ids.has(user.id)); + return [...prevPlayers, ...filteredNewUsers]; }); - console.log("Host"); - - } - else { - console.log("pas Host id room : ", quizIdOrRoomId); - newSocket.emit("joinRoom", { roomId: quizIdOrRoomId }); - setTimeout(() => { - console.log("Sending generateRun"); - newSocket.emit("generateRun"); - },1000); - } - - newSocket.on("connect_error", (err) => { - console.error("Connection error: ", err.message); }); - newSocket.on("error", (err) => { - console.error("Socket error: ", err.message); + es.addEventListener("error", (err) => { + console.error("Error with SSE:", err); }); - // Mettez à jour la liste des joueurs lorsque quelqu'un rejoint - newSocket.on("userJoined", (data: User[]) => { - console.log("Updated user list:", data); - setPlayers([userInvite, ...data]); - }); - - newSocket.on("runId", (data) => { - console.log("Run ID:", data); - setRunId(data); - }); - } - - + return () => es.close(); + }; useEffect(() => { - initSocket(); - // return () => { - // if(socket === null) return; - // socket.disconnect(); - // setSocket(null); - // }; + initSSE(); }, []); const item = ({ item }: { item: User }) => { @@ -139,7 +112,7 @@ export default function Lobby({navigation, route}: Props) { }; const onPlayPressed = () => { - navigation.navigate("PlayingQuizMultiMode", {runId: runId, socket: socket, roomId: roomId}); + navigation.navigate("PlayingQuizMultiMode", {runId: runId, socket: socket, roomId: roomIdCreated}); }; const onCancelPressed = () => { @@ -180,7 +153,7 @@ export default function Lobby({navigation, route}: Props) { } </View> </TemplateMenu> - <ModalInvitePlayer showModal={showModal} onClosePressed={() => setShowModal(false)} roomId={roomId} /> + <ModalInvitePlayer showModal={showModal} onClosePressed={() => setShowModal(false)} roomId={roomIdCreated} /> </View> ) } diff --git a/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx b/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx index f8f5d1a0bc0fb590240a1fbcb0534fe18230456d..7f7e8eeb8dc8e32c94dd46d32f4be9a3f893fdfe 100644 --- a/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx +++ b/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx @@ -1,4 +1,4 @@ -import { Text,View, StyleSheet } from "react-native"; +import { View, StyleSheet } from "react-native"; import React, {useEffect, useState} from "react"; import InputPlayQuiz from "../../../components/PlayQuiz/InputPlayQuiz"; import {useQuizService} from "../../../services/QuizService"; @@ -61,7 +61,8 @@ export default function OnlineCreateLobby({navigation}: Props) { } navigation.navigate("Lobby", { - quizIdOrRoomId: quizGenerated.quizId, + quizId: quizGenerated.quizId, + roomId: null, nbPlayers: parseInt(nbPlayers), isHost: true, }); diff --git a/screens/Multiplayer/OnlineQuiz/OnlinePlayQuiz.tsx b/screens/Multiplayer/OnlineQuiz/OnlinePlayQuiz.tsx index 03aba42ec9319504dc907b6476958634eb04a686..c91647d21dd31fc2c030d1a80d1f8b78514b54aa 100644 --- a/screens/Multiplayer/OnlineQuiz/OnlinePlayQuiz.tsx +++ b/screens/Multiplayer/OnlineQuiz/OnlinePlayQuiz.tsx @@ -1,8 +1,6 @@ import { StyleSheet, View, Text } from "react-native"; -import AboutAQuiz from "../../../components/PlayQuiz/AboutAQuiz"; -import React, { useEffect } from "react"; +import React from "react"; import InputPlayQuiz from "../../../components/PlayQuiz/InputPlayQuiz"; -import {Quiz} from "../../../models/Quiz"; import { useQuizService } from "../../../services/QuizService"; import HttpError from "../../../services/HttpError"; // import {getIdOfCategory} from "../../../helper/QuizHelper"; @@ -33,7 +31,8 @@ export default function OnlinePlayQuiz({navigation}: Props) { } setError(""); navigation.navigate("Lobby", { - quizIdOrRoomId: codeQuiz, + quizId: null, + roomId: codeQuiz, nbPlayers: 10, isHost: false, }); diff --git a/services/MultiService.ts b/services/MultiService.ts new file mode 100644 index 0000000000000000000000000000000000000000..f0de634c0a7254082968608c5a25bfe4fa12f067 --- /dev/null +++ b/services/MultiService.ts @@ -0,0 +1,36 @@ +import axios from "axios"; +import {QuestionResponse} from "../models/Response/QuestionResponse"; +import HttpError from "./HttpError"; + +export const useMultiService = () => { + const url = "https://klebert-host.com:33037" + + const createParty = async (quizId: string) : Promise<string | HttpError> => { + console.log("Create party with ID :", quizId); + console.log(url+"/party"); + const requestBody = { + quizId: quizId, + }; + + try { + const response = await axios.post(`${url}/party`, requestBody, { + withCredentials: true, + }); + console.log("Response data:", response.data.id); + return response.data.id; + } catch (error: any) { + console.log("Error while create party:", error); + + if (error.response) { + return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); + } else { + return new HttpError(500, "Unexpected error: " + error.message); + } + } + } + + return { + createParty: createParty, + } + +} \ No newline at end of file diff --git a/services/QuizService.ts b/services/QuizService.ts index 8dbbc262f727450ad4af595b0df6275f7d31a0e0..18da39998fc5bd9b12f82e03203c9bfc1cad1940 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -1,5 +1,5 @@ import HttpError from "./HttpError"; -import {Quiz, QuizCommunityResponse} from "../models/Quiz"; +import {QuizCommunityResponse} from "../models/Quiz"; import axios from "axios"; import {Answer} from "../models/Answer"; import {Question} from "../models/Question"; @@ -8,7 +8,7 @@ import {QuizInformations} from "../models/QuizInformations"; import {QuestionResponse} from "../models/Response/QuestionResponse"; import {QuizGenerateAnswer} from "../models/QuizGenerateAnswer"; import {RunInformations} from "../models/RunInformations"; -import {RunsInformationResponse} from "../models/Response/RunsInformationResponse"; +import {AllRunsResponse} from "../models/Response/AllRunsResponse"; export const useQuizService = () => { const url = "https://klebert-host.com:33037"; // Remplace par l'URL de ton endpoint API @@ -200,7 +200,7 @@ export const useQuizService = () => { // Récupération du run ID return {id: runId, quizId: response.data.quizId}; } catch (error: any) { - console.log("Error while generating quiz:", error); + console.log("Error while generating random quiz:", error); if (error.response) { // Gestion des erreurs HTTP @@ -285,15 +285,15 @@ export const useQuizService = () => { } } - const getAllRuns = async (): Promise<RunInformations[] | HttpError> => { + const getAllRuns = async (): Promise<AllRunsResponse | HttpError> => { try { - const response = await axios.get<RunsInformationResponse>(`${url}/run/userRuns`, { + const response = await axios.get<AllRunsResponse>(`${url}/run/userRuns`, { headers: { "Content-Type": "application/json", }, }); - return response.data.runs; + return response.data; } catch (error: any) { console.log("Error while getAllRuns:", error); if (error.response) { diff --git a/templates/TemplateRunList.tsx b/templates/TemplateRunList.tsx index 20125c14da5600589b2027a173e9f93e452eddb8..8a542abd5218231cf9e7fd9bb1e14f998bcf3835 100644 --- a/templates/TemplateRunList.tsx +++ b/templates/TemplateRunList.tsx @@ -2,11 +2,8 @@ import React from "react"; import {View, Text, StyleSheet} from "react-native"; import TemplateMenu from "./TemplateMenu"; import {NavigationProp} from "@react-navigation/native"; -import InputPlayQuiz from "../components/PlayQuiz/InputPlayQuiz"; -import QuizList from "../components/lists/QuizList"; -import {Quiz} from "../models/Quiz"; import RunStartedList from "../components/PlayQuiz/RunStartedList"; -import {RunInformations} from "../models/RunInformations"; +import {RunsInformationsAllRuns} from "../models/RunsInformationsAllRuns"; /** @@ -24,11 +21,11 @@ interface Props { navigation: NavigationProp<any> title: string setTextInInput: (text: string) => void - runList: RunInformations[] | undefined + runList: RunsInformationsAllRuns[] | undefined buttonGoBack: boolean isLoadingData: boolean nextPage?: () => void; - onRunPressed:(runInfo: RunInformations) => void; + onRunPressed:(runInfo: RunsInformationsAllRuns) => void; } export default function TemplateRunList({navigation, title, setTextInInput, runList, buttonGoBack, isLoadingData, onRunPressed }: Props) {