diff --git a/client/src/App.vue b/client/src/App.vue index a6928f0c9d4de9bea138eab3a53a6d52df92906b..bc3679024521228af0e982ccffe504f5c57e722a 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -18,6 +18,11 @@ </div> <div id="navMenu" class="navbar-menu" :class="{ 'is-active': showNav }"> <div class="navbar-start"> + <a v-if="$store.state.isLoggedIn" class="navbar-item" + ><router-link to="/results" + ><i class="fas fa-flag-checkered" /> Enregistrer des résultats</router-link + ></a + > <a class="navbar-item" ><router-link to="/encoder" ><i class="fas fa-code" /> Encodeur NFC</router-link @@ -38,7 +43,9 @@ > </div> <div class="navbar-end"> + <div class="buttons has-addons"> + <a class="navbar-item button is-info" @click="connectUsb()"><span><i class="fab fa-usb"></i> Connexion lecteur NFC</span></a> <a v-if="!$store.state.isLoggedIn" class="navbar-item button" ><router-link to="/register" ><span @@ -77,7 +84,8 @@ <script> import PageFooter from "./components/PageFooter.vue"; - +import UsbNfcReader from "./usbNfcReader.js" +var usbNfcReader = new UsbNfcReader(); export default { name: "app", data() { @@ -92,6 +100,9 @@ export default { console.log(response); this.$store.commit("logout"); }); + }, + connectUsb() { + usbNfcReader.selectAndConnect() } }, components: { diff --git a/client/src/components/ResultsDisplay.vue b/client/src/components/ResultsDisplay.vue new file mode 100644 index 0000000000000000000000000000000000000000..85dbb58e338e1606abf5ddb77f4e2c97c7f2aa21 --- /dev/null +++ b/client/src/components/ResultsDisplay.vue @@ -0,0 +1,104 @@ +<template> + <div class="content"> + <article v-if="this.team.name.length > 0" class="message is-info"> + <div class="message-header"> + Résultats de l'équipe {{ team.name }} ({{ memberCount }} membres) + </div> + <div class="message-body"> + <div> + <p class="is-size-4"><strong>Membres de l'équipe :</strong></p> + <ul> + <li v-for="member in team.members" :key="member">{{ member }}</li> + </ul> + </div> + <div> + <p v-if="team.donePuzzles.length > 0" class="is-size-4"> + <strong>Puzzles effectués :</strong> + </p> + <div v-for="puzzle in team.donePuzzles" :key="puzzle.name"> + <div class="level" v-if="puzzle.score"> + <div class="level-item level-left"> + <p> + <strong>{{ puzzle.name }}</strong> ({{ puzzle.room }}) : + </p> + </div> + <div class="level-item level-right"> + <progress + class="progress is-small" + :value="puzzle.score" + :max="puzzle.maxScore" + ></progress> + </div> + <div class="level-item level-right"> + <p>{{ puzzle.score }}/{{ puzzle.maxScore }}</p> + </div> + </div> + <div v-else class="level "> + <div class="level-item level-left"> + <p> + <strong>{{ puzzle.name }}</strong> ({{ puzzle.room }}) : + </p> + </div> + <div class="level-item level-right"> + <span v-if="puzzle.validated" class="icon has-text-success"> + <i class="fas fa-check-circle"></i> + </span> + <span v-else class="icon has-text-danger"> + <i class="fas fa-times-circle"></i> + </span> + </div> + </div> + </div> + </div> + </div> + </article> + <article v-else> + <p>Merci de scanner une carte d'équipe pour afficher et enregistrer des résultats</p> + <button class="button is-primary" @click="showPairingModal = !showPairingModal"><i class="fas fa-download"></i> Scanner la carte de l'équipe !</button> + </article> + + <div class="modal" :class="{ 'is-active': showPairingModal }"> + <div class="modal-background"></div> + <div class="modal-content"> + <div class="message"> + <div class="message-header"> + Lecture d'une carte NFC d'équipe + </div> + <div class="message-body"> + Veuillez scanner une carte sur le lecteur pour charger ses résultats + équipe. + </div> + </div> + </div> + <button + class="modal-close is-large" + @click="showPairingModal = false" + ></button> + </div> + + </div> +</template> + +<script> +export default { + name: "ResultDisplay", + + data() { + return { + showPairingModal: false, + team : { name: "", members: [], donePuzzles : [] } + }; + }, + + computed: { + memberCount: function() { + return this.team.members.length; + } + }, + + methods: { + } +}; +</script> + +<style scoped></style> diff --git a/client/src/main.js b/client/src/main.js index 9b4846d4c8684e58b676f6d36c5850472e7ce884..c8c53d248a04c31550e6561ef486e07c3729d950 100644 --- a/client/src/main.js +++ b/client/src/main.js @@ -6,12 +6,14 @@ import App from "./App.vue"; import VueAxios from "vue-axios"; import axios from "axios"; + import HomeComponent from "./components/HomeComponent.vue"; import SchoolManager from "./components/SchoolManager.vue"; import MentionsLegales from "./components/MentionsLegales.vue"; import Login from "./components/Login.vue"; import Register from "./components/Register.vue"; import Encoder from "./components/Encoder.vue"; +import ResultsDisplay from "./components/ResultsDisplay.vue"; require("@/assets/main.scss"); import "@fortawesome/fontawesome-free/css/all.css"; import "@fortawesome/fontawesome-free/js/all.js"; @@ -28,6 +30,11 @@ const routes = [ path: "/", component: HomeComponent }, + { + name: "resultsDisplay", + path: "/results", + component: ResultsDisplay + }, { name: "encoder", path: "/encoder", diff --git a/client/src/serial.js b/client/src/serial.js new file mode 100644 index 0000000000000000000000000000000000000000..0cf2e4e3783c5a5741d6f29576fcc36233720fe1 --- /dev/null +++ b/client/src/serial.js @@ -0,0 +1,101 @@ +var serial = {}; + +(function() { + 'use strict'; + + serial.getPorts = function() { + return navigator.usb.getDevices().then(devices => { + return devices.map(device => new serial.Port(device)); + }); + }; + + serial.requestPort = function() { + const filters = [ + { 'vendorId': 0x2341, 'productId': 0x8036 }, // Arduino Leonardo + { 'vendorId': 0x2341, 'productId': 0x8037 }, // Arduino Micro + { 'vendorId': 0x2341, 'productId': 0x804d }, // Arduino/Genuino Zero + { 'vendorId': 0x2341, 'productId': 0x804e }, // Arduino/Genuino MKR1000 + { 'vendorId': 0x2341, 'productId': 0x804f }, // Arduino MKRZERO + { 'vendorId': 0x2341, 'productId': 0x8050 }, // Arduino MKR FOX 1200 + { 'vendorId': 0x2341, 'productId': 0x8052 }, // Arduino MKR GSM 1400 + { 'vendorId': 0x2341, 'productId': 0x8053 }, // Arduino MKR WAN 1300 + { 'vendorId': 0x2341, 'productId': 0x8054 }, // Arduino MKR WiFi 1010 + { 'vendorId': 0x2341, 'productId': 0x8055 }, // Arduino MKR NB 1500 + { 'vendorId': 0x2341, 'productId': 0x8056 }, // Arduino MKR Vidor 4000 + { 'vendorId': 0x2341, 'productId': 0x8057 }, // Arduino NANO 33 IoT + { 'vendorId': 0x239A }, // Adafruit Boards! + ]; + return navigator.usb.requestDevice({ 'filters': filters }).then( + device => new serial.Port(device) + ); + } + + serial.Port = function(device) { + this.device_ = device; + this.interfaceNumber_ = 2; // original interface number of WebUSB Arduino demo + this.endpointIn_ = 5; // original in endpoint ID of WebUSB Arduino demo + this.endpointOut_ = 4; // original out endpoint ID of WebUSB Arduino demo + }; + + serial.Port.prototype.connect = function() { + let readLoop = () => { + this.device_.transferIn(this.endpointIn_, 64).then(result => { + this.onReceive(result.data); + readLoop(); + }, error => { + this.onReceiveError(error); + }); + }; + + return this.device_.open() + .then(() => { + if (this.device_.configuration === null) { + return this.device_.selectConfiguration(1); + } + }) + .then(() => { + var configurationInterfaces = this.device_.configuration.interfaces; + configurationInterfaces.forEach(element => { + element.alternates.forEach(elementalt => { + if (elementalt.interfaceClass==0xff) { + this.interfaceNumber_ = element.interfaceNumber; + elementalt.endpoints.forEach(elementendpoint => { + if (elementendpoint.direction == "out") { + this.endpointOut_ = elementendpoint.endpointNumber; + } + if (elementendpoint.direction=="in") { + this.endpointIn_ =elementendpoint.endpointNumber; + } + }) + } + }) + }) + }) + .then(() => this.device_.claimInterface(this.interfaceNumber_)) + .then(() => this.device_.selectAlternateInterface(this.interfaceNumber_, 0)) + .then(() => this.device_.controlTransferOut({ + 'requestType': 'class', + 'recipient': 'interface', + 'request': 0x22, + 'value': 0x01, + 'index': this.interfaceNumber_})) + .then(() => { + readLoop(); + }); + }; + + serial.Port.prototype.disconnect = function() { + return this.device_.controlTransferOut({ + 'requestType': 'class', + 'recipient': 'interface', + 'request': 0x22, + 'value': 0x00, + 'index': this.interfaceNumber_}) + .then(() => this.device_.close()); + }; + + serial.Port.prototype.send = function(data) { + return this.device_.transferOut(this.endpointOut_, data); + }; +})(); +export default serial; diff --git a/client/src/usbNfcReader.js b/client/src/usbNfcReader.js new file mode 100644 index 0000000000000000000000000000000000000000..ec97a22dfe979f0c45fed8c15ff223b375a5b085 --- /dev/null +++ b/client/src/usbNfcReader.js @@ -0,0 +1,59 @@ +import Serial from "./serial.js" + +export default class UsbNfcReader { + constructor() { + this.textEncoder = new TextEncoder(); + //Tentative de connexion/reconnexion automatique + Serial.getPorts().then(ports => { + if (ports.length == 0) { + console.log('No devices found.'); + } else { + this.port = ports[0]; + console.log("Port choisi :", this.port); + this.connect(); + } + }); + } + + selectAndConnect() { + Serial.requestPort().then(selectedPort => { + this.port = selectedPort; + this.connect(); + }).catch(error => { + console.log('Connection error: ' + error); + }); + } + + disconnect() { + this.port.disconnect(); + this.port = null; + } + + connect() { + console.log('Connecting to ' + this.port.device_.productName + '...'); + this.port.connect().then(() => { + console.log(this.port, 'Connected.'); + + //Important : callback de réception + this.port.onReceive = data => { + let textDecoder = new TextDecoder(); + this.receivedMsg = textDecoder.decode(data); + console.log("Reçu :" + this.receivedMsg); + } + this.port.onReceiveError = error => { + console.log('Receive error: ' + error); + }; + }, error => { + console.log('Connection error: ' + error); + }); + } + + //Envoie ce string à l'Arduino + sendString(str) { + if (this.port !== undefined) { + this.port.send(this.textEncoder.encode(str)).catch(error => { + console.log('Send error: ' + error); + }); + } + } +}