v0.2.0
This commit is contained in:
parent
cd5f4ad91a
commit
498a8253fd
9
.hmrc
9
.hmrc
@ -2,7 +2,7 @@
|
|||||||
"path": "G:\\Other\\Development\\Projects\\[ideas]\\domino-client",
|
"path": "G:\\Other\\Development\\Projects\\[ideas]\\domino-client",
|
||||||
"name": "domino-client",
|
"name": "domino-client",
|
||||||
"initialVersion": "0.1.4",
|
"initialVersion": "0.1.4",
|
||||||
"version": "0.1.12",
|
"version": "0.2.0",
|
||||||
"docker": {
|
"docker": {
|
||||||
"useRegistry": true,
|
"useRegistry": true,
|
||||||
"registry": "192.168.1.115:5000",
|
"registry": "192.168.1.115:5000",
|
||||||
@ -84,7 +84,7 @@
|
|||||||
},
|
},
|
||||||
"_backup": {
|
"_backup": {
|
||||||
"name": "domino-client",
|
"name": "domino-client",
|
||||||
"version": "0.1.11",
|
"version": "0.1.12",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -97,8 +97,8 @@
|
|||||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||||
"format": "prettier --write src/",
|
"format": "prettier --write src/",
|
||||||
"docker-build": "docker build -t 192.168.1.115:5000/arhuako/domino-client:latest .",
|
"docker-build": "docker build -t 192.168.1.115:5000/arhuako/domino-client:latest .",
|
||||||
"docker-tag": "docker tag 192.168.1.115:5000/arhuako/domino-client:latest 192.168.1.115:5000/arhuako/domino-client:0.1.11",
|
"docker-tag": "docker tag 192.168.1.115:5000/arhuako/domino-client:latest 192.168.1.115:5000/arhuako/domino-client:0.1.12",
|
||||||
"docker-push": "docker push 192.168.1.115:5000/arhuako/domino-client:latest && docker push 192.168.1.115:5000/arhuako/domino-client:0.1.11",
|
"docker-push": "docker push 192.168.1.115:5000/arhuako/domino-client:latest && docker push 192.168.1.115:5000/arhuako/domino-client:0.1.12",
|
||||||
"publish": "npm run docker-build && npm run docker-tag && npm run docker-push",
|
"publish": "npm run docker-build && npm run docker-tag && npm run docker-push",
|
||||||
"serve": "npm run build-only && http-server ./dist -c-1 -s ",
|
"serve": "npm run build-only && http-server ./dist -c-1 -s ",
|
||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
@ -109,6 +109,7 @@
|
|||||||
"bulma": "^1.0.1",
|
"bulma": "^1.0.1",
|
||||||
"colorette": "^2.0.20",
|
"colorette": "^2.0.20",
|
||||||
"dayjs": "^1.11.11",
|
"dayjs": "^1.11.11",
|
||||||
|
"gsap": "^3.12.5",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"pino": "^9.2.0",
|
"pino": "^9.2.0",
|
||||||
"pixi-actions": "^1.1.11",
|
"pixi-actions": "^1.1.11",
|
||||||
|
28
CHANGELOG.md
28
CHANGELOG.md
@ -1,18 +1,41 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
## 0.1.12 - 2024-07-22
|
## 0.2.0 - 2024-07-25
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- session expiration and renew handling
|
||||||
|
- Allow unique session per user
|
||||||
|
- Socket.io room management
|
||||||
|
- Game: added turn waiting timer
|
||||||
|
- Game: added timed button on game summaries
|
||||||
|
- Added new header
|
||||||
|
- New sound library (Howlerjs)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- App data persistance
|
||||||
|
- Some other Fixes
|
||||||
|
|
||||||
|
## 0.1.12 - 2024-07-22
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
- I18n translations
|
- I18n translations
|
||||||
- Win conditions
|
- Win conditions
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Multiplayer join button not accessible
|
- Multiplayer join button not accessible
|
||||||
|
|
||||||
## 0.1.10 - 2024-07-20
|
## 0.1.10 - 2024-07-20
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Updater
|
- Updater
|
||||||
- Refresh authentication when expires
|
- Refresh authentication when expires
|
||||||
- Match summary page phase 1
|
- Match summary page phase 1
|
||||||
@ -26,11 +49,14 @@ All notable changes to this project will be documented in this file.
|
|||||||
## 0.1.6 - 2024-07-17
|
## 0.1.6 - 2024-07-17
|
||||||
|
|
||||||
## 0.1.5 - 2024-07-17
|
## 0.1.5 - 2024-07-17
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Initial commit
|
- Initial commit
|
||||||
- Match page back button
|
- Match page back button
|
||||||
- Team play
|
- Team play
|
||||||
- Movement synchronized netween clients and AI players
|
- Movement synchronized netween clients and AI players
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Button statuses
|
- Button statuses
|
||||||
|
@ -7,9 +7,7 @@
|
|||||||
<title>XXX App</title>
|
<title>XXX App</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main class="container">
|
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
</main>
|
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
23
package-lock.json
generated
23
package-lock.json
generated
@ -1,18 +1,20 @@
|
|||||||
{
|
{
|
||||||
"name": "domino-client",
|
"name": "domino-client",
|
||||||
"version": "0.1.8",
|
"version": "0.2.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "domino-client",
|
"name": "domino-client",
|
||||||
"version": "0.1.8",
|
"version": "0.2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@pixi/sound": "^6.0.0",
|
"@pixi/sound": "^6.0.0",
|
||||||
"@tauri-apps/api": "^1.6.0",
|
"@tauri-apps/api": "^1.6.0",
|
||||||
"bulma": "^1.0.1",
|
"bulma": "^1.0.1",
|
||||||
"colorette": "^2.0.20",
|
"colorette": "^2.0.20",
|
||||||
"dayjs": "^1.11.11",
|
"dayjs": "^1.11.11",
|
||||||
|
"gsap": "^3.12.5",
|
||||||
|
"howler": "^2.2.4",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"pino": "^9.2.0",
|
"pino": "^9.2.0",
|
||||||
"pixi-actions": "^1.1.11",
|
"pixi-actions": "^1.1.11",
|
||||||
@ -28,6 +30,7 @@
|
|||||||
"@rushstack/eslint-patch": "^1.8.0",
|
"@rushstack/eslint-patch": "^1.8.0",
|
||||||
"@tauri-apps/cli": "^1.6.0",
|
"@tauri-apps/cli": "^1.6.0",
|
||||||
"@tsconfig/node20": "^20.1.4",
|
"@tsconfig/node20": "^20.1.4",
|
||||||
|
"@types/howler": "^2.2.11",
|
||||||
"@types/jsdom": "^21.1.7",
|
"@types/jsdom": "^21.1.7",
|
||||||
"@types/node": "^20.14.5",
|
"@types/node": "^20.14.5",
|
||||||
"@vitejs/plugin-vue": "^5.0.5",
|
"@vitejs/plugin-vue": "^5.0.5",
|
||||||
@ -1194,6 +1197,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/gradient-parser/-/gradient-parser-0.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/gradient-parser/-/gradient-parser-0.1.5.tgz",
|
||||||
"integrity": "sha512-r7K3NkJz3A95WkVVmjs0NcchhHstC2C/VIYNX4JC6tieviUNo774FFeOHjThr3Vw/WCeMP9kAT77MKbIRlO/4w=="
|
"integrity": "sha512-r7K3NkJz3A95WkVVmjs0NcchhHstC2C/VIYNX4JC6tieviUNo774FFeOHjThr3Vw/WCeMP9kAT77MKbIRlO/4w=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/howler": {
|
||||||
|
"version": "2.2.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/howler/-/howler-2.2.11.tgz",
|
||||||
|
"integrity": "sha512-7aBoUL6RbSIrqKnpEgfa1wSNUBK06mn08siP2QI0zYk7MXfEJAaORc4tohamQYqCqVESoDyRWSdQn2BOKWj2Qw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/jsdom": {
|
"node_modules/@types/jsdom": {
|
||||||
"version": "21.1.7",
|
"version": "21.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz",
|
||||||
@ -3182,6 +3191,11 @@
|
|||||||
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
|
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/gsap": {
|
||||||
|
"version": "3.12.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/gsap/-/gsap-3.12.5.tgz",
|
||||||
|
"integrity": "sha512-srBfnk4n+Oe/ZnMIOXt3gT605BX9x5+rh/prT2F1SsNJsU1XuMiP0E2aptW481OnonOGACZWBqseH5Z7csHxhQ=="
|
||||||
|
},
|
||||||
"node_modules/has-flag": {
|
"node_modules/has-flag": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
@ -3248,6 +3262,11 @@
|
|||||||
"he": "bin/he"
|
"he": "bin/he"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/howler": {
|
||||||
|
"version": "2.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/howler/-/howler-2.2.4.tgz",
|
||||||
|
"integrity": "sha512-iARIBPgcQrwtEr+tALF+rapJ8qSc+Set2GJQl7xT1MQzWaVkFebdJhR3alVlSiUf5U7nAANKuj3aWpwerocD5w=="
|
||||||
|
},
|
||||||
"node_modules/html-encoding-sniffer": {
|
"node_modules/html-encoding-sniffer": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "domino-client",
|
"name": "domino-client",
|
||||||
"version": "0.1.12",
|
"version": "0.2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -13,8 +13,8 @@
|
|||||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||||
"format": "prettier --write src/",
|
"format": "prettier --write src/",
|
||||||
"docker-build": "docker build -t 192.168.1.115:5000/arhuako/domino-client:latest .",
|
"docker-build": "docker build -t 192.168.1.115:5000/arhuako/domino-client:latest .",
|
||||||
"docker-tag": "docker tag 192.168.1.115:5000/arhuako/domino-client:latest 192.168.1.115:5000/arhuako/domino-client:0.1.12",
|
"docker-tag": "docker tag 192.168.1.115:5000/arhuako/domino-client:latest 192.168.1.115:5000/arhuako/domino-client:0.2.0",
|
||||||
"docker-push": "docker push 192.168.1.115:5000/arhuako/domino-client:latest && docker push 192.168.1.115:5000/arhuako/domino-client:0.1.12",
|
"docker-push": "docker push 192.168.1.115:5000/arhuako/domino-client:latest && docker push 192.168.1.115:5000/arhuako/domino-client:0.2.0",
|
||||||
"publish": "npm run docker-build && npm run docker-tag && npm run docker-push",
|
"publish": "npm run docker-build && npm run docker-tag && npm run docker-push",
|
||||||
"serve": "npm run build-only && http-server ./dist -c-1 -s ",
|
"serve": "npm run build-only && http-server ./dist -c-1 -s ",
|
||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
@ -25,6 +25,8 @@
|
|||||||
"bulma": "^1.0.1",
|
"bulma": "^1.0.1",
|
||||||
"colorette": "^2.0.20",
|
"colorette": "^2.0.20",
|
||||||
"dayjs": "^1.11.11",
|
"dayjs": "^1.11.11",
|
||||||
|
"gsap": "^3.12.5",
|
||||||
|
"howler": "^2.2.4",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"pino": "^9.2.0",
|
"pino": "^9.2.0",
|
||||||
"pixi-actions": "^1.1.11",
|
"pixi-actions": "^1.1.11",
|
||||||
@ -40,6 +42,7 @@
|
|||||||
"@rushstack/eslint-patch": "^1.8.0",
|
"@rushstack/eslint-patch": "^1.8.0",
|
||||||
"@tauri-apps/cli": "^1.6.0",
|
"@tauri-apps/cli": "^1.6.0",
|
||||||
"@tsconfig/node20": "^20.1.4",
|
"@tsconfig/node20": "^20.1.4",
|
||||||
|
"@types/howler": "^2.2.11",
|
||||||
"@types/jsdom": "^21.1.7",
|
"@types/jsdom": "^21.1.7",
|
||||||
"@types/node": "^20.14.5",
|
"@types/node": "^20.14.5",
|
||||||
"@vitejs/plugin-vue": "^5.0.5",
|
"@vitejs/plugin-vue": "^5.0.5",
|
||||||
|
@ -1,5 +1,19 @@
|
|||||||
<h1>Changelog</h1>
|
<h1>Changelog</h1>
|
||||||
<p>All notable changes to this project will be documented in this file.</p>
|
<p>All notable changes to this project will be documented in this file.</p>
|
||||||
|
<h2>0.2.0 - 2024-07-24</h2>
|
||||||
|
<h3>Added</h3>
|
||||||
|
<ul>
|
||||||
|
<li>session expiration and renew handling</li>
|
||||||
|
<li>Allow unique session per user</li>
|
||||||
|
<li>Socket.io room management</li>
|
||||||
|
<li>Game: added turn waiting timer</li>
|
||||||
|
<li>Game: added timed button on game summaries</li>
|
||||||
|
<li>Added new header</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Fixed</h3>
|
||||||
|
<ul>
|
||||||
|
<li>App data persistance</li>
|
||||||
|
</ul>
|
||||||
<h2>0.1.12 - 2024-07-22</h2>
|
<h2>0.1.12 - 2024-07-22</h2>
|
||||||
<h3>Added</h3>
|
<h3>Added</h3>
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "domino-client",
|
"productName": "domino-client",
|
||||||
"version": "0.1.11"
|
"version": "0.2.0"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
@ -16,16 +16,10 @@
|
|||||||
"all": true
|
"all": true
|
||||||
},
|
},
|
||||||
"fs": {
|
"fs": {
|
||||||
"copyFile": true,
|
"scope": {
|
||||||
"createDir": true,
|
"allow": ["$APPDATA/data/**", "$APPDATA/data/*", "$APPDATA/data", "$APPDATA/*"],
|
||||||
"exists": true,
|
"requireLiteralLeadingDot": false
|
||||||
"readDir": true,
|
},
|
||||||
"readFile": true,
|
|
||||||
"removeDir": true,
|
|
||||||
"removeFile": true,
|
|
||||||
"renameFile": true,
|
|
||||||
"scope": ["$APPDATA/*", "$APP/*"],
|
|
||||||
"writeFile": true,
|
|
||||||
"all": true
|
"all": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -78,7 +72,7 @@
|
|||||||
"fullscreen": false,
|
"fullscreen": false,
|
||||||
"height": 720,
|
"height": 720,
|
||||||
"resizable": true,
|
"resizable": true,
|
||||||
"title": "Domino v0.1.11",
|
"title": "Domino v0.2.0",
|
||||||
"width": 1280,
|
"width": 1280,
|
||||||
"minHeight": 720,
|
"minHeight": 720,
|
||||||
"minWidth": 1280,
|
"minWidth": 1280,
|
||||||
|
63
src/App.vue
63
src/App.vue
@ -1,60 +1,23 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { inject, onMounted, onUnmounted } from 'vue'
|
import { inject, onBeforeMount } from 'vue'
|
||||||
import { RouterView } from 'vue-router'
|
import { RouterView, useRouter } from 'vue-router'
|
||||||
import type { AuthenticationService } from './services/AuthenticationService'
|
import type { AuthenticationService } from './services/AuthenticationService'
|
||||||
import { useEventBusStore } from './stores/eventBus'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { sound } from '@pixi/sound'
|
import { storeToRefs } from 'pinia'
|
||||||
|
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
const { isLoggedIn } = storeToRefs(authStore)
|
||||||
const auth: AuthenticationService = inject<AuthenticationService>('auth') as AuthenticationService
|
const auth: AuthenticationService = inject<AuthenticationService>('auth') as AuthenticationService
|
||||||
auth.fromStorage()
|
const router = useRouter()
|
||||||
const eventBus = useEventBusStore()
|
|
||||||
|
|
||||||
const handleBeforeUnload = (evt: any) => {
|
async function checkLoggedIn() {
|
||||||
// evt.preventDefault()
|
await auth.fromStorage()
|
||||||
// const isGame = location.pathname === '/game' ? true : ''
|
if (isLoggedIn.value) {
|
||||||
// console.log('isGame :>> ', isGame)
|
router.push('home')
|
||||||
// evt.returnValue = isGame
|
}
|
||||||
// eventBus.publish('window-before-unload')
|
|
||||||
// console.log('location.href :>> ', location.pathname)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// document.addEventListener('visibilitychange', () => {
|
onBeforeMount(checkLoggedIn)
|
||||||
// console.log('visibilitychange')
|
|
||||||
// let playingOnHide = false
|
|
||||||
// if (document.hidden) {
|
|
||||||
// playingOnHide = true
|
|
||||||
// sound.pauseAll()
|
|
||||||
// } else {
|
|
||||||
// // Page became visible! Resume playing if audio was "playing on hide"
|
|
||||||
// if (playingOnHide) {
|
|
||||||
// sound.resumeAll()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
// const soundContextResume = () => {
|
|
||||||
// const context = sound.context.audioContext
|
|
||||||
// if (context.state === 'suspended' || context.state === 'interrupted') {
|
|
||||||
// context.resume()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// document.addEventListener('click', function (event) {
|
|
||||||
// console.log('click document :>> ', event)
|
|
||||||
// console.log('screen :>> ', screen)
|
|
||||||
// // if (event.target instanceof HTMLButtonElement) {
|
|
||||||
// // sound.play('click')
|
|
||||||
// // }
|
|
||||||
// })
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
window.addEventListener('beforeunload', handleBeforeUnload)
|
|
||||||
// window.addEventListener('focus', soundContextResume)
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
window.removeEventListener('beforeunload', handleBeforeUnload)
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template><RouterView /></template>
|
<template><RouterView /></template>
|
||||||
|
BIN
src/assets/images/profile-stub.png
Normal file
BIN
src/assets/images/profile-stub.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
@ -2,5 +2,6 @@
|
|||||||
|
|
||||||
html {
|
html {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
overflow: auto;
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
BIN
src/assets/sounds/button_click.wav
Normal file
BIN
src/assets/sounds/button_click.wav
Normal file
Binary file not shown.
BIN
src/assets/sounds/ding-1.wav
Normal file
BIN
src/assets/sounds/ding-1.wav
Normal file
Binary file not shown.
7
src/common/errors/ErrorBase.ts
Normal file
7
src/common/errors/ErrorBase.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export class ErrorBase extends Error {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = this.constructor.name;
|
||||||
|
this.stack = (new Error()).stack;
|
||||||
|
}
|
||||||
|
}
|
7
src/common/errors/SessionExpiredError.ts
Normal file
7
src/common/errors/SessionExpiredError.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { ErrorBase } from './ErrorBase'
|
||||||
|
|
||||||
|
export class SessionExpiredError extends ErrorBase {
|
||||||
|
constructor() {
|
||||||
|
super('Session expired error')
|
||||||
|
}
|
||||||
|
}
|
@ -44,7 +44,7 @@ export function createButton(options: {
|
|||||||
parent?: Container
|
parent?: Container
|
||||||
}): Container {
|
}): Container {
|
||||||
const { text: textStr, dimension, action, parent } = options
|
const { text: textStr, dimension, action, parent } = options
|
||||||
const { x, y, width, height = 25 } = dimension
|
const { x = 0, y = 0, width = 0, height = 25 } = dimension
|
||||||
const rectangle = new Graphics().roundRect(x, y, width + 4, height + 4, 5).fill(0xffff00)
|
const rectangle = new Graphics().roundRect(x, y, width + 4, height + 4, 5).fill(0xffff00)
|
||||||
const text = new Text({
|
const text = new Text({
|
||||||
text: textStr,
|
text: textStr,
|
||||||
@ -87,8 +87,9 @@ export function createButton(options: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createCrosshair(container: Container, color: number = 0xff0000, d: Dimension) {
|
export function createCrosshair(container: Container, color: number = 0xff0000, d: Dimension) {
|
||||||
const verticalLine = new Graphics().moveTo(d.x, 0).lineTo(d.x, d.height).stroke(color)
|
const { x = 0, y = 0, width = 0, height = 0 } = d
|
||||||
const horizontalLine = new Graphics().moveTo(0, d.y).lineTo(d.width, d.y).stroke(color)
|
const verticalLine = new Graphics().moveTo(x, 0).lineTo(x, height).stroke(color)
|
||||||
|
const horizontalLine = new Graphics().moveTo(0, y).lineTo(width, y).stroke(color)
|
||||||
verticalLine.alpha = 0.2
|
verticalLine.alpha = 0.2
|
||||||
horizontalLine.alpha = 0.2
|
horizontalLine.alpha = 0.2
|
||||||
container.addChild(verticalLine)
|
container.addChild(verticalLine)
|
||||||
|
@ -39,6 +39,7 @@ export interface MatchSessionDto {
|
|||||||
matchInProgress: boolean
|
matchInProgress: boolean
|
||||||
playersReady: number
|
playersReady: number
|
||||||
gameSummaries: GameSummary[]
|
gameSummaries: GameSummary[]
|
||||||
|
room: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GameDto {
|
export interface GameDto {
|
||||||
@ -79,10 +80,10 @@ export interface ContainerOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Dimension {
|
export interface Dimension {
|
||||||
width: number
|
width?: number
|
||||||
height: number
|
height?: number
|
||||||
x: number
|
x?: number
|
||||||
y: number
|
y?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SocketEvent {
|
export interface SocketEvent {
|
||||||
@ -116,6 +117,7 @@ export interface MatchSessionOptions {
|
|||||||
winType: 'points' | 'rounds'
|
winType: 'points' | 'rounds'
|
||||||
sessionName: string
|
sessionName: string
|
||||||
numPlayers: 1 | 2 | 3 | 4
|
numPlayers: 1 | 2 | 3 | 4
|
||||||
|
turnWaitSeconds: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GameSummary {
|
export interface GameSummary {
|
||||||
|
232
src/common/testMocks.ts
Normal file
232
src/common/testMocks.ts
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
import type { PlayerDto } from './interfaces'
|
||||||
|
|
||||||
|
export const players = [
|
||||||
|
{
|
||||||
|
id: '668977662eb15e2ef3bdac3f',
|
||||||
|
name: 'arhuako',
|
||||||
|
score: 0,
|
||||||
|
hand: [],
|
||||||
|
ready: false,
|
||||||
|
teamedWith: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'b4a4a1c8-da5f-492f-b002-69cdda33effd',
|
||||||
|
name: 'Alice (AI)',
|
||||||
|
score: 0,
|
||||||
|
hand: [],
|
||||||
|
ready: true,
|
||||||
|
teamedWith: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'c617989c-6449-4c9c-8fc0-8b97aad62c5e',
|
||||||
|
name: 'Bob (AI)',
|
||||||
|
score: 0,
|
||||||
|
hand: [],
|
||||||
|
ready: true,
|
||||||
|
teamedWith: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'c8579c74-22e0-4efe-9590-55d6c5e58b9e',
|
||||||
|
name: 'Charlie (AI)',
|
||||||
|
score: 0,
|
||||||
|
hand: [],
|
||||||
|
ready: true,
|
||||||
|
teamedWith: null,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export const dataPlayer: PlayerDto = {
|
||||||
|
id: '668977662eb15e2ef3bdac3f',
|
||||||
|
name: 'arhuako',
|
||||||
|
score: 0,
|
||||||
|
hand: [
|
||||||
|
{
|
||||||
|
id: '53978793-a2bc-490c-9e42-6047112aece3',
|
||||||
|
pips: [3, 0],
|
||||||
|
playerId: '668977662eb15e2ef3bdac3f',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3d35a1f1-ab44-4445-9abf-ed22df2a1cb7',
|
||||||
|
pips: [6, 4],
|
||||||
|
playerId: '668977662eb15e2ef3bdac3f',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'd4b9bf35-8c84-40e4-afb5-90a35b0acd5c',
|
||||||
|
pips: [4, 4],
|
||||||
|
playerId: '668977662eb15e2ef3bdac3f',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'bc935092-9328-4af8-a90d-3774b8d4fd0e',
|
||||||
|
pips: [5, 1],
|
||||||
|
playerId: '668977662eb15e2ef3bdac3f',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '987270de-0eec-42b5-8e24-e3cde0bee3e3',
|
||||||
|
pips: [0, 0],
|
||||||
|
playerId: '668977662eb15e2ef3bdac3f',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4f5626e2-b7b7-4cd3-8d18-498bbf79f796',
|
||||||
|
pips: [3, 2],
|
||||||
|
playerId: '668977662eb15e2ef3bdac3f',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'c1ff8494-5ece-4a73-a816-475416952c42',
|
||||||
|
pips: [3, 1],
|
||||||
|
playerId: '668977662eb15e2ef3bdac3f',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
ready: true,
|
||||||
|
teamedWith: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dataGameStatePlayer: PlayerDto[] = [
|
||||||
|
{
|
||||||
|
id: '668977662eb15e2ef3bdac3f',
|
||||||
|
name: 'arhuako',
|
||||||
|
score: 0,
|
||||||
|
hand: [
|
||||||
|
{
|
||||||
|
id: '53978793-a2bc-490c-9e42-6047112aece3',
|
||||||
|
playerId: '668977662eb15e2ef3bdac3f',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3d35a1f1-ab44-4445-9abf-ed22df2a1cb7',
|
||||||
|
playerId: '668977662eb15e2ef3bdac3f',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'd4b9bf35-8c84-40e4-afb5-90a35b0acd5c',
|
||||||
|
playerId: '668977662eb15e2ef3bdac3f',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'bc935092-9328-4af8-a90d-3774b8d4fd0e',
|
||||||
|
playerId: '668977662eb15e2ef3bdac3f',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '987270de-0eec-42b5-8e24-e3cde0bee3e3',
|
||||||
|
playerId: '668977662eb15e2ef3bdac3f',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4f5626e2-b7b7-4cd3-8d18-498bbf79f796',
|
||||||
|
playerId: '668977662eb15e2ef3bdac3f',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'c1ff8494-5ece-4a73-a816-475416952c42',
|
||||||
|
playerId: '668977662eb15e2ef3bdac3f',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
ready: false,
|
||||||
|
teamedWith: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'c60d0a87-dd85-49d3-a1af-6fb543e6a242',
|
||||||
|
name: 'Alice (AI)',
|
||||||
|
score: 0,
|
||||||
|
hand: [
|
||||||
|
{
|
||||||
|
id: '68765ee6-9755-4c92-b16b-baf90980cc57',
|
||||||
|
playerId: 'c60d0a87-dd85-49d3-a1af-6fb543e6a242',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'f3ad0e4d-4bcb-4382-947e-51da8295fbf4',
|
||||||
|
playerId: 'c60d0a87-dd85-49d3-a1af-6fb543e6a242',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'acabba58-10f7-43d3-8388-f1f2f32e42e8',
|
||||||
|
playerId: 'c60d0a87-dd85-49d3-a1af-6fb543e6a242',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '213fb154-8ac3-457f-9ca2-cdebe39d6059',
|
||||||
|
playerId: 'c60d0a87-dd85-49d3-a1af-6fb543e6a242',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'da6168c3-1ea4-48f3-b2fa-181fbc8f18c7',
|
||||||
|
playerId: 'c60d0a87-dd85-49d3-a1af-6fb543e6a242',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '9131a434-2b2b-466d-8b8a-73fa34b75d1c',
|
||||||
|
playerId: 'c60d0a87-dd85-49d3-a1af-6fb543e6a242',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '0a777281-8421-4f12-a406-12fe37bdaf5f',
|
||||||
|
playerId: 'c60d0a87-dd85-49d3-a1af-6fb543e6a242',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
ready: true,
|
||||||
|
teamedWith: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'db074bca-2d19-45e5-9646-3dadaddb092a',
|
||||||
|
name: 'Bob (AI)',
|
||||||
|
score: 0,
|
||||||
|
hand: [
|
||||||
|
{
|
||||||
|
id: '02375d76-4162-442c-9667-af755c494a62',
|
||||||
|
playerId: 'db074bca-2d19-45e5-9646-3dadaddb092a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '6cb15d5c-5512-42d5-b278-1c4ac0584bb1',
|
||||||
|
playerId: 'db074bca-2d19-45e5-9646-3dadaddb092a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '695ac5a4-6fbb-4735-ba5a-b938d096f878',
|
||||||
|
playerId: 'db074bca-2d19-45e5-9646-3dadaddb092a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '67371083-1493-4de3-aa53-dab9583db002',
|
||||||
|
playerId: 'db074bca-2d19-45e5-9646-3dadaddb092a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '988d57fc-295a-4ad3-b8dc-cc799a9084ed',
|
||||||
|
playerId: 'db074bca-2d19-45e5-9646-3dadaddb092a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5128ab10-0b60-4640-8425-e205f89b2a27',
|
||||||
|
playerId: 'db074bca-2d19-45e5-9646-3dadaddb092a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '576e9c1a-195c-427b-b54c-d1fdd41b1721',
|
||||||
|
playerId: 'db074bca-2d19-45e5-9646-3dadaddb092a',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
ready: true,
|
||||||
|
teamedWith: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5eccfb2d-2fb7-40ab-be39-9630d4775eab',
|
||||||
|
name: 'Charlie (AI)',
|
||||||
|
score: 0,
|
||||||
|
hand: [
|
||||||
|
{
|
||||||
|
id: '6938b04c-d5fb-4f9f-98b2-a1a61c4eaa0b',
|
||||||
|
playerId: '5eccfb2d-2fb7-40ab-be39-9630d4775eab',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'fb2a5add-3829-47ef-84d9-37bfa9d78a0e',
|
||||||
|
playerId: '5eccfb2d-2fb7-40ab-be39-9630d4775eab',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'd2d0360f-a8f3-43ff-ac4f-14aa4152e9cb',
|
||||||
|
playerId: '5eccfb2d-2fb7-40ab-be39-9630d4775eab',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '68b10359-df28-4d4d-8e04-45f0b98a0f17',
|
||||||
|
playerId: '5eccfb2d-2fb7-40ab-be39-9630d4775eab',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '44dbbfc4-fd3f-4091-b71d-5aac80df34bf',
|
||||||
|
playerId: '5eccfb2d-2fb7-40ab-be39-9630d4775eab',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '7cba5113-de2a-4388-92ff-47a7ea151f9a',
|
||||||
|
playerId: '5eccfb2d-2fb7-40ab-be39-9630d4775eab',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'a112485c-ce19-49bd-8ae6-c26266abe6d5',
|
||||||
|
playerId: '5eccfb2d-2fb7-40ab-be39-9630d4775eab',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
ready: true,
|
||||||
|
teamedWith: null,
|
||||||
|
},
|
||||||
|
]
|
@ -21,8 +21,7 @@ const { updateGameState } = gameStore
|
|||||||
const { gameOptions } = storeToRefs(gameOptionsStore)
|
const { gameOptions } = storeToRefs(gameOptionsStore)
|
||||||
|
|
||||||
const minScreenWidth = 1280
|
const minScreenWidth = 1280
|
||||||
const minScreenHeight = 72
|
const minScreenHeight = 720
|
||||||
0
|
|
||||||
|
|
||||||
let screenWidth = window.innerWidth - 10
|
let screenWidth = window.innerWidth - 10
|
||||||
let screenHeight = window.innerHeight - 10
|
let screenHeight = window.innerHeight - 10
|
||||||
@ -44,6 +43,7 @@ const defaultOptions: MatchSessionOptions = {
|
|||||||
seed: '',
|
seed: '',
|
||||||
sessionName: `Test #${Date.now()}`,
|
sessionName: `Test #${Date.now()}`,
|
||||||
numPlayers: 1,
|
numPlayers: 1,
|
||||||
|
turnWaitSeconds: 30,
|
||||||
}
|
}
|
||||||
const gameOptionsValue: MatchSessionOptions = {
|
const gameOptionsValue: MatchSessionOptions = {
|
||||||
...defaultOptions,
|
...defaultOptions,
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { MatchSessionOptions } from '@/common/interfaces'
|
import type { MatchSessionOptions } from '@/common/interfaces'
|
||||||
import { computed, ref } from 'vue'
|
import type { InfoService } from '@/services/InfoService'
|
||||||
|
import { computed, inject, ref } from 'vue'
|
||||||
|
|
||||||
const emit = defineEmits(['createMatch', 'startSingleMatch'])
|
const emit = defineEmits(['createMatch', 'startSingleMatch'])
|
||||||
|
const info = inject<InfoService>('info')
|
||||||
|
|
||||||
let options = ref<MatchSessionOptions>({
|
let options = ref<MatchSessionOptions>({
|
||||||
background: 'green',
|
background: 'green',
|
||||||
@ -10,12 +12,14 @@ let options = ref<MatchSessionOptions>({
|
|||||||
winType: 'points',
|
winType: 'points',
|
||||||
winTarget: 100,
|
winTarget: 100,
|
||||||
seed: '',
|
seed: '',
|
||||||
sessionName: `Test #${Date.now()}`,
|
sessionName: info?.development ? `Test #${Date.now()}` : `Session #${Date.now()}`,
|
||||||
numPlayers: 1,
|
numPlayers: 1,
|
||||||
|
turnWaitSeconds: 30,
|
||||||
})
|
})
|
||||||
|
|
||||||
const winTargetPointsList = [20, 50, 80, 100, 150, 200]
|
const winTargetPointsList = [20, 50, 80, 100, 150, 200]
|
||||||
const winTargetRoundsList = [1, 2, 3, 4, 5, 6]
|
const winTargetRoundsList = [1, 2, 3, 4, 5, 6]
|
||||||
|
const turnWaitSecondsList = [15, 20, 30, 40, 50, 60]
|
||||||
|
|
||||||
const backgroundOptiopnList = [
|
const backgroundOptiopnList = [
|
||||||
{
|
{
|
||||||
@ -123,7 +127,7 @@ function startSingleMatch() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid">
|
<div class="grid" v-if="info?.development">
|
||||||
<div class="cell">
|
<div class="cell">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">{{ $t('session-name') }}</label>
|
<label class="label">{{ $t('session-name') }}</label>
|
||||||
@ -235,6 +239,24 @@ function startSingleMatch() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="cell">
|
||||||
|
<div class="field">
|
||||||
|
<label for="winTarget" class="label">Turn Wait</label>
|
||||||
|
<div class="control">
|
||||||
|
<div class="select">
|
||||||
|
<select v-model="options.turnWaitSeconds" name="turnWait">
|
||||||
|
<option
|
||||||
|
v-bind:key="turnWait"
|
||||||
|
v-for="turnWait in turnWaitSecondsList"
|
||||||
|
:value="turnWait"
|
||||||
|
>
|
||||||
|
{{ turnWait }} seconds
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons mt-6">
|
<div class="buttons mt-6">
|
||||||
<button class="button is-primary" @click.prevent="createMatch" v-if="isMultiPlayer">
|
<button class="button is-primary" @click.prevent="createMatch" v-if="isMultiPlayer">
|
||||||
|
@ -1,8 +1,21 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { inject, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { RouterView } from 'vue-router'
|
import { RouterView } from 'vue-router'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
import { AuthenticationService } from '@/services/AuthenticationService'
|
||||||
|
import { emit, listen } from '@tauri-apps/api/event'
|
||||||
|
import profileStubImage from '@/assets/images/profile-stub.png'
|
||||||
|
|
||||||
|
const authStore = useAuthStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const userProfileMenuActive = ref(false)
|
||||||
|
const profileMenu = ref<HTMLElement | null>(null)
|
||||||
|
const { user } = storeToRefs(authStore)
|
||||||
|
const authService = inject<AuthenticationService>('auth')
|
||||||
|
const info: any = inject('info')
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
navbar: Boolean,
|
navbar: Boolean,
|
||||||
@ -13,27 +26,94 @@ defineOptions({
|
|||||||
})
|
})
|
||||||
|
|
||||||
function logout() {
|
function logout() {
|
||||||
|
authService?.logout()
|
||||||
router.push({ name: 'landing' })
|
router.push({ name: 'landing' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleProfileMenu() {
|
||||||
|
userProfileMenuActive.value = !userProfileMenuActive.value
|
||||||
|
if (userProfileMenuActive.value) {
|
||||||
|
addClickOutsideListener()
|
||||||
|
} else {
|
||||||
|
removeClickOutsideListener()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addClickOutsideListener() {
|
||||||
|
document.addEventListener('click', closeProfileMenu)
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeClickOutsideListener() {
|
||||||
|
document.removeEventListener('click', closeProfileMenu)
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeProfileMenu(event: any) {
|
||||||
|
console.log('profileMenu.value :>> ', profileMenu.value)
|
||||||
|
if (profileMenu.value !== null && !profileMenu.value.contains(event.target)) {
|
||||||
|
userProfileMenuActive.value = false
|
||||||
|
removeClickOutsideListener()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkForUpdates() {
|
||||||
|
emit('tauri://update')
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (userProfileMenuActive.value) {
|
||||||
|
addClickOutsideListener()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
removeClickOutsideListener()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Listen for update download progress
|
||||||
|
if (info.tauri) {
|
||||||
|
listen('tauri://update-not-available', () => {
|
||||||
|
userProfileMenuActive.value = false
|
||||||
|
removeClickOutsideListener()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const mode = import.meta.env.MODE
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="authenticated-layout">
|
<header class="container" v-if="props.navbar">
|
||||||
<header v-if="props.navbar">
|
{{ mode }} - {{ info.tauri }} - {{ info }}
|
||||||
<nav class="navbar">
|
<nav class="navbar is-transparent">
|
||||||
|
<div class="navbar-menu">
|
||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
<div class="navbar-item">
|
<div
|
||||||
<div class="buttons">
|
class="navbar-item has-dropdown"
|
||||||
<button class="button is-primary" @click="logout">Logout</button>
|
:class="{ 'is-active': userProfileMenuActive }"
|
||||||
|
ref="profileMenu"
|
||||||
|
v-if="user"
|
||||||
|
>
|
||||||
|
<a class="navbar-link" @click.prevent="toggleProfileMenu">
|
||||||
|
<figure class="image is-32x32">
|
||||||
|
<img class="is-rounded" :src="profileStubImage" alt="{{ user.username }}" />
|
||||||
|
</figure>
|
||||||
|
<span class="ml-2">{{ user.username }}</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="navbar-dropdown is-boxed is-right">
|
||||||
|
<a class="navbar-item" @click.prevent="logout">Logout</a>
|
||||||
|
<a class="navbar-item" @click.prevent="checkForUpdates" v-if="info.tauri"
|
||||||
|
>Check for update</a
|
||||||
|
>
|
||||||
|
<hr class="navbar-divider" />
|
||||||
|
<div class="navbar-item">Version 0.2.0</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main class="container">
|
||||||
<RouterView></RouterView>
|
<RouterView></RouterView>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
import { Application, Container, EventEmitter, Text, Ticker } from 'pixi.js'
|
import { Application, Container, EventEmitter, Text, Ticker } from 'pixi.js'
|
||||||
import { Scale, type ScaleFunction } from '@/game/utilities/scale'
|
import { Scale, type ScaleFunction } from '@/game/utilities/scale'
|
||||||
import type { AnimationOptions, Movement, PlayerDto, TileDto } from '@/common/interfaces'
|
import type {
|
||||||
|
AnimationOptions,
|
||||||
|
MatchSessionOptions,
|
||||||
|
Movement,
|
||||||
|
PlayerDto,
|
||||||
|
TileDto,
|
||||||
|
} from '@/common/interfaces'
|
||||||
import { Tile } from '@/game/Tile'
|
import { Tile } from '@/game/Tile'
|
||||||
import { createContainer, isTilePair } from '@/common/helpers'
|
import { createContainer, isTilePair } from '@/common/helpers'
|
||||||
import { createText } from '@/game/utilities/fonts'
|
import { createText } from '@/game/utilities/fonts'
|
||||||
@ -8,13 +14,14 @@ import { LoggingService } from '@/services/LoggingService'
|
|||||||
import { GlowFilter } from 'pixi-filters'
|
import { GlowFilter } from 'pixi-filters'
|
||||||
import { DIRECTION_INDEXES, DIRECTIONS, ORIENTATION_ANGLES } from '@/common/constants'
|
import { DIRECTION_INDEXES, DIRECTIONS, ORIENTATION_ANGLES } from '@/common/constants'
|
||||||
import type { OtherHand } from './OtherHand'
|
import type { OtherHand } from './OtherHand'
|
||||||
import { sound } from '@pixi/sound'
|
import { SoundManager } from '@/game/utilities/SoundManager'
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
export class Board extends EventEmitter {
|
export class Board extends EventEmitter {
|
||||||
private _scale: number = 1
|
private _scale: number = 1
|
||||||
private _canMove: boolean = false
|
private _canMove: boolean = false
|
||||||
private logger: LoggingService = new LoggingService()
|
private logger: LoggingService = new LoggingService()
|
||||||
|
private soundManager: SoundManager = new SoundManager()
|
||||||
|
|
||||||
ticker: Ticker
|
ticker: Ticker
|
||||||
height: number
|
height: number
|
||||||
@ -45,7 +52,10 @@ export class Board extends EventEmitter {
|
|||||||
currentPlayer!: PlayerDto
|
currentPlayer!: PlayerDto
|
||||||
otherPlayerHands: OtherHand[] = []
|
otherPlayerHands: OtherHand[] = []
|
||||||
|
|
||||||
constructor(app: Application) {
|
constructor(
|
||||||
|
app: Application,
|
||||||
|
private options: MatchSessionOptions,
|
||||||
|
) {
|
||||||
super()
|
super()
|
||||||
this.ticker = app.ticker
|
this.ticker = app.ticker
|
||||||
this.height = app.canvas.height - 130 - 130
|
this.height = app.canvas.height - 130 - 130
|
||||||
@ -147,6 +157,7 @@ export class Board extends EventEmitter {
|
|||||||
async playerMove(move: any, playerId: string) {
|
async playerMove(move: any, playerId: string) {
|
||||||
const { move: lastMove } = move
|
const { move: lastMove } = move
|
||||||
if (lastMove === null) {
|
if (lastMove === null) {
|
||||||
|
// Player passed
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.emit('game:tile-animation-ended')
|
this.emit('game:tile-animation-ended')
|
||||||
}, 500)
|
}, 500)
|
||||||
@ -162,7 +173,6 @@ export class Board extends EventEmitter {
|
|||||||
this.nextTile = tile
|
this.nextTile = tile
|
||||||
lastMove.tile = tile.toPlain()
|
lastMove.tile = tile.toPlain()
|
||||||
this.movements.push(lastMove)
|
this.movements.push(lastMove)
|
||||||
console.log('this.movements :>> ', this.movements)
|
|
||||||
await this.addTile(tile, lastMove)
|
await this.addTile(tile, lastMove)
|
||||||
this.setFreeEnd(lastMove)
|
this.setFreeEnd(lastMove)
|
||||||
}
|
}
|
||||||
@ -237,7 +247,7 @@ export class Board extends EventEmitter {
|
|||||||
this.tiles.push(tile)
|
this.tiles.push(tile)
|
||||||
const moveSound = this.getRandomClickSound()
|
const moveSound = this.getRandomClickSound()
|
||||||
await this.animateTile(tile, x, y, orientation, move)
|
await this.animateTile(tile, x, y, orientation, move)
|
||||||
sound.play(moveSound)
|
this.soundManager.play(moveSound)
|
||||||
this.emit('game:tile-animation-ended', tile.toPlain())
|
this.emit('game:tile-animation-ended', tile.toPlain())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
132
src/game/Button.ts
Normal file
132
src/game/Button.ts
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import type { Dimension } from '@/common/interfaces'
|
||||||
|
import { BlurFilter, Container, Graphics, Point, Text, TextStyle } from 'pixi.js'
|
||||||
|
import { gsap } from 'gsap'
|
||||||
|
import { SoundManager } from '@/game/utilities/SoundManager'
|
||||||
|
|
||||||
|
export class Button extends Container {
|
||||||
|
private dimension: Dimension
|
||||||
|
private isDisabled: boolean = false
|
||||||
|
private soundManager: SoundManager = new SoundManager()
|
||||||
|
|
||||||
|
public disabledColor: number = 0xffff00
|
||||||
|
public activeColor: number = 0xffff00
|
||||||
|
public hoverColor: number = 0xffff00
|
||||||
|
public shadowColor: number = 0x121212
|
||||||
|
public clickSound: string = 'snd-click-btn'
|
||||||
|
public textStyle: any = {
|
||||||
|
fontFamily: 'Arial',
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: 'bolder',
|
||||||
|
fill: 0x121212,
|
||||||
|
align: 'center',
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected text: string,
|
||||||
|
protected point: Point,
|
||||||
|
dimension: Dimension,
|
||||||
|
protected action: Function,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
this.dimension = { ...{ width: 100, height: 30 }, ...dimension }
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getTextString() {
|
||||||
|
return this.text
|
||||||
|
}
|
||||||
|
|
||||||
|
protected executeAction() {
|
||||||
|
this.action()
|
||||||
|
}
|
||||||
|
|
||||||
|
set disabled(value: boolean) {
|
||||||
|
this.isDisabled = value
|
||||||
|
this.off('pointerdown')
|
||||||
|
this.off('pointerover')
|
||||||
|
this.off('pointerout')
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
get disabled() {
|
||||||
|
return this.isDisabled
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getTextObject(): Text {
|
||||||
|
const { x, y } = this.point
|
||||||
|
const { width = 0, height = 0 } = this.dimension
|
||||||
|
const text = new Text({
|
||||||
|
text: this.getTextString(),
|
||||||
|
style: this.textStyle,
|
||||||
|
})
|
||||||
|
text.anchor = 0.5
|
||||||
|
|
||||||
|
text.y = 2 + y + height / 2
|
||||||
|
text.x = 2 + x + width / 2
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getButtonBack() {
|
||||||
|
const { x, y } = this.point
|
||||||
|
const { width = 0, height = 0 } = this.dimension
|
||||||
|
const color = this.isDisabled ? this.disabledColor : this.activeColor
|
||||||
|
const back = new Graphics().roundRect(x, y, width + 4, height + 4, 5).fill(color)
|
||||||
|
const shadow = new Graphics()
|
||||||
|
.roundRect(x + 3, y + 2, width + 4, height + 4, 5)
|
||||||
|
.fill(this.shadowColor)
|
||||||
|
back.alpha = 0.7
|
||||||
|
shadow.alpha = 1
|
||||||
|
shadow.filters = [new BlurFilter({ strength: 15 })]
|
||||||
|
this.addChild(shadow)
|
||||||
|
this.addChild(back)
|
||||||
|
return back
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
this.removeChildren()
|
||||||
|
const back = this.getButtonBack()
|
||||||
|
const text = this.getTextObject()
|
||||||
|
this.addChild(text)
|
||||||
|
|
||||||
|
if (!this.isDisabled) {
|
||||||
|
back.fill(this.activeColor)
|
||||||
|
back.alpha = 0.7
|
||||||
|
this.eventMode = 'static'
|
||||||
|
this.cursor = 'pointer'
|
||||||
|
this.on('pointerdown', (event) => {
|
||||||
|
this.emit('click', event)
|
||||||
|
this.onPointerDown(text, back)
|
||||||
|
})
|
||||||
|
this.on('pointerover', () => {
|
||||||
|
back.alpha = 1
|
||||||
|
})
|
||||||
|
|
||||||
|
this.on('pointerout', () => {
|
||||||
|
back.alpha = 0.7
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
text.alpha = 0.7
|
||||||
|
back.alpha = 0.5
|
||||||
|
this.eventMode = 'none'
|
||||||
|
this.cursor = 'default'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onPointerDown(text: Text, back: Graphics) {
|
||||||
|
this.soundManager.play(this.clickSound)
|
||||||
|
gsap
|
||||||
|
.timeline()
|
||||||
|
.to(
|
||||||
|
[back, text],
|
||||||
|
{
|
||||||
|
duration: 0.06,
|
||||||
|
x: '+=1',
|
||||||
|
y: '+=2',
|
||||||
|
ease: 'linear',
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.to([back, text], { duration: 0.06, x: '-=1', y: '-=2', ease: 'linear' })
|
||||||
|
this.executeAction()
|
||||||
|
}
|
||||||
|
}
|
@ -19,7 +19,7 @@ import { GameSummayView } from './GameSummayView'
|
|||||||
import Config from './Config'
|
import Config from './Config'
|
||||||
import { createText, grayStyle } from './utilities/fonts'
|
import { createText, grayStyle } from './utilities/fonts'
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
|
import { DIRECTION_INDEXES, DIRECTIONS } from '@/common/constants'
|
||||||
export class Game extends EventEmitter {
|
export class Game extends EventEmitter {
|
||||||
public board!: Board
|
public board!: Board
|
||||||
public hand!: Hand
|
public hand!: Hand
|
||||||
@ -33,7 +33,7 @@ export class Game extends EventEmitter {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private options: MatchSessionOptions,
|
private options: MatchSessionOptions,
|
||||||
private socketService: SocketIoClientService,
|
private socketService: SocketIoClientService | undefined,
|
||||||
private playerId: string,
|
private playerId: string,
|
||||||
private sessionId: string,
|
private sessionId: string,
|
||||||
) {
|
) {
|
||||||
@ -47,6 +47,10 @@ export class Game extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get stage() {
|
||||||
|
return this.app.stage
|
||||||
|
}
|
||||||
|
|
||||||
async setup(): Promise<HTMLCanvasElement> {
|
async setup(): Promise<HTMLCanvasElement> {
|
||||||
const width = this.options.screen?.width || 1280
|
const width = this.options.screen?.width || 1280
|
||||||
const height = this.options.screen?.height || 720
|
const height = this.options.screen?.height || 720
|
||||||
@ -58,8 +62,8 @@ export class Game extends EventEmitter {
|
|||||||
|
|
||||||
async start(players: PlayerDto[] = []) {
|
async start(players: PlayerDto[] = []) {
|
||||||
this.iniialStuff(this.app)
|
this.iniialStuff(this.app)
|
||||||
this.board = new Board(this.app)
|
this.board = new Board(this.app, this.options)
|
||||||
this.hand = new Hand(this.app)
|
this.hand = new Hand(this.app, this.options)
|
||||||
this.otherHands = [
|
this.otherHands = [
|
||||||
new OtherHand(this.app, 'left'),
|
new OtherHand(this.app, 'left'),
|
||||||
new OtherHand(this.app, 'top'),
|
new OtherHand(this.app, 'top'),
|
||||||
@ -73,7 +77,8 @@ export class Game extends EventEmitter {
|
|||||||
this.setBoardEvents()
|
this.setBoardEvents()
|
||||||
this.setHandEvents()
|
this.setHandEvents()
|
||||||
this.initEventBus()
|
this.initEventBus()
|
||||||
wait(3000)
|
await wait(500)
|
||||||
|
this.socketService &&
|
||||||
this.socketService.sendMessage('client:set-client-ready', {
|
this.socketService.sendMessage('client:set-client-ready', {
|
||||||
sessionId: this.sessionId,
|
sessionId: this.sessionId,
|
||||||
userId: this.playerId,
|
userId: this.playerId,
|
||||||
@ -82,7 +87,11 @@ export class Game extends EventEmitter {
|
|||||||
|
|
||||||
iniialStuff(app: Application) {
|
iniialStuff(app: Application) {
|
||||||
app.stage.addChild(this.backgroundLayer)
|
app.stage.addChild(this.backgroundLayer)
|
||||||
const background = new TilingSprite(Assets.get(`bg-${this.options.background}`))
|
const background = new TilingSprite({
|
||||||
|
texture: Assets.get(`bg-${this.options.background}`),
|
||||||
|
width: app.canvas.width,
|
||||||
|
height: app.canvas.height,
|
||||||
|
})
|
||||||
|
|
||||||
this.backgroundLayer.addChild(background)
|
this.backgroundLayer.addChild(background)
|
||||||
|
|
||||||
@ -160,19 +169,9 @@ export class Game extends EventEmitter {
|
|||||||
this.board.setPlayerHand(tiles)
|
this.board.setPlayerHand(tiles)
|
||||||
})
|
})
|
||||||
this.hand.on('game:tile-click', (tile: TileDto) => this.highlightMoves(tile))
|
this.hand.on('game:tile-click', (tile: TileDto) => this.highlightMoves(tile))
|
||||||
|
this.hand.on('game:timer-timeout', this.handleHandPlayerTimeout.bind(this))
|
||||||
|
|
||||||
this.hand.on('game:button-pass-click', async () => {
|
this.hand.on('game:button-pass-click', this.sendPassEvent.bind(this))
|
||||||
const move: Movement = {
|
|
||||||
id: '',
|
|
||||||
type: 'pass',
|
|
||||||
playerId: this.playerId,
|
|
||||||
}
|
|
||||||
this.socketService.sendMessage('client:player-move', {
|
|
||||||
sessionId: this.sessionId,
|
|
||||||
move: move,
|
|
||||||
})
|
|
||||||
await this.board.updateBoard(move, undefined)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.gameSummaryView.on('finishClick', (data) => {
|
this.gameSummaryView.on('finishClick', (data) => {
|
||||||
this.emit('game:finish-click', data)
|
this.emit('game:finish-click', data)
|
||||||
@ -181,6 +180,7 @@ export class Game extends EventEmitter {
|
|||||||
this.gameSummaryView.on('nextClick', (data) => {
|
this.gameSummaryView.on('nextClick', (data) => {
|
||||||
this.board.clean()
|
this.board.clean()
|
||||||
this.updateScoreboard(data.sessionState)
|
this.updateScoreboard(data.sessionState)
|
||||||
|
this.socketService &&
|
||||||
this.socketService.sendMessage('client:set-client-ready-for-next-game', {
|
this.socketService.sendMessage('client:set-client-ready-for-next-game', {
|
||||||
userId: this.playerId,
|
userId: this.playerId,
|
||||||
sessionId: this.sessionId,
|
sessionId: this.sessionId,
|
||||||
@ -190,6 +190,59 @@ export class Game extends EventEmitter {
|
|||||||
this.hand.on('hand-initialized', () => {})
|
this.hand.on('hand-initialized', () => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleHandPlayerTimeout(tile: TileDto | null) {
|
||||||
|
if (tile === null) {
|
||||||
|
this.sendPassEvent()
|
||||||
|
this.hand.disablePassButton()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const freeEnds = this.board.freeEnds
|
||||||
|
console.log('freeEnds :>> ', freeEnds, this.playerId)
|
||||||
|
const move: Movement = {
|
||||||
|
id: '',
|
||||||
|
tile,
|
||||||
|
type: 'right',
|
||||||
|
playerId: this.playerId,
|
||||||
|
}
|
||||||
|
if (freeEnds !== undefined) {
|
||||||
|
const side = tile.pips?.includes(freeEnds[0]) ? 'left' : 'right'
|
||||||
|
const direction = side === 'left' ? this.board.leftDirection : this.board.rightDirection
|
||||||
|
let dirIndex = DIRECTION_INDEXES[direction]
|
||||||
|
const validMoves = this.board.nextTileValidMoves(tile, side)
|
||||||
|
const validPoints = this.board.nextTileValidPoints(tile, side, validMoves)
|
||||||
|
let safeCount = 4
|
||||||
|
while (safeCount > 0 && !validMoves[dirIndex % 4]) {
|
||||||
|
dirIndex += 1
|
||||||
|
safeCount -= 1
|
||||||
|
}
|
||||||
|
console.log('validMoves :>> ', validMoves)
|
||||||
|
console.log('validPoints :>> ', validPoints)
|
||||||
|
const validPoint = validPoints[dirIndex % 4]
|
||||||
|
if (validPoint !== undefined) {
|
||||||
|
move.x = validPoint[0]
|
||||||
|
move.y = validPoint[1]
|
||||||
|
}
|
||||||
|
move.direction = DIRECTIONS[dirIndex % 4]
|
||||||
|
move.type = side
|
||||||
|
}
|
||||||
|
this.currentMove = move
|
||||||
|
this.board.updateBoard(move, this.hand.tileMoved(tile))
|
||||||
|
}
|
||||||
|
|
||||||
|
private async sendPassEvent() {
|
||||||
|
const move: Movement = {
|
||||||
|
id: '',
|
||||||
|
type: 'pass',
|
||||||
|
playerId: this.playerId,
|
||||||
|
}
|
||||||
|
this.socketService &&
|
||||||
|
this.socketService.sendMessage('client:player-move', {
|
||||||
|
sessionId: this.sessionId,
|
||||||
|
move: move,
|
||||||
|
})
|
||||||
|
await this.board.updateBoard(move, undefined)
|
||||||
|
}
|
||||||
|
|
||||||
private updateScoreboard(sessionState: MatchSessionDto) {
|
private updateScoreboard(sessionState: MatchSessionDto) {
|
||||||
const scoreboard = sessionState.scoreboard
|
const scoreboard = sessionState.scoreboard
|
||||||
const myScore = scoreboard.find((d) => d.id === this.playerId)?.score || 0
|
const myScore = scoreboard.find((d) => d.id === this.playerId)?.score || 0
|
||||||
@ -269,11 +322,13 @@ export class Game extends EventEmitter {
|
|||||||
|
|
||||||
this.board.on('game:tile-animation-ended', async (tile) => {
|
this.board.on('game:tile-animation-ended', async (tile) => {
|
||||||
if (tile !== null && tile !== undefined && tile.playerId === this.playerId) {
|
if (tile !== null && tile !== undefined && tile.playerId === this.playerId) {
|
||||||
|
this.socketService &&
|
||||||
this.socketService.sendMessage('client:player-move', {
|
this.socketService.sendMessage('client:player-move', {
|
||||||
sessionId: this.sessionId,
|
sessionId: this.sessionId,
|
||||||
move: this.currentMove,
|
move: this.currentMove,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
this.socketService &&
|
||||||
this.socketService.sendMessage('client:animation-ended', {
|
this.socketService.sendMessage('client:animation-ended', {
|
||||||
sessionId: this.sessionId,
|
sessionId: this.sessionId,
|
||||||
userId: this.playerId,
|
userId: this.playerId,
|
||||||
@ -306,6 +361,8 @@ export class Game extends EventEmitter {
|
|||||||
if (otherHand) {
|
if (otherHand) {
|
||||||
otherHand.update(data.move)
|
otherHand.update(data.move)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// ShowPassed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
import { createButton, createContainer } from '@/common/helpers'
|
import { createContainer } from '@/common/helpers'
|
||||||
import type { GameSummary, MatchSessionDto, MatchSessionOptions } from '@/common/interfaces'
|
import type {
|
||||||
import { EventEmitter, type Application, type Container } from 'pixi.js'
|
Dimension,
|
||||||
|
GameSummary,
|
||||||
|
MatchSessionDto,
|
||||||
|
MatchSessionOptions,
|
||||||
|
} from '@/common/interfaces'
|
||||||
|
import { EventEmitter, Point, type Application, type Container } from 'pixi.js'
|
||||||
import { createText, whiteStyle, yellowStyle } from './utilities/fonts'
|
import { createText, whiteStyle, yellowStyle } from './utilities/fonts'
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
|
import { TimedButton } from './TimedButton'
|
||||||
|
|
||||||
export class GameSummayView extends EventEmitter {
|
export class GameSummayView extends EventEmitter {
|
||||||
public width: number
|
public width: number
|
||||||
@ -136,41 +142,20 @@ export class GameSummayView extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderButtons() {
|
renderButtons() {
|
||||||
if (this.type === 'round') {
|
const x = this.width / 2 - 25
|
||||||
this.layer.addChild(
|
const y = this.height - 55
|
||||||
createButton({
|
const d: Dimension = { x, y, width: 90, height: 30 }
|
||||||
text: 'Next',
|
const text = this.type === 'round' ? 'Next' : 'Finish'
|
||||||
dimension: {
|
const event = this.type === 'round' ? 'nextClick' : 'finishClick'
|
||||||
x: this.width / 2 - 25,
|
const action = () => {
|
||||||
y: this.height - 50,
|
this.emit(event, { sessionState: this.matchState })
|
||||||
width: 60,
|
|
||||||
height: 25,
|
|
||||||
},
|
|
||||||
action: () => {
|
|
||||||
this.emit('nextClick', { sessionState: this.matchState })
|
|
||||||
this.container.visible = false
|
this.container.visible = false
|
||||||
},
|
|
||||||
parent: this.layer,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
this.layer.addChild(
|
|
||||||
createButton({
|
|
||||||
text: 'Finish',
|
|
||||||
dimension: {
|
|
||||||
x: this.width / 2 - 25,
|
|
||||||
y: this.height - 50,
|
|
||||||
width: 60,
|
|
||||||
height: 25,
|
|
||||||
},
|
|
||||||
action: () => {
|
|
||||||
this.emit('finishClick', this.gameSummary)
|
|
||||||
this.container.visible = false
|
|
||||||
},
|
|
||||||
parent: this.layer,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
const btn: TimedButton = new TimedButton(text, new Point(x, y), d, action, 30)
|
||||||
|
|
||||||
|
btn.on('timeout', action)
|
||||||
|
this.layer.addChild(btn)
|
||||||
|
btn.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -1,17 +1,28 @@
|
|||||||
import { Application, Container, EventEmitter, Graphics, Sprite, Texture, Ticker } from 'pixi.js'
|
import {
|
||||||
|
Application,
|
||||||
|
Container,
|
||||||
|
EventEmitter,
|
||||||
|
Graphics,
|
||||||
|
Point,
|
||||||
|
Sprite,
|
||||||
|
Texture,
|
||||||
|
Ticker,
|
||||||
|
} from 'pixi.js'
|
||||||
import { Tile } from '@/game/Tile'
|
import { Tile } from '@/game/Tile'
|
||||||
import type { PlayerDto, TileDto } from '@/common/interfaces'
|
import type { MatchSessionOptions, PlayerDto, TileDto } from '@/common/interfaces'
|
||||||
import { GlowFilter } from 'pixi-filters'
|
import { GlowFilter } from 'pixi-filters'
|
||||||
import { Scale, type ScaleFunction } from './utilities/scale'
|
import { Scale, type ScaleFunction } from './utilities/scale'
|
||||||
import { LoggingService } from '@/services/LoggingService'
|
import { LoggingService } from '@/services/LoggingService'
|
||||||
import { createButton, createContainer } from '@/common/helpers'
|
import { createContainer } from '@/common/helpers'
|
||||||
import { createText, playerNameText, whiteStyle } from './utilities/fonts'
|
import { createText, playerNameText, whiteStyle } from './utilities/fonts'
|
||||||
import Config from '@/game/Config'
|
import Config from '@/game/Config'
|
||||||
|
import { TimerText } from './TimerText'
|
||||||
|
import { Button } from './Button'
|
||||||
|
|
||||||
export class Hand extends EventEmitter {
|
export class Hand extends EventEmitter {
|
||||||
tiles: Tile[] = []
|
tiles: Tile[] = []
|
||||||
container: Container = new Container()
|
container: Container = new Container()
|
||||||
buttonPass: Container = new Container()
|
buttonPass!: Button
|
||||||
scoreLayer: Container = new Container()
|
scoreLayer: Container = new Container()
|
||||||
activeLayer: Container = new Container()
|
activeLayer: Container = new Container()
|
||||||
height: number
|
height: number
|
||||||
@ -32,8 +43,12 @@ export class Hand extends EventEmitter {
|
|||||||
score: number = 0
|
score: number = 0
|
||||||
active: boolean = false
|
active: boolean = false
|
||||||
private player!: PlayerDto
|
private player!: PlayerDto
|
||||||
|
private timer: TimerText
|
||||||
|
|
||||||
constructor(app: Application) {
|
constructor(
|
||||||
|
app: Application,
|
||||||
|
private options: MatchSessionOptions,
|
||||||
|
) {
|
||||||
super()
|
super()
|
||||||
app.stage.addChild(this.container)
|
app.stage.addChild(this.container)
|
||||||
this.ticker = app.ticker
|
this.ticker = app.ticker
|
||||||
@ -43,6 +58,8 @@ export class Hand extends EventEmitter {
|
|||||||
this.container.x = app.canvas.width / 2 - this.width / 2
|
this.container.x = app.canvas.width / 2 - this.width / 2
|
||||||
this.container.width = this.width
|
this.container.width = this.width
|
||||||
this.container.height = this.height
|
this.container.height = this.height
|
||||||
|
this.buttonPass = this.createPassButton()
|
||||||
|
this.timer = this.createTimer(this.options.turnWaitSeconds)
|
||||||
this.calculateScale()
|
this.calculateScale()
|
||||||
this.initLayers()
|
this.initLayers()
|
||||||
this.render()
|
this.render()
|
||||||
@ -67,6 +84,8 @@ export class Hand extends EventEmitter {
|
|||||||
})
|
})
|
||||||
this.container.addChild(this.scoreLayer)
|
this.container.addChild(this.scoreLayer)
|
||||||
this.container.addChild(this.activeLayer)
|
this.container.addChild(this.activeLayer)
|
||||||
|
this.interactionsLayer.addChild(this.buttonPass)
|
||||||
|
this.interactionsLayer.addChild(this.timer)
|
||||||
}
|
}
|
||||||
|
|
||||||
gameFinished() {
|
gameFinished() {
|
||||||
@ -90,7 +109,7 @@ export class Hand extends EventEmitter {
|
|||||||
? this.tiles
|
? this.tiles
|
||||||
: this.tiles.filter((tile) => this.hasMoves(tile.toPlain(), freeEnds))
|
: this.tiles.filter((tile) => this.hasMoves(tile.toPlain(), freeEnds))
|
||||||
if (this.availableTiles.length === 0) {
|
if (this.availableTiles.length === 0) {
|
||||||
this.createPassButton()
|
this.buttonPass.disabled = false
|
||||||
}
|
}
|
||||||
this.availableTiles.forEach((tile) => {
|
this.availableTiles.forEach((tile) => {
|
||||||
tile.animateTo({
|
tile.animateTo({
|
||||||
@ -99,6 +118,31 @@ export class Hand extends EventEmitter {
|
|||||||
})
|
})
|
||||||
tile.interactive = true
|
tile.interactive = true
|
||||||
})
|
})
|
||||||
|
if (this.timer) {
|
||||||
|
this.timer.reset()
|
||||||
|
this.timer.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createTimer(seconds: number): TimerText {
|
||||||
|
const timer = new TimerText(seconds, new Point(this.width - 60, 45))
|
||||||
|
// timer.animation = false
|
||||||
|
timer.on('timeout', () => {
|
||||||
|
this.timerTimeout()
|
||||||
|
})
|
||||||
|
return timer
|
||||||
|
}
|
||||||
|
|
||||||
|
timerTimeout() {
|
||||||
|
if (this.timer === undefined) return
|
||||||
|
this.timer.reset()
|
||||||
|
if (this.availableTiles.length === 0) {
|
||||||
|
this.emit('game:timer-timeout', null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const randomTile = this.availableTiles[Math.floor(Math.random() * this.availableTiles.length)] // pick random tile
|
||||||
|
randomTile.alpha = 1
|
||||||
|
this.emit('game:timer-timeout', randomTile.toPlain())
|
||||||
}
|
}
|
||||||
|
|
||||||
afterMove() {
|
afterMove() {
|
||||||
@ -110,6 +154,7 @@ export class Hand extends EventEmitter {
|
|||||||
tile.setPosition(tile.x, tile.y + 10)
|
tile.setPosition(tile.x, tile.y + 10)
|
||||||
tile.interactive = false
|
tile.interactive = false
|
||||||
})
|
})
|
||||||
|
this.timer && this.timer.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
hasMoves(tile: TileDto, freeEnds?: [number, number]): boolean {
|
hasMoves(tile: TileDto, freeEnds?: [number, number]): boolean {
|
||||||
@ -181,18 +226,22 @@ export class Hand extends EventEmitter {
|
|||||||
return tile
|
return tile
|
||||||
}
|
}
|
||||||
|
|
||||||
private createPassButton() {
|
private createPassButton(): Button {
|
||||||
const lastTile = this.tiles[this.tiles.length - 1]
|
const x = this.width - 100
|
||||||
const x = lastTile ? lastTile.x + lastTile.width : this.scaleX(0)
|
const y = this.height - 45
|
||||||
this.buttonPass = createButton({
|
const btn = new Button('PASS', new Point(x, y), { width: 80, height: 30 }, () => {
|
||||||
text: 'PASS',
|
this.timer.reset()
|
||||||
dimension: { x, y: this.height / 2, width: 50, height: 20 },
|
this.buttonPass.disabled = true
|
||||||
action: () => {
|
|
||||||
this.interactionsLayer.removeChild(this.buttonPass)
|
|
||||||
this.emit('game:button-pass-click')
|
this.emit('game:button-pass-click')
|
||||||
},
|
|
||||||
parent: this.interactionsLayer,
|
|
||||||
})
|
})
|
||||||
|
btn.disabledColor = 0xbabf95
|
||||||
|
btn.disabled = true
|
||||||
|
this.buttonPass = btn
|
||||||
|
return btn
|
||||||
|
}
|
||||||
|
|
||||||
|
disablePassButton() {
|
||||||
|
this.buttonPass.disabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
update(playerState: PlayerDto) {
|
update(playerState: PlayerDto) {
|
||||||
|
49
src/game/TimedButton.ts
Normal file
49
src/game/TimedButton.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import type { Point } from 'pixi.js'
|
||||||
|
import { Button } from './Button'
|
||||||
|
import type { Dimension } from '@/common/interfaces'
|
||||||
|
import { Timer } from './utilities/Timer'
|
||||||
|
|
||||||
|
export class TimedButton extends Button {
|
||||||
|
private timer: Timer
|
||||||
|
private countdown: number = 0
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected text: string,
|
||||||
|
protected point: Point,
|
||||||
|
dimension: Dimension,
|
||||||
|
protected action: Function,
|
||||||
|
private seconds: number,
|
||||||
|
) {
|
||||||
|
super(text, point, dimension, action)
|
||||||
|
this.timer = new Timer(seconds)
|
||||||
|
this.timer.on('timeout', this.onTImeOut.bind(this))
|
||||||
|
this.timer.on('tick', this.onTick.bind(this))
|
||||||
|
this.countdown = seconds
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
private onTImeOut() {
|
||||||
|
this.disabled = true
|
||||||
|
this.timer.reset()
|
||||||
|
this.emit('timeout')
|
||||||
|
}
|
||||||
|
|
||||||
|
private onTick(seconds: number) {
|
||||||
|
this.countdown = seconds
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.timer.reset()
|
||||||
|
this.timer.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getTextString() {
|
||||||
|
return `${this.text} (${this.countdown})`
|
||||||
|
}
|
||||||
|
|
||||||
|
protected executeAction() {
|
||||||
|
this.action()
|
||||||
|
this.timer.reset()
|
||||||
|
}
|
||||||
|
}
|
93
src/game/TimerText.ts
Normal file
93
src/game/TimerText.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import { Container, Point, Text, TextStyle } from 'pixi.js'
|
||||||
|
import { createText, timerStyle } from './utilities/fonts'
|
||||||
|
|
||||||
|
import { gsap } from 'gsap'
|
||||||
|
|
||||||
|
export class TimerText extends Container {
|
||||||
|
private intervalHandle?: any
|
||||||
|
private text?: Text
|
||||||
|
animation: boolean = false
|
||||||
|
textStyle: TextStyle = timerStyle()
|
||||||
|
private countdown: number = 0
|
||||||
|
private isInitiated: boolean = false
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private seconds: number,
|
||||||
|
private point: Point,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
this.countdown = seconds
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.stop()
|
||||||
|
this.countdown = this.seconds
|
||||||
|
this.isInitiated = false
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
clearInterval(this.intervalHandle)
|
||||||
|
this.isInitiated = true
|
||||||
|
this.removeChildren()
|
||||||
|
this.render()
|
||||||
|
this.intervalHandle = setInterval(() => {
|
||||||
|
this.countdown--
|
||||||
|
!this.animation && this.text?.destroy()
|
||||||
|
this.render()
|
||||||
|
if (this.countdown === 0) {
|
||||||
|
clearInterval(this.intervalHandle)
|
||||||
|
this.emit('timeout')
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.stop()
|
||||||
|
this.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
clearInterval(this.intervalHandle)
|
||||||
|
this.emit('stop', this.countdown)
|
||||||
|
}
|
||||||
|
|
||||||
|
private render() {
|
||||||
|
if (!this.animation) this.removeChildren()
|
||||||
|
this.text = createText({
|
||||||
|
text: this.countdown.toString(),
|
||||||
|
x: this.point.x,
|
||||||
|
y: this.point.y,
|
||||||
|
style: this.textStyle,
|
||||||
|
container: this,
|
||||||
|
})
|
||||||
|
this.text.scale.set(1)
|
||||||
|
if (this.animation && this.isInitiated) this.animate(this.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
private animate(text: Text) {
|
||||||
|
if (text) {
|
||||||
|
gsap.timeline().to(text, {
|
||||||
|
delay: 0.5,
|
||||||
|
duration: 1.2,
|
||||||
|
x: this.point.x + 2,
|
||||||
|
y: this.point.y - 25,
|
||||||
|
ease: 'power4.out',
|
||||||
|
alpha: 0.0,
|
||||||
|
onComplete: () => {
|
||||||
|
text.destroy()
|
||||||
|
},
|
||||||
|
// scale: 1,
|
||||||
|
})
|
||||||
|
// gsap.to(this.text, {
|
||||||
|
// duration: 1,
|
||||||
|
// alpha: 0,
|
||||||
|
// x: this.app.canvas.width / 2,
|
||||||
|
// y: this.app.canvas.height / 2 - 20,
|
||||||
|
// scale: 1.3,
|
||||||
|
// ease: 'linear',
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
60
src/game/utilities/SoundManager.ts
Normal file
60
src/game/utilities/SoundManager.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import snd_move_1 from '@/assets/sounds/move-1.mp3'
|
||||||
|
import snd_move_2 from '@/assets/sounds/move-2.mp3'
|
||||||
|
import snd_move_3 from '@/assets/sounds/move-3.mp3'
|
||||||
|
import snd_move_4 from '@/assets/sounds/move-4.mp3'
|
||||||
|
import snd_intro from '@/assets/sounds/intro.mp3'
|
||||||
|
import snd_click_btn from '@/assets/sounds/button_click.wav'
|
||||||
|
import snd_ding_1 from '@/assets/sounds/ding-1.wav'
|
||||||
|
import { Howl } from 'howler'
|
||||||
|
|
||||||
|
const soundAssets = [
|
||||||
|
{ name: 'snd-move-1', src: [snd_move_1], loop: false, volume: 1 },
|
||||||
|
{ name: 'snd-move-2', src: [snd_move_2], loop: false, volume: 1 },
|
||||||
|
{ name: 'snd-move-3', src: [snd_move_3], loop: false, volume: 1 },
|
||||||
|
{ name: 'snd-move-4', src: [snd_move_4], loop: false, volume: 1 },
|
||||||
|
{ name: 'snd-intro', src: [snd_intro], loop: true, volume: 1 },
|
||||||
|
{ name: 'snd-click-btn', src: [snd_click_btn], loop: false, volume: 1 },
|
||||||
|
{ name: 'snd-ding-1', src: [snd_ding_1], loop: false, volume: 1 },
|
||||||
|
]
|
||||||
|
|
||||||
|
export class SoundManager {
|
||||||
|
private sounds: Record<string, Howl> = {}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
soundAssets.forEach(({ name, src, loop, volume = 1 }) => {
|
||||||
|
this.sounds[name] = new Howl({
|
||||||
|
src,
|
||||||
|
loop,
|
||||||
|
volume: volume,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
play(name: string) {
|
||||||
|
const sound = this.sounds[name]
|
||||||
|
if (sound === undefined) {
|
||||||
|
console.error(`Sound ${name} not found`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sound.play()
|
||||||
|
}
|
||||||
|
|
||||||
|
pause(name: string) {
|
||||||
|
const sound = this.sounds[name]
|
||||||
|
if (sound === undefined) {
|
||||||
|
console.error(`Sound ${name} not found`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sound.pause()
|
||||||
|
}
|
||||||
|
|
||||||
|
stop(name: string) {
|
||||||
|
const sound = this.sounds[name]
|
||||||
|
if (sound === undefined) {
|
||||||
|
console.error(`Sound ${name} not found`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sound.pause()
|
||||||
|
sound.seek(0)
|
||||||
|
}
|
||||||
|
}
|
35
src/game/utilities/Timer.ts
Normal file
35
src/game/utilities/Timer.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { EventEmitter } from 'pixi.js'
|
||||||
|
|
||||||
|
export class Timer extends EventEmitter {
|
||||||
|
private intervalHandle?: any
|
||||||
|
private countdown: number = 0
|
||||||
|
|
||||||
|
constructor(private seconds: number) {
|
||||||
|
super()
|
||||||
|
this.countdown = seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
clearInterval(this.intervalHandle)
|
||||||
|
this.intervalHandle = setInterval(() => {
|
||||||
|
this.countdown--
|
||||||
|
if (this.countdown === 0) {
|
||||||
|
clearInterval(this.intervalHandle)
|
||||||
|
this.emit('timeout')
|
||||||
|
} else {
|
||||||
|
this.emit('tick', this.countdown)
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
clearInterval(this.intervalHandle)
|
||||||
|
this.emit('stop', this.countdown)
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.stop()
|
||||||
|
this.countdown = this.seconds
|
||||||
|
this.emit('reset', this.countdown)
|
||||||
|
}
|
||||||
|
}
|
@ -33,11 +33,13 @@ import bg_red from '@/assets/images/backgrounds/bg-red.png'
|
|||||||
import bg_yellow from '@/assets/images/backgrounds/bg-yellow.png'
|
import bg_yellow from '@/assets/images/backgrounds/bg-yellow.png'
|
||||||
import bg_blue from '@/assets/images/backgrounds/bg-blue.png'
|
import bg_blue from '@/assets/images/backgrounds/bg-blue.png'
|
||||||
import bg_gray from '@/assets/images/backgrounds/bg-1.png'
|
import bg_gray from '@/assets/images/backgrounds/bg-1.png'
|
||||||
import snd_move_1 from '@/assets/sounds/move-1.mp3'
|
// import snd_move_1 from '@/assets/sounds/move-1.mp3'
|
||||||
import snd_move_2 from '@/assets/sounds/move-2.mp3'
|
// import snd_move_2 from '@/assets/sounds/move-2.mp3'
|
||||||
import snd_move_3 from '@/assets/sounds/move-3.mp3'
|
// import snd_move_3 from '@/assets/sounds/move-3.mp3'
|
||||||
import snd_move_4 from '@/assets/sounds/move-4.mp3'
|
// import snd_move_4 from '@/assets/sounds/move-4.mp3'
|
||||||
import snd_intro from '@/assets/sounds/intro.mp3'
|
// import snd_intro from '@/assets/sounds/intro.mp3'
|
||||||
|
// import snd_click_btn from '@/assets/sounds/button_click.wav'
|
||||||
|
// import snd_ding_1 from '@/assets/sounds/ding-1.wav'
|
||||||
|
|
||||||
export const assets = [
|
export const assets = [
|
||||||
{ alias: 'tile-back', src: tileBack },
|
{ alias: 'tile-back', src: tileBack },
|
||||||
@ -75,9 +77,11 @@ export const assets = [
|
|||||||
{ alias: 'bg-yellow', src: bg_yellow },
|
{ alias: 'bg-yellow', src: bg_yellow },
|
||||||
{ alias: 'bg-blue', src: bg_blue },
|
{ alias: 'bg-blue', src: bg_blue },
|
||||||
{ alias: 'bg-gray', src: bg_gray },
|
{ alias: 'bg-gray', src: bg_gray },
|
||||||
{ alias: 'snd-move-1', src: snd_move_1 },
|
// { alias: 'snd-move-1', src: snd_move_1 },
|
||||||
{ alias: 'snd-move-2', src: snd_move_2 },
|
// { alias: 'snd-move-2', src: snd_move_2 },
|
||||||
{ alias: 'snd-move-3', src: snd_move_3 },
|
// { alias: 'snd-move-3', src: snd_move_3 },
|
||||||
{ alias: 'snd-move-4', src: snd_move_4 },
|
// { alias: 'snd-move-4', src: snd_move_4 },
|
||||||
{ alias: 'snd-intro', src: snd_intro },
|
// { alias: 'snd-intro', src: snd_intro },
|
||||||
|
// { alias: 'snd-click-btn', src: snd_click_btn },
|
||||||
|
// { alias: 'snd-ding-1', src: snd_ding_1 },
|
||||||
]
|
]
|
||||||
|
@ -103,6 +103,14 @@ export const whiteStyle = (
|
|||||||
dropShadow,
|
dropShadow,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const timerStyle = () =>
|
||||||
|
getStyle({
|
||||||
|
fill: 0xffff66,
|
||||||
|
fontSize: 36,
|
||||||
|
// fontWeight: 'bold',
|
||||||
|
stroke: { color: '#000000', width: 6, join: 'round' },
|
||||||
|
})
|
||||||
|
|
||||||
export const yellowStyle = (
|
export const yellowStyle = (
|
||||||
fontSize: number = 15,
|
fontSize: number = 15,
|
||||||
fontWeight: TextStyleFontWeight = 'normal',
|
fontWeight: TextStyleFontWeight = 'normal',
|
||||||
|
@ -14,6 +14,14 @@ import { AuthenticationService } from './services/AuthenticationService'
|
|||||||
import { GameService } from './services/GameService'
|
import { GameService } from './services/GameService'
|
||||||
import { PersistenceService } from './services/PersistenceService'
|
import { PersistenceService } from './services/PersistenceService'
|
||||||
|
|
||||||
|
import * as PIXI from 'pixi.js'
|
||||||
|
import { gsap } from 'gsap'
|
||||||
|
import { PixiPlugin } from 'gsap/PixiPlugin'
|
||||||
|
import { InfoService } from './services/InfoService'
|
||||||
|
|
||||||
|
gsap.registerPlugin(PixiPlugin)
|
||||||
|
PixiPlugin.registerPIXI(PIXI)
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
app.use(createPinia())
|
app.use(createPinia())
|
||||||
@ -24,6 +32,7 @@ app.provide('socket', new SocketIoClientService(import.meta.env.VITE_SOCKET_URL)
|
|||||||
app.provide('logger', new LoggingService())
|
app.provide('logger', new LoggingService())
|
||||||
app.provide('auth', new AuthenticationService())
|
app.provide('auth', new AuthenticationService())
|
||||||
app.provide('game', new GameService())
|
app.provide('game', new GameService())
|
||||||
|
app.provide('info', new InfoService())
|
||||||
PersistenceService.getInstance()
|
PersistenceService.getInstance()
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
@ -3,13 +3,14 @@ import AuthenticatedLayout from '@/components/layouts/AuthenticatedLayout.vue'
|
|||||||
import UnauthenticatedLayout from '@/components/layouts/UnauthenticatedLayout.vue'
|
import UnauthenticatedLayout from '@/components/layouts/UnauthenticatedLayout.vue'
|
||||||
import HomeView from '@/views/HomeView.vue'
|
import HomeView from '@/views/HomeView.vue'
|
||||||
import LandingView from '@/views/LandingView.vue'
|
import LandingView from '@/views/LandingView.vue'
|
||||||
import { PersistenceService } from '@/services/PersistenceService'
|
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
|
import TestView from '@/views/TestView.vue'
|
||||||
|
import { InfoService } from '@/services/InfoService'
|
||||||
|
|
||||||
const router = createRouter({
|
const info = new InfoService()
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
|
||||||
routes: [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
component: UnauthenticatedLayout,
|
component: UnauthenticatedLayout,
|
||||||
@ -70,13 +71,30 @@ const router = createRouter({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
if (info.development) {
|
||||||
|
routes.push({
|
||||||
|
path: '/test',
|
||||||
|
component: UnauthenticatedLayout,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: TestView,
|
||||||
|
name: 'test',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes,
|
||||||
})
|
})
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
const { user } = storeToRefs(auth)
|
const { user } = storeToRefs(auth)
|
||||||
console.log('user.value :>> ', user.value)
|
|
||||||
const isLoggedIn = user.value === undefined ? false : true
|
const isLoggedIn = user.value === undefined ? false : true
|
||||||
if (to.name === 'landing' && isLoggedIn) {
|
if (to.name === 'landing' && isLoggedIn) {
|
||||||
next({ name: 'home' })
|
next({ name: 'home' })
|
||||||
|
@ -22,7 +22,8 @@ export class AuthenticationService extends ServiceBase {
|
|||||||
body: { username, password },
|
body: { username, password },
|
||||||
})
|
})
|
||||||
const { token, refreshToken } = response
|
const { token, refreshToken } = response
|
||||||
this.persist(token, refreshToken)
|
this.persist(token)
|
||||||
|
this.persistRefreshToken(refreshToken)
|
||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,18 +31,17 @@ export class AuthenticationService extends ServiceBase {
|
|||||||
this.removePersistence()
|
this.removePersistence()
|
||||||
}
|
}
|
||||||
|
|
||||||
private removePersistence() {
|
removePersistence() {
|
||||||
const { clearUser, clearToken, clearRefreshToken } = this.auth
|
const { clearUser, clearToken, clearRefreshToken } = this.auth
|
||||||
clearToken()
|
clearToken()
|
||||||
clearUser()
|
clearUser()
|
||||||
clearRefreshToken()
|
clearRefreshToken()
|
||||||
this.persistanceService.saveToken('')
|
this.persistanceService.clearTokens()
|
||||||
this.persistanceService.saveRefreshToken('')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async persist(jwt: string, refreshJwt?: string) {
|
async persist(jwt: string) {
|
||||||
const { setToken, setUser, setRefreshToken } = this.auth
|
const { setToken, setUser } = this.auth
|
||||||
const loggedUser = this.parseJwt(jwt)
|
const { user: loggedUser } = this.parseJwt(jwt)
|
||||||
setToken(jwt)
|
setToken(jwt)
|
||||||
setUser(loggedUser)
|
setUser(loggedUser)
|
||||||
try {
|
try {
|
||||||
@ -49,15 +49,17 @@ export class AuthenticationService extends ServiceBase {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(error, 'Error saving token')
|
this.logger.error(error, 'Error saving token')
|
||||||
}
|
}
|
||||||
if (refreshJwt) {
|
}
|
||||||
setRefreshToken(refreshJwt)
|
|
||||||
|
persistRefreshToken(jwt: string) {
|
||||||
|
const { setRefreshToken } = this.auth
|
||||||
|
setRefreshToken(jwt)
|
||||||
try {
|
try {
|
||||||
await this.persistanceService.saveRefreshToken(refreshJwt)
|
this.persistanceService.saveRefreshToken(jwt)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(error, 'Error saving refresh token')
|
this.logger.error(error, 'Error saving refresh token')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private parseJwt(token: string) {
|
private parseJwt(token: string) {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
@ -71,19 +73,20 @@ export class AuthenticationService extends ServiceBase {
|
|||||||
async fromStorage() {
|
async fromStorage() {
|
||||||
console.log('fromStorage')
|
console.log('fromStorage')
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
const { setToken, setUser } = auth
|
const { setToken, setRefreshToken, setUser } = auth
|
||||||
const token = await this.persistanceService.readToken()
|
const token = await this.persistanceService.readToken()
|
||||||
const refreshToken = await this.persistanceService.readRefreshToken()
|
const refreshToken = await this.persistanceService.readRefreshToken()
|
||||||
|
|
||||||
if (token && refreshToken) {
|
if (token && refreshToken) {
|
||||||
try {
|
try {
|
||||||
const parsed = this.parseJwt(token)
|
const { user: parsed } = this.parseJwt(token)
|
||||||
const isAfter = dayjs().isAfter(parsed.exp * 1000)
|
const isAfter = dayjs().isAfter(parsed.exp * 1000)
|
||||||
if (isAfter) {
|
if (isAfter) {
|
||||||
this.removePersistence()
|
this.removePersistence()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setToken(token)
|
setToken(token)
|
||||||
|
setRefreshToken(refreshToken)
|
||||||
setUser(parsed)
|
setUser(parsed)
|
||||||
this.logger.debug('Token loaded from storage', parsed)
|
this.logger.debug('Token loaded from storage', parsed)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
8
src/services/InfoService.ts
Normal file
8
src/services/InfoService.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export class InfoService {
|
||||||
|
mode: string = import.meta.env.MODE
|
||||||
|
development: boolean = import.meta.env.DEV
|
||||||
|
production: boolean = import.meta.env.PROD
|
||||||
|
tauri: boolean = window.__TAURI_METADATA__ ? true : false
|
||||||
|
ssr: boolean = import.meta.env.SSR
|
||||||
|
baseUrl: string = import.meta.env.BASE_URL
|
||||||
|
}
|
@ -27,4 +27,12 @@ export class LocalStorageService implements StorageInterface {
|
|||||||
const content: string | null = localStorage.getItem('net.xintanalabs.domino.config')
|
const content: string | null = localStorage.getItem('net.xintanalabs.domino.config')
|
||||||
return JSON.parse(content || '{}')
|
return JSON.parse(content || '{}')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteUserDataText(fileName: string) {
|
||||||
|
localStorage.removeItem(`net.xintanalabs.domino.${fileName}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteUserDataJson(fileName: string) {
|
||||||
|
localStorage.removeItem(`net.xintanalabs.domino.${fileName}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import type { AuthenticationService } from './AuthenticationService'
|
import { PersistenceService } from './PersistenceService'
|
||||||
|
import { SessionExpiredError } from '@/common/errors/SessionExpiredError'
|
||||||
|
|
||||||
interface RequestOptions {
|
interface RequestOptions {
|
||||||
uri: string
|
uri: string
|
||||||
@ -13,6 +14,7 @@ interface RequestOptions {
|
|||||||
export class NetworkService {
|
export class NetworkService {
|
||||||
private API_URL = import.meta.env.VITE_API_URL
|
private API_URL = import.meta.env.VITE_API_URL
|
||||||
private auth = useAuthStore()
|
private auth = useAuthStore()
|
||||||
|
private persistanceService: PersistenceService = PersistenceService.getInstance()
|
||||||
|
|
||||||
async post(options: RequestOptions) {
|
async post(options: RequestOptions) {
|
||||||
options.method = 'POST'
|
options.method = 'POST'
|
||||||
@ -40,7 +42,7 @@ export class NetworkService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async request(options: RequestOptions) {
|
async request(options: RequestOptions) {
|
||||||
const { uri, params } = options
|
const { uri, params, auth } = options
|
||||||
if (!uri) {
|
if (!uri) {
|
||||||
throw new Error('URL is required')
|
throw new Error('URL is required')
|
||||||
}
|
}
|
||||||
@ -48,18 +50,19 @@ export class NetworkService {
|
|||||||
const urlParams = this.getURLParams(params)
|
const urlParams = this.getURLParams(params)
|
||||||
let response = await fetch(`${this.API_URL}${uri}${urlParams}`, fetchOptions)
|
let response = await fetch(`${this.API_URL}${uri}${urlParams}`, fetchOptions)
|
||||||
|
|
||||||
if (response.status === 401) {
|
const text = await response.text()
|
||||||
|
if (!response.ok) {
|
||||||
|
if (auth && response.status === 401) {
|
||||||
const newAccessToken = await this.refresh()
|
const newAccessToken = await this.refresh()
|
||||||
if (newAccessToken) {
|
if (newAccessToken) {
|
||||||
fetchOptions.headers.Authorization = newAccessToken
|
fetchOptions.headers.Authorization = newAccessToken
|
||||||
|
this.persistanceService.saveToken(newAccessToken)
|
||||||
response = await fetch(`${this.API_URL}${uri}${urlParams}`, fetchOptions)
|
response = await fetch(`${this.API_URL}${uri}${urlParams}`, fetchOptions)
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Network response was not ok')
|
throw new Error('Network response was not ok')
|
||||||
}
|
}
|
||||||
const text = await response.text()
|
}
|
||||||
|
|
||||||
if (text === '') {
|
if (text === '') {
|
||||||
return
|
return
|
||||||
@ -71,13 +74,22 @@ export class NetworkService {
|
|||||||
async refresh() {
|
async refresh() {
|
||||||
const { refreshToken } = storeToRefs(this.auth)
|
const { refreshToken } = storeToRefs(this.auth)
|
||||||
const { setToken } = this.auth
|
const { setToken } = this.auth
|
||||||
const response = await await this.post({
|
try {
|
||||||
uri: '/refresh',
|
const fetchResponse = await fetch(`${this.API_URL}/refresh`, {
|
||||||
body: { token: refreshToken.value },
|
method: 'POST',
|
||||||
|
headers: this.getHeaders({ auth: false }),
|
||||||
|
body: JSON.stringify({ refreshToken: refreshToken.value }),
|
||||||
})
|
})
|
||||||
|
if (!fetchResponse.ok) {
|
||||||
|
throw new SessionExpiredError()
|
||||||
|
}
|
||||||
|
const response = await fetchResponse.json()
|
||||||
const { token } = response
|
const { token } = response
|
||||||
setToken(token)
|
setToken(token)
|
||||||
return token
|
return token
|
||||||
|
} catch (error) {
|
||||||
|
throw new SessionExpiredError()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getURLParams(params: any) {
|
getURLParams(params: any) {
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
|
import { InfoService } from './InfoService'
|
||||||
import { LocalStorageService } from './LocalStorageService'
|
import { LocalStorageService } from './LocalStorageService'
|
||||||
import type { StorageInterface } from './StorageInterface'
|
import type { StorageInterface } from './StorageInterface'
|
||||||
import { TauriFileStorageService } from './TauriFileStorageService'
|
import { TauriFileStorageService } from './TauriFileStorageService'
|
||||||
|
|
||||||
export class PersistenceService {
|
export class PersistenceService {
|
||||||
|
private infoService: InfoService = new InfoService()
|
||||||
private static instance: PersistenceService
|
private static instance: PersistenceService
|
||||||
private isTauri: boolean = false
|
|
||||||
private storage: StorageInterface
|
private storage: StorageInterface
|
||||||
private constructor() {
|
private constructor() {
|
||||||
this.isTauri = window.__TAURI_METADATA__ ? true : false
|
this.storage = this.infoService.tauri
|
||||||
this.storage = this.isTauri ? new TauriFileStorageService() : new LocalStorageService()
|
? new TauriFileStorageService()
|
||||||
console.log('PersistenceService created', this.isTauri)
|
: new LocalStorageService()
|
||||||
}
|
}
|
||||||
|
|
||||||
static getInstance(): PersistenceService {
|
static getInstance(): PersistenceService {
|
||||||
@ -47,6 +48,11 @@ export class PersistenceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async clearTokens() {
|
||||||
|
await this.storage.deleteUserDataText('token')
|
||||||
|
await this.storage.deleteUserDataText('refreshToken')
|
||||||
|
}
|
||||||
|
|
||||||
async saveConfig(config: any) {
|
async saveConfig(config: any) {
|
||||||
await this.storage.saveConfigData(config)
|
await this.storage.saveConfigData(config)
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,14 @@ export class SocketIoClientService extends ServiceBase {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async joinRoom(room: string): Promise<void> {
|
||||||
|
if (this.isConnected) {
|
||||||
|
this.socket.emit('client:join-room', room)
|
||||||
|
} else {
|
||||||
|
this.logger.debug('SOCKET: Not connected to server')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addEvents(): void {
|
addEvents(): void {
|
||||||
this.socket.on('disconnect', () => {
|
this.socket.on('disconnect', () => {
|
||||||
this.isConnected = false
|
this.isConnected = false
|
||||||
|
@ -5,4 +5,6 @@ export interface StorageInterface {
|
|||||||
readUserDataText(fileName: string): Promise<string>
|
readUserDataText(fileName: string): Promise<string>
|
||||||
readUserDataJson(fileName: string): Promise<any>
|
readUserDataJson(fileName: string): Promise<any>
|
||||||
readConfigData(): Promise<any>
|
readConfigData(): Promise<any>
|
||||||
|
deleteUserDataText(fileName: string): Promise<void>
|
||||||
|
deleteUserDataJson(fileName: string): Promise<void>
|
||||||
}
|
}
|
||||||
|
@ -1,75 +1,70 @@
|
|||||||
import { appConfigDir, appLocalDataDir, appCacheDir, appDataDir, join } from '@tauri-apps/api/path'
|
import { appDataDir, join } from '@tauri-apps/api/path'
|
||||||
import { writeTextFile, readTextFile, exists, createDir } from '@tauri-apps/api/fs'
|
import { writeTextFile, readTextFile, exists, createDir, removeFile } from '@tauri-apps/api/fs'
|
||||||
import type { StorageInterface } from './StorageInterface'
|
import type { StorageInterface } from './StorageInterface'
|
||||||
import { ServiceBase } from './ServiceBase'
|
import { ServiceBase } from './ServiceBase'
|
||||||
|
|
||||||
export class TauriFileStorageService extends ServiceBase implements StorageInterface {
|
export class TauriFileStorageService extends ServiceBase implements StorageInterface {
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
this.showDirs()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async showDirs() {
|
private async getFilePath(filename: string, dir: string = 'data'): Promise<string> {
|
||||||
this.logger.debug(`=> appConfigDir ${await appConfigDir()}`)
|
const userAppDir = await appDataDir()
|
||||||
this.logger.debug(`=> appLocalDataDir ${await appLocalDataDir()}`)
|
await this.ensureDirExists(await join(userAppDir, dir))
|
||||||
this.logger.debug(`=> appCacheDir ${await appCacheDir()}`)
|
return await join(userAppDir, dir, filename)
|
||||||
this.logger.debug(`=> appDataDir ${await appDataDir()}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveUserDataText(fileName: string, content: any) {
|
async saveUserDataText(fileName: string, content: any) {
|
||||||
const userAppDir = await appDataDir()
|
const filePath = await this.getFilePath(fileName + '.txt')
|
||||||
await this.ensureDirExists(userAppDir)
|
|
||||||
const filePath = await join(userAppDir, fileName + '.txt')
|
|
||||||
await this.write(filePath, content, false)
|
await this.write(filePath, content, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveUserDataJson(fileName: string, content: any) {
|
async saveUserDataJson(fileName: string, content: any) {
|
||||||
const userAppDir = await appDataDir()
|
const filePath = await this.getFilePath(fileName + '.json')
|
||||||
await this.ensureDirExists(userAppDir)
|
|
||||||
const filePath = await join(userAppDir, fileName + '.json')
|
|
||||||
await this.write(filePath, content, true)
|
await this.write(filePath, content, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
async readUserDataText(fileName: string) {
|
async readUserDataText(fileName: string) {
|
||||||
const userAppDir = await appDataDir()
|
const filePath = await this.getFilePath(fileName + '.txt')
|
||||||
await this.ensureDirExists(userAppDir)
|
|
||||||
const filePath = await join(userAppDir, fileName + '.txt')
|
|
||||||
const content = await this.read(filePath, false)
|
const content = await this.read(filePath, false)
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
async readUserDataJson(fileName: string) {
|
async readUserDataJson(fileName: string) {
|
||||||
const userAppDir = await appDataDir()
|
const filePath = await this.getFilePath(fileName + '.json')
|
||||||
await this.ensureDirExists(userAppDir)
|
|
||||||
const filePath = await join(userAppDir, fileName + '.json')
|
|
||||||
const content = await this.read(filePath, true)
|
const content = await this.read(filePath, true)
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveConfigData(content: any) {
|
async saveConfigData(content: any) {
|
||||||
const userAppDir = await appConfigDir()
|
const filePath = await this.getFilePath('config.json')
|
||||||
await this.ensureDirExists(userAppDir)
|
|
||||||
const filePath = await join(userAppDir, 'config.json')
|
|
||||||
await this.write(filePath, content)
|
await this.write(filePath, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
async readConfigData() {
|
async readConfigData() {
|
||||||
const userAppDir = await appConfigDir()
|
const filePath = await this.getFilePath('config.json')
|
||||||
await this.ensureDirExists(userAppDir)
|
|
||||||
const filePath = await join(userAppDir, 'config.json')
|
|
||||||
const content = await this.read(filePath, true)
|
const content = await this.read(filePath, true)
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteUserDataText(fileName: string) {
|
||||||
|
const filePath = await this.getFilePath(fileName + '.txt')
|
||||||
|
await removeFile(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteUserDataJson(fileName: string) {
|
||||||
|
const filePath = await this.getFilePath(fileName + '.json')
|
||||||
|
await removeFile(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
private async ensureDirExists(dir: string) {
|
private async ensureDirExists(dir: string) {
|
||||||
const dirExists = await exists(dir)
|
const dirExists = await exists(dir)
|
||||||
if (!dirExists) {
|
if (!dirExists) {
|
||||||
await createDir(dir)
|
await createDir(dir, { recursive: true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async write(filePath: string, content: any, json: boolean = false) {
|
private async write(filePath: string, content: any, json: boolean = false) {
|
||||||
this.logger.trace(`write ${filePath}`, content)
|
|
||||||
if (json) {
|
if (json) {
|
||||||
return writeTextFile(filePath, JSON.stringify(content, null, 2))
|
return writeTextFile(filePath, JSON.stringify(content, null, 2))
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
const user = ref<any | undefined>(undefined)
|
const user = ref<any | undefined>(undefined)
|
||||||
const roles = ref<string[]>([])
|
const roles = ref<string[]>([])
|
||||||
|
|
||||||
const isLoggedIn = computed(() => jwt.value !== undefined)
|
const isLoggedIn = computed(() => jwt.value !== undefined && user.value !== undefined)
|
||||||
|
|
||||||
const token = computed(() => {
|
const token = computed(() => {
|
||||||
if (jwt.value) {
|
if (jwt.value) {
|
||||||
|
@ -7,11 +7,12 @@ import type { LoggingService } from '@/services/LoggingService'
|
|||||||
import type { GameService } from '@/services/GameService'
|
import type { GameService } from '@/services/GameService'
|
||||||
import type { MatchSessionOptions, MatchSessionDto } from '@/common/interfaces'
|
import type { MatchSessionOptions, MatchSessionDto } from '@/common/interfaces'
|
||||||
import { useEventBusStore } from '@/stores/eventBus'
|
import { useEventBusStore } from '@/stores/eventBus'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
|
||||||
import { copyToclipboard, wait } from '@/common/helpers'
|
import { copyToclipboard, wait } from '@/common/helpers'
|
||||||
import { useGameOptionsStore } from '@/stores/gameOptions'
|
import { useGameOptionsStore } from '@/stores/gameOptions'
|
||||||
import MatchConfiguration from '@/components/MatchConfiguration.vue'
|
import MatchConfiguration from '@/components/MatchConfiguration.vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { SessionExpiredError } from '@/common/errors/SessionExpiredError'
|
||||||
|
import type { AuthenticationService } from '@/services/AuthenticationService'
|
||||||
|
|
||||||
let teamedWith = ref<string | undefined>(undefined)
|
let teamedWith = ref<string | undefined>(undefined)
|
||||||
|
|
||||||
@ -21,16 +22,17 @@ let loadingSessions = ref<boolean>(false)
|
|||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
const auth = useAuthStore()
|
|
||||||
const gameOptionsStore = useGameOptionsStore()
|
const gameOptionsStore = useGameOptionsStore()
|
||||||
|
|
||||||
const socketService: any = inject('socket')
|
const socketService: any = inject('socket')
|
||||||
const gameService: GameService = inject<GameService>('game') as GameService
|
const gameService: GameService = inject<GameService>('game') as GameService
|
||||||
const logger: LoggingService = inject<LoggingService>('logger') as LoggingService
|
const logger: LoggingService = inject<LoggingService>('logger') as LoggingService
|
||||||
|
const authService: AuthenticationService = inject<AuthenticationService>(
|
||||||
|
'auth',
|
||||||
|
) as AuthenticationService
|
||||||
|
|
||||||
const { sessionState, isSessionStarted, playerState, amIHost, readyForStart } =
|
const { sessionState, isSessionStarted, playerState, amIHost, readyForStart } =
|
||||||
storeToRefs(gameStore)
|
storeToRefs(gameStore)
|
||||||
const { user } = storeToRefs(auth)
|
|
||||||
const { gameOptions } = storeToRefs(gameOptionsStore)
|
const { gameOptions } = storeToRefs(gameOptionsStore)
|
||||||
const { updateSessionState, updatePlayerState, updateGameState } = gameStore
|
const { updateSessionState, updatePlayerState, updateGameState } = gameStore
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@ -44,7 +46,8 @@ async function createMatch(options: MatchSessionOptions) {
|
|||||||
logger.debug('Creating match')
|
logger.debug('Creating match')
|
||||||
await socketService.connect()
|
await socketService.connect()
|
||||||
gameOptions.value = options
|
gameOptions.value = options
|
||||||
await gameService.createMatchSession(options)
|
const sessionId = await gameService.createMatchSession(options)
|
||||||
|
socketService.joinRoom(`room-${sessionId}`)
|
||||||
logger.debug('Match created successfully')
|
logger.debug('Match created successfully')
|
||||||
// router.push({ name: 'match', params: { id } })
|
// router.push({ name: 'match', params: { id } })
|
||||||
}
|
}
|
||||||
@ -101,7 +104,8 @@ eventBus.subscribe('server:match-starting', (data) => {
|
|||||||
|
|
||||||
async function joinMatch(id: string) {
|
async function joinMatch(id: string) {
|
||||||
if (id) {
|
if (id) {
|
||||||
await socketService.connect()
|
await socketService.connect(id)
|
||||||
|
socketService.joinRoom(`room-${id}`)
|
||||||
await gameService.joinMatchSession(id)
|
await gameService.joinMatchSession(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -133,14 +137,26 @@ const isMultiplayer = computed(
|
|||||||
|
|
||||||
async function loadData() {
|
async function loadData() {
|
||||||
loadingSessions.value = true
|
loadingSessions.value = true
|
||||||
const listResponse = await gameService.listMatchSessions()
|
let listResponse
|
||||||
|
try {
|
||||||
|
listResponse = await gameService.listMatchSessions()
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error instanceof SessionExpiredError) {
|
||||||
|
authService.removePersistence()
|
||||||
|
router.push({ name: 'landing' })
|
||||||
|
} else {
|
||||||
|
logger.error('Error loading match sessions', error)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
loadingSessions.value = false
|
loadingSessions.value = false
|
||||||
matchSessions.value = listResponse.data
|
matchSessions.value = listResponse.data
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadData()
|
loadData()
|
||||||
dataInterval = setInterval(loadData, 5000)
|
dataInterval = setInterval(loadData, 10000)
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
@ -183,10 +199,10 @@ async function onStartSingleMatch(options: MatchSessionOptions) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="container home">
|
<div class="container home">
|
||||||
<section class="section">
|
<section class="mt-4">
|
||||||
<h1 class="title is-2">
|
<!-- <h1 class="title is-2">
|
||||||
{{ $t('welcome-to-the-user-username-s-home-page', [user.username]) }}
|
{{ $t('welcome-to-the-user-username-s-home-page', [user.username]) }}
|
||||||
</h1>
|
</h1> -->
|
||||||
<!-- Tabs -->
|
<!-- Tabs -->
|
||||||
<div class="block" v-if="!isSessionStarted">
|
<div class="block" v-if="!isSessionStarted">
|
||||||
<div class="tabs is-centered">
|
<div class="tabs is-centered">
|
||||||
@ -202,7 +218,7 @@ async function onStartSingleMatch(options: MatchSessionOptions) {
|
|||||||
</div>
|
</div>
|
||||||
<!-- Tabs End -->
|
<!-- Tabs End -->
|
||||||
<!-- Match Configuration -->
|
<!-- Match Configuration -->
|
||||||
<section class="section" v-if="isCreateTab">
|
<section class="section pt-5" v-if="isCreateTab">
|
||||||
<MatchConfiguration
|
<MatchConfiguration
|
||||||
@create-match="onCreateMatch"
|
@create-match="onCreateMatch"
|
||||||
@start-single-match="onStartSingleMatch"
|
@start-single-match="onStartSingleMatch"
|
||||||
|
@ -3,14 +3,12 @@ import { AuthenticationService } from '@/services/AuthenticationService'
|
|||||||
import { inject, ref } from 'vue'
|
import { inject, ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { emit, listen } from '@tauri-apps/api/event'
|
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const username = ref('')
|
const username = ref('')
|
||||||
const password = ref('')
|
const password = ref('')
|
||||||
const errorLogin = ref(false)
|
const errorLogin = ref(false)
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const isTauri = window.__TAURI_METADATA__ ? true : false
|
|
||||||
|
|
||||||
const authService = inject<AuthenticationService>('auth')
|
const authService = inject<AuthenticationService>('auth')
|
||||||
|
|
||||||
@ -51,10 +49,6 @@ async function login() {
|
|||||||
// console.log('Update download finished.')
|
// console.log('Update download finished.')
|
||||||
// // You can notify the user to restart the app
|
// // You can notify the user to restart the app
|
||||||
// })
|
// })
|
||||||
|
|
||||||
function checkForUpdates() {
|
|
||||||
emit('tauri://update')
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -98,6 +92,5 @@ function checkForUpdates() {
|
|||||||
</div> -->
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<a href="#" @click="checkForUpdates" v-if="isTauri">Update</a>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
81
src/views/TestView.vue
Normal file
81
src/views/TestView.vue
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="appEl" class="game-app"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { wait } from '@/common/helpers'
|
||||||
|
import type { MatchSessionOptions } from '@/common/interfaces'
|
||||||
|
import { dataGameStatePlayer, dataPlayer, players } from '@/common/testMocks'
|
||||||
|
import { Button } from '@/game/Button'
|
||||||
|
import { Game } from '@/game/Game'
|
||||||
|
import { TimedButton } from '@/game/TimedButton'
|
||||||
|
import { Point } from 'pixi.js'
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
|
||||||
|
const minScreenWidth = 1280
|
||||||
|
const minScreenHeight = 720
|
||||||
|
|
||||||
|
let screenWidth = window.innerWidth - 10
|
||||||
|
let screenHeight = window.innerHeight - 10
|
||||||
|
|
||||||
|
if (screenWidth < minScreenWidth) screenWidth = minScreenWidth
|
||||||
|
if (screenHeight < minScreenHeight) screenHeight = minScreenHeight
|
||||||
|
const minSide = Math.min(screenWidth, screenHeight)
|
||||||
|
const boardScale = minSide > 1440 ? 1 : minSide > 1080 ? 0.8 : minSide > 720 ? 0.7 : 0.5
|
||||||
|
|
||||||
|
let appEl = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
|
const defaultOptions: MatchSessionOptions = {
|
||||||
|
background: 'green',
|
||||||
|
teamed: false,
|
||||||
|
winType: 'points',
|
||||||
|
winTarget: 100,
|
||||||
|
seed: '',
|
||||||
|
sessionName: `Test #${Date.now()}`,
|
||||||
|
numPlayers: 1,
|
||||||
|
turnWaitSeconds: 30,
|
||||||
|
}
|
||||||
|
const gameOptionsValue: MatchSessionOptions = {
|
||||||
|
...defaultOptions,
|
||||||
|
...{ screen: { width: screenWidth, height: screenHeight, boardScale: boardScale, handScale: 1 } },
|
||||||
|
}
|
||||||
|
|
||||||
|
const game = new Game(gameOptionsValue, undefined, dataPlayer.id, 'test-session-id')
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (appEl.value === null) return
|
||||||
|
const canvas = await game.setup()
|
||||||
|
appEl.value.appendChild(canvas)
|
||||||
|
await game.preload()
|
||||||
|
game.start(players)
|
||||||
|
game.hand.update(dataPlayer)
|
||||||
|
game.updateOtherHands(dataGameStatePlayer)
|
||||||
|
await wait(1000)
|
||||||
|
game.hand.setActive(true)
|
||||||
|
game.hand.prepareForMove(true)
|
||||||
|
const btn = new Button('TEST BUTTON', new Point(100, 100), { width: 130 }, () => {
|
||||||
|
console.log('Button clicked')
|
||||||
|
})
|
||||||
|
btn.disabled = false
|
||||||
|
btn.disabledColor = 0x666666
|
||||||
|
game.stage.addChild(btn)
|
||||||
|
const timedBtn = new TimedButton(
|
||||||
|
'TIMED BUTTON',
|
||||||
|
new Point(100, 400),
|
||||||
|
{ width: 150, height: 40 },
|
||||||
|
() => {
|
||||||
|
console.log('Timed button clicked')
|
||||||
|
timedBtn.disabled = true
|
||||||
|
},
|
||||||
|
5,
|
||||||
|
)
|
||||||
|
timedBtn.on('timeout', () => {
|
||||||
|
console.log('Timed button timed out')
|
||||||
|
timedBtn.disabled = true
|
||||||
|
})
|
||||||
|
game.stage.addChild(timedBtn)
|
||||||
|
timedBtn.start()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
Loading…
x
Reference in New Issue
Block a user