Added sse and a new screen

This commit is contained in:
2023-06-17 18:17:36 +03:00
parent 6b161f2e2c
commit 3cdd827708
21 changed files with 525 additions and 312 deletions

View File

@@ -1,42 +1,19 @@
<template>
<h1>🎱 Флекспатрульное Бинго 🎱</h1>
<Login id="login" @loggedIn="login" v-show="!loggedIn" />
<Game id="game" v-if="loggedIn" :score="score" />
<a class="source" href="https://github.com/anatolykopyl/vk-bingo">Исходный код</a>
<h1>Флекспатруль мультиплеер</h1>
<router-view />
<a
class="source"
href="https://github.com/anatolykopyl/vk-bingo"
target="_blank"
>
Исходный код
</a>
</template>
<script>
import axios from 'axios'
import Login from './components/Login.vue'
import Game from './components/Game.vue'
<script setup>
export default {
name: 'App',
components: {
Login,
Game
},
data() {
return {
loggedIn: null,
score: {
"right": 0,
"wrong": 0
}
}
},
methods: {
login: function(success) {
this.loggedIn = success
axios
.get(process.env.VUE_APP_BACKEND + '/score')
.then(response => {
if (Object.keys(response.data).length !== 0)
this.score = response.data
})
}
}
}
</script>
<style>

View File

@@ -1,154 +0,0 @@
<template>
<div>
<div class="card" v-if="card !== null" v-on:click="nextCard()" v-bind:class="{clickable: showResult}">
<img class="meme" v-bind:src="card.image">
<h2>Кто скинул этот мем?</h2>
<div class="interactive">
<transition name="fade-answers">
<List v-if="selectedAnswer === null"
:options="options" @selectedAnswer="selectAnswer" />
</transition>
<transition name="spin-result">
<Result v-if="showResult"
:name="card.name" :selectedName="selectedAnswer" :date="card.date" :correct="correctAnswer" />
</transition>
</div>
</div>
<Score v-if="card !== null" :score="score" />
<square-loader v-if="card === null" :color="'#f3f3f3'" class="loader" />
<Stats />
</div>
</template>
<script>
import axios from 'axios'
import List from './List.vue'
import Result from './Result.vue'
import Score from './Score.vue'
import Stats from './Stats.vue'
import SquareLoader from 'vue-spinner/src/SquareLoader.vue'
export default {
name: 'Game',
components: {
List,
Result,
Score,
Stats,
SquareLoader
},
props: {
score: Object
},
data() {
return {
options: null,
card: null,
correctAnswer: null, // True or False
selectedAnswer: null, // Чье-то имя
showResult: false
}
},
methods: {
getCard: function() {
this.correctAnswer = null
this.selectedAnswer = null
this.showResult = false
axios
.get(process.env.VUE_APP_BACKEND + '/card')
.then(response => {
this.card = response.data
})
},
nextCard: function() {
if (this.showResult) {
this.card = null
this.getCard()
}
},
selectAnswer: function(selection) {
this.selectedAnswer = selection
let innerThis = this
setTimeout(function() {
innerThis.showResult = true
if (innerThis.correctAnswer) {
innerThis.score.right++
} else {
innerThis.score.wrong++
}
}, 805)
axios
.post(process.env.VUE_APP_BACKEND + '/answer', {
'data': {
'id': this.card._id,
'name': this.selectedAnswer
}
})
.then((response) => {
this.correctAnswer = response.data.correct
this.card = response.data.card
})
}
},
mounted() {
this.getCard()
axios
.get(process.env.VUE_APP_BACKEND + '/options')
.then(response => (this.options = response.data))
}
}
</script>
<style scoped>
.card {
width: 450px;
padding: 18px;
border-radius: 17px;
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.6);
background-color: #121212;
margin: auto;
}
.meme {
width: 100%;
border-radius: 8px;
}
.clickable {
cursor: pointer;
}
.interactive {
position: relative;
-webkit-perspective: 900000px;
perspective: 900000px;
}
.fade-answers-leave-active {
transition: all 0.8s ease;
}
.fade-answers-leave-to {
opacity: 0;
transform: scale(0.3);
}
.spin-result-enter-active {
transition: all 2s ease;
}
.spin-result-enter-from {
transform: scale(0.2);
transform: rotateY(120deg);
}
.loader {
margin-top: 100px;
}
@media screen and (max-width: 520px) {
.card {
width: 94%;
padding: 3%;
}
}
</style>

View File

@@ -1,80 +0,0 @@
<template>
<div>
<h1>Авторизация:</h1>
<p>{{ question }}</p>
<input v-model="answer"><br>
<button v-on:click="login" v-bind:class="{wrong: loggedIn === false}">Ввод</button>
</div>
</template>
<script>
import axios from 'axios'
axios.defaults.withCredentials = true
export default {
name: 'Login',
data() {
return {
question: null,
answer: null,
loggedIn: null
}
},
methods: {
login: function() {
axios
.post(process.env.VUE_APP_BACKEND + '/auth', {
"pass": this.answer,
})
.then(response => {
this.loggedIn = response.status == "200"
this.$emit('loggedIn', this.loggedIn)
})
}
},
mounted() {
this.question = process.env.VUE_APP_QUESTION
this.login()
}
}
</script>
<style scoped>
div {
background-color: #121212;
width: 400px;
margin: auto;
border-radius: 18px;
padding: 40px 10px;
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.6);
}
input {
font-size: 1em;
text-align: center;
padding: 5px 8px;
margin-bottom: 1em;
border-radius: 6px;
border: none;
width: 20ch;
}
button {
color: white;
font-size: 1em;
box-sizing: content-box;
background-color: #5a5a5a;
border-radius: 6px;
border: none;
width: 20ch;
padding: 5px 8px;
cursor: pointer;
}
@media only screen and (max-width: 520px) {
div {
width: 100%;
padding: 40px 0;
}
}
</style>

View File

@@ -0,0 +1,17 @@
// import useStore from '../store'
export default () => {
// const store = useStore()
const evtSource = new EventSource(`${process.env.VUE_APP_BACKEND}/stream`);
function addAnswerListener(handler) {
evtSource.addEventListener('answer', (event) => handler(JSON.parse(event.data)))
}
function addUserlistListener(handler) {
evtSource.addEventListener('userlist', (event) => handler(JSON.parse(event.data)))
}
return { addAnswerListener, addUserlistListener }
}

View File

@@ -1,4 +1,8 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App).mount('#app')
const app = createApp(App)
app.use(router)
app.mount('#app')

15
frontend/src/router.js Normal file
View File

@@ -0,0 +1,15 @@
import { createRouter, createWebHistory } from 'vue-router'
import Game from './views/Game/Index.vue'
import Login from './views/Login/Index.vue'
import Screen from './views/Screen/Index.vue'
const routes = [
{ path: '/', component: Game },
{ path: '/login', component: Login },
{ path: '/screen', component: Screen }
]
export default createRouter({
history: createWebHistory(),
routes,
})

7
frontend/src/store.js Normal file
View File

@@ -0,0 +1,7 @@
import { defineStore } from 'pinia'
export default defineStore('store', {
state: () => ({
username: null
}),
})

View File

@@ -0,0 +1,18 @@
<template>
<div>
<button @click="endGame">
Закончить игру
</button>
</div>
</template>
<script setup>
import axios from 'axios'
import { useRouter } from 'vue-router';
const router = useRouter()
function endGame() {
axios.post(process.env.VUE_APP_BACKEND + '/end')
router.push('/login')
}
</script>

View File

@@ -0,0 +1,153 @@
<template>
<div>
<div
class="card"
v-if="card"
>
<img
class="meme"
:src="card.image"
>
<h2>Кто скинул этот мем?</h2>
<div class="interactive">
<transition name="fade-answers">
<List
v-if="!selectedAnswer"
:options="options"
@selectedAnswer="selectAnswer"
/>
</transition>
<transition name="spin-result">
<Result
v-if="showResult"
:name="card.name"
:selectedName="selectedAnswer"
:date="card.date"
:correct="correctAnswer"
/>
</transition>
</div>
</div>
<Score
v-if="card"
:score="score"
/>
<EndGame />
<square-loader
v-if="!card"
:color="'#f3f3f3'"
class="loader"
/>
<Stats />
</div>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue'
import axios from 'axios'
import List from './List.vue'
import Result from './Result.vue'
import Score from './Score.vue'
import Stats from './Stats.vue'
import EndGame from './EndGame.vue'
import SquareLoader from 'vue-spinner/src/SquareLoader.vue'
const options = ref()
const card = ref()
const correctAnswer = ref()
const selectedAnswer = ref()
const showResult = ref()
const score = reactive({
"right": 0,
"wrong": 0
})
async function getCard() {
correctAnswer.value = null
selectedAnswer.value = null
showResult.value = false
const response = await axios.get(process.env.VUE_APP_BACKEND + '/card')
card.value = response.data
}
async function selectAnswer(selection) {
selectedAnswer.value = selection
// setTimeout(function() {
// showResult.value = true
// if (correctAnswer.value) {
// score.right++
// } else {
// score.wrong++
// }
// }, 805)
await axios.post(process.env.VUE_APP_BACKEND + '/answer', {
'data': {
'id': card.value._id,
'name': selectedAnswer.value
}
})
}
onMounted(async () => {
getCard()
const response = await axios.get(process.env.VUE_APP_BACKEND + '/options')
options.value = response.data
})
</script>
<style scoped>
.card {
width: 450px;
padding: 18px;
border-radius: 17px;
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.6);
background-color: #121212;
margin: auto;
}
.meme {
width: 100%;
border-radius: 8px;
}
.clickable {
cursor: pointer;
}
.interactive {
position: relative;
-webkit-perspective: 900000px;
perspective: 900000px;
}
.fade-answers-leave-active {
transition: all 0.8s ease;
}
.fade-answers-leave-to {
opacity: 0;
transform: scale(0.3);
}
.spin-result-enter-active {
transition: all 2s ease;
}
.spin-result-enter-from {
transform: scale(0.2);
transform: rotateY(120deg);
}
.loader {
margin-top: 100px;
}
@media screen and (max-width: 520px) {
.card {
width: 94%;
padding: 3%;
}
}
</style>

View File

@@ -0,0 +1,101 @@
<template>
<div>
<h1>Авторизация:</h1>
<p>{{ question }}</p>
<input
placeholder="Ответ"
v-model="answer"
>
<br>
<input
placeholder="Ваше имя"
v-model="username"
>
<br>
<button
@click="login"
:class="{
wrong: loggedIn === false
}"
>
Ввод
</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import useStore from '../../store'
import axios from 'axios'
axios.defaults.withCredentials = true
const question = process.env.VUE_APP_QUESTION
const router = useRouter()
const store = useStore()
const answer = ref()
const loggedIn = ref()
const username = ref()
async function login() {
store.username = username.value
await axios
.post(process.env.VUE_APP_BACKEND + '/auth', {
"pass": answer.value,
})
.then(response => {
loggedIn.value = response.status == "200"
router.push('/')
})
axios
.post(process.env.VUE_APP_BACKEND + '/connect', {
'data': {
'username': username.value
}
})
}
</script>
<style scoped>
div {
background-color: #121212;
width: 400px;
margin: auto;
border-radius: 18px;
padding: 40px 10px;
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.6);
}
input {
font-size: 1em;
text-align: center;
padding: 5px 8px;
margin-bottom: 1em;
border-radius: 6px;
border: none;
width: 20ch;
}
button {
color: white;
font-size: 1em;
box-sizing: content-box;
background-color: #5a5a5a;
border-radius: 6px;
border: none;
width: 20ch;
padding: 5px 8px;
cursor: pointer;
}
@media only screen and (max-width: 520px) {
div {
width: 100%;
padding: 40px 0;
}
}
</style>

View File

@@ -0,0 +1,36 @@
<template>
<div v-if="card">
<img
:src="card.image"
>
<div>
{{ users }}
</div>
</div>
</template>
<script setup>
import axios from 'axios'
import { ref, onMounted } from 'vue'
import useServerEvents from '../../composables/useServerEvents';
const { addAnswerListener, addUserlistListener } = useServerEvents()
addAnswerListener(console.log)
const card = ref()
const users = ref([])
async function getCard() {
const response = await axios.get(process.env.VUE_APP_BACKEND + '/card')
card.value = response.data
}
addUserlistListener((data) => {
users.value = data
})
onMounted(() => {
getCard()
})
</script>