This commit is contained in:
Jose Conde 2024-07-22 21:04:51 +02:00
parent a8d6129d3e
commit cd5f4ad91a
38 changed files with 1311 additions and 182 deletions

2
.gitignore vendored
View File

@ -29,4 +29,4 @@ coverage
*.tsbuildinfo
.history
.env
.env*

8
.hmrc
View File

@ -2,7 +2,7 @@
"path": "G:\\Other\\Development\\Projects\\[ideas]\\domino-client",
"name": "domino-client",
"initialVersion": "0.1.4",
"version": "0.1.8",
"version": "0.1.12",
"docker": {
"useRegistry": true,
"registry": "192.168.1.115:5000",
@ -84,7 +84,7 @@
},
"_backup": {
"name": "domino-client",
"version": "0.1.7",
"version": "0.1.11",
"private": true,
"type": "commonjs",
"scripts": {
@ -97,8 +97,8 @@
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/",
"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.7",
"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.7",
"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-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",
"publish": "npm run docker-build && npm run docker-tag && npm run docker-push",
"serve": "npm run build-only && http-server ./dist -c-1 -s ",
"tauri": "tauri"

View File

@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file.
## Unreleased
## 0.1.12 - 2024-07-22
### Added
- I18n translations
- Win conditions
### Fixed
- Multiplayer join button not accessible
## 0.1.10 - 2024-07-20
### Added
- Updater
- Refresh authentication when expires
- Match summary page phase 1
## 0.1.9 - 2024-07-19
## 0.1.8 - 2024-07-18
## 0.1.7 - 2024-07-17

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "domino-client",
"version": "0.1.7",
"version": "0.1.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "domino-client",
"version": "0.1.7",
"version": "0.1.8",
"dependencies": {
"@pixi/sound": "^6.0.0",
"@tauri-apps/api": "^1.6.0",

View File

@ -1,6 +1,6 @@
{
"name": "domino-client",
"version": "0.1.8",
"version": "0.1.12",
"private": true,
"type": "commonjs",
"scripts": {
@ -13,8 +13,8 @@
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/",
"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.8",
"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.8",
"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.12",
"publish": "npm run docker-build && npm run docker-tag && npm run docker-push",
"serve": "npm run build-only && http-server ./dist -c-1 -s ",
"tauri": "tauri"

View File

@ -1,5 +1,23 @@
<h1>Changelog</h1>
<p>All notable changes to this project will be documented in this file.</p>
<h2>0.1.12 - 2024-07-22</h2>
<h3>Added</h3>
<ul>
<li>I18n translations</li>
<li>Win conditions</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Multiplayer join button not accessible</li>
</ul>
<h2>0.1.10 - 2024-07-20</h2>
<h3>Added</h3>
<ul>
<li>Updater</li>
<li>Refresh authentication when expires</li>
<li>Match summary page phase 1</li>
</ul>
<h2>0.1.9 - 2024-07-19</h2>
<h2>0.1.8 - 2024-07-18</h2>
<h2>0.1.7 - 2024-07-17</h2>
<h2>0.1.6 - 2024-07-17</h2>

511
src-tauri/Cargo.lock generated
View File

@ -216,6 +216,9 @@ name = "bytes"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952"
dependencies = [
"serde",
]
[[package]]
name = "cairo-rs"
@ -630,7 +633,7 @@ dependencies = [
"rustc_version",
"toml 0.8.15",
"vswhom",
"winreg",
"winreg 0.52.0",
]
[[package]]
@ -794,6 +797,12 @@ dependencies = [
"syn 2.0.71",
]
[[package]]
name = "futures-sink"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
[[package]]
name = "futures-task"
version = "0.3.30"
@ -807,8 +816,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
dependencies = [
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
@ -1120,6 +1132,25 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "h2"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
dependencies = [
"bytes",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http",
"indexmap 2.2.6",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
@ -1190,12 +1221,72 @@ dependencies = [
"itoa 1.0.11",
]
[[package]]
name = "http-body"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [
"bytes",
"http",
"pin-project-lite",
]
[[package]]
name = "http-range"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
[[package]]
name = "httparse"
version = "1.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9"
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
version = "0.14.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"httparse",
"httpdate",
"itoa 1.0.11",
"pin-project-lite",
"socket2",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]]
name = "hyper-tls"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [
"bytes",
"hyper",
"native-tls",
"tokio",
"tokio-native-tls",
]
[[package]]
name = "iana-time-zone"
version = "0.1.60"
@ -1313,6 +1404,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "ipnet"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
[[package]]
name = "itoa"
version = "0.4.8"
@ -1519,6 +1616,18 @@ dependencies = [
"autocfg",
]
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minisign-verify"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "933dca44d65cdd53b355d0b73d380a2ff5da71f87f036053188bf1eab6a19881"
[[package]]
name = "miniz_oxide"
version = "0.7.4"
@ -1529,6 +1638,34 @@ dependencies = [
"simd-adler32",
]
[[package]]
name = "mio"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.48.0",
]
[[package]]
name = "native-tls"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
dependencies = [
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "ndk"
version = "0.6.0"
@ -1635,6 +1772,17 @@ dependencies = [
"objc_exception",
]
[[package]]
name = "objc-foundation"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
dependencies = [
"block",
"objc",
"objc_id",
]
[[package]]
name = "objc_exception"
version = "0.1.2"
@ -1668,6 +1816,50 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "openssl"
version = "0.10.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f"
dependencies = [
"bitflags 2.6.0",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.71",
]
[[package]]
name = "openssl-probe"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "overload"
version = "0.1.1"
@ -2151,6 +2343,72 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]]
name = "reqwest"
version = "0.11.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
dependencies = [
"base64 0.21.7",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"hyper",
"hyper-tls",
"ipnet",
"js-sys",
"log",
"mime",
"native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite",
"rustls-pemfile",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"system-configuration",
"tokio",
"tokio-native-tls",
"tokio-util",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-streams",
"web-sys",
"winreg 0.50.0",
]
[[package]]
name = "rfd"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0149778bd99b6959285b0933288206090c50e2327f47a9c463bfdbf45c8823ea"
dependencies = [
"block",
"dispatch",
"glib-sys",
"gobject-sys",
"gtk-sys",
"js-sys",
"lazy_static",
"log",
"objc",
"objc-foundation",
"objc_id",
"raw-window-handle",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"windows 0.37.0",
]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
@ -2179,6 +2437,15 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "rustls-pemfile"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
dependencies = [
"base64 0.21.7",
]
[[package]]
name = "rustversion"
version = "1.0.17"
@ -2200,6 +2467,15 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "schannel"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "scoped-tls"
version = "1.0.1"
@ -2212,6 +2488,29 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags 2.6.0",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "selectors"
version = "0.22.0"
@ -2293,6 +2592,18 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa 1.0.11",
"ryu",
"serde",
]
[[package]]
name = "serde_with"
version = "3.9.0"
@ -2402,6 +2713,16 @@ version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "socket2"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "soup2"
version = "0.2.1"
@ -2499,6 +2820,33 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "sync_wrapper"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
name = "system-configuration"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "system-deps"
version = "5.0.0"
@ -2607,6 +2955,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "336bc661a3f3250853fa83c6e5245449ed1c26dce5dcb28bdee7efedf6278806"
dependencies = [
"anyhow",
"base64 0.21.7",
"bytes",
"cocoa",
"dirs-next",
"dunce",
@ -2621,11 +2971,15 @@ dependencies = [
"heck 0.5.0",
"http",
"ignore",
"indexmap 1.9.3",
"minisign-verify",
"objc",
"once_cell",
"percent-encoding",
"rand 0.8.5",
"raw-window-handle",
"reqwest",
"rfd",
"semver",
"serde",
"serde_json",
@ -2639,12 +2993,14 @@ dependencies = [
"tauri-utils",
"tempfile",
"thiserror",
"time",
"tokio",
"url",
"uuid",
"webkit2gtk",
"webview2-com",
"windows 0.39.0",
"zip",
]
[[package]]
@ -2899,8 +3255,35 @@ checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"num_cpus",
"pin-project-lite",
"socket2",
"windows-sys 0.48.0",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
]
[[package]]
@ -2971,6 +3354,12 @@ dependencies = [
"winnow 0.6.13",
]
[[package]]
name = "tower-service"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
[[package]]
name = "tracing"
version = "0.1.40"
@ -3032,6 +3421,12 @@ dependencies = [
"tracing-log",
]
[[package]]
name = "try-lock"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "typenum"
version = "1.17.0"
@ -3098,6 +3493,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version-compare"
version = "0.0.11"
@ -3146,6 +3547,15 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [
"try-lock",
]
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
@ -3183,6 +3593,18 @@ dependencies = [
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.92"
@ -3212,6 +3634,29 @@ version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
[[package]]
name = "wasm-streams"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129"
dependencies = [
"futures-util",
"js-sys",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "web-sys"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "webkit2gtk"
version = "0.18.2"
@ -3328,6 +3773,19 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647"
dependencies = [
"windows_aarch64_msvc 0.37.0",
"windows_i686_gnu 0.37.0",
"windows_i686_msvc 0.37.0",
"windows_x86_64_gnu 0.37.0",
"windows_x86_64_msvc 0.37.0",
]
[[package]]
name = "windows"
version = "0.39.0"
@ -3462,6 +3920,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a"
[[package]]
name = "windows_aarch64_msvc"
version = "0.39.0"
@ -3480,6 +3944,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1"
[[package]]
name = "windows_i686_gnu"
version = "0.39.0"
@ -3504,6 +3974,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c"
[[package]]
name = "windows_i686_msvc"
version = "0.39.0"
@ -3522,6 +3998,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d"
[[package]]
name = "windows_x86_64_gnu"
version = "0.39.0"
@ -3552,6 +4034,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.39.0"
@ -3588,6 +4076,16 @@ dependencies = [
"memchr",
]
[[package]]
name = "winreg"
version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]]
name = "winreg"
version = "0.52.0"
@ -3667,3 +4165,14 @@ dependencies = [
"linux-raw-sys",
"rustix",
]
[[package]]
name = "zip"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
dependencies = [
"byteorder",
"crc32fast",
"crossbeam-utils",
]

View File

@ -17,7 +17,7 @@ tauri-build = { version = "1.5.3", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.7.0", features = [] }
tauri = { version = "1.7.0", features = [ "fs-all", "path-all", "updater"] }
[features]
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.

View File

@ -8,11 +8,26 @@
},
"package": {
"productName": "domino-client",
"version": "0.1.8"
"version": "0.1.11"
},
"tauri": {
"allowlist": {
"all": false
"path": {
"all": true
},
"fs": {
"copyFile": true,
"createDir": true,
"exists": true,
"readDir": true,
"readFile": true,
"removeDir": true,
"removeFile": true,
"renameFile": true,
"scope": ["$APPDATA/*", "$APP/*"],
"writeFile": true,
"all": true
}
},
"bundle": {
"active": true,
@ -51,14 +66,19 @@
"csp": null
},
"updater": {
"active": false
"active": true,
"dialog": true,
"endpoints": [
"https://domserv.xintanalabs.net/api/updater/{{target}}/{{arch}}/{{current_version}}"
],
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDQwRTBDNUU1QzIyNEYzQUQKUldTdDh5VEM1Y1hnUVA2M01pcEZMbVltVUpuMlhpN09Rc3BnN1A3NC9BOGs5OE92MndRZzVXSm4K"
},
"windows": [
{
"fullscreen": false,
"height": 720,
"resizable": true,
"title": "Domino",
"title": "Domino v0.1.11",
"width": 1280,
"minHeight": 720,
"minWidth": 1280,

View File

@ -57,8 +57,6 @@ onUnmounted(() => {
})
</script>
<template>
<RouterView />
</template>
<template><RouterView /></template>
<style scoped></style>

View File

@ -111,3 +111,34 @@ export function copyToclipboard(value: string) {
const { toClipboard } = useClipboard()
toClipboard(value)
}
export function transposeMatrix(matrix: any[][]): any[][] {
// Get the number of rows and columns
const numRows = matrix.length
const numCols = matrix[0].length
// Create a new matrix with transposed dimensions
const transposed = Array.from({ length: numCols }, () => Array(numRows).fill(null))
// Transpose the matrix
for (let row = 0; row < numRows; row++) {
for (let col = 0; col < numCols; col++) {
transposed[col][row] = matrix[row][col]
}
}
return transposed
}
export function createStringMatrix(
rows: number,
cols: number,
initialValue: string = '',
): string[][] {
// Create an array of arrays (matrix)
const matrix: string[][] = Array.from({ length: rows }, () =>
Array.from({ length: cols }, () => initialValue),
)
return matrix
}

View File

@ -1,8 +1,8 @@
<script setup lang="ts">
import type { MatchSessionOptions } from '@/common/interfaces'
import { ref } from 'vue'
import { computed, ref } from 'vue'
const emit = defineEmits(['createMatch'])
const emit = defineEmits(['createMatch', 'startSingleMatch'])
let options = ref<MatchSessionOptions>({
background: 'green',
@ -14,9 +14,42 @@ let options = ref<MatchSessionOptions>({
numPlayers: 1,
})
const winTargetPointsList = [20, 50, 80, 100, 150, 200]
const winTargetRoundsList = [1, 2, 3, 4, 5, 6]
const backgroundOptiopnList = [
{
label: 'green-fabric',
value: 'green',
},
{
label: 'gray-fabric',
value: 'gray',
},
{
label: 'blue-fabric',
value: 'blue',
},
{
label: 'yellow-fabric',
value: 'yellow',
},
{
label: 'red-fabric',
value: 'red',
},
]
const isSinglePlayer = computed(() => options.value.numPlayers === 1)
const isMultiPlayer = computed(() => options.value.numPlayers > 1)
function createMatch() {
emit('createMatch', options.value)
}
function startSingleMatch() {
emit('startSingleMatch', options.value)
}
</script>
<template>
@ -29,7 +62,7 @@ function createMatch() {
<div class="buttons has-addons">
<button
class="button"
:class="{ 'is-primary is-selected': options.numPlayers === 1 }"
:class="{ 'is-primary is-selected': isSinglePlayer }"
@click="
() => {
console.log('options :>> ', options)
@ -41,7 +74,7 @@ function createMatch() {
</button>
<button
class="button"
:class="{ 'is-primary is-selected': options.numPlayers > 1 }"
:class="{ 'is-primary is-selected': isMultiPlayer }"
@click="
() => {
console.log('options :>> ', options)
@ -56,7 +89,7 @@ function createMatch() {
</div>
</div>
<div class="cell">
<div class="field" v-if="options.numPlayers > 1">
<div class="field" v-if="isMultiPlayer">
<label class="label">{{ $t('players-number') }}</label>
<div class="control">
<div class="buttons has-addons">
@ -80,7 +113,7 @@ function createMatch() {
</div>
</div>
<div class="cell">
<div class="field" v-if="options.numPlayers > 1">
<div class="field" v-if="isMultiPlayer">
<div class="control">
<label for="teamed" class="checkbox">
<input v-model="options.teamed" name="teamed" type="checkbox" />
@ -128,12 +161,13 @@ function createMatch() {
<div class="control">
<div class="select">
<select v-model="options.background" name="background">
<option value="wood-1">{{ $t('wood-1') }}</option>
<option value="green">{{ $t('green-fabric') }}</option>
<option value="gray">{{ $t('gray-fabric') }}</option>
<option value="blue">{{ $t('blue-fabric') }}</option>
<option value="yellow">{{ $t('yellow-fabric') }}</option>
<option value="red">{{ $t('red-fabric') }}</option>
<option
v-bind:key="option.value"
v-for="option in backgroundOptiopnList"
:value="option.value"
>
{{ $t(option.label) }}
</option>
</select>
</div>
</div>
@ -178,22 +212,24 @@ function createMatch() {
<div class="control">
<div class="select" v-if="options.winType === 'points'">
<select v-model="options.winTarget" name="winTarget">
<option value="20">{{ $t('n-points', [20]) }}</option>
<option value="50">{{ $t('n-points', [50]) }}</option>
<option value="80">{{ $t('n-points', [80]) }}</option>
<option value="100">{{ $t('n-points', [100]) }}</option>
<option value="150">{{ $t('n-points', [150]) }}</option>
<option value="200">{{ $t('n-points', [200]) }}</option>
<option
v-bind:key="winTarget"
v-for="winTarget in winTargetPointsList"
:value="winTarget"
>
{{ $t('n-points', winTarget) }}
</option>
</select>
</div>
<div class="select" v-if="options.winType === 'rounds'">
<select v-model="options.winTarget" name="winTarget">
<option value="1">{{ $t('n-of-m-rounds', [1, 1]) }}</option>
<option value="2">{{ $t('n-of-m-rounds', [2, 3]) }}</option>
<option value="3">{{ $t('n-of-m-rounds', [3, 5]) }}</option>
<option value="4">{{ $t('n-of-m-rounds', [4, 7]) }}</option>
<option value="5">{{ $t('n-of-m-rounds', [5, 9]) }}</option>
<option value="6">{{ $t('n-of-m-rounds', [6, 11]) }}</option>
<option
v-bind:key="winTarget"
v-for="winTarget in winTargetRoundsList"
:value="winTarget"
>
{{ $t('n-of-m-rounds', [winTarget, winTarget * 2 - 1]) }}
</option>
</select>
</div>
</div>
@ -201,9 +237,12 @@ function createMatch() {
</div>
</div>
<div class="buttons mt-6">
<button class="button is-primary" @click.prevent="createMatch">
<button class="button is-primary" @click.prevent="createMatch" v-if="isMultiPlayer">
{{ $t('create-match-session') }}
</button>
<button class="button is-primary" @click.prevent="startSingleMatch" v-if="isSinglePlayer">
{{ $t('start-game') }}
</button>
</div>
</div>
</template>

View File

@ -0,0 +1,24 @@
<template>
<h3 class="title is-5">{{ title }}</h3>
<div v-if="scoreboard">
<div v-bind:key="$index" v-for="(score, $index) in sortedScoreboard">
<p class="">
<span class="title is-5">{{ score.name }}</span>
<span class="is-size-5 ml-4">{{ score.score }}</span>
</p>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
const { title, scoreboard } = defineProps(['scoreboard', 'title'])
const sortedScoreboard = computed(() => {
const copy = [...(scoreboard || [])]
return copy.sort((a, b) => b.score - a.score)
})
</script>
<style scoped></style>

View File

@ -0,0 +1,77 @@
<script setup lang="ts">
import { createStringMatrix } from '@/common/helpers'
import { computed } from 'vue'
const props = defineProps(['games', 'finalScore', 'winner'])
const playerNames = computed<any[]>(() =>
(props.finalScore || []).map((score: any) => {
const type = score.name === props.winner.name ? 'winner-name' : 'name'
return { type, value: score.name }
}),
)
const totals = computed<any[]>(() =>
props.finalScore.map((score: any) => {
const type = score.name === props.winner.name ? 'winner-final-score' : 'final-score'
return { type, value: `${score.score}` }
}),
)
const matrix = computed<any[][]>(() => {
if (props.games === undefined) {
return []
}
const m = props.games.map((game: any) => {
const winner = game.winner.name
return game.players.map((player: any) => {
const type = player.name === winner ? 'winner-score' : 'score'
return { type, value: `${player.score}` }
})
})
m.unshift(playerNames.value)
m.push(totals.value)
const rows = m.length
const cols = m[0].length
const t: any[][] = createStringMatrix(cols, rows)
try {
for (let row = 0; row < m.length; row++) {
for (let col = 0; col < m[0].length; col++) {
t[col][row] = m[row][col]
}
}
} catch (error) {
console.error('error :>> ', error)
}
return t
})
function getCellClasses(value: any) {
const { type } = value
return {
'has-text-weight-bold':
type === 'name' ||
type === 'final-score' ||
type === 'winner-final-score' ||
type === 'winner-name',
'has-text-primary':
type === 'winner-score' || type === 'winner-name' || type === 'winner-final-score',
}
}
</script>
<template>
<table class="table is-striped is-fullwidth is-hoverable">
<thead>
<th>{{ $t('player') }}</th>
<th v-for="(game, $index) in games" :key="game">{{ $t('round-index', [$index]) }}</th>
<th>{{ $t('final-score') }}</th>
</thead>
<tbody>
<tr v-for="(row, $index) in matrix" :key="$index">
<td :class="getCellClasses(col)" v-for="(col, $index) in row" :key="$index">
{{ col.value }}
</td>
</tr>
</tbody>
</table>
</template>
<style scoped></style>

View File

@ -1,24 +1,33 @@
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { RouterLink, RouterView } from 'vue-router'
import { RouterView } from 'vue-router'
const router = useRouter()
const props = defineProps({
navbar: Boolean,
})
defineOptions({
name: 'AuthenticatedLayout'
name: 'AuthenticatedLayout',
})
function logout() {
localStorage.removeItem('isLoggedIn')
router.push({ name: 'landing' })
}
</script>
<template>
<div class="authenticated-layout">
<header>
<nav>
<!-- <button @click="logout">Logout</button> -->
<header v-if="props.navbar">
<nav class="navbar">
<div class="navbar-end">
<div class="navbar-item">
<div class="buttons">
<button class="button is-primary" @click="logout">Logout</button>
</div>
</div>
</div>
</nav>
</header>
<main>

View File

@ -162,6 +162,7 @@ export class Board extends EventEmitter {
this.nextTile = tile
lastMove.tile = tile.toPlain()
this.movements.push(lastMove)
console.log('this.movements :>> ', this.movements)
await this.addTile(tile, lastMove)
this.setFreeEnd(lastMove)
}

View File

@ -17,14 +17,8 @@ import { Actions } from 'pixi-actions'
import { OtherHand } from './OtherHand'
import { GameSummayView } from './GameSummayView'
import Config from './Config'
interface GameOptions {
boardScale: number
handScale: number
width: number
height: number
background: string
}
import { createText, grayStyle } from './utilities/fonts'
import { t } from '@/i18n'
export class Game extends EventEmitter {
public board!: Board
@ -91,6 +85,25 @@ export class Game extends EventEmitter {
const background = new TilingSprite(Assets.get(`bg-${this.options.background}`))
this.backgroundLayer.addChild(background)
const actor = this.options.teamed ? t('team') : t('player')
const type =
this.options.winType === 'points'
? t('n-points', this.options.winTarget)
: t('n-rounds', this.options.winTarget)
const helptext = t('first-actor-to-win-this-options-wintarget-this-options-wintype', [
actor.toLowerCase(),
type.toLowerCase(),
])
this.backgroundLayer.addChild(
createText({
text: `${helptext}`,
x: this.app.canvas.width / 2,
y: 120,
style: grayStyle(14, 'lighter', false),
}),
)
}
initPlayers(players: PlayerDto[]) {

View File

@ -2,6 +2,7 @@ import { createButton, createContainer } from '@/common/helpers'
import type { GameSummary, MatchSessionDto, MatchSessionOptions } from '@/common/interfaces'
import { EventEmitter, type Application, type Container } from 'pixi.js'
import { createText, whiteStyle, yellowStyle } from './utilities/fonts'
import { t } from '@/i18n'
export class GameSummayView extends EventEmitter {
public width: number
@ -59,7 +60,7 @@ export class GameSummayView extends EventEmitter {
let line = y + 12
this.layer.addChild(
createText({
text: `Winner: ${this.gameSummary.winner.name}`,
text: t('winner-name', [this.gameSummary.winner.name]),
x: this.width / 2,
y: line,
style: whiteStyle(20),
@ -70,7 +71,7 @@ export class GameSummayView extends EventEmitter {
line += 30
this.layer.addChild(
createText({
text: '(Blocked)',
text: `(${t('blocked')})`,
x: this.width / 2,
y: line,
style: whiteStyle(),
@ -78,15 +79,29 @@ export class GameSummayView extends EventEmitter {
)
}
line += 30
this.layer.addChild(
createText({
text: `Points this round: ${this.gameSummary.winner.score}`,
x: this.width / 2,
y: line,
style: whiteStyle(20),
}),
)
if (this.options.winType === 'points') {
line += 30
this.layer.addChild(
createText({
text: `Points this round: ${this.gameSummary.winner.score}`,
// text: `Points this round: ${this.gameSummary.winner.score}, needed to win: ${this.options.winTarget}`,
x: this.width / 2,
y: line,
style: whiteStyle(20),
}),
)
}
// } else if (this.options.winType === 'rounds') {
// line += 30
// this.layer.addChild(
// createText({
// text: `Rounds needed to win: ${this.options.winTarget}`,
// x: this.width / 2,
// y: line,
// style: whiteStyle(20),
// }),
// )
// }
return line + 16
}
@ -159,7 +174,7 @@ export class GameSummayView extends EventEmitter {
}
render() {
const title: string = this.type === 'round' ? 'Round Summary' : 'Match Finished!'
const title: string = this.type === 'round' ? t('round-summary') : t('match-finished')
this.layer.removeChildren()
let y = this.renderTitle(30, title.toUpperCase())
y = this.renderWinner(y)

View File

@ -57,19 +57,19 @@ export const scoreText = new TextStyle({
function getStyle(styleOptions: TextStyleOptions = {}) {
const {
fill = 0xa2a2a2,
stroke = 0x565656,
fontSize = 15,
fontFamily = 'Arial, Helvetica, sans-serif',
fontWeight = 'normal',
fontStyle = 'normal',
dropShadow,
letterSpacing = 1,
stroke,
} = styleOptions
const style = new TextStyle({
fill,
fontFamily,
letterSpacing,
stroke,
stroke: stroke ? stroke : undefined,
fontSize,
fontStyle,
fontWeight: fontWeight as any,
@ -78,6 +78,20 @@ function getStyle(styleOptions: TextStyleOptions = {}) {
return style
}
const styleFactory = (fill: number) => {
return (
fontSize: number = 15,
fontWeight: TextStyleFontWeight = 'normal',
dropShadow: boolean = false,
) =>
getStyle({
fill,
fontSize,
fontWeight,
dropShadow,
})
}
export const whiteStyle = (
fontSize: number = 15,
fontWeight: TextStyleFontWeight = 'normal',
@ -101,6 +115,8 @@ export const yellowStyle = (
dropShadow,
})
export const grayStyle = styleFactory(0x444444)
interface TextOptions {
text: string
x: number

View File

@ -55,9 +55,20 @@
"win-type": "Win unit",
"points": "Points",
"rounds": "Rounds",
"n-points": "{value} Points",
"n-points": "{count} Points",
"n-rounds": "One Round|{count} Rounds",
"n-of-m-rounds": "{0} of {1} Rounds",
"create-session": "Create Session",
"join-a-multiplayer-session": "Join a Multiplayer Session",
"tournaments": "Tournaments"
"join-a-multiplayer-session": "Join a Multiplayer Session (No sessions)|Join a Multiplayer Session ({count})|Join a Multiplayer Session ({count})",
"tournaments": "Tournaments",
"start-game": "Start Game",
"player": "Player",
"final-score": "Final Score",
"round-index": "Round #{0}",
"first-actor-to-win-this-options-wintarget-this-options-wintype": "First {0} to win {1}",
"team": "team",
"winner-name": "Winner: {0}",
"blocked": "Blocked",
"round-summary": "Round Summary",
"match-finished": "Match Finished"
}

View File

@ -55,9 +55,20 @@
"win-type": "Unidad de puntaje",
"points": "Puntos",
"rounds": "Rondas",
"n-points": "{0} puntos",
"n-points": "{count} puntos",
"n-of-m-rounds": "{0} de {1} rondas",
"create-session": "Crear sesión",
"join-a-multiplayer-session": "Únete a una sesión multijugador",
"tournaments": "Torneos"
"join-a-multiplayer-session": "Únete a una sesión multijugador|Únete a una sesión multijugador ({count})|Únete a una sesión multijugador ({count})",
"tournaments": "Torneos",
"start-game": "Empezar la partida",
"player": "Jugador",
"final-score": "Puntuación final",
"round-index": "Juego #{0}",
"first-actor-to-win-this-options-wintarget-this-options-wintype": "Primer {0} en ganar {1}",
"n-rounds": "Una ronda|{count} rondas",
"winner-name": "Ganador: {0}",
"blocked": "Cerrado",
"round-summary": "Resumen de la ronda",
"match-finished": "Partida terminado",
"team": "equipo"
}

View File

@ -12,6 +12,7 @@ import { SocketIoClientService } from '@/services/SocketIoClientService'
import { LoggingService } from '@/services/LoggingService'
import { AuthenticationService } from './services/AuthenticationService'
import { GameService } from './services/GameService'
import { PersistenceService } from './services/PersistenceService'
const app = createApp(App)
@ -23,5 +24,6 @@ app.provide('socket', new SocketIoClientService(import.meta.env.VITE_SOCKET_URL)
app.provide('logger', new LoggingService())
app.provide('auth', new AuthenticationService())
app.provide('game', new GameService())
PersistenceService.getInstance()
app.mount('#app')

View File

@ -3,6 +3,9 @@ import AuthenticatedLayout from '@/components/layouts/AuthenticatedLayout.vue'
import UnauthenticatedLayout from '@/components/layouts/UnauthenticatedLayout.vue'
import HomeView from '@/views/HomeView.vue'
import LandingView from '@/views/LandingView.vue'
import { PersistenceService } from '@/services/PersistenceService'
import { useAuthStore } from '@/stores/auth'
import { storeToRefs } from 'pinia'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
@ -19,6 +22,7 @@ const router = createRouter({
],
},
{
props: { navbar: true },
path: '/home',
component: AuthenticatedLayout,
children: [
@ -37,6 +41,7 @@ const router = createRouter({
// component: () => import('../views/AboutView.vue')
},
{
props: { navbar: true },
path: '/match/:id',
component: AuthenticatedLayout,
children: [
@ -51,6 +56,7 @@ const router = createRouter({
],
},
{
props: { navbar: false },
path: '/game/:id',
component: AuthenticatedLayout,
children: [
@ -68,8 +74,13 @@ const router = createRouter({
})
router.beforeEach((to, from, next) => {
const isLoggedIn = !!sessionStorage.getItem('token')
if (to.matched.some((record) => record.meta.requiresAuth) && !isLoggedIn) {
const auth = useAuthStore()
const { user } = storeToRefs(auth)
console.log('user.value :>> ', user.value)
const isLoggedIn = user.value === undefined ? false : true
if (to.name === 'landing' && isLoggedIn) {
next({ name: 'home' })
} else if (to.matched.some((record) => record.meta.requiresAuth) && !isLoggedIn) {
next({ name: 'landing' })
} else {
next()

View File

@ -3,9 +3,12 @@ import { useAuthStore } from '@/stores/auth'
import { storeToRefs } from 'pinia'
import { NetworkService } from '@/services/NetworkService'
import dayjs from 'dayjs'
import { PersistenceService } from './PersistenceService'
export class AuthenticationService extends ServiceBase {
private networkService = new NetworkService()
private auth = useAuthStore()
private persistanceService: PersistenceService = PersistenceService.getInstance()
isAuthenticated() {
const auth = useAuthStore()
@ -14,17 +17,13 @@ export class AuthenticationService extends ServiceBase {
}
async login(username: string, password: string) {
try {
const res = await this.networkService.post({
uri: '/login',
body: { username, password }
})
const { token } = res
this.persist(token)
return token
} catch (error) {
console.error(error)
}
const response = await this.networkService.post({
uri: '/login',
body: { username, password },
})
const { token, refreshToken } = response
this.persist(token, refreshToken)
return token
}
async logout() {
@ -32,20 +31,32 @@ export class AuthenticationService extends ServiceBase {
}
private removePersistence() {
const auth = useAuthStore()
const { setJwt, setUser } = auth
setJwt(undefined)
setUser(undefined)
sessionStorage.removeItem('token')
const { clearUser, clearToken, clearRefreshToken } = this.auth
clearToken()
clearUser()
clearRefreshToken()
this.persistanceService.saveToken('')
this.persistanceService.saveRefreshToken('')
}
private persist(jwt: string) {
const auth = useAuthStore()
const { setJwt, setUser } = auth
async persist(jwt: string, refreshJwt?: string) {
const { setToken, setUser, setRefreshToken } = this.auth
const loggedUser = this.parseJwt(jwt)
setJwt(jwt)
setToken(jwt)
setUser(loggedUser)
sessionStorage.setItem('token', jwt)
try {
await this.persistanceService.saveToken(jwt)
} catch (error) {
this.logger.error(error, 'Error saving token')
}
if (refreshJwt) {
setRefreshToken(refreshJwt)
try {
await this.persistanceService.saveRefreshToken(refreshJwt)
} catch (error) {
this.logger.error(error, 'Error saving refresh token')
}
}
}
private parseJwt(token: string) {
@ -57,9 +68,14 @@ export class AuthenticationService extends ServiceBase {
return JSON.parse(window.atob(base64))
}
fromStorage() {
const token = sessionStorage.getItem('token')
if (token) {
async fromStorage() {
console.log('fromStorage')
const auth = useAuthStore()
const { setToken, setUser } = auth
const token = await this.persistanceService.readToken()
const refreshToken = await this.persistanceService.readRefreshToken()
if (token && refreshToken) {
try {
const parsed = this.parseJwt(token)
const isAfter = dayjs().isAfter(parsed.exp * 1000)
@ -67,7 +83,8 @@ export class AuthenticationService extends ServiceBase {
this.removePersistence()
return
}
this.persist(token)
setToken(token)
setUser(parsed)
this.logger.debug('Token loaded from storage', parsed)
} catch (error) {
this.logger.error(error, 'Error parsing token')

View File

@ -0,0 +1,30 @@
import type { StorageInterface } from './StorageInterface'
export class LocalStorageService implements StorageInterface {
async saveUserDataText(fileName: string, content: any) {
localStorage.setItem(`net.xintanalabs.domino.${fileName}`, content)
}
async saveUserDataJson(fileName: string, content: any) {
localStorage.setItem(`net.xintanalabs.domino.${fileName}`, JSON.stringify(content))
}
async saveConfigData(content: any) {
localStorage.setItem('net.xintanalabs.domino.config', JSON.stringify(content))
}
async readUserDataText(fileName: string) {
const content: string | null = localStorage.getItem(`net.xintanalabs.domino.${fileName}`)
return content || ''
}
async readUserDataJson(fileName: string) {
const content: string | null = localStorage.getItem(`net.xintanalabs.domino.${fileName}`)
return JSON.parse(content || 'null')
}
async readConfigData() {
const content: string | null = localStorage.getItem('net.xintanalabs.domino.config')
return JSON.parse(content || '{}')
}
}

View File

@ -1,5 +1,6 @@
import { useAuthStore } from '@/stores/auth'
import { storeToRefs } from 'pinia'
import type { AuthenticationService } from './AuthenticationService'
interface RequestOptions {
uri: string
@ -45,12 +46,20 @@ export class NetworkService {
}
const fetchOptions = this.getFetchOptions(options)
const urlParams = this.getURLParams(params)
const res = await fetch(`${this.API_URL}${uri}${urlParams}`, fetchOptions)
let response = await fetch(`${this.API_URL}${uri}${urlParams}`, fetchOptions)
if (!res.ok) {
if (response.status === 401) {
const newAccessToken = await this.refresh()
if (newAccessToken) {
fetchOptions.headers.Authorization = newAccessToken
response = await fetch(`${this.API_URL}${uri}${urlParams}`, fetchOptions)
}
}
if (!response.ok) {
throw new Error('Network response was not ok')
}
const text = await res.text()
const text = await response.text()
if (text === '') {
return
@ -59,6 +68,18 @@ export class NetworkService {
}
}
async refresh() {
const { refreshToken } = storeToRefs(this.auth)
const { setToken } = this.auth
const response = await await this.post({
uri: '/refresh',
body: { token: refreshToken.value },
})
const { token } = response
setToken(token)
return token
}
getURLParams(params: any) {
if (!params) {
return ''
@ -72,7 +93,7 @@ export class NetworkService {
const { body, auth, method = 'GET' } = opts
const options: any = {
method,
headers: this.getHeaders({ auth })
headers: this.getHeaders({ auth }),
}
if (!['GET', 'HEAD'].includes(method) && body) {
options.body = typeof body === 'string' ? body : JSON.stringify(body)
@ -84,7 +105,7 @@ export class NetworkService {
getHeaders({ auth = true }): any {
const { jwt } = storeToRefs(this.auth)
const headers: any = {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
}
if (auth) {
headers.Authorization = jwt.value

View File

@ -0,0 +1,58 @@
import { LocalStorageService } from './LocalStorageService'
import type { StorageInterface } from './StorageInterface'
import { TauriFileStorageService } from './TauriFileStorageService'
export class PersistenceService {
private static instance: PersistenceService
private isTauri: boolean = false
private storage: StorageInterface
private constructor() {
this.isTauri = window.__TAURI_METADATA__ ? true : false
this.storage = this.isTauri ? new TauriFileStorageService() : new LocalStorageService()
console.log('PersistenceService created', this.isTauri)
}
static getInstance(): PersistenceService {
if (!PersistenceService.instance) {
PersistenceService.instance = new PersistenceService()
}
return PersistenceService.instance
}
async saveToken(token: string) {
await this.storage.saveUserDataText('token', token)
}
async saveRefreshToken(refreshToken: string) {
await this.storage.saveUserDataText('refreshToken', refreshToken)
}
async readToken(): Promise<string> {
try {
const token = await this.storage.readUserDataText('token')
return token
} catch (error) {
console.error(error)
return ''
}
}
async readRefreshToken() {
try {
const refreshToken = await this.storage.readUserDataText('refreshToken')
return refreshToken
} catch (error) {
console.error(error)
return ''
}
}
async saveConfig(config: any) {
await this.storage.saveConfigData(config)
}
async readConfig() {
const config = await this.storage.readConfigData()
return config
}
}

View File

@ -0,0 +1,8 @@
export interface StorageInterface {
saveUserDataText(fileName: string, content: any): Promise<void>
saveUserDataJson(fileName: string, content: any): Promise<void>
saveConfigData(content: any): Promise<void>
readUserDataText(fileName: string): Promise<string>
readUserDataJson(fileName: string): Promise<any>
readConfigData(): Promise<any>
}

View File

@ -0,0 +1,94 @@
import { appConfigDir, appLocalDataDir, appCacheDir, appDataDir, join } from '@tauri-apps/api/path'
import { writeTextFile, readTextFile, exists, createDir } from '@tauri-apps/api/fs'
import type { StorageInterface } from './StorageInterface'
import { ServiceBase } from './ServiceBase'
export class TauriFileStorageService extends ServiceBase implements StorageInterface {
constructor() {
super()
this.showDirs()
}
async showDirs() {
this.logger.debug(`=> appConfigDir ${await appConfigDir()}`)
this.logger.debug(`=> appLocalDataDir ${await appLocalDataDir()}`)
this.logger.debug(`=> appCacheDir ${await appCacheDir()}`)
this.logger.debug(`=> appDataDir ${await appDataDir()}`)
}
async saveUserDataText(fileName: string, content: any) {
const userAppDir = await appDataDir()
await this.ensureDirExists(userAppDir)
const filePath = await join(userAppDir, fileName + '.txt')
await this.write(filePath, content, false)
}
async saveUserDataJson(fileName: string, content: any) {
const userAppDir = await appDataDir()
await this.ensureDirExists(userAppDir)
const filePath = await join(userAppDir, fileName + '.json')
await this.write(filePath, content, true)
}
async readUserDataText(fileName: string) {
const userAppDir = await appDataDir()
await this.ensureDirExists(userAppDir)
const filePath = await join(userAppDir, fileName + '.txt')
const content = await this.read(filePath, false)
return content
}
async readUserDataJson(fileName: string) {
const userAppDir = await appDataDir()
await this.ensureDirExists(userAppDir)
const filePath = await join(userAppDir, fileName + '.json')
const content = await this.read(filePath, true)
return content
}
async saveConfigData(content: any) {
const userAppDir = await appConfigDir()
await this.ensureDirExists(userAppDir)
const filePath = await join(userAppDir, 'config.json')
await this.write(filePath, content)
}
async readConfigData() {
const userAppDir = await appConfigDir()
await this.ensureDirExists(userAppDir)
const filePath = await join(userAppDir, 'config.json')
const content = await this.read(filePath, true)
return content
}
private async ensureDirExists(dir: string) {
const dirExists = await exists(dir)
if (!dirExists) {
await createDir(dir)
}
}
private async write(filePath: string, content: any, json: boolean = false) {
this.logger.trace(`write ${filePath}`, content)
if (json) {
return writeTextFile(filePath, JSON.stringify(content, null, 2))
}
return writeTextFile(filePath, content)
}
private async read(filePath: string, json: boolean = false) {
let content = null
try {
if (await exists(filePath)) {
content = await readTextFile(filePath)
if (json) {
content = JSON.parse(content as string)
}
}
} catch (error) {
this.logger.error(error)
}
return content
}
}

View File

@ -3,15 +3,42 @@ import { computed, ref } from 'vue'
export const useAuthStore = defineStore('auth', () => {
const jwt = ref<string | undefined>(undefined)
const refreshJwt = ref<string | undefined>(undefined)
const user = ref<any | undefined>(undefined)
const roles = ref<string[]>([])
const isLoggedIn = computed(() => jwt.value !== undefined)
function setJwt(token: string | undefined) {
const token = computed(() => {
if (jwt.value) {
return jwt.value
}
return sessionStorage.getItem('token')
})
const refreshToken = computed(() => {
if (refreshJwt.value) {
return refreshJwt.value
}
return sessionStorage.getItem('token_refresh')
})
function setToken(token: string | undefined) {
jwt.value = token
}
function clearToken() {
jwt.value = undefined
}
function setRefreshToken(token: string | undefined) {
refreshJwt.value = token
}
function clearRefreshToken() {
refreshJwt.value = undefined
}
function setUser(userIn: any | undefined) {
user.value = userIn
setRoles(userIn?.roles ?? [])
@ -21,13 +48,23 @@ export const useAuthStore = defineStore('auth', () => {
roles.value = rolesIn
}
function clearUser() {
user.value = undefined
}
return {
jwt,
user,
roles,
isLoggedIn,
setJwt,
token,
refreshToken,
setToken,
clearToken,
setRefreshToken,
clearRefreshToken,
setUser,
setRoles
clearUser,
setRoles,
}
})

View File

@ -9,7 +9,7 @@ import { useRouter } from 'vue-router'
const { toClipboard } = useClipboard()
const gameStore = useGameStore()
const { moveToMake, canMakeMove, sessionState, gameState, playerState } = storeToRefs(gameStore)
const { moveToMake, canMakeMove, sessionState, playerState } = storeToRefs(gameStore)
onMounted(async () => {
// startMatch()
@ -39,7 +39,7 @@ function copySeed() {
<template>
<div class="block">
<section class="block info">
<!-- <section class="block info">
<p>Running: {{ sessionState?.sessionInProgress }}</p>
<p>Seed: {{ sessionState?.seed }}</p>
<p>
@ -50,7 +50,7 @@ function copySeed() {
<p>Score: {{ sessionState?.scoreboard }}</p>
<p v-if="sessionState?.id">SessionID: {{ sessionState.id }}</p>
<p>PlayerID: {{ playerState?.id }}</p>
</section>
</section> -->
<section class="block">
<div class="game-container">
<GameComponent :playerId="playerState?.id" :canMakeMove="canMakeMove" @move="makeMove" />

View File

@ -8,7 +8,7 @@ import type { GameService } from '@/services/GameService'
import type { MatchSessionOptions, MatchSessionDto } from '@/common/interfaces'
import { useEventBusStore } from '@/stores/eventBus'
import { useAuthStore } from '@/stores/auth'
import { copyToclipboard } from '@/common/helpers'
import { copyToclipboard, wait } from '@/common/helpers'
import { useGameOptionsStore } from '@/stores/gameOptions'
import MatchConfiguration from '@/components/MatchConfiguration.vue'
import { useI18n } from 'vue-i18n'
@ -127,14 +127,20 @@ const canStart = computed(() => {
return (!options?.teamed && allReady) || (options?.teamed && !!teamedWith.value && allReady)
})
const isMultiplayer = computed(
() => (sessionState?.value?.options?.numPlayers || gameOptions.value?.numPlayers || 0) > 1,
)
async function loadData() {
loadingSessions.value = true
const listResponse = await gameService.listMatchSessions()
loadingSessions.value = false
matchSessions.value = listResponse.data
}
onMounted(() => {
// loadData()
// dataInterval = setInterval(loadData, 5000)
loadData()
dataInterval = setInterval(loadData, 5000)
})
onUnmounted(() => {
@ -145,31 +151,34 @@ function copy(sessionSeed: string) {
copyToclipboard(sessionSeed)
}
let tabs = ref<any[]>([
{ label: t('create-session'), id: 'create-tab', active: true, disabled: false },
{ label: t('join-a-multiplayer-session'), id: 'join-tab', active: false, disabled: false },
{ label: t('tournaments'), id: 'torunaments-tab', active: false, disabled: true },
])
const selectedTab = computed(() => tabs.value.find((t) => t.active)?.id)
let selectedTab = ref('create-tab')
const isCreateTab = computed(() => selectedTab.value === 'create-tab')
const isJoinTab = computed(() => selectedTab.value === 'join-tab')
const isTournamentTab = computed(() => selectedTab.value === 'torunaments-tab')
const tabs = computed<any[]>((): any => [
{ label: t('create-session'), id: 'create-tab', disabled: false },
{
label: t('join-a-multiplayer-session', matchSessions.value.length),
id: 'join-tab',
disabled: matchSessions.value.length <= 0,
},
{ label: t('tournaments'), id: 'torunaments-tab', disabled: true },
])
async function tabClick(tab: any) {
tabs.value.forEach((t) => (t.active = t === tab))
if (tab.id === 'join-tab') {
loadingSessions.value = true
await loadData()
dataInterval = setInterval(loadData, 5000)
} else {
clearInterval(dataInterval)
}
selectedTab.value = tab.id
}
function onCreateMatch(options: MatchSessionOptions) {
console.log('Creating match', options)
createMatch(options)
}
async function onStartSingleMatch(options: MatchSessionOptions) {
await createMatch(options)
await wait(1000)
startMatch()
}
</script>
<template>
@ -185,7 +194,7 @@ function onCreateMatch(options: MatchSessionOptions) {
<li
v-bind:key="tab.label"
v-for="tab in tabs"
:class="{ 'is-active': tab.active, 'is-disabled': tab.disabled }"
:class="{ 'is-active': selectedTab === tab.id, 'is-disabled': tab.disabled }"
>
<a @click="() => tabClick(tab)">{{ tab.label }}</a>
</li>
@ -194,7 +203,10 @@ function onCreateMatch(options: MatchSessionOptions) {
<!-- Tabs End -->
<!-- Match Configuration -->
<section class="section" v-if="isCreateTab">
<MatchConfiguration @create-match="onCreateMatch" />
<MatchConfiguration
@create-match="onCreateMatch"
@start-single-match="onStartSingleMatch"
/>
</section>
<!-- Match Configuration End -->
<!-- Join a Multiplayer Session -->
@ -244,8 +256,7 @@ function onCreateMatch(options: MatchSessionOptions) {
<section class="section" v-if="isTournamentTab"></section>
<!-- Tournaments End -->
</div>
<div class="block" v-if="isSessionStarted">
<div class="block" v-if="isSessionStarted && isMultiplayer">
<h2 class="title is-4">{{ sessionState?.name }}</h2>
<h6 class="title is-size-5">Players</h6>
<div v-for="player in sessionState?.players" :key="player.id">

View File

@ -3,11 +3,14 @@ import { AuthenticationService } from '@/services/AuthenticationService'
import { inject, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { emit, listen } from '@tauri-apps/api/event'
const router = useRouter()
const username = ref('')
const password = ref('')
const errorLogin = ref(false)
const { t } = useI18n()
const isTauri = window.__TAURI_METADATA__ ? true : false
const authService = inject<AuthenticationService>('auth')
@ -16,7 +19,7 @@ async function login() {
await authService?.login(username.value, password.value)
router.push({ name: 'home' })
} catch (error) {
alert(t('invalid-username-or-password'))
errorLogin.value = true
}
// if (username.value === 'admin' && password.value === 'password') {
// localStorage.setItem('token', 'true')
@ -25,11 +28,43 @@ async function login() {
// alert('Invalid username or password')
// }
}
// // Listen for update available event
// listen('tauri://update-available', () => {
// console.log('Update is available!')
// // You can show a dialog or notify the user here
// })
// // Listen for update not available event
// listen('tauri://update-not-available', () => {
// console.log('No update available.')
// })
// // Listen for update download progress
// listen('tauri://update-download-progress', (event) => {
// console.log('Update download progress:', event.payload)
// // You can update a progress bar here
// })
// // Listen for update download finished
// listen('tauri://update-download-finished', () => {
// console.log('Update download finished.')
// // You can notify the user to restart the app
// })
function checkForUpdates() {
emit('tauri://update')
}
</script>
<template>
<div class="login">
<h1 class="title">{{ $t('login') }}</h1>
<div class="message is-danger">
<div class="message-body" v-if="errorLogin">
{{ $t('invalid-username-or-password') }}
</div>
</div>
<form class="form" @submit.prevent="login">
<div class="field">
<label class="label">{{ $t('username') }}</label>
@ -63,5 +98,6 @@ async function login() {
</div> -->
</div>
</form>
<a href="#" @click="checkForUpdates" v-if="isTauri">Update</a>
</div>
</template>

View File

@ -2,14 +2,17 @@
import type { MatchSessionDto } from '@/common/interfaces'
import type { GameService } from '@/services/GameService'
import type { LoggingService } from '@/services/LoggingService'
import ScoreboardTableComponent from '@/components/ScoreboardTableComponent.vue'
import { inject, onBeforeMount, ref, toRaw } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useGameStore } from '@/stores/game'
const route = useRoute()
const router = useRouter()
const gameStore = useGameStore()
const gameService: GameService = inject<GameService>('game') as GameService
const logger: LoggingService = inject<LoggingService>('logger') as LoggingService
const { updateSessionState } = gameStore
let sessionId: string
let matchSession = ref<MatchSessionDto | undefined>(undefined)
@ -28,57 +31,45 @@ onBeforeMount(() => {
router.push({ name: 'home' })
}
})
function gotoHome() {
updateSessionState(undefined)
router.push({ name: 'home' })
}
</script>
<template>
<div class="container">
<h1 class="title is-1">{{ $t('match-page') }}</h1>
<h2 class="title is-3">{{ matchSession?.name }}</h2>
<div class="block mt-6">
<p class="mb-4">
<span class="title is-5">{{ $t('winner') }}</span>
<span class="is-size-5 ml-4">{{ matchSession?.matchWinner?.name }}</span>
</p>
<p class="mb-4">
<span class="title is-5">{{ $t('win-type') }}</span>
<span class="is-size-5 ml-4">{{ matchSession?.options.winType }}</span>
</p>
<p class="mb-4">
<span class="title is-5">{{ $t('points-to-win') }}</span>
<span class="is-size-5 ml-4">{{ matchSession?.options.winTarget }}</span>
</p>
<h3 class="title is-5">{{ $t('final-scoreboard') }}</h3>
<div v-bind:key="$index" v-for="(score, $index) in matchSession?.scoreboard">
<p class="">
<span class="title is-5">{{ score.name }}</span>
<span class="is-size-5 ml-4">{{ score.score }}</span>
</p>
<div class="level">
<div class="level-item has-text-centered">
<div>
<p class="heading">{{ $t('winner') }}</p>
<p class="title is-size-3">{{ matchSession?.matchWinner?.name }}</p>
</div>
</div>
</div>
<div class="grid">
<div
class="cell"
v-bind:key="$index"
v-for="(summary, $index) in matchSession?.gameSummaries"
>
<div class="block mt-6">
<h3 class="title is-5">{{ $t('round-index-1', [$index + 1]) }}</h3>
<p class="mb-4">
<span class="title is-5">{{ $t('winner') }}</span>
<span class="is-size-5 ml-4">{{ summary.winner?.name }}</span>
</p>
<h4 class="title is-6">{{ $t('scoreboard') }}</h4>
<div v-bind:key="$index" v-for="(gameScore, $index) in summary.score">
<p class="">
<span class="title is-5">{{ gameScore.name }}</span>
<span class="is-size-5 ml-4">{{ gameScore.score }}</span>
</p>
</div>
<div class="level-item has-text-centered">
<div>
<p class="heading">{{ $t('win-type') }}</p>
<p class="title">{{ matchSession?.options.winType }}</p>
</div>
</div>
<div class="level-item has-text-centered">
<div>
<p class="heading">{{ $t('points-to-win') }}</p>
<p class="title">{{ matchSession?.options.winTarget }}</p>
</div>
</div>
</div>
<ScoreboardTableComponent
:games="matchSession?.gameSummaries"
:final-score="matchSession?.scoreboard"
:winner="matchSession?.matchWinner"
/>
<div class="buttons">
<button class="button is-primary" @click="router.push({ name: 'home' })">
<button class="button is-primary" @click="gotoHome">
{{ $t('back') }}
</button>
</div>

View File

@ -1,6 +1,6 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"include": ["types/env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,

View File

@ -3,6 +3,7 @@
"types": ["vite/client"]
},
"files": [],
"typeRoots": ["./src/types"],
"references": [
{
"path": "./tsconfig.node.json"

View File

View File

@ -3,3 +3,7 @@ declare module 'socket.io' {
data: any
}
}
declare interface Window {
__TAURI__: any
}