Splitting more, added chat, and fancier draw-animations
This commit is contained in:
		
							
								
								
									
										25
									
								
								api/chat.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								api/chat.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | module.exports = io => { | ||||||
|  |   io.on("connection", socket => { | ||||||
|  |     let username = null; | ||||||
|  |  | ||||||
|  |     socket.on("username", msg => { | ||||||
|  |       if (msg.username == null) { | ||||||
|  |         username = null; | ||||||
|  |         socket.emit("accept_username", false); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       if (msg.username.length > 3 && msg.username.length < 30) { | ||||||
|  |         username = msg.username; | ||||||
|  |         socket.emit("accept_username", true); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       socket.emit("accept_username", false); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     socket.on("chat", msg => { | ||||||
|  |       msg.username = username; | ||||||
|  |       msg.timestamp = new Date().getTime(); | ||||||
|  |       io.emit("chat", msg); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
| @@ -52,28 +52,33 @@ router.route("/winners/secure").get(mustBeAuthenticated, async (req, res) => { | |||||||
| }); | }); | ||||||
|  |  | ||||||
| router.route("/winner").get(mustBeAuthenticated, async (req, res) => { | router.route("/winner").get(mustBeAuthenticated, async (req, res) => { | ||||||
|   let colorWinner = Math.floor(Math.random() * 4); |   let allContestants = await Attendee.find(); | ||||||
|   let colorToChoseFrom; |   if (allContestants.length == 0) { | ||||||
|  |     res.json(false); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   let ballotColors = []; | ||||||
|  |   for (let i = 0; i < allContestants.length; i++) { | ||||||
|  |     let currentContestant = allContestants[i]; | ||||||
|  |     for (let blue = 0; blue < currentContestant.blue; blue++) { | ||||||
|  |       ballotColors.push("blue"); | ||||||
|  |     } | ||||||
|  |     for (let red = 0; red < currentContestant.red; red++) { | ||||||
|  |       ballotColors.push("red"); | ||||||
|  |     } | ||||||
|  |     for (let green = 0; green < currentContestant.green; green++) { | ||||||
|  |       ballotColors.push("green"); | ||||||
|  |     } | ||||||
|  |     for (let yellow = 0; yellow < currentContestant.yellow; yellow++) { | ||||||
|  |       ballotColors.push("yellow"); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   let colorToChoseFrom = | ||||||
|  |     ballotColors[Math.floor(Math.random() * ballotColors.length)]; | ||||||
|   let findObject = {}; |   let findObject = {}; | ||||||
|  |  | ||||||
|   switch (colorWinner) { |  | ||||||
|     case 0: |  | ||||||
|       colorToChoseFrom = "red"; |  | ||||||
|       break; |  | ||||||
|     case 1: |  | ||||||
|       colorToChoseFrom = "blue"; |  | ||||||
|       break; |  | ||||||
|     case 2: |  | ||||||
|       colorToChoseFrom = "green"; |  | ||||||
|       break; |  | ||||||
|     case 3: |  | ||||||
|       colorToChoseFrom = "yellow"; |  | ||||||
|       break; |  | ||||||
|   } |  | ||||||
|   io.emit("color_winner", { color: colorToChoseFrom }); |  | ||||||
|  |  | ||||||
|   findObject[colorToChoseFrom] = { $gt: 0 }; |   findObject[colorToChoseFrom] = { $gt: 0 }; | ||||||
|  |  | ||||||
|   let contestantsToChoseFrom = await Attendee.find(findObject); |   let contestantsToChoseFrom = await Attendee.find(findObject); | ||||||
|   let attendeeListDemocratic = []; |   let attendeeListDemocratic = []; | ||||||
|  |  | ||||||
| @@ -93,7 +98,7 @@ router.route("/winner").get(mustBeAuthenticated, async (req, res) => { | |||||||
|       Math.floor(Math.random() * attendeeListDemocratic.length) |       Math.floor(Math.random() * attendeeListDemocratic.length) | ||||||
|     ]; |     ]; | ||||||
|  |  | ||||||
|   io.emit("winner", { name: winner.name }); |   io.emit("winner", { color: colorToChoseFrom, name: winner.name }); | ||||||
|  |  | ||||||
|   let newWinnerElement = new VirtualWinner({ |   let newWinnerElement = new VirtualWinner({ | ||||||
|     name: winner.name, |     name: winner.name, | ||||||
|   | |||||||
| @@ -12,6 +12,10 @@ const subscriptionApi = require(path.join(__dirname + "/api/subscriptions")); | |||||||
| const loginApi = require(path.join(__dirname + "/api/login")); | const loginApi = require(path.join(__dirname + "/api/login")); | ||||||
| const wineinfoApi = require(path.join(__dirname + "/api/wineinfo")); | const wineinfoApi = require(path.join(__dirname + "/api/wineinfo")); | ||||||
| const virtualApi = require(path.join(__dirname + "/api/virtualLottery")); | const virtualApi = require(path.join(__dirname + "/api/virtualLottery")); | ||||||
|  |  | ||||||
|  | //This is required for the chat to work | ||||||
|  | const chat = require(path.join(__dirname + "/api/chat"))(io); | ||||||
|  |  | ||||||
| const bodyParser = require("body-parser"); | const bodyParser = require("body-parser"); | ||||||
|  |  | ||||||
| const mongoose = require("mongoose"); | const mongoose = require("mongoose"); | ||||||
|   | |||||||
| @@ -11,93 +11,86 @@ | |||||||
|     > |     > | ||||||
|       Her var det lite.. Sikker på at det er en virtuell trekning nå? |       Her var det lite.. Sikker på at det er en virtuell trekning nå? | ||||||
|     </h2> |     </h2> | ||||||
|     <div class="current-draw" v-if="currentWinnerDrawn"> |     <WinnerDraw | ||||||
|       <h2>NY VINNER:</h2> |       :currentWinnerDrawn="currentWinnerDrawn" | ||||||
|       <div |       :currentWinner="currentWinner" | ||||||
|         :class="currentWinnerColor + '-ballot'" |       :attendees="attendees" | ||||||
|         class="ballot-element center-new-winner" |     /> | ||||||
|       > |  | ||||||
|         <span v-if="currentWinnerName">{{ currentWinnerName }}</span> |  | ||||||
|         <span v-if="!currentWinnerName">{{ secondsNameLeft }}</span> |  | ||||||
|       </div> |  | ||||||
|       <br /> |  | ||||||
|       <br /> |  | ||||||
|       <br /> |  | ||||||
|       <br /> |  | ||||||
|       <br /> |  | ||||||
|       <div v-if="countdownStarted && attendees.length > 0"> |  | ||||||
|         <h2>Trekker ny om: {{ secondsLeft }}</h2> |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
|  |  | ||||||
|     <h2 v-if="winners.length > 0">Vinnere</h2> |     <Winners :winners="winners" /> | ||||||
|     <div class="winners" v-if="winners.length > 0"> |     <hr /> | ||||||
|       <div class="winner" v-for="(winner, index) in winners" :key="index"> |     <div class="middle-elements"> | ||||||
|         <div :class="winner.color + '-ballot'" class="ballot-element"> |       <Attendees :attendees="attendees" class="outer-attendees" /> | ||||||
|           {{ winner.name }} |       <Chat | ||||||
|         </div> |         class="outer-chat" | ||||||
|       </div> |         :chatHistory="chatHistory" | ||||||
|     </div> |         :usernameAllowed="usernameAllowed" | ||||||
|     <div class="attendees" v-if="attendees.length > 0"> |         v-on:message="sendMessage" | ||||||
|       <h2>Deltakere</h2> |         v-on:username="setUsername" | ||||||
|       <div class="attendee" v-for="(attendee, index) in attendees" :key="index"> |       /> | ||||||
|         <span class="attendee-name">{{ attendee.name }}</span> |  | ||||||
|         <div class="red-ballot ballot-element small">{{ attendee.red }}</div> |  | ||||||
|         <div class="blue-ballot ballot-element small">{{ attendee.blue }}</div> |  | ||||||
|         <div class="green-ballot ballot-element small"> |  | ||||||
|           {{ attendee.green }} |  | ||||||
|         </div> |  | ||||||
|         <div class="yellow-ballot ballot-element small"> |  | ||||||
|           {{ attendee.yellow }} |  | ||||||
|         </div> |  | ||||||
|       </div> |  | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import { attendees, winners } from "@/api"; | import { attendees, winners } from "@/api"; | ||||||
|  | import Chat from "@/ui/Chat"; | ||||||
|  | import Attendees from "@/ui/Attendees"; | ||||||
|  | import Winners from "@/ui/Winners"; | ||||||
|  | import WinnerDraw from "@/ui/WinnerDraw"; | ||||||
| import io from "socket.io-client"; | import io from "socket.io-client"; | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|  |   components: { Chat, Attendees, Winners, WinnerDraw }, | ||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
|       attendees: [], |       attendees: [], | ||||||
|       winners: [], |       winners: [], | ||||||
|       currentWinnerDrawn: false, |       currentWinnerDrawn: false, | ||||||
|       currentWinnerName: null, |       currentWinner: {}, | ||||||
|       currentWinnerColor: null, |  | ||||||
|       countdownStarted: false, |  | ||||||
|       secondsLeft: 15, |  | ||||||
|       secondsNameLeft: 5, |  | ||||||
|       socket: null, |       socket: null, | ||||||
|       attendeesFetched: false, |       attendeesFetched: false, | ||||||
|       winnersFetched: false |       winnersFetched: false, | ||||||
|  |       chatHistory: [], | ||||||
|  |       usernameAccepted: false, | ||||||
|  |       username: null, | ||||||
|  |       wasDisconnected: false, | ||||||
|  |       emitUsernameOnConnect: false | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|   mounted() { |   mounted() { | ||||||
|     this.getAttendees(); |     this.getAttendees(); | ||||||
|     this.getWinners(); |     this.getWinners(); | ||||||
|     this.socket = io(`${window.location.hostname}:${window.location.port}`); |     this.socket = io(`${window.location.hostname}:${window.location.port}`); | ||||||
|     this.socket.on("color_winner", msg => { |     this.socket.on("color_winner", msg => {}); | ||||||
|       this.currentWinnerDrawn = true; |  | ||||||
|       this.currentWinnerColor = msg.color; |     this.socket.on("chat", msg => { | ||||||
|  |       this.chatHistory.push(msg); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     this.socket.on("disconnect", msg => { | ||||||
|  |       this.wasDisconnected = true; | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     this.socket.on("connect", msg => { | ||||||
|  |       if ( | ||||||
|  |         this.emitUsernameOnConnect || | ||||||
|  |         (this.wasDisconnected && this.username != null) | ||||||
|  |       ) { | ||||||
|  |         this.setUsername(this.username); | ||||||
|  |       } | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     this.socket.on("winner", async msg => { |     this.socket.on("winner", async msg => { | ||||||
|       this.currentWinnerDrawn = true; |       this.currentWinnerDrawn = true; | ||||||
|       this.countdown(); |       this.currentWinner = { name: msg.name, color: msg.color }; | ||||||
|       setTimeout(() => { |  | ||||||
|         this.currentWinnerName = msg.name; |  | ||||||
|         this.getWinners(); |  | ||||||
|         this.getAttendees(); |  | ||||||
|       }, 5000); |  | ||||||
|  |  | ||||||
|       setTimeout(() => { |       setTimeout(() => { | ||||||
|         this.currentWinnerColor = null; |         this.getWinners(); | ||||||
|         this.currentWinnerName = null; |         this.getAttendees(); | ||||||
|  |         this.currentWinner = null; | ||||||
|         this.currentWinnerDrawn = false; |         this.currentWinnerDrawn = false; | ||||||
|       }, 15000); |       }, 12000); | ||||||
|     }); |     }); | ||||||
|     this.socket.on("refresh_data", async msg => { |     this.socket.on("refresh_data", async msg => { | ||||||
|       this.getAttendees(); |       this.getAttendees(); | ||||||
| @@ -106,25 +99,30 @@ export default { | |||||||
|     this.socket.on("new_attendee", async msg => { |     this.socket.on("new_attendee", async msg => { | ||||||
|       this.getAttendees(); |       this.getAttendees(); | ||||||
|     }); |     }); | ||||||
|  |     this.socket.on("accept_username", accepted => { | ||||||
|  |       this.usernameAccepted = accepted; | ||||||
|  |       if (!accepted) { | ||||||
|  |         this.username = null; | ||||||
|  |       } else { | ||||||
|  |         window.localStorage.setItem("username", this.username); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|   }, |   }, | ||||||
|   beforeDestroy() { |   beforeDestroy() { | ||||||
|     this.socket.disconnect(); |     this.socket.disconnect(); | ||||||
|     this.socket = null; |     this.socket = null; | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     countdown: function() { |     setUsername: function(username) { | ||||||
|       this.secondsLeft -= 1; |       this.username = username; | ||||||
|       this.secondsNameLeft -= 1; |       if (!this.socket || !this.socket.emit) { | ||||||
|       this.countdownStarted = true; |         this.emitUsernameOnConnect = true; | ||||||
|       if (this.secondsLeft <= 0) { |  | ||||||
|         this.secondsLeft = 15; |  | ||||||
|         this.secondsNameLeft = 5; |  | ||||||
|         this.countdownStarted = false; |  | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|       setTimeout(() => { |       this.socket.emit("username", { username }); | ||||||
|         this.countdown(); |     }, | ||||||
|       }, 1000); |     sendMessage: function(msg) { | ||||||
|  |       this.socket.emit("chat", { message: msg }); | ||||||
|     }, |     }, | ||||||
|     getWinners: async function() { |     getWinners: async function() { | ||||||
|       let response = await winners(); |       let response = await winners(); | ||||||
| @@ -137,11 +135,6 @@ export default { | |||||||
|       let response = await attendees(); |       let response = await attendees(); | ||||||
|       if (response) { |       if (response) { | ||||||
|         this.attendees = response; |         this.attendees = response; | ||||||
|         if (attendees <= 0) { |  | ||||||
|           this.secondsLeft = 0; |  | ||||||
|           this.secondsNameLeft = 0; |  | ||||||
|           this.countdown(); |  | ||||||
|         } |  | ||||||
|       } |       } | ||||||
|       this.attendeesFetched = true; |       this.attendeesFetched = true; | ||||||
|     } |     } | ||||||
| @@ -153,7 +146,9 @@ export default { | |||||||
| @import "../styles/global.scss"; | @import "../styles/global.scss"; | ||||||
| @import "../styles/variables.scss"; | @import "../styles/variables.scss"; | ||||||
| @import "../styles/media-queries.scss"; | @import "../styles/media-queries.scss"; | ||||||
|  | hr { | ||||||
|  |   width: 80%; | ||||||
|  | } | ||||||
| h1, | h1, | ||||||
| h2 { | h2 { | ||||||
|   text-align: center; |   text-align: center; | ||||||
| @@ -162,72 +157,28 @@ h2 { | |||||||
|   margin: auto; |   margin: auto; | ||||||
| } | } | ||||||
|  |  | ||||||
| .attendee-name { | .outer-chat { | ||||||
|   width: 60%; |   margin: 0 60px 0 10px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .outer-attendees { | ||||||
|  |   margin: 0 10px 0 45px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .center-new-winner { | .center-new-winner { | ||||||
|   margin: auto !important; |   margin: auto !important; | ||||||
| } | } | ||||||
|  |  | ||||||
| .ballot-element { | .middle-elements { | ||||||
|   width: 140px; |  | ||||||
|   height: 150px; |  | ||||||
|   margin: 20px 0; |  | ||||||
|   -webkit-mask-image: url(/../../public/assets/images/lodd.svg); |  | ||||||
|   background-repeat: no-repeat; |  | ||||||
|   mask-image: url(/../../public/assets/images/lodd.svg); |  | ||||||
|   -webkit-mask-repeat: no-repeat; |  | ||||||
|   mask-repeat: no-repeat; |  | ||||||
|   color: #333333; |  | ||||||
|   font-size: 0.75rem; |  | ||||||
|   font-weight: bold; |  | ||||||
|   display: flex; |   display: flex; | ||||||
|  |   flex-direction: row; | ||||||
|   justify-content: center; |   justify-content: center; | ||||||
|   align-items: center; |   align-items: center; | ||||||
|   text-align: center; |   height: 400px; | ||||||
|  |  | ||||||
|   &.small { |   @include mobile { | ||||||
|     width: 45px; |     height: auto; | ||||||
|     height: 45px; |     flex-direction: column; | ||||||
|     font-size: 1rem; |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   &.green-ballot { |  | ||||||
|     background-color: $light-green; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   &.blue-ballot { |  | ||||||
|     background-color: $light-blue; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   &.yellow-ballot { |  | ||||||
|     background-color: $light-yellow; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   &.red-ballot { |  | ||||||
|     background-color: $light-red; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .winners { |  | ||||||
|   display: flex; |  | ||||||
|   justify-content: space-around; |  | ||||||
|   align-items: center; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .attendees { |  | ||||||
|   display: flex; |  | ||||||
|   flex-direction: column; |  | ||||||
|   justify-content: center; |  | ||||||
|   align-items: center; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .attendee { |  | ||||||
|   display: flex; |  | ||||||
|   justify-content: space-between; |  | ||||||
|   align-items: center; |  | ||||||
|   width: 50%; |  | ||||||
|   margin: 0 auto; |  | ||||||
| } | } | ||||||
| </style> | </style> | ||||||
|   | |||||||
| @@ -3,12 +3,21 @@ | |||||||
|     <h1>Virtuelt lotteri registrering</h1> |     <h1>Virtuelt lotteri registrering</h1> | ||||||
|     <br /> |     <br /> | ||||||
|     <div class="draw-winner-container" v-if="attendees.length > 0"> |     <div class="draw-winner-container" v-if="attendees.length > 0"> | ||||||
|       <span v-if="drawingWinner" |       <div v-if="drawingWinner"> | ||||||
|         >Vent {{ secondsLeft }} sekunder til å trekke på nytt</span |         <span | ||||||
|       > |           >Trekker {{ currentWinners }} av {{ numberOfWinners }} vinnere. | ||||||
|       <button class="vin-button" v-if="!drawingWinner" @click="drawWinner"> |           {{ secondsLeft }} sekunder av {{ drawTime }} igjen</span | ||||||
|         Trekk en vinner |         > | ||||||
|       </button> |         <button class="vin-button" @click="stopDraw"> | ||||||
|  |           Stopp trekning | ||||||
|  |         </button> | ||||||
|  |       </div> | ||||||
|  |       <div class="draw-container" v-if="!drawingWinner"> | ||||||
|  |         <button class="vin-button" @click="drawWinner"> | ||||||
|  |           Trekk vinnere | ||||||
|  |         </button> | ||||||
|  |         <input type="number" v-model="numberOfWinners" /> | ||||||
|  |       </div> | ||||||
|     </div> |     </div> | ||||||
|     <h2 v-if="winners.length > 0">Vinnere</h2> |     <h2 v-if="winners.length > 0">Vinnere</h2> | ||||||
|     <div class="winners" v-if="winners.length > 0"> |     <div class="winners" v-if="winners.length > 0"> | ||||||
| @@ -39,7 +48,7 @@ | |||||||
|       </button> |       </button> | ||||||
|     </div> |     </div> | ||||||
|     <div class="attendees" v-if="attendees.length > 0"> |     <div class="attendees" v-if="attendees.length > 0"> | ||||||
|       <h2>Deltakere hittil</h2> |       <h2>Deltakere ({{ attendees.length }})</h2> | ||||||
|       <div class="attendee" v-for="(attendee, index) in attendees" :key="index"> |       <div class="attendee" v-for="(attendee, index) in attendees" :key="index"> | ||||||
|         <div class="name-and-phone"> |         <div class="name-and-phone"> | ||||||
|           <span class="name">{{ attendee.name }}</span> |           <span class="name">{{ attendee.name }}</span> | ||||||
| @@ -148,7 +157,10 @@ export default { | |||||||
|       attendees: [], |       attendees: [], | ||||||
|       winners: [], |       winners: [], | ||||||
|       drawingWinner: false, |       drawingWinner: false, | ||||||
|       secondsLeft: 15 |       secondsLeft: 20, | ||||||
|  |       drawTime: 20, | ||||||
|  |       currentWinners: 1, | ||||||
|  |       numberOfWinners: 4 | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|   mounted() { |   mounted() { | ||||||
| @@ -183,11 +195,19 @@ export default { | |||||||
|       let response = await attendeesSecure(); |       let response = await attendeesSecure(); | ||||||
|       this.attendees = response; |       this.attendees = response; | ||||||
|     }, |     }, | ||||||
|  |     stopDraw: function() { | ||||||
|  |       this.drawingWinner = false; | ||||||
|  |       this.secondsLeft = this.drawTime; | ||||||
|  |     }, | ||||||
|     drawWinner: async function() { |     drawWinner: async function() { | ||||||
|       this.drawingWinner = true; |       this.drawingWinner = true; | ||||||
|       let response = await getVirtualWinner(); |       let response = await getVirtualWinner(); | ||||||
|       if (response) { |       if (response) { | ||||||
|         this.countdown(); |         if (this.currentWinners < this.numberOfWinners) { | ||||||
|  |           this.countdown(); | ||||||
|  |         } else { | ||||||
|  |           this.drawingWinner = false; | ||||||
|  |         } | ||||||
|         this.getWinners(); |         this.getWinners(); | ||||||
|         this.getAttendees(); |         this.getAttendees(); | ||||||
|       } else { |       } else { | ||||||
| @@ -197,9 +217,17 @@ export default { | |||||||
|     }, |     }, | ||||||
|     countdown: function() { |     countdown: function() { | ||||||
|       this.secondsLeft -= 1; |       this.secondsLeft -= 1; | ||||||
|  |       if (!this.drawingWinner) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|       if (this.secondsLeft <= 0) { |       if (this.secondsLeft <= 0) { | ||||||
|         this.secondsLeft = 15; |         this.secondsLeft = this.drawTime; | ||||||
|         this.drawingWinner = false; |         this.currentWinners += 1; | ||||||
|  |         if (this.currentWinners <= this.numberOfWinners) { | ||||||
|  |           this.drawWinner(); | ||||||
|  |         } else { | ||||||
|  |           this.drawingWinner = false; | ||||||
|  |         } | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|       setTimeout(() => { |       setTimeout(() => { | ||||||
| @@ -238,6 +266,10 @@ export default { | |||||||
| @import "../styles/global.scss"; | @import "../styles/global.scss"; | ||||||
| @import "../styles/media-queries.scss"; | @import "../styles/media-queries.scss"; | ||||||
|  |  | ||||||
|  | .draw-container { | ||||||
|  |   display: flex; | ||||||
|  | } | ||||||
|  |  | ||||||
| .draw-winner-container, | .draw-winner-container, | ||||||
| .delete-buttons { | .delete-buttons { | ||||||
|   margin-bottom: 20px; |   margin-bottom: 20px; | ||||||
|   | |||||||
| @@ -85,7 +85,8 @@ body { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| input, textarea { | input, | ||||||
|  | textarea { | ||||||
|   border-radius: 0; |   border-radius: 0; | ||||||
|   box-shadow: none; |   box-shadow: none; | ||||||
|   -webkit-appearance: none; |   -webkit-appearance: none; | ||||||
| @@ -116,7 +117,7 @@ input, textarea { | |||||||
|   touch-action: manipulation; |   touch-action: manipulation; | ||||||
|  |  | ||||||
|   &::after { |   &::after { | ||||||
|     content: ''; |     content: ""; | ||||||
|     position: absolute; |     position: absolute; | ||||||
|     transition: opacity 0.3s ease-in-out; |     transition: opacity 0.3s ease-in-out; | ||||||
|     z-index: -1; |     z-index: -1; | ||||||
| @@ -125,12 +126,9 @@ input, textarea { | |||||||
|     top: 0; |     top: 0; | ||||||
|     left: 0; |     left: 0; | ||||||
|     opacity: 0; |     opacity: 0; | ||||||
|     box-shadow: 0 1px 2px rgba(0,0,0,0.07), |     box-shadow: 0 1px 2px rgba(0, 0, 0, 0.07), 0 2px 4px rgba(0, 0, 0, 0.07), | ||||||
|                 0 2px 4px rgba(0,0,0,0.07), |       0 4px 8px rgba(0, 0, 0, 0.07), 0 8px 16px rgba(0, 0, 0, 0.07), | ||||||
|                 0 4px 8px rgba(0,0,0,0.07), |       0 16px 32px rgba(0, 0, 0, 0.07), 0 32px 64px rgba(0, 0, 0, 0.07); | ||||||
|                 0 8px 16px rgba(0,0,0,0.07), |  | ||||||
|                 0 16px 32px rgba(0,0,0,0.07), |  | ||||||
|                 0 32px 64px rgba(0,0,0,0.07); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   &:hover { |   &:hover { | ||||||
| @@ -141,3 +139,29 @@ input, textarea { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .ballot-element { | ||||||
|  |   margin: 20px 0; | ||||||
|  |   -webkit-mask-image: url(/../../public/assets/images/lodd.svg); | ||||||
|  |   background-repeat: no-repeat; | ||||||
|  |   mask-image: url(/../../public/assets/images/lodd.svg); | ||||||
|  |   -webkit-mask-repeat: no-repeat; | ||||||
|  |   mask-repeat: no-repeat; | ||||||
|  |   color: #333333; | ||||||
|  |  | ||||||
|  |   &.green-ballot { | ||||||
|  |     background-color: $light-green; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   &.blue-ballot { | ||||||
|  |     background-color: $light-blue; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   &.yellow-ballot { | ||||||
|  |     background-color: $light-yellow; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   &.red-ballot { | ||||||
|  |     background-color: $light-red; | ||||||
|  |   } | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										64
									
								
								src/ui/Attendees.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/ui/Attendees.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | |||||||
|  | <template> | ||||||
|  |   <div class="attendees" v-if="attendees.length > 0"> | ||||||
|  |     <h2>Deltakere ({{ attendees.length }})</h2> | ||||||
|  |     <div class="attendee" v-for="(attendee, index) in attendees" :key="index"> | ||||||
|  |       <span class="attendee-name">{{ attendee.name }}</span> | ||||||
|  |       <div class="red-ballot ballot-element small">{{ attendee.red }}</div> | ||||||
|  |       <div class="blue-ballot ballot-element small"> | ||||||
|  |         {{ attendee.blue }} | ||||||
|  |       </div> | ||||||
|  |       <div class="green-ballot ballot-element small"> | ||||||
|  |         {{ attendee.green }} | ||||||
|  |       </div> | ||||||
|  |       <div class="yellow-ballot ballot-element small"> | ||||||
|  |         {{ attendee.yellow }} | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  | export default { | ||||||
|  |   props: { | ||||||
|  |     attendees: { | ||||||
|  |       type: Array | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | @import "../styles/global.scss"; | ||||||
|  | @import "../styles/variables.scss"; | ||||||
|  | @import "../styles/media-queries.scss"; | ||||||
|  | .attendee-name { | ||||||
|  |   width: 60%; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ballot-element { | ||||||
|  |   font-size: 0.75rem; | ||||||
|  |   width: 45px; | ||||||
|  |   height: 45px; | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: center; | ||||||
|  |   align-items: center; | ||||||
|  |   font-weight: bold; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .attendees { | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: column; | ||||||
|  |   align-items: center; | ||||||
|  |   width: 65%; | ||||||
|  |   height: 100%; | ||||||
|  |   overflow-y: scroll; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .attendee { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: space-between; | ||||||
|  |   align-items: center; | ||||||
|  |   width: 100%; | ||||||
|  |   margin: 0 auto; | ||||||
|  | } | ||||||
|  | </style> | ||||||
							
								
								
									
										180
									
								
								src/ui/Chat.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								src/ui/Chat.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,180 @@ | |||||||
|  | <template> | ||||||
|  |   <div class="chat-container"> | ||||||
|  |     <hr /> | ||||||
|  |     <h2>Chat</h2> | ||||||
|  |     <div v-if="usernameSet" class="chat-inner-container"> | ||||||
|  |       <div class="history" ref="history"> | ||||||
|  |         <div | ||||||
|  |           v-for="(history, index) in chatHistory" | ||||||
|  |           :key="`${history.username}-${history.timestamp}-${index}`" | ||||||
|  |         > | ||||||
|  |           <span class="timestamp">[{{ getTime(history.timestamp) }}]</span> | ||||||
|  |           <span class="user-name">{{ history.username }}:</span> | ||||||
|  |           <span class="message">{{ history.message }}</span> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="input"> | ||||||
|  |         <input | ||||||
|  |           @keyup.enter="sendMessage" | ||||||
|  |           type="text" | ||||||
|  |           v-model="message" | ||||||
|  |           maxlength="30" | ||||||
|  |         /> | ||||||
|  |         <button @click="sendMessage">Send</button | ||||||
|  |         ><button @click="removeUsername">Logg ut</button> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     <div v-if="!usernameSet" class="username-dialog"> | ||||||
|  |       <input | ||||||
|  |         type="text" | ||||||
|  |         @keyup.enter="setUsername" | ||||||
|  |         v-model="temporaryUsername" | ||||||
|  |         title | ||||||
|  |       /> | ||||||
|  |       <button @click="setUsername">Lagre brukernavn</button> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  | export default { | ||||||
|  |   props: { | ||||||
|  |     usernameAllowed: { | ||||||
|  |       type: Boolean | ||||||
|  |     }, | ||||||
|  |     chatHistory: { | ||||||
|  |       type: Array | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   data() { | ||||||
|  |     return { | ||||||
|  |       message: "", | ||||||
|  |       temporaryUsername: null, | ||||||
|  |       username: null, | ||||||
|  |       usernameSet: false | ||||||
|  |     }; | ||||||
|  |   }, | ||||||
|  |   watch: { | ||||||
|  |     chatHistory: { | ||||||
|  |       handler() { | ||||||
|  |         setTimeout(() => { | ||||||
|  |           this.$refs.history.scrollTop = this.$refs.history.scrollHeight; | ||||||
|  |         }, 10); | ||||||
|  |       }, | ||||||
|  |       deep: true | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   mounted() { | ||||||
|  |     let username = window.localStorage.getItem("username"); | ||||||
|  |     if (!username) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     this.username = username; | ||||||
|  |     this.usernameSet = true; | ||||||
|  |     this.$emit("username", username); | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     pad: function(num) { | ||||||
|  |       if (num > 9) return num; | ||||||
|  |       return `0${num}`; | ||||||
|  |     }, | ||||||
|  |     getTime: function(timestamp) { | ||||||
|  |       let date = new Date(timestamp); | ||||||
|  |       return `${this.pad(date.getHours())}:${this.pad( | ||||||
|  |         date.getMinutes() | ||||||
|  |       )}:${this.pad(date.getSeconds())}`; | ||||||
|  |     }, | ||||||
|  |     sendMessage: function() { | ||||||
|  |       this.$emit("message", this.message); | ||||||
|  |       this.message = ""; | ||||||
|  |     }, | ||||||
|  |     removeUsername: function() { | ||||||
|  |       this.username = null; | ||||||
|  |       this.temporaryUsername = null; | ||||||
|  |       this.usernameSet = false; | ||||||
|  |       window.localStorage.removeItem("username"); | ||||||
|  |       this.$emit("username", null); | ||||||
|  |     }, | ||||||
|  |     setUsername: function() { | ||||||
|  |       if ( | ||||||
|  |         this.temporaryUsername.length > 3 && | ||||||
|  |         this.temporaryUsername.length < 30 | ||||||
|  |       ) { | ||||||
|  |         this.username = this.temporaryUsername; | ||||||
|  |         this.usernameSet = true; | ||||||
|  |         this.$emit("username", this.username); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | @import "../styles/media-queries.scss"; | ||||||
|  | h2 { | ||||||
|  |   text-align: center; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | hr { | ||||||
|  |   display: none; | ||||||
|  |  | ||||||
|  |   @include mobile { | ||||||
|  |     display: block; | ||||||
|  |     width: 80%; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | .chat-container { | ||||||
|  |   width: 50%; | ||||||
|  |  | ||||||
|  |   @include mobile { | ||||||
|  |     width: 100%; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | input { | ||||||
|  |   width: 80%; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .input { | ||||||
|  |   display: flex; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .chat-container, | ||||||
|  | .chat-inner-container { | ||||||
|  |   height: 100%; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .history { | ||||||
|  |   height: 75%; | ||||||
|  |   overflow-y: scroll; | ||||||
|  |  | ||||||
|  |   @include mobile { | ||||||
|  |     height: 300px; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .username-dialog { | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: row; | ||||||
|  |   justify-content: center; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | button { | ||||||
|  |   position: relative; | ||||||
|  |   display: inline-block; | ||||||
|  |   background: #b7debd; | ||||||
|  |   color: #333; | ||||||
|  |   padding: 10px 30px; | ||||||
|  |   border: 0; | ||||||
|  |   width: fit-content; | ||||||
|  |   font-size: 1rem; | ||||||
|  |   /* height: 1.5rem; */ | ||||||
|  |   /* max-height: 1.5rem; */ | ||||||
|  |   margin: 0 2px; | ||||||
|  |   cursor: pointer; | ||||||
|  |   font-weight: 500; | ||||||
|  |   transition: transform 0.5s ease; | ||||||
|  |   -webkit-font-smoothing: antialiased; | ||||||
|  |   touch-action: manipulation; | ||||||
|  | } | ||||||
|  | </style> | ||||||
| @@ -4,14 +4,17 @@ | |||||||
|     <div class="bought-container"> |     <div class="bought-container"> | ||||||
|       <div |       <div | ||||||
|         v-for="color in colors" |         v-for="color in colors" | ||||||
|         :class="color.name + '-container ' + color.name + '-ballot inner-bought-container ballot-element'" |         :class=" | ||||||
|  |           color.name + | ||||||
|  |             '-container ' + | ||||||
|  |             color.name + | ||||||
|  |             '-ballot inner-bought-container ballot-element' | ||||||
|  |         " | ||||||
|         :key="color.name" |         :key="color.name" | ||||||
|       > |       > | ||||||
|         <div class="number-container"> |         <div class="number-container"> | ||||||
|           <span class="color-total bought-number-span"> |           <span class="color-total bought-number-span"> | ||||||
|             {{ |             {{ color.total }} | ||||||
|             color.total |  | ||||||
|             }} |  | ||||||
|           </span> |           </span> | ||||||
|           <span>kjøpte</span> |           <span>kjøpte</span> | ||||||
|         </div> |         </div> | ||||||
| @@ -35,7 +38,6 @@ | |||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
| <script> | <script> | ||||||
|  |  | ||||||
| import { colorStatistics } from "@/api"; | import { colorStatistics } from "@/api"; | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
| @@ -123,6 +125,7 @@ export default { | |||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||||
|  | @import "../styles/global.scss"; | ||||||
| @import "../styles/variables.scss"; | @import "../styles/variables.scss"; | ||||||
| @import "../styles/media-queries.scss"; | @import "../styles/media-queries.scss"; | ||||||
|  |  | ||||||
| @@ -137,28 +140,6 @@ export default { | |||||||
|   width: 140px; |   width: 140px; | ||||||
|   height: 150px; |   height: 150px; | ||||||
|   margin: 20px 0; |   margin: 20px 0; | ||||||
|   -webkit-mask-image: url(/../../public/assets/images/lodd.svg); |  | ||||||
|   background-repeat: no-repeat; |  | ||||||
|   mask-image: url(/../../public/assets/images/lodd.svg); |  | ||||||
|   -webkit-mask-repeat: no-repeat; |  | ||||||
|   mask-repeat: no-repeat; |  | ||||||
|   color: #333333; |  | ||||||
|  |  | ||||||
|   &.green-ballot { |  | ||||||
|     background-color: $light-green; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   &.blue-ballot { |  | ||||||
|     background-color: $light-blue; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   &.yellow-ballot { |  | ||||||
|     background-color: $light-yellow; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   &.red-ballot { |  | ||||||
|     background-color: $light-red; |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| .number-container { | .number-container { | ||||||
|   | |||||||
							
								
								
									
										178
									
								
								src/ui/WinnerDraw.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								src/ui/WinnerDraw.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,178 @@ | |||||||
|  | <template> | ||||||
|  |   <div class="current-drawn-container"> | ||||||
|  |     <div class="current-draw" v-if="drawing"> | ||||||
|  |       <h2>TREKKER</h2> | ||||||
|  |       <div | ||||||
|  |         :class="currentColor + '-ballot'" | ||||||
|  |         class="ballot-element center-new-winner" | ||||||
|  |         :style="{ transform: 'rotate(' + getRotation() + 'deg)' }" | ||||||
|  |       > | ||||||
|  |         <span v-if="currentName && colorDone">{{ currentName }}</span> | ||||||
|  |       </div> | ||||||
|  |       <br /> | ||||||
|  |       <br /> | ||||||
|  |       <br /> | ||||||
|  |       <br /> | ||||||
|  |       <br /> | ||||||
|  |     </div> | ||||||
|  |     <div class="current-draw" v-if="drawingDone"> | ||||||
|  |       <h2>VINNER</h2> | ||||||
|  |       <div | ||||||
|  |         :class="currentColor + '-ballot'" | ||||||
|  |         class="ballot-element center-new-winner" | ||||||
|  |         :style="{ transform: 'rotate(' + getRotation() + 'deg)' }" | ||||||
|  |       > | ||||||
|  |         <span v-if="currentName && colorDone">{{ currentName }}</span> | ||||||
|  |       </div> | ||||||
|  |       <br /> | ||||||
|  |       <br /> | ||||||
|  |       <br /> | ||||||
|  |       <br /> | ||||||
|  |       <br /> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  | export default { | ||||||
|  |   props: { | ||||||
|  |     currentWinner: { | ||||||
|  |       type: Object | ||||||
|  |     }, | ||||||
|  |     currentWinnerDrawn: { | ||||||
|  |       type: Boolean | ||||||
|  |     }, | ||||||
|  |     attendees: { | ||||||
|  |       type: Array | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   data() { | ||||||
|  |     return { | ||||||
|  |       currentWinnerLocal: {}, | ||||||
|  |       winnerColor: null, | ||||||
|  |       currentColor: null, | ||||||
|  |       winnerName: null, | ||||||
|  |       currentName: null, | ||||||
|  |       colorRounds: 0, | ||||||
|  |       nameRounds: 0, | ||||||
|  |       colorTimeout: null, | ||||||
|  |       nameTimeout: null, | ||||||
|  |       colorDone: false, | ||||||
|  |       drawing: false, | ||||||
|  |       drawingDone: false, | ||||||
|  |       winnerQueue: [] | ||||||
|  |     }; | ||||||
|  |   }, | ||||||
|  |   watch: { | ||||||
|  |     currentWinner: function(currentWinner) { | ||||||
|  |       if (currentWinner == null) { | ||||||
|  |         this.drawingDone = false; | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       if (this.drawing) { | ||||||
|  |         this.winnerQueue.push(currentWinner); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       this.drawing = true; | ||||||
|  |       this.currentName = null; | ||||||
|  |       this.currentColor = null; | ||||||
|  |       this.nameRounds = 0; | ||||||
|  |       this.colorRounds = 0; | ||||||
|  |       this.colorDone = false; | ||||||
|  |       this.currentWinnerLocal = currentWinner; | ||||||
|  |       this.drawColor(this.currentWinnerLocal.color); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     drawName: function(winnerName) { | ||||||
|  |       if (this.nameRounds == 100) { | ||||||
|  |         clearTimeout(this.nameTimeout); | ||||||
|  |         this.currentName = winnerName; | ||||||
|  |         if (this.winnerQueue.length > 0) { | ||||||
|  |           this.currentWinnerLocal = this.winnerQueue.shift(); | ||||||
|  |           this.drawing = true; | ||||||
|  |           this.nameRounds = 0; | ||||||
|  |           this.colorRounds = 0; | ||||||
|  |           this.colorDone = false; | ||||||
|  |           this.drawColor(this.currentWinnerLocal.color); | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |         this.drawing = false; | ||||||
|  |         this.drawingDone = true; | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       this.currentName = this.attendees[ | ||||||
|  |         this.nameRounds % this.attendees.length | ||||||
|  |       ].name; | ||||||
|  |       this.nameRounds += 1; | ||||||
|  |       clearTimeout(this.nameTimeout); | ||||||
|  |       this.nameTimeout = setTimeout(() => { | ||||||
|  |         this.drawName(winnerName); | ||||||
|  |       }, 50); | ||||||
|  |     }, | ||||||
|  |     drawColor: function(winnerColor) { | ||||||
|  |       this.drawingDone = false; | ||||||
|  |       if (this.colorRounds == 100) { | ||||||
|  |         this.currentColor = winnerColor; | ||||||
|  |         this.colorDone = true; | ||||||
|  |         this.drawName(this.currentWinnerLocal.name); | ||||||
|  |  | ||||||
|  |         clearTimeout(this.colorTimeout); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       this.currentColor = this.getColor(this.colorRounds % 4); | ||||||
|  |       this.colorRounds += 1; | ||||||
|  |  | ||||||
|  |       clearTimeout(this.colorTimeout); | ||||||
|  |       this.colorTimeout = setTimeout(() => { | ||||||
|  |         this.drawColor(winnerColor); | ||||||
|  |       }, 50); | ||||||
|  |     }, | ||||||
|  |     getRotation: function() { | ||||||
|  |       if (this.colorDone) { | ||||||
|  |         return 0; | ||||||
|  |       } | ||||||
|  |       let num = Math.floor(Math.random() * 15); | ||||||
|  |       let neg = Math.floor(Math.random() * 2); | ||||||
|  |       return neg == 0 ? -num : num; | ||||||
|  |     }, | ||||||
|  |     getColor: function(number) { | ||||||
|  |       switch (number) { | ||||||
|  |         case 0: | ||||||
|  |           return "red"; | ||||||
|  |         case 1: | ||||||
|  |           return "blue"; | ||||||
|  |         case 2: | ||||||
|  |           return "green"; | ||||||
|  |         case 3: | ||||||
|  |           return "yellow"; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | @import "../styles/global.scss"; | ||||||
|  | @import "../styles/variables.scss"; | ||||||
|  | @import "../styles/media-queries.scss"; | ||||||
|  |  | ||||||
|  | h2 { | ||||||
|  |   text-align: center; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .current-drawn-container { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: center; | ||||||
|  |   align-items: center; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ballot-element { | ||||||
|  |   width: 140px; | ||||||
|  |   height: 140px; | ||||||
|  |   font-size: 1.2rem; | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: center; | ||||||
|  |   align-items: center; | ||||||
|  | } | ||||||
|  | </style> | ||||||
							
								
								
									
										48
									
								
								src/ui/Winners.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/ui/Winners.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | <template> | ||||||
|  |   <div> | ||||||
|  |     <h2 v-if="winners.length > 0">Vinnere</h2> | ||||||
|  |     <div class="winners" v-if="winners.length > 0"> | ||||||
|  |       <div class="winner" v-for="(winner, index) in winners" :key="index"> | ||||||
|  |         <div :class="winner.color + '-ballot'" class="ballot-element"> | ||||||
|  |           {{ winner.name }} | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  | export default { | ||||||
|  |   props: { | ||||||
|  |     winners: { | ||||||
|  |       type: Array | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | @import "../styles/global.scss"; | ||||||
|  | @import "../styles/variables.scss"; | ||||||
|  | @import "../styles/media-queries.scss"; | ||||||
|  |  | ||||||
|  | h2 { | ||||||
|  |   text-align: center; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .winners { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: space-around; | ||||||
|  |   align-items: center; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ballot-element { | ||||||
|  |   font-size: 1rem; | ||||||
|  |   width: 145px; | ||||||
|  |   height: 145px; | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: center; | ||||||
|  |   align-items: center; | ||||||
|  |   font-weight: bold; | ||||||
|  | } | ||||||
|  | </style> | ||||||
		Reference in New Issue
	
	Block a user