diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c6bba59 --- /dev/null +++ b/.gitignore @@ -0,0 +1,130 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* diff --git a/traque-back/.gitignore b/traque-back/.gitignore new file mode 100644 index 0000000..c6bba59 --- /dev/null +++ b/traque-back/.gitignore @@ -0,0 +1,130 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* diff --git a/traque-back/game.js b/traque-back/game.js new file mode 100644 index 0000000..4a170cf --- /dev/null +++ b/traque-back/game.js @@ -0,0 +1,106 @@ +import { Socket } from "socket.io"; + +export default class Game { + constructor() { + this.teams = []; + this.started = false; + } + + start() { + this.started = true; + } + + stop() { + this.started = false; + } + + getNewTeamId() { + let id = null; + while(id === null || this.teams.find(t => t.id === id)) { + id = Math.floor(Math.random() * 1000000); + } + return id; + } + + addTeam(teamName) { + let id = this.getNewTeamId(); + this.teams.push({ + id: id, + name: teamName, + chasing: null, + chased: null, + currentLocation: [0, 0], + lastSentLocation: [0, 0], + enemyLocation: [0, 0] + }); + this.updateTeamChasing(); + return true; + } + + updateTeamChasing() { + if(this.teams.length <= 1) { + return false; + } + this.teams[0].chased = this.teams[this.teams.length - 1].id; + this.teams[this.teams.length - 1].chasing = this.teams[0].id; + + for(let i = 0; i < this.teams.length - 1; i++) { + this.teams[i].chasing = this.teams[i + 1].id; + this.teams[i+1].chased = this.teams[i].id; + } + return true; + } + + reorderTeams(newOrder) { + this.teams = newOrder; + return this.updateTeamChasing(); + } + + getTeam(teamId) { + return this.teams.find(t => t.id === teamId); + } + + renameTeam(teamId, newName) { + let team = this.getTeam(teamId); + if(team == undefined) { + return false; + } + team.name = newName; + return true; + } + + updateLocation(teamId, location) { + if(!this.started) { + return false; + } + let team = this.getTeam(teamId); + if(team == undefined) { + return false; + } + team.currentLocation = location; + return true; + } + + sendLocation(teamId) { + if(!this.started) { + return false; + } + let team = this.getTeam(teamId); + if(team == undefined) { + return false; + } + team.lastSentLocation = team.currentLocation; + team.enemyLocation = this.getTeam(team.chasing).lastSentLocation; + return team; + } + + removeTeam(teamId) { + if(this.getTeam(teamId) == undefined) { + return false; + } + //remove the team from the list + this.teams = this.teams.filter(t => t.id !== teamId); + this.updateTeamChasing(); + return true; + } +} \ No newline at end of file diff --git a/traque-back/index.js b/traque-back/index.js new file mode 100644 index 0000000..581b62a --- /dev/null +++ b/traque-back/index.js @@ -0,0 +1,186 @@ +import { createServer } from "http"; +import { Server } from "socket.io"; +import Game from "./game.js"; + +const httpServer = createServer(); +//Password that socket clients will have to send to be able to send admin commands +//TODO: put this in an environment variable +const ADMIN_PASSWORD = "admin"; + +//set cors to allow all origins +const io = new Server(httpServer, { + cors: { + origin: "*", + methods: ["GET", "POST"] + } +}); + + +/** + * Send a message to all logged in sockets + * @param {String} event The event name + * @param {String} data The data to send + */ +function secureBroadcast(event, data) { + loggedInSockets.forEach(s => { + io.of("admin").to(s).emit(event, data); + }); +} + + +const game = new Game(); + +//Array of logged in sockets +let loggedInSockets = []; + + +//Admin namespace +io.of("admin").on("connection", (socket) => { + //Flag to check if the user is logged in, defined for each socket + let loggedIn = false; + + socket.on("disconnect", () => { + console.log("user disconnected"); + //Remove the socket from the logged in sockets array + loggedInSockets = loggedInSockets.filter(s => s !== socket.id); + }); + + //User is attempting to log in + socket.on("login", (password) => { + if (password === ADMIN_PASSWORD && !loggedIn) { + //Attempt successful + socket.emit("login_response", true); + loggedInSockets.push(socket.id); + loggedIn = true; + } else { + //Attempt unsuccessful + socket.emit("login_response", false); + } + }); + + //User is attempting to add a new team + socket.on("add_team", (teamName) => { + if(!loggedIn) { + socket.emit("error", "Not logged in"); + return; + } + if(game.addTeam(teamName)) { + secureBroadcast("teams", game.teams); + }else { + socket.emit("error", "Error adding team"); + } + }); + + //User is attempting to remove a team + socket.on("remove_team", (teamId) => { + if(!loggedIn) { + socket.emit("error", "Not logged in"); + return; + } + if(game.removeTeam(teamId)) { + secureBroadcast("teams", game.teams); + }else { + socket.emit("error", "Error removing team"); + } + }); + + //User is attempting to start the game + socket.on("start_game", () => { + if(!loggedIn) { + socket.emit("error", "Not logged in"); + return; + } + if(game.start()) { + secureBroadcast("game_started", true); + }else { + socket.emit("error", "Error starting game"); + } + }); + + //User is attempting to stop the game + socket.on("stop_game", () => { + if(!loggedIn) { + socket.emit("error", "Not logged in"); + return; + } + if(game.stop()) { + secureBroadcast("game_started", false); + }else { + socket.emit("error", "Error stopping game"); + } + }); + + //Use is sending a new list containing the new order of the teams + //Note that we never check if the new order contains the same teams as the old order, so it behaves more like a setTeams function + //But the frontend should always send the same teams in a different order + socket.on("reorder_teams", (newOrder) => { + if(!loggedIn) { + socket.emit("error", "Not logged in"); + return; + } + if(game.reorderTeams(newOrder)) { + secureBroadcast("teams", game.teams); + } else { + socket.emit("error", "Error reordering teams"); + } + }); + + //Change the name of a team given its id + socket.on("rename_team", (teamId, newName) => { + if(!loggedIn) { + socket.emit("error", "Not logged in"); + return; + } + if(game.renameTeam(teamId, newName)) { + secureBroadcast("teams", game.teams); + } else { + socket.emit("error", "Error renaming team"); + } + }); + + //Request an update of the team list + //We only reply to the sender to prevent spam + socket.on("get_teams", () => { + if(!loggedIn) { + socket.emit("error", "Not logged in"); + return; + } + socket.emit("teams", game.teams); + }); + +}); + + + +io.of("player").on("connection", (socket) => { + let teamId = null; + console.log("a user connected"); + + socket.on("disconnect", () => { + console.log("user disconnected"); + game.getTeam(teamId).sockets = game.getTeam(teamId).sockets.filter(s => s !== socket.id); + }); + + socket.on("login", (teamId) => { + if(game.getTeam(teamId) === undefined) { + socket.emit("login_response", false); + return; + } + game.getTeam(teamId).sockets.push(socket.id); + socket.emit("login_response", true); + }); + + socket.on("update_position", (position) => { + game.updateLocation(teamId, position); + }); + + socket.on("send_position", () => { + console.log("send_position", position); + game.sendLocation(teamId); + game.getTeam(teamId).sockets.forEach(s => { + io.of("player").to(s).emit("enemy_position", game.getTeam(teamId).enemyLocation); + }); + }); +}); + +httpServer.listen(3000); \ No newline at end of file diff --git a/traque-back/package-lock.json b/traque-back/package-lock.json new file mode 100644 index 0000000..a1b9260 --- /dev/null +++ b/traque-back/package-lock.json @@ -0,0 +1,237 @@ +{ + "name": "traque-back", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "traque-back", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "socket.io": "^4.7.5" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "20.11.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", + "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/socket.io": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", + "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz", + "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/traque-back/package.json b/traque-back/package.json index 275fc11..961ebbe 100644 --- a/traque-back/package.json +++ b/traque-back/package.json @@ -4,8 +4,13 @@ "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node index.js" }, "author": "Quentin Roussel", - "license": "ISC" + "license": "ISC", + "type": "module", + "dependencies": { + "socket.io": "^4.7.5" + } } diff --git a/traque-front/app/admin/layout.js b/traque-front/app/admin/layout.js new file mode 100644 index 0000000..c1d7931 --- /dev/null +++ b/traque-front/app/admin/layout.js @@ -0,0 +1,12 @@ +import { AdminConnexionProvider } from "@/context/adminConnexionContext"; +import { AdminProvider } from "@/context/adminContext"; + +export default function AdminLayout({ children}) { + return ( + + + {children} + + + ) +} \ No newline at end of file diff --git a/traque-front/app/admin/login/page.js b/traque-front/app/admin/login/page.js new file mode 100644 index 0000000..041b393 --- /dev/null +++ b/traque-front/app/admin/login/page.js @@ -0,0 +1,17 @@ +"use client"; +import LoginForm from '@/components/team/loginForm' +import { useAdminConnexion } from '@/context/adminConnexionContext'; +import { redirect } from 'next/navigation'; +import React, { useEffect } from 'react' + +export default function AdminLoginPage() { + const { login, loggedIn } = useAdminConnexion(); + useEffect(() => { + if (loggedIn) { + redirect("/admin"); + } + }, [loggedIn]); + return ( + + ) +} diff --git a/traque-front/app/admin/page.js b/traque-front/app/admin/page.js new file mode 100644 index 0000000..1806388 --- /dev/null +++ b/traque-front/app/admin/page.js @@ -0,0 +1,36 @@ +"use client"; +import TeamAddForm from '@/components/admin/teamAdd'; +import TeamEdit from '@/components/admin/teamEdit'; +import TeamList from '@/components/admin/teamList'; +import { useAdminConnexion } from '@/context/adminConnexionContext'; +import useAdmin from '@/hook/useAdmin'; +import { redirect } from 'next/navigation'; +import React, { useEffect, useState } from 'react' + + +export default function Admin() { + const [selectedTeamId, setSelectedTeamId] = useState(null); + const { loggedIn } = useAdminConnexion(); + const { addTeam } = useAdmin(); + + useEffect(() => { + if (!loggedIn) { + redirect("/admin/login"); + } + }, [loggedIn]); + + + + return ( +
+
+

Team list

+ + +
+
+ +
+
+ ) +} diff --git a/traque-front/app/layout.js b/traque-front/app/layout.js index f0b9fa0..4434414 100644 --- a/traque-front/app/layout.js +++ b/traque-front/app/layout.js @@ -1,5 +1,6 @@ import { Inter } from "next/font/google"; import "./globals.css"; +import SocketProvider from "@/context/socketContext"; const inter = Inter({ subsets: ["latin"] }); @@ -10,7 +11,9 @@ export const metadata = { export default function RootLayout({ children }) { return ( - {children} + + {children} + ); } diff --git a/traque-front/app/page.js b/traque-front/app/page.js index aa20eab..2d175e1 100644 --- a/traque-front/app/page.js +++ b/traque-front/app/page.js @@ -1,9 +1,13 @@ +"use client" import LoginForm from "@/components/team/loginForm"; export default function Home() { + function login(teamId) { + console.log(teamId); + } return (
- +
); } diff --git a/traque-front/app/track/page.js b/traque-front/app/track/page.js index c79dffa..3d7f9cb 100644 --- a/traque-front/app/track/page.js +++ b/traque-front/app/track/page.js @@ -1,7 +1,8 @@ "use client"; import Button from '@/components/util/button'; import dynamic from 'next/dynamic'; -import React, { useEffect, useState } from 'react' +import React, { useEffect } from 'react' +import useGame from '../../hook/useGame'; //Load the map without SSR const LiveMap = dynamic(() => import('@/components/team/map'), { @@ -9,27 +10,13 @@ const LiveMap = dynamic(() => import('@/components/team/map'), { }); export default function Track() { - const [currentPosition, setCurrentPosition] = useState([0,0]); - const [enemyPosition, setEnemyPosition] = useState([0,0]); - - useEffect(() => { - const t = setTimeout(() => { - setEnemyPosition([currentPosition[0] + Math.random() / 100, currentPosition[1] + Math.random() / 100]); - }, 1000); - return () => clearInterval(t); - }, [currentPosition]); - - useEffect(() => { - navigator.geolocation.watchPosition((position) => { - setCurrentPosition([position.coords.latitude, position.coords.longitude]); - }); - }, []); + const { currentPosition, enemyPosition, updateCurrentPosition, sendCurrentPosition } = useGame(); return (
- +

30min

before penalty

diff --git a/traque-front/components/admin/teamAdd.jsx b/traque-front/components/admin/teamAdd.jsx new file mode 100644 index 0000000..3d5c7fc --- /dev/null +++ b/traque-front/components/admin/teamAdd.jsx @@ -0,0 +1,22 @@ +import React from 'react' +import TextInput from '../util/textInput' +import Button from '../util/button' + +export default function TeamAddForm({onAddTeam}) { + const [teamName, setTeamName] = React.useState(''); + function handleSubmit(e) { + e.preventDefault(); + onAddTeam(teamName); + setTeamName("") + } + return ( +
+
+ setTeamName(e.target.value)}/> +
+
+ +
+
+ ) +} diff --git a/traque-front/components/admin/teamEdit.jsx b/traque-front/components/admin/teamEdit.jsx new file mode 100644 index 0000000..e0c5b97 --- /dev/null +++ b/traque-front/components/admin/teamEdit.jsx @@ -0,0 +1,62 @@ +import React, { useEffect, useState } from 'react' +import TextInput from '../util/textInput' +import Button from '../util/button'; +import useAdmin from '@/hook/useAdmin'; + +export default function TeamEdit({selectedTeamId, setSelectedTeamId}) { + const [newTeamName, setNewTeamName] = React.useState(''); + const {setTeamName, getTeamName, removeTeam, getTeam} = useAdmin(); + const [team, setTeam] = useState({}) + + useEffect(() => { + let team = getTeam(selectedTeamId); + if (team != undefined) { + setNewTeamName(team.name); + } + },[selectedTeamId]) + + + useEffect(() => { + let team = getTeam(selectedTeamId); + if (team != undefined) { + setTeam(team); + } + }, [selectedTeamId]) + + + function handleSubmit(e) { + e.preventDefault(); + setTeamName(team.id, newTeamName); + } + + function handleRemove() { + removeTeam(team.id); + setSelectedTeamId(null); + } + + return (team && +
+
+

Actions

+
+
+ setNewTeamName(e.target.value)}/> +
+
+ +
+
+ +
+
+

Team details

+
+

Secret : {team.id}

+

Name : {team.name}

+

Chasing : {getTeamName(team.chasing)}

+

Chased by : {getTeamName(team.chased)}

+
+
+
+ ) +} diff --git a/traque-front/components/admin/teamList.jsx b/traque-front/components/admin/teamList.jsx new file mode 100644 index 0000000..85e828a --- /dev/null +++ b/traque-front/components/admin/teamList.jsx @@ -0,0 +1,63 @@ +"use client"; +import useAdmin from '@/hook/useAdmin'; +import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'; +import React from 'react' + +const reorder = (list, startIndex, endIndex) => { + const result = Array.from(list); + const [removed] = result.splice(startIndex, 1); + result.splice(endIndex, 0, removed); + + return result; +}; + +function TeamListItem({ team, index, onSelected, itemSelected }) { + const classNames = 'w-full p-3 m-3 shadow ' + (itemSelected ? "bg-blue-400" : "bg-gray-100"); + return ( + onSelected(team.id)}> + {provided => ( +
+

{team.name}

+
+ + )} +
+ ) +} + +export default function TeamList({selectedTeamId, onSelected}) { + const {teams, reorderTeams} = useAdmin(); + function onDragEnd(result) { + if (!result.destination) { + return; + } + + if (result.destination.index === result.source.index) { + return; + } + + const newTeams = reorder( + teams, + result.source.index, + result.destination.index + ); + + reorderTeams(newTeams); + } + return ( + + + {provided => ( +
    + {teams.map((team, i) => ( +
  • onSelected(team.id)}> + +
  • + ))} + {provided.placeholder} +
+ )} +
+
+ ) +} diff --git a/traque-front/components/team/loginForm.jsx b/traque-front/components/team/loginForm.jsx index f39ac68..20ff93b 100644 --- a/traque-front/components/team/loginForm.jsx +++ b/traque-front/components/team/loginForm.jsx @@ -1,12 +1,20 @@ +"use client"; +import { useState } from "react"; import Button from "../util/button"; import TextInput from "../util/textInput"; -export default function LoginForm() { +export default function LoginForm({ onSubmit, title, placeholder, buttonText}) { + const [value, setValue] = useState(""); + function handleSubmit(e) { + e.preventDefault(); + setValue(""); + onSubmit(value); + } return ( -
-

Connexion équipe

- - + +

{title}

+ setValue(e.target.value)} name="team-id"/> + ) } \ No newline at end of file diff --git a/traque-front/components/team/map.jsx b/traque-front/components/team/map.jsx index 7a234b1..290a234 100644 --- a/traque-front/components/team/map.jsx +++ b/traque-front/components/team/map.jsx @@ -22,13 +22,28 @@ function MapPan(props) { } export default function LiveMap({enemyPosition, currentPosition, ...props}) { + const [positionSet, setPositionSet] = useState(false); + useEffect(() => { + if(!positionSet && JSON.stringify(currentPosition) != "[0,0]") { + setPositionSet(true); + } + }, [currentPosition]); + const [enemyPositionSet, setEnemyPositionSet] = useState(false); + useEffect(() => { + if(!enemyPositionSet && JSON.stringify(enemyPosition) != "[0,0]") { + setEnemyPositionSet(true); + } + }, [enemyPosition]); + + + return ( - Votre position - - } + {enemyPositionSet && Position de l'ennemi - + } ) diff --git a/traque-front/context/adminConnexionContext.jsx b/traque-front/context/adminConnexionContext.jsx new file mode 100644 index 0000000..46227e1 --- /dev/null +++ b/traque-front/context/adminConnexionContext.jsx @@ -0,0 +1,37 @@ +"use client"; +import { createContext, useContext, useEffect, useState } from "react"; +import { useSocket } from "./socketContext"; + +const adminContext = createContext(); +const AdminConnexionProvider = ({ children }) => { + const [loggedIn, setLoggedIn] = useState(false); + const { adminSocket } = useSocket(); + + function login(password) { + adminSocket.emit("login", password); + } + + useEffect(() => { + function updateLoginStatus(status) { + setLoggedIn(status); + } + adminSocket.on("login_response", updateLoginStatus); + + return () => { + adminSocket.off("login_response", updateLoginStatus); + }; + }, []); + + return ( + + {children} + + ); +} + +function useAdminConnexion() { + return useContext(adminContext); +} + +export { AdminConnexionProvider, useAdminConnexion}; + diff --git a/traque-front/context/adminContext.jsx b/traque-front/context/adminContext.jsx new file mode 100644 index 0000000..e6ce9ec --- /dev/null +++ b/traque-front/context/adminContext.jsx @@ -0,0 +1,20 @@ +"use client"; +import { createContext, useContext, useState } from "react"; + +const adminContext = createContext(); + +function AdminProvider({children}) { + const [teams, setTeams] = useState([]); + const [started, setStarted] = useState(false); + return ( + + {children} + + ); +} + +function useAdminContext() { + return useContext(adminContext); +} + +export { AdminProvider, useAdminContext }; \ No newline at end of file diff --git a/traque-front/context/socketContext.jsx b/traque-front/context/socketContext.jsx new file mode 100644 index 0000000..7c2bd12 --- /dev/null +++ b/traque-front/context/socketContext.jsx @@ -0,0 +1,22 @@ +"use client"; +import { createContext, useContext } from "react"; + +const { io } = require("socket.io-client"); + +const SOCKET_URL = "http://localhost:3000"; +const USER_SOCKET_URL = SOCKET_URL + "/user"; +const ADMIN_SOCKET_URL = SOCKET_URL + "/admin"; + +export const userSocket = io(USER_SOCKET_URL); +export const adminSocket = io(ADMIN_SOCKET_URL); +export const SocketContext = createContext(); + +export default function SocketProvider({ children }) { + return ( + {children} + ); +} + +export function useSocket() { + return useContext(SocketContext); +} diff --git a/traque-front/hook/useAdmin.jsx b/traque-front/hook/useAdmin.jsx new file mode 100644 index 0000000..077fe30 --- /dev/null +++ b/traque-front/hook/useAdmin.jsx @@ -0,0 +1,68 @@ +import { useAdminContext } from "@/context/adminContext"; +import { useSocket } from "@/context/socketContext"; +import { Underdog } from "next/font/google"; + +const { useEffect, useState } = require("react"); + +export default function useAdmin(){ + const {teams, setTeams, started, setStarted} = useAdminContext(); + const {adminSocket} = useSocket(); + + function pollTeams() { + adminSocket.emit("get_teams"); + } + + useEffect(() => { + pollTeams(); + }, []); + useEffect(() => { + adminSocket.emit("get_teams"); + adminSocket.on("teams", setTeams); + return () => { + adminSocket.off("teams", setTeams); + } + }, []); + + function getTeam(teamId) { + return teams.find(team => team.id === teamId); + } + + function getTeamName(teamId) { + let team = getTeam(teamId); + return team ? team.name : ""; + } + + function reorderTeams(newOrder) { + adminSocket.emit("reorder_teams", newOrder); + } + + function addTeam(teamName) { + adminSocket.emit("add_team", teamName); + } + + function removeTeam(teamId) { + adminSocket.emit("remove_team", teamId); + } + + function setTeamName(teamId, newName) { + adminSocket.emit("rename_team", teamId, newName); + } + + function startGame() { + adminSocket.emit("start_game"); + } + + function stopGame() { + adminSocket.emit("stop_game"); + } + + useState(() => { + adminSocket.on("game_started", setStarted); + return () => { + adminSocket.off("game_started", setStarted); + } + }, []); + + return { teams, started, pollTeams, getTeam, getTeamName, reorderTeams, addTeam, removeTeam, startGame, stopGame, setTeamName }; + +} \ No newline at end of file diff --git a/traque-front/hook/useGame.jsx b/traque-front/hook/useGame.jsx new file mode 100644 index 0000000..d422bf5 --- /dev/null +++ b/traque-front/hook/useGame.jsx @@ -0,0 +1,61 @@ +"use client"; + +import { useSocket } from "@/context/socketContext"; +import { useEffect, useState } from "react"; + +export default function useGame() { + const {userSocket} = useSocket(); + const [loggedIn, setLoggedIn] = useState(false); + const [teamId, setTeamId] = useState(null); + const [enemyPosition, setEnemyPosition] = useState([0, 0]); + const [currentPosition, setCurrentPosition] = useState([0, 0]); + + function updateCurrentPosition(position) { + setCurrentPosition(position); + userSocket.emit("update_position", position); + } + + function sendCurrentPosition() { + userSocket.emit("send_position", currentPosition); + } + useEffect(() => { + function updateEnemyPosition(position) { + setEnemyPosition(position); + } + userSocket.on("enemy_position", updateEnemyPosition); + + return () => { + userSocket.off("enemy_position", updateEnemyPosition); + } + }, []); + + function login(teamId) { + setTeamId(teamId); + userSocket.emit("login", teamId); + } + useEffect(() => { + function updateLoginStatus(status) { + setLoggedIn(status); + } + userSocket.on("login_reponse", updateLoginStatus); + + return () => { + userSocket.off("login_response", updateLoginStatus); + } + }, []); + + useEffect(() => { + function udpate() { + console.log("update") + const position = navigator.geolocation.getCurrentPosition((position) => { + updateCurrentPosition([position.coords.latitude, position.coords.longitude]); + }, () => { }, { enableHighAccuracy: true, timeout: Infinity, maximumAge: 0 }); + } + setInterval(udpate, 1000); + return () => { + clearInterval(udpate); + } + }, []); + + return { updateCurrentPosition, sendCurrentPosition, login, enemyPosition, currentPosition, loggedIn, teamId }; +} \ No newline at end of file diff --git a/traque-front/package-lock.json b/traque-front/package-lock.json index 20d7c23..e74d06a 100644 --- a/traque-front/package-lock.json +++ b/traque-front/package-lock.json @@ -8,11 +8,13 @@ "name": "traque-front", "version": "0.1.0", "dependencies": { + "@hello-pangea/dnd": "^16.6.0", "leaflet-defaulticon-compatibility": "^0.1.2", "next": "14.1.4", "react": "^18", "react-dom": "^18", - "react-leaflet": "^4.2.1" + "react-leaflet": "^4.2.1", + "socket.io-client": "^4.7.5" }, "devDependencies": { "autoprefixer": "^10.0.1", @@ -47,7 +49,6 @@ "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz", "integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==", - "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -111,6 +112,72 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@hello-pangea/dnd": { + "version": "16.6.0", + "resolved": "https://registry.npmjs.org/@hello-pangea/dnd/-/dnd-16.6.0.tgz", + "integrity": "sha512-vfZ4GydqbtUPXSLfAvKvXQ6xwRzIjUSjVU0Sx+70VOhc2xx6CdmJXJ8YhH70RpbTUGjxctslQTHul9sIOxCfFQ==", + "dependencies": { + "@babel/runtime": "^7.24.1", + "css-box-model": "^1.2.1", + "memoize-one": "^6.0.0", + "raf-schd": "^4.0.3", + "react-redux": "^8.1.3", + "redux": "^4.2.1", + "use-memo-one": "^1.1.3" + }, + "peerDependencies": { + "react": "^16.8.5 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@hello-pangea/dnd/node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, + "node_modules/@hello-pangea/dnd/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/@hello-pangea/dnd/node_modules/react-redux": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.3.tgz", + "integrity": "sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==", + "dependencies": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^16.8 || ^17.0 || ^18.0", + "@types/react-dom": "^16.8 || ^17.0 || ^18.0", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0", + "react-native": ">=0.59", + "redux": "^4 || ^5.0.0-beta.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -446,6 +513,11 @@ "integrity": "sha512-0HejFckBN2W+ucM6cUOlwsByTKt9/+0tWhqUffNIcHqCXkthY/mZ7AuYPK/2IIaGWhdl0h+tICDO0ssLMd6XMQ==", "dev": true }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, "node_modules/@swc/helpers": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", @@ -454,12 +526,46 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", + "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/prop-types": { + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" + }, + "node_modules/@types/react": { + "version": "18.2.69", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.69.tgz", + "integrity": "sha512-W1HOMUWY/1Yyw0ba5TkCV+oqynRjG7BnteBB+B7JmAK7iw3l2SW+VGOxL+akPweix6jk2NNJtyJKpn4TkpfK3Q==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@typescript-eslint/parser": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", @@ -1181,6 +1287,14 @@ "node": ">= 8" } }, + "node_modules/css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "dependencies": { + "tiny-invariant": "^1.0.6" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -1193,6 +1307,11 @@ "node": ">=4" } }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -1254,7 +1373,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -1370,6 +1488,26 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "node_modules/engine.io-client": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz", + "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.16.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz", @@ -2443,6 +2581,14 @@ "node": ">= 0.4" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -3150,8 +3296,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/mz": { "version": "2.7.0", @@ -3785,6 +3930,11 @@ } ] }, + "node_modules/raf-schd": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", + "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -3811,8 +3961,7 @@ "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-leaflet": { "version": "4.2.1", @@ -3848,6 +3997,14 @@ "node": ">=8.10.0" } }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", @@ -3872,8 +4029,7 @@ "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", @@ -4158,6 +4314,32 @@ "node": ">=8" } }, + "node_modules/socket.io-client": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz", + "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/source-map-js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", @@ -4501,6 +4683,11 @@ "node": ">=0.8" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4713,6 +4900,22 @@ "punycode": "^2.1.0" } }, + "node_modules/use-memo-one": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", + "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4913,6 +5116,34 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/traque-front/package.json b/traque-front/package.json index 1e2f2d2..975cc67 100644 --- a/traque-front/package.json +++ b/traque-front/package.json @@ -10,11 +10,13 @@ "lint": "next lint" }, "dependencies": { + "@hello-pangea/dnd": "^16.6.0", "leaflet-defaulticon-compatibility": "^0.1.2", "next": "14.1.4", "react": "^18", "react-dom": "^18", - "react-leaflet": "^4.2.1" + "react-leaflet": "^4.2.1", + "socket.io-client": "^4.7.5" }, "devDependencies": { "autoprefixer": "^10.0.1",