38 Commits
Author SHA1 Message Date
tothbt c48f043f7c feat: release 1.1.4
Build and Release Court Pilot / draft (push) Canceled after 0s
Build and Release Court Pilot / windows (push) Canceled after 0s
Build and Release Court Pilot / snap (push) Canceled after 0s
Build and Release Court Pilot / publish (push) Canceled after 0s
* Add LICENCSE
* Set executable name
* Release on the stable channel (snapcraft)
2026-09-07 19:29:00 +02:00
tothbt fbc214d56c use core24 2026-09-06 12:33:16 +02:00
tothbt 486ce47c39 snapcraft and version number fixes 2026-09-06 11:59:01 +02:00
tothbt b9ac324d21 release 1.1.3
Build and Release Court Pilot / draft (push) Canceled after 0s
Build and Release Court Pilot / windows (push) Canceled after 0s
Build and Release Court Pilot / snap (push) Canceled after 0s
Build and Release Court Pilot / publish (push) Canceled after 0s
2026-09-06 11:08:16 +02:00
tothbt 5494a4a26b release 1.1.2
Build and Release Court Pilot / draft (push) Canceled after 0s
Build and Release Court Pilot / build (ubuntu-22.04) (push) Canceled after 0s
Build and Release Court Pilot / build (windows-latest) (push) Canceled after 0s
Build and Release Court Pilot / publish (push) Canceled after 0s
2026-09-05 13:19:01 +02:00
tothbt 174d5440f5 fix updater script 2026-09-05 12:30:52 +02:00
tothbt 62745172dd fix version number on settings tab 2026-09-05 11:18:34 +02:00
tothbt 6319f2c270 fix: small bugfixes
Build and Release Court Pilot / draft (push) Canceled after 0s
Build and Release Court Pilot / build (ubuntu-22.04) (push) Canceled after 0s
Build and Release Court Pilot / build (windows-latest) (push) Canceled after 0s
Build and Release Court Pilot / publish (push) Canceled after 0s
* Typo in the Hungarian vocab
* Translate language labels
* Start app on full screen
* Clear TOs data in the add-modal
* Change version number to 1.1.1
2026-09-05 11:13:43 +02:00
tothbt 0dd30c6c14 fix releaser 2026-09-04 22:06:06 +02:00
tothbt 06f7cb5c8c fix releaser 2026-09-04 21:35:21 +02:00
tothbt e1c519adf5 fix releaser 2026-09-04 20:33:08 +02:00
tothbt ae86f6962a fix(git): add path to publish job 2026-09-04 19:45:52 +02:00
tothbt 4f95404f0b fix(git): remove duplicated process 2026-09-04 19:14:58 +02:00
tothbt 0d7e4b5c3a fix(git): install ionic for Github acitons 2026-09-04 18:46:59 +02:00
tothbt ac133fd5b6 feat(git): add github release yml
Build and Release Court Pilot / draft (push) Canceled after 0s
Build and Release Court Pilot / build (ubuntu-22.04) (push) Canceled after 0s
Build and Release Court Pilot / build (windows-latest) (push) Canceled after 0s
Build and Release Court Pilot / publish (push) Canceled after 0s
2026-09-04 18:33:50 +02:00
tothbt 35c30b8213 feat(tauri): setup auto-update on startup 2026-08-30 18:48:42 +02:00
tothbt 948b930395 tauri updates to make my first release 2026-08-30 17:44:29 +02:00
tothbt a2a7bc6100 add logos and version number to settings tab 2026-08-29 14:38:56 +02:00
tothbt 0c1a9f86d6 feat: add english and hungarian translations 2026-08-29 11:42:16 +02:00
tothbt ab32f01e07 some tauri updates 2026-08-29 09:48:38 +02:00
tothbt 07f6919617 feat: handle service judge setting on the UI 2026-08-18 11:51:42 +02:00
tothbt 1d84985a76 chore(npm): update packages 2026-08-18 09:55:16 +02:00
tothbt 33d635839f feat(settings): handle warning message setting 2026-06-06 19:54:44 +02:00
tothbt abf0cd2359 feat(design): add some custom favicons 2026-06-06 11:54:02 +02:00
tothbt 02fc32942a feat(settings): handle court number settings 2026-06-06 11:53:40 +02:00
tothbt ccfca0dd25 fix(style): change global alert style
* darker backdrop
* wider alert
2026-06-05 15:50:10 +02:00
tothbt 60f12cc333 feat(pilot): add next TOs to a court 2026-06-05 15:41:42 +02:00
tothbt 63737e46eb feat(pilot): remove TOs from court and add them to the waiting list 2026-06-05 14:40:26 +02:00
tothbt 2fb8d34ec1 feat(pilot): do not add 2 umpires to the same splace
* placeholder fixes
2026-06-05 11:37:47 +02:00
tothbt 12871ef7d2 feat(umpires): move umpires between any lists 2026-05-31 09:52:11 +02:00
tothbt d317ce6179 fix(tauri): set minimum window size for tauri 2026-05-27 21:11:01 +02:00
tothbt 0c071c0621 umpires can be added to courts as umpires (removal also) 2026-05-27 21:10:38 +02:00
tothbt 71c3733363 feat(umpires): move umpires between 3 lists by drag-and-drop 2026-05-27 16:54:51 +02:00
tothbt 03bfc8fc16 chore: install angular cdk 2026-05-27 16:54:27 +02:00
tothbt 56b9a186d4 feat: list and remove umpires from tab 2 2026-05-24 17:47:42 +02:00
tothbt 0f46cb3c93 feat: add umpire CRUD service 2026-05-24 17:47:21 +02:00
tothbt 08d64bbaaf feat: setup db.ts 2026-05-24 17:47:07 +02:00
tothbt a79c082d45 chore: install Dexie 2026-05-24 17:46:46 +02:00
108 changed files with 6781 additions and 1845 deletions
+179
View File
@@ -0,0 +1,179 @@
name: Build and Release Court Pilot
on:
push:
tags:
- 'v*'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
CN_APPLICATION: 'trweb/court-pilot'
jobs:
# ---------------------------------------------------------
# Create CrabNebula draft release
# ---------------------------------------------------------
draft:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Create CrabNebula draft release
uses: crabnebula-dev/cloud-release@v0
with:
command: release draft ${{ env.CN_APPLICATION }} --framework tauri
api-key: ${{ secrets.CN_API_KEY }}
# ---------------------------------------------------------
# Windows Tauri build
# ---------------------------------------------------------
windows:
needs: draft
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
cache: true
- name: Install dependencies
run: npm ci
- name: Install Ionic CLI
run: npm install -g @ionic/cli
- name: Build Tauri app
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: npm run tauri build
- name: Upload release assets to CrabNebula
uses: crabnebula-dev/cloud-release@v0
with:
command: release upload ${{ env.CN_APPLICATION }} --framework tauri
api-key: ${{ secrets.CN_API_KEY }}
path: ./src-tauri
# ---------------------------------------------------------
# Linux Tauri build + Snap
# ---------------------------------------------------------
snap:
needs: draft
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
cache: true
- name: Install Linux dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
libssl-dev \
libxdo-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
libwebkit2gtk-4.1-dev \
dpkg
- name: Install dependencies
run: npm ci
- name: Install Ionic CLI
run: npm install -g @ionic/cli
- name: Build Tauri Debian package
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: npm run tauri build -- --bundles deb
- name: Extract Debian package
run: |
rm -rf snap-root
mkdir -p snap-root
dpkg -x src-tauri/target/release/bundle/deb/*.deb snap-root/
- name: Show extracted Snap filesystem
run: |
echo "Snap root:"
find snap-root -maxdepth 5 -type f | sort
- name: Build Snap
uses: snapcore/action-build@v1
id: snapcraft
with:
snapcraft-channel: stable
- name: Show generated Snap
run: |
echo "Snap created:"
echo "${{ steps.snapcraft.outputs.snap }}"
- name: Upload Snap as GitHub artifact
uses: actions/upload-artifact@v4
with:
name: court-pilot-snap
path: ${{ steps.snapcraft.outputs.snap }}
- name: Publish Snap to Snap Store
uses: snapcore/action-publish@v1
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
with:
snap: ${{ steps.snapcraft.outputs.snap }}
release: stable
# ---------------------------------------------------------
# Publish CrabNebula release
# ---------------------------------------------------------
publish:
needs:
- windows
- snap
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Publish CrabNebula release
uses: crabnebula-dev/cloud-release@v0
with:
command: release publish ${{ env.CN_APPLICATION }} --framework tauri
api-key: ${{ secrets.CN_API_KEY }}
path: ./src-tauri
working-directory: ./src-tauri
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Court Pilot
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+61
View File
@@ -0,0 +1,61 @@
import Dexie, { type EntityTable } from 'dexie';
export interface Umpire {
id: number;
firstName: string;
lastName: string;
country: string;
gender: string;
courtNo?: number;
}
export interface CourtUmpire {
id: number;
umpireId: number | null;
courtNo: number;
}
export interface CourtServiceJudge {
id: number;
umpireId: number | null;
courtNo: number;
}
export interface WaitingAsUmpire {
id: number;
umpireId: number;
order: number;
}
export interface WaitingAsServiceJudge {
id: number;
serviceJudgeId: number;
order: number;
}
export interface Settings {
id: number;
withServiceJudge: boolean;
numberOfCourts: number;
showAlert: boolean;
}
const db = new Dexie('CourtPilot') as Dexie & {
umpires: EntityTable<Umpire, 'id'>;
courtUmpires: EntityTable<CourtUmpire, 'id'>;
courtServiceJudges: EntityTable<CourtServiceJudge, 'id'>;
settings: EntityTable<Settings, 'id'>;
waitingUmpires: EntityTable<WaitingAsUmpire, 'id'>;
waitingServiceJudges: EntityTable<WaitingAsServiceJudge, 'id'>;
};
db.version(1).stores({
umpires: '++id, lastName',
courtUmpires: '++id, courtNo, umpireId',
courtServiceJudges: '++id, courtNo, umpireId',
settings: '++id',
waitingUmpires: '++id, order, umpireId',
waitingServiceJudges: '++id, order, serviceJudgeId'
});
export { db };
+3662 -1663
View File
File diff suppressed because it is too large Load Diff
+25 -19
View File
@@ -1,6 +1,6 @@
{ {
"name": "court-pilot", "name": "court-pilot",
"version": "0.0.1", "version": "1.1.3",
"author": "Ionic Framework", "author": "Ionic Framework",
"homepage": "https://ionicframework.com/", "homepage": "https://ionicframework.com/",
"scripts": { "scripts": {
@@ -9,39 +9,45 @@
"build": "ng build", "build": "ng build",
"watch": "ng build --watch --configuration development", "watch": "ng build --watch --configuration development",
"test": "ng test", "test": "ng test",
"lint": "ng lint" "lint": "ng lint",
"tauri": "tauri"
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^20.0.0", "@angular/animations": "^20.3.28",
"@angular/common": "^20.0.0", "@angular/cdk": "^20.2.14",
"@angular/compiler": "^20.0.0", "@angular/common": "^20.3.28",
"@angular/core": "^20.0.0", "@angular/compiler": "^20.3.28",
"@angular/forms": "^20.0.0", "@angular/core": "^20.3.28",
"@angular/platform-browser": "^20.0.0", "@angular/forms": "^20.3.28",
"@angular/platform-browser-dynamic": "^20.0.0", "@angular/platform-browser": "^20.3.28",
"@angular/router": "^20.0.0", "@angular/platform-browser-dynamic": "^20.3.28",
"@angular/router": "^20.3.28",
"@capacitor/app": "8.1.0", "@capacitor/app": "8.1.0",
"@capacitor/core": "8.3.4", "@capacitor/core": "8.3.4",
"@capacitor/haptics": "8.0.2", "@capacitor/haptics": "8.0.2",
"@capacitor/keyboard": "8.0.3", "@capacitor/keyboard": "8.0.3",
"@capacitor/status-bar": "8.0.2", "@capacitor/status-bar": "8.0.2",
"@ionic/angular": "^8.0.0", "@ionic/angular": "^8.0.0",
"@tauri-apps/plugin-os": "^2.3.2",
"@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-updater": "^2.10.1",
"dexie": "^4.4.2",
"ionicons": "^7.0.0", "ionicons": "^7.0.0",
"rxjs": "~7.8.0", "rxjs": "~7.8.0",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"zone.js": "~0.15.0" "zone.js": "~0.15.0"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^20.0.0", "@angular-devkit/build-angular": "^20.3.34",
"@angular-eslint/builder": "^20.0.0", "@angular-eslint/builder": "^20.3.0",
"@angular-eslint/eslint-plugin": "^20.0.0", "@angular-eslint/eslint-plugin": "^20.3.0",
"@angular-eslint/eslint-plugin-template": "^20.0.0", "@angular-eslint/eslint-plugin-template": "^20.3.0",
"@angular-eslint/schematics": "^20.0.0", "@angular-eslint/schematics": "^20.3.0",
"@angular-eslint/template-parser": "^20.0.0", "@angular-eslint/template-parser": "^20.3.0",
"@angular/cli": "^20.0.0", "@angular/cli": "^20.3.34",
"@angular/compiler-cli": "^20.0.0", "@angular/compiler-cli": "^20.3.28",
"@angular/language-service": "^20.0.0", "@angular/language-service": "^20.3.28",
"@capacitor/cli": "8.3.4", "@capacitor/cli": "8.3.4",
"@ionic/angular-toolkit": "^12.0.0", "@ionic/angular-toolkit": "^12.0.0",
"@tauri-apps/cli": "^2.11.2", "@tauri-apps/cli": "^2.11.2",
+9
View File
@@ -0,0 +1,9 @@
[Desktop Entry]
Categories=
Comment=Court Pilot
Exec=court-pilot
StartupWMClass=court-pilot
Icon=${SNAP}/meta/gui/court-pilot.png
Name=court-pilot
Terminal=false
Type=Application
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

+48
View File
@@ -0,0 +1,48 @@
name: court-pilot
title: Court Pilot
contact:
- toth.richard@trweb.hu
issues:
- https://github.com/tricsusz/court-pilot/issues
source-code:
- https://github.com/tricsusz/court-pilot
website:
- https://github.com/tricsusz/court-pilot
license: MIT
base: core24
icon: snap/gui/court-pilot.png
version: '1.1.4'
summary: Badminton umpire rotation application
description: |
Court Pilot is a desktop application for managing badminton
umpire rotations and court assignments.
grade: stable
confinement: strict
apps:
court-pilot:
command: usr/bin/court-pilot
desktop: usr/share/applications/court-pilot.desktop
extensions:
- gnome
plugs:
- network
- home
environment:
LD_LIBRARY_PATH: $SNAP_DESKTOP_RUNTIME/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH
parts:
court-pilot:
plugin: dump
source: snap-root
stage-packages:
- libwebkit2gtk-4.1-0
- libayatana-appindicator3-1
+568 -14
View File
@@ -76,15 +76,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]] [[package]]
name = "app" name = "arbitrary"
version = "0.1.0" version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
dependencies = [ dependencies = [
"log", "derive_arbitrary",
"serde",
"serde_json",
"tauri",
"tauri-build",
"tauri-plugin-log",
] ]
[[package]] [[package]]
@@ -503,6 +500,21 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "court-pilot"
version = "0.1.0"
dependencies = [
"log",
"serde",
"serde_json",
"tauri",
"tauri-build",
"tauri-plugin-log",
"tauri-plugin-os",
"tauri-plugin-process",
"tauri-plugin-updater",
]
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.17" version = "0.2.17"
@@ -640,6 +652,17 @@ dependencies = [
"serde_core", "serde_core",
] ]
[[package]]
name = "derive_arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]] [[package]]
name = "derive_more" name = "derive_more"
version = "2.1.1" version = "2.1.1"
@@ -851,6 +874,16 @@ dependencies = [
"typeid", "typeid",
] ]
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
]
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.4.1" version = "2.4.1"
@@ -885,6 +918,16 @@ dependencies = [
"rustc_version", "rustc_version",
] ]
[[package]]
name = "filetime"
version = "0.2.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759"
dependencies = [
"cfg-if",
"libc",
]
[[package]] [[package]]
name = "find-msvc-tools" name = "find-msvc-tools"
version = "0.1.9" version = "0.1.9"
@@ -1141,6 +1184,16 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "gethostname"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
dependencies = [
"rustix",
"windows-link 0.2.1",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.17" version = "0.2.17"
@@ -1436,6 +1489,21 @@ dependencies = [
"want", "want",
] ]
[[package]]
name = "hyper-rustls"
version = "0.27.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
dependencies = [
"http",
"hyper",
"hyper-util",
"rustls",
"tokio",
"tokio-rustls",
"tower-service",
]
[[package]] [[package]]
name = "hyper-util" name = "hyper-util"
version = "0.1.20" version = "0.1.20"
@@ -1691,6 +1759,36 @@ dependencies = [
"windows-sys 0.45.0", "windows-sys 0.45.0",
] ]
[[package]]
name = "jni"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
dependencies = [
"cfg-if",
"combine",
"jni-macros",
"jni-sys 0.4.1",
"log",
"simd_cesu8",
"thiserror 2.0.18",
"walkdir",
"windows-link 0.2.1",
]
[[package]]
name = "jni-macros"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
dependencies = [
"proc-macro2",
"quote",
"rustc_version",
"simd_cesu8",
"syn 2.0.117",
]
[[package]] [[package]]
name = "jni-sys" name = "jni-sys"
version = "0.3.1" version = "0.3.1"
@@ -1828,6 +1926,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]] [[package]]
name = "litemap" name = "litemap"
version = "0.8.2" version = "0.8.2"
@@ -1884,6 +1988,12 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minisign-verify"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.8.9" version = "0.8.9"
@@ -1956,6 +2066,18 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
[[package]]
name = "nix"
version = "0.31.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
dependencies = [
"bitflags 2.11.1",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]] [[package]]
name = "num-conv" name = "num-conv"
version = "0.2.1" version = "0.2.1"
@@ -2125,6 +2247,7 @@ checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [ dependencies = [
"bitflags 2.11.1", "bitflags 2.11.1",
"block2", "block2",
"libc",
"objc2", "objc2",
"objc2-core-foundation", "objc2-core-foundation",
] ]
@@ -2140,6 +2263,18 @@ dependencies = [
"objc2-core-foundation", "objc2-core-foundation",
] ]
[[package]]
name = "objc2-osa-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0"
dependencies = [
"bitflags 2.11.1",
"objc2",
"objc2-app-kit",
"objc2-foundation",
]
[[package]] [[package]]
name = "objc2-quartz-core" name = "objc2-quartz-core"
version = "0.3.2" version = "0.3.2"
@@ -2203,12 +2338,48 @@ version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "openssl-probe"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]] [[package]]
name = "option-ext" name = "option-ext"
version = "0.2.0" version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "os_info"
version = "3.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cf20a545b305cf1da722b236b5155c9bb35f1d5ceb28c048bd96ca842f41b5b"
dependencies = [
"android_system_properties",
"log",
"nix",
"objc2",
"objc2-foundation",
"objc2-ui-kit",
"serde",
"windows-sys 0.61.2",
]
[[package]]
name = "osakit"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b"
dependencies = [
"objc2",
"objc2-foundation",
"objc2-osa-kit",
"serde",
"serde_json",
"thiserror 2.0.18",
]
[[package]] [[package]]
name = "pango" name = "pango"
version = "0.18.3" version = "0.18.3"
@@ -2653,15 +2824,20 @@ dependencies = [
"http-body", "http-body",
"http-body-util", "http-body-util",
"hyper", "hyper",
"hyper-rustls",
"hyper-util", "hyper-util",
"js-sys", "js-sys",
"log", "log",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"rustls",
"rustls-pki-types",
"rustls-platform-verifier",
"serde", "serde",
"serde_json", "serde_json",
"sync_wrapper", "sync_wrapper",
"tokio", "tokio",
"tokio-rustls",
"tokio-util", "tokio-util",
"tower", "tower",
"tower-http", "tower-http",
@@ -2673,6 +2849,20 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.17",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "rkyv" name = "rkyv"
version = "0.7.46" version = "0.7.46"
@@ -2734,6 +2924,92 @@ dependencies = [
"semver", "semver",
] ]
[[package]]
name = "rustix"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
"bitflags 2.11.1",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls"
version = "0.23.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
dependencies = [
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-native-certs"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
dependencies = [
"openssl-probe",
"rustls-pki-types",
"schannel",
"security-framework",
]
[[package]]
name = "rustls-pki-types"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
dependencies = [
"zeroize",
]
[[package]]
name = "rustls-platform-verifier"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
dependencies = [
"core-foundation",
"core-foundation-sys",
"jni 0.22.4",
"log",
"once_cell",
"rustls",
"rustls-native-certs",
"rustls-platform-verifier-android",
"rustls-webpki",
"security-framework",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls-platform-verifier-android"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]]
name = "rustls-webpki"
version = "0.103.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.22" version = "1.0.22"
@@ -2749,6 +3025,15 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "schannel"
version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
dependencies = [
"windows-sys 0.61.2",
]
[[package]] [[package]]
name = "schemars" name = "schemars"
version = "0.8.22" version = "0.8.22"
@@ -2812,6 +3097,29 @@ version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "security-framework"
version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [
"bitflags 2.11.1",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "selectors" name = "selectors"
version = "0.36.1" version = "0.36.1"
@@ -3022,6 +3330,16 @@ version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
[[package]]
name = "simd_cesu8"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520"
dependencies = [
"rustc_version",
"simdutf8",
]
[[package]] [[package]]
name = "simdutf8" name = "simdutf8"
version = "0.1.5" version = "0.1.5"
@@ -3140,6 +3458,12 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "swift-rs" name = "swift-rs"
version = "1.0.7" version = "1.0.7"
@@ -3193,6 +3517,15 @@ dependencies = [
"syn 2.0.117", "syn 2.0.117",
] ]
[[package]]
name = "sys-locale"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "system-deps" name = "system-deps"
version = "6.2.2" version = "6.2.2"
@@ -3224,7 +3557,7 @@ dependencies = [
"gdkwayland-sys", "gdkwayland-sys",
"gdkx11-sys", "gdkx11-sys",
"gtk", "gtk",
"jni", "jni 0.21.1",
"libc", "libc",
"log", "log",
"ndk", "ndk",
@@ -3263,6 +3596,17 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tar"
version = "0.4.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840"
dependencies = [
"filetime",
"libc",
"xattr",
]
[[package]] [[package]]
name = "target-lexicon" name = "target-lexicon"
version = "0.12.16" version = "0.12.16"
@@ -3286,7 +3630,7 @@ dependencies = [
"gtk", "gtk",
"heck 0.5.0", "heck 0.5.0",
"http", "http",
"jni", "jni 0.21.1",
"libc", "libc",
"log", "log",
"mime", "mime",
@@ -3420,6 +3764,67 @@ dependencies = [
"time", "time",
] ]
[[package]]
name = "tauri-plugin-os"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8f08346c8deb39e96f86973da0e2d76cbb933d7ac9b750f6dc4daf955a6f997"
dependencies = [
"gethostname",
"log",
"os_info",
"serde",
"serde_json",
"serialize-to-javascript",
"sys-locale",
"tauri",
"tauri-plugin",
"thiserror 2.0.18",
]
[[package]]
name = "tauri-plugin-process"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d55511a7bf6cd70c8767b02c97bf8134fa434daf3926cfc1be0a0f94132d165a"
dependencies = [
"tauri",
"tauri-plugin",
]
[[package]]
name = "tauri-plugin-updater"
version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "806d9dac662c2e4594ff03c647a552f2c9bd544e7d0f683ec58f872f952ce4af"
dependencies = [
"base64 0.22.1",
"dirs",
"flate2",
"futures-util",
"http",
"infer",
"log",
"minisign-verify",
"osakit",
"percent-encoding",
"reqwest",
"rustls",
"semver",
"serde",
"serde_json",
"tar",
"tauri",
"tauri-plugin",
"tempfile",
"thiserror 2.0.18",
"time",
"tokio",
"url",
"windows-sys 0.60.2",
"zip",
]
[[package]] [[package]]
name = "tauri-runtime" name = "tauri-runtime"
version = "2.11.2" version = "2.11.2"
@@ -3430,7 +3835,7 @@ dependencies = [
"dpi", "dpi",
"gtk", "gtk",
"http", "http",
"jni", "jni 0.21.1",
"objc2", "objc2",
"objc2-ui-kit", "objc2-ui-kit",
"objc2-web-kit", "objc2-web-kit",
@@ -3453,7 +3858,7 @@ checksum = "b83849ee63ecb27a8e8d0fe51915ca215076914aca43f96db1179f0f415f6cd9"
dependencies = [ dependencies = [
"gtk", "gtk",
"http", "http",
"jni", "jni 0.21.1",
"log", "log",
"objc2", "objc2",
"objc2-app-kit", "objc2-app-kit",
@@ -3520,6 +3925,19 @@ dependencies = [
"toml 1.1.2+spec-1.1.0", "toml 1.1.2+spec-1.1.0",
] ]
[[package]]
name = "tempfile"
version = "3.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
"getrandom 0.4.2",
"once_cell",
"rustix",
"windows-sys 0.61.2",
]
[[package]] [[package]]
name = "tendril" name = "tendril"
version = "0.5.0" version = "0.5.0"
@@ -3642,6 +4060,16 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "tokio-rustls"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls",
"tokio",
]
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.18" version = "0.7.18"
@@ -3938,6 +4366,12 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]] [[package]]
name = "url" name = "url"
version = "2.5.8" version = "2.5.8"
@@ -4242,6 +4676,15 @@ dependencies = [
"system-deps", "system-deps",
] ]
[[package]]
name = "webpki-root-certs"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b"
dependencies = [
"rustls-pki-types",
]
[[package]] [[package]]
name = "webview2-com" name = "webview2-com"
version = "0.38.2" version = "0.38.2"
@@ -4472,6 +4915,15 @@ dependencies = [
"windows-targets 0.42.2", "windows-targets 0.42.2",
] ]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.6",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.59.0" version = "0.59.0"
@@ -4481,6 +4933,15 @@ dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.5",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.61.2" version = "0.61.2"
@@ -4514,13 +4975,30 @@ dependencies = [
"windows_aarch64_gnullvm 0.52.6", "windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6", "windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6", "windows_i686_gnu 0.52.6",
"windows_i686_gnullvm", "windows_i686_gnullvm 0.52.6",
"windows_i686_msvc 0.52.6", "windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6", "windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6", "windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6", "windows_x86_64_msvc 0.52.6",
] ]
[[package]]
name = "windows-targets"
version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link 0.2.1",
"windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 0.53.1",
"windows_i686_gnullvm 0.53.1",
"windows_i686_msvc 0.53.1",
"windows_x86_64_gnu 0.53.1",
"windows_x86_64_gnullvm 0.53.1",
"windows_x86_64_msvc 0.53.1",
]
[[package]] [[package]]
name = "windows-threading" name = "windows-threading"
version = "0.1.0" version = "0.1.0"
@@ -4551,6 +5029,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.42.2" version = "0.42.2"
@@ -4563,6 +5047,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.42.2" version = "0.42.2"
@@ -4575,12 +5065,24 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]] [[package]]
name = "windows_i686_gnullvm" name = "windows_i686_gnullvm"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.42.2" version = "0.42.2"
@@ -4593,6 +5095,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.42.2" version = "0.42.2"
@@ -4605,6 +5113,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.42.2" version = "0.42.2"
@@ -4617,6 +5131,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.42.2" version = "0.42.2"
@@ -4629,6 +5149,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.5.40" version = "0.5.40"
@@ -4781,7 +5307,7 @@ dependencies = [
"gtk", "gtk",
"http", "http",
"javascriptcore-rs", "javascriptcore-rs",
"jni", "jni 0.21.1",
"libc", "libc",
"ndk", "ndk",
"objc2", "objc2",
@@ -4837,6 +5363,16 @@ dependencies = [
"pkg-config", "pkg-config",
] ]
[[package]]
name = "xattr"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
dependencies = [
"libc",
"rustix",
]
[[package]] [[package]]
name = "yoke" name = "yoke"
version = "0.8.2" version = "0.8.2"
@@ -4901,6 +5437,12 @@ dependencies = [
"synstructure", "synstructure",
] ]
[[package]]
name = "zeroize"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
[[package]] [[package]]
name = "zerotrie" name = "zerotrie"
version = "0.2.4" version = "0.2.4"
@@ -4934,6 +5476,18 @@ dependencies = [
"syn 2.0.117", "syn 2.0.117",
] ]
[[package]]
name = "zip"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1"
dependencies = [
"arbitrary",
"crc32fast",
"indexmap 2.14.0",
"memchr",
]
[[package]] [[package]]
name = "zmij" name = "zmij"
version = "1.0.21" version = "1.0.21"
+10 -5
View File
@@ -1,10 +1,10 @@
[package] [package]
name = "app" name = "court-pilot"
version = "0.1.0" version = "0.1.0"
description = "A Tauri App" description = "Court Pilot"
authors = ["you"] authors = ["Richard Toth"]
license = "" license = "MIT"
repository = "" repository = "https://github.com/tricsusz/court-pilot"
edition = "2021" edition = "2021"
rust-version = "1.77.2" rust-version = "1.77.2"
@@ -23,3 +23,8 @@ serde = { version = "1.0", features = ["derive"] }
log = "0.4" log = "0.4"
tauri = { version = "2.11.2", features = [] } tauri = { version = "2.11.2", features = [] }
tauri-plugin-log = "2" tauri-plugin-log = "2"
tauri-plugin-process = "2"
tauri-plugin-os = "2"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-updater = "2"
+1 -1
View File
@@ -1,3 +1,3 @@
fn main() { fn main() {
tauri_build::build() tauri_build::build()
} }
+3 -1
View File
@@ -6,6 +6,8 @@
"main" "main"
], ],
"permissions": [ "permissions": [
"core:default" "core:default",
"process:default",
"os:default"
] ]
} }
+15
View File
@@ -0,0 +1,15 @@
{
"identifier": "desktop-capability",
"platforms": [
"macOS",
"windows",
"linux"
],
"windows": [
"main"
],
"permissions": [
"updater:default",
"os:default"
]
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 838 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<background android:drawable="@color/ic_launcher_background"/>
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#fff</color>
</resources>
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 751 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

+16 -13
View File
@@ -1,16 +1,19 @@
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
tauri::Builder::default() tauri::Builder::default()
.setup(|app| { .plugin(tauri_plugin_os::init())
if cfg!(debug_assertions) { .plugin(tauri_plugin_process::init())
app.handle().plugin( .plugin(tauri_plugin_updater::Builder::new().build())
tauri_plugin_log::Builder::default() .setup(|app| {
.level(log::LevelFilter::Info) if cfg!(debug_assertions) {
.build(), app.handle().plugin(
)?; tauri_plugin_log::Builder::default()
} .level(log::LevelFilter::Info)
Ok(()) .build(),
}) )?;
.run(tauri::generate_context!()) }
.expect("error while running tauri application"); Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
} }
+1 -1
View File
@@ -2,5 +2,5 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() { fn main() {
app_lib::run(); app_lib::run();
} }
+19 -4
View File
@@ -1,10 +1,11 @@
{ {
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "court-pilot", "productName": "court-pilot",
"version": "0.1.0", "mainBinaryName": "Court Pilot",
"identifier": "com.tauri.dev", "version": "1.1.4",
"identifier": "com.courtpilot.dev",
"build": { "build": {
"frontendDist": "../wwww", "frontendDist": "../www",
"devUrl": "http://localhost:8103", "devUrl": "http://localhost:8103",
"beforeDevCommand": "npm start", "beforeDevCommand": "npm start",
"beforeBuildCommand": "ionic build" "beforeBuildCommand": "ionic build"
@@ -12,11 +13,14 @@
"app": { "app": {
"windows": [ "windows": [
{ {
"minHeight": 600,
"minWidth": 800,
"title": "Court Pilot", "title": "Court Pilot",
"width": 800, "width": 800,
"height": 600, "height": 600,
"resizable": true, "resizable": true,
"fullscreen": false "fullscreen": false,
"center": true
} }
], ],
"security": { "security": {
@@ -35,6 +39,17 @@
], ],
"android": { "android": {
"debugApplicationIdSuffix": ".debug" "debugApplicationIdSuffix": ".debug"
},
"createUpdaterArtifacts": true,
"license": "MIT",
"licenseFile": "../LICENSE"
},
"plugins": {
"updater": {
"endpoints": [
"https://cdn.crabnebula.app/update/trweb/court-pilot/{{target}}-{{arch}}/{{current_version}}"
],
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEY3MDU5NkNDN0Y3QTQ3RDgKUldUWVIzcC96SllGOTduTUFweEUraTE2WUxjczRJNDdoaFFIb1VRT1o5aHdJeFg2RlRlUGoxRTcK"
} }
} }
} }
+116 -4
View File
@@ -1,11 +1,123 @@
import { Component } from '@angular/core'; import { Component, inject } from '@angular/core';
import { IonApp, IonRouterOutlet } from '@ionic/angular/standalone'; import {
AlertController,
IonApp,
IonRouterOutlet,
LoadingController
} from '@ionic/angular/standalone';
import { check, DownloadEvent } from '@tauri-apps/plugin-updater';
import { isTauri } from '@tauri-apps/api/core';
import { platform } from '@tauri-apps/plugin-os';
import { relaunch } from '@tauri-apps/plugin-process';
import { TranslationService } from './i18n/translation.service';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
templateUrl: 'app.component.html', templateUrl: 'app.component.html',
imports: [IonApp, IonRouterOutlet], imports: [IonApp, IonRouterOutlet]
}) })
export class AppComponent { export class AppComponent {
constructor() {} readonly alertController: AlertController = inject(AlertController);
readonly loadingController: LoadingController = inject(LoadingController);
readonly translationService: TranslationService = inject(TranslationService);
constructor() {
this.checkForUpdates();
}
private async checkForUpdates(): Promise<void> {
if (!isTauri()) {
return;
}
// Snap handles updates on Linux.
if (platform() === 'linux') {
return;
}
try {
const update = await check();
if (update) {
const alert = await this.alertController.create({
header: this.translationService.translate('updateAvailable'),
message: this.translationService.translate('newVersion', {
version: update.version
}),
buttons: [
{
text: 'OK',
handler: async () => {
const loading = await this.loadingController.create({
message:
this.translationService.translate('updateDownloading'),
spinner: 'crescent',
backdropDismiss: false
});
await loading.present();
let downloaded = 0;
let total = 0;
try {
await update.downloadAndInstall((event: DownloadEvent) => {
switch (event.event) {
case 'Started':
total = event.data.contentLength ?? 0;
downloaded = 0;
break;
case 'Progress':
downloaded += event.data.chunkLength;
if (total > 0) {
const percent = Math.min(
100,
Math.round((downloaded / total) * 100)
);
loading.message = `${this.translationService.translate('updateDownloading')} ${percent}%`;
}
break;
case 'Finished':
loading.message =
this.translationService.translate('updateInstalling');
break;
}
});
await loading.dismiss();
await relaunch();
} catch (error) {
console.error('Failed to install update:', error);
await loading.dismiss();
const errorAlert = await this.alertController.create({
header: this.translationService.translate('updateFailed'),
message: String(error),
buttons: ['OK']
});
await errorAlert.present();
}
}
}
]
});
await alert.present();
return;
}
console.log('No updates available.');
} catch (error) {
console.error('Failed to check for updates:', error);
}
}
} }
@@ -1,12 +0,0 @@
<div id="container">
<strong>{{ name }}</strong>
<p>
Explore
<a
target="_blank"
rel="noopener noreferrer"
href="https://ionicframework.com/docs/components"
>UI Components</a
>
</p>
</div>
@@ -1,27 +0,0 @@
#container {
text-align: center;
position: absolute;
left: 0;
right: 0;
top: 50%;
transform: translateY(-50%);
}
#container strong {
font-size: 20px;
line-height: 26px;
}
#container p {
font-size: 16px;
line-height: 22px;
color: #8c8c8c;
margin: 0;
}
#container a {
text-decoration: none;
}
@@ -1,18 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ExploreContainerComponent } from './explore-container.component';
describe('ExploreContainerComponent', () => {
let component: ExploreContainerComponent;
let fixture: ComponentFixture<ExploreContainerComponent>;
beforeEach(async () => {
fixture = TestBed.createComponent(ExploreContainerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -1,10 +0,0 @@
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-explore-container',
templateUrl: './explore-container.component.html',
styleUrls: ['./explore-container.component.scss'],
})
export class ExploreContainerComponent {
@Input() name?: string;
}
+8
View File
@@ -0,0 +1,8 @@
import { FullnamePipe } from './fullname-pipe';
describe('FullnamePipePipe', () => {
it('create an instance', () => {
const pipe = new FullnamePipe();
expect(pipe).toBeTruthy();
});
});
+24
View File
@@ -0,0 +1,24 @@
import { inject, Pipe, PipeTransform } from '@angular/core';
import { Umpire } from 'db';
import { TranslationService } from './i18n/translation.service';
@Pipe({
name: 'fullname',
standalone: true,
pure: false
})
export class FullnamePipe implements PipeTransform {
readonly translationService = inject(TranslationService);
transform(value: Umpire | undefined, ...args: unknown[]): string {
if (typeof value === 'undefined') {
return '';
}
if (this.translationService.language() === 'en') {
return value.firstName + ' ' + value.lastName;
}
return value.lastName + ' ' + value.firstName;
}
}
+16
View File
@@ -0,0 +1,16 @@
import { Pipe, PipeTransform, inject } from '@angular/core';
import { TranslationKey, TranslationParams } from './translations';
import { TranslationService } from './translation.service';
@Pipe({
name: 'translate',
standalone: true,
pure: false
})
export class TranslatePipe implements PipeTransform {
private readonly translationService = inject(TranslationService);
transform(key: TranslationKey, params: TranslationParams = {}): string {
return this.translationService.translate(key, params);
}
}
+47
View File
@@ -0,0 +1,47 @@
import { Injectable, signal } from '@angular/core';
import {
translations,
Language,
TranslationKey,
TranslationParams
} from './translations';
@Injectable({
providedIn: 'root'
})
export class TranslationService {
private readonly storageKey = 'language';
readonly language = signal<Language>(this.getInitialLanguage());
setLanguage(language: Language): void {
this.language.set(language);
localStorage.setItem(this.storageKey, language);
}
translate(key: TranslationKey, params: TranslationParams = {}): string {
let text: string = translations[this.language()][key];
for (const [param, value] of Object.entries(params)) {
text = text.replaceAll(`{${param}}`, String(value));
}
return text;
}
private getInitialLanguage(): Language {
const stored = localStorage.getItem(this.storageKey);
if (stored && stored in translations) {
return stored as Language;
}
const browserLanguage = navigator.language.split('-')[0] as Language;
if (browserLanguage in translations) {
return browserLanguage;
}
return 'en';
}
}
+104
View File
@@ -0,0 +1,104 @@
import { language, male, settings } from 'ionicons/icons';
import { noop } from 'rxjs';
export const translations = {
en: {
courts: 'Courts',
numberOfCourts: 'Number of Courts',
showAlerts: 'Show alerts on quick buttons',
serviceJudge: 'Service Judge',
language: 'Language',
settings: 'Settings',
courtOfficials: 'Court Officials',
add: 'Add',
noCourtOfficials: 'No court officials',
newCourtOfficial: 'New Court Official',
cancel: 'Cancel',
save: 'Save',
firstName: 'First Name',
lastName: 'Last Name',
country: 'Country',
gender: 'Gender',
male: 'Male',
female: 'Female',
confirmation: 'Confirmation',
confirmDeleteUmpire: 'Do you really want to delete this court official?',
no: 'No',
yes: 'Yes',
umpires: 'Umpires',
serviceJudges: 'Service Judges',
notOnDuty: 'Not on Duty',
shortCourtName: 'C',
court: 'Court',
umpire: 'Umpire',
removeUmpireFromCourt:
'Are you sure you want to remove them from the court?',
removeUmpiresFromCourt:
'Are you sure you want to remove them from the court?',
courtConfirm: 'Court {courtNo}',
sendUmpireToCourt: 'Are you sure you want to send them to the court?',
sendUmpiresToCourt: 'Are you sure you want to send them to the court?',
sendUmpireMessage: '{umpire}',
sendUmpiresMessage: '{umpire} - {serviceJudge}',
version: 'Version',
updateAvailable: 'Update available',
newVersion:
'A new version {version} is available. The application will restart to install the update.',
english: 'English',
hungarian: 'Hungarian',
updateDownloading: 'Downloading update...',
updateInstalling: 'Installing update...',
updateFailed: 'Failed to install the update. Please try again.'
},
hu: {
courts: 'Pályák',
numberOfCourts: 'Pályák száma',
showAlerts: 'Figyelmeztetések megjelenítése a gyors gomboknál',
serviceJudge: 'Adogatásbíró',
language: 'Nyelv',
settings: 'Beállítások',
courtOfficials: 'Tisztségviselők',
add: 'Hozzáadás',
noCourtOfficials: 'Nincsenek tisztségviselők',
newCourtOfficial: 'Új Tisztségviselő',
cancel: 'Mégse',
save: 'Mentés',
firstName: 'Keresztnév',
lastName: 'Vezetéknév',
country: 'Ország',
gender: 'Nem',
male: 'Férfi',
female: 'Nő',
confirmation: 'Megerősítés',
confirmDeleteUmpire: 'Biztosan törölni szeretnéd ezt a tisztségviselőt?',
no: 'Nem',
yes: 'Igen',
umpires: 'Játékvezetők',
serviceJudges: 'Adogatásbírók',
notOnDuty: 'Pihenő',
shortCourtName: 'P',
court: 'Pálya',
umpire: 'Játékvezető',
removeUmpireFromCourt: 'Biztos leveszed őt a pályáról?',
removeUmpiresFromCourt: 'Biztos leveszed őket a pályáról?',
courtConfirm: 'Pálya {courtNo}',
sendUmpireToCourt: 'Biztos pályára küldöd őt?',
sendUmpiresToCourt: 'Biztos pályára küldöd őket?',
sendUmpireMessage: '{umpire}',
sendUmpiresMessage: '{umpire} - {serviceJudge}',
version: 'Verzió',
updateAvailable: 'Új verzió elérhető',
newVersion:
'Új elérhető verzió: {version}. A frissítések telepítése után az alkalmazás újraindul.',
english: 'Angol',
hungarian: 'Magyar',
updateDownloading: 'Frissítés letöltése...',
updateFailed: 'Frissítés sikertelen. Kérlek próbáld újra.',
updateInstalling: 'Frissítés telepítése...'
}
} as const;
export type Language = keyof typeof translations;
export type TranslationKey = keyof typeof translations.en;
export type TranslationParams = Record<string, string | number>;
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { CourtServiceJudgeService } from './court.service.judge.service';
describe('CourtServiceJudgeService', () => {
let service: CourtServiceJudgeService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(CourtServiceJudgeService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,61 @@
import { Injectable } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { CourtServiceJudge, db } from 'db';
import { liveQuery } from 'dexie';
import { from } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class CourtServiceJudgeService {
/**
* All service judges (reactive)
*/
readonly umpires = toSignal(
from(liveQuery(() => db.courtServiceJudges.orderBy('courtNo').toArray())),
{ initialValue: [] }
);
/**
* Get by court number (reactive)
*/
getByCourtNo(courtNo: number) {
return toSignal<CourtServiceJudge | undefined>(
liveQuery(() =>
db.courtServiceJudges.where('courtNo').equals(courtNo).first()
),
{ initialValue: undefined }
);
}
/**
* Create / update
*/
async save(item: Omit<CourtServiceJudge, 'id'>): Promise<number> {
return db.courtServiceJudges.add(item as CourtServiceJudge);
}
async update(
id: number,
changes: Partial<CourtServiceJudge>
): Promise<number> {
return db.courtServiceJudges.update(id, changes);
}
async delete(id: number): Promise<void> {
await db.courtServiceJudges.delete(id);
}
async removeByUmpireId(umpireId: number): Promise<void> {
const item = await db.courtServiceJudges
.where('umpireId')
.equals(umpireId)
.first();
if (!item?.id) {
return;
}
await db.courtServiceJudges.delete(item.id);
}
}
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { CourtUmpireService } from './court.umpire.service';
describe('CourtUmpireService', () => {
let service: CourtUmpireService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(CourtUmpireService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
+56
View File
@@ -0,0 +1,56 @@
import { Injectable } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { CourtUmpire, db } from 'db';
import { liveQuery } from 'dexie';
import { from } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class CourtUmpireService {
/**
* All court umpires (reactive)
*/
readonly umpires = toSignal(
from(liveQuery(() => db.courtUmpires.orderBy('courtNo').toArray())),
{ initialValue: [] }
);
/**
* Get by court number (reactive single)
*/
getByCourtNo(courtNo: number) {
return toSignal<CourtUmpire | undefined>(
liveQuery(() => db.courtUmpires.where('courtNo').equals(courtNo).first()),
{ initialValue: undefined }
);
}
/**
* Create / update
*/
async save(item: Omit<CourtUmpire, 'id'>): Promise<number> {
return db.courtUmpires.add(item as CourtUmpire);
}
async update(id: number, changes: Partial<CourtUmpire>): Promise<number> {
return db.courtUmpires.update(id, changes);
}
async delete(id: number): Promise<void> {
await db.courtUmpires.delete(id);
}
async removeByUmpireId(umpireId: number): Promise<void> {
const item = await db.courtUmpires
.where('umpireId')
.equals(umpireId)
.first();
if (!item?.id) {
return;
}
await db.courtUmpires.delete(item.id);
}
}
+16
View File
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { SettingsService } from './settings-service';
describe('SettingsService', () => {
let service: SettingsService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(SettingsService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
+47
View File
@@ -0,0 +1,47 @@
import { Injectable } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { db, Settings } from 'db';
import { liveQuery } from 'dexie';
@Injectable({
providedIn: 'root'
})
export class SettingsService {
constructor() {
void this.ensureSettings();
}
/**
* Reactive settings signal
*/
readonly settings = toSignal<Settings | undefined>(
liveQuery(() => db.settings.get(1)),
{ initialValue: undefined }
);
/**
* Update settings
*/
async update(changes: Partial<Omit<Settings, 'id'>>): Promise<void> {
const current = await db.settings.get(1);
if (!current) {
return;
}
await db.settings.update(1, changes);
}
private async ensureSettings() {
const existing = await db.settings.get(1);
if (!existing) {
await db.settings.put({
id: 1,
numberOfCourts: 5,
withServiceJudge: true,
showAlert: true
});
}
}
}
+16
View File
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { UmpireService } from './umpire.service';
describe('Umpire', () => {
let service: UmpireService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(UmpireService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
+61
View File
@@ -0,0 +1,61 @@
import { Injectable } from '@angular/core';
import { db } from 'db';
import { liveQuery } from 'dexie';
import { from } from 'rxjs';
import { Umpire } from '../../../db';
import { toSignal } from '@angular/core/rxjs-interop';
@Injectable({
providedIn: 'root'
})
export class UmpireService {
/**
* Reactive signal with all umpires
*/
readonly umpires = toSignal(
from(liveQuery(() => db.umpires.orderBy('lastName').toArray())),
{
initialValue: []
}
);
/**
* Create
*/
async create(umpire: Omit<Umpire, 'id'>): Promise<number> {
return await db.umpires.add({
...umpire
} as Umpire);
}
/**
* Read one
*/
async getById(id: number): Promise<Umpire | undefined> {
return await db.umpires.get(id);
}
/**
* Update
*/
async update(
id: number,
changes: Partial<Omit<Umpire, 'id'>>
): Promise<number> {
return await db.umpires.update(id, changes);
}
/**
* Delete
*/
async delete(id: number): Promise<void> {
await db.umpires.delete(id);
}
/**
* Delete all
*/
async clear(): Promise<void> {
await db.umpires.clear();
}
}
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { WaitingServiceJudgesService } from './waiting-service-judges.service';
describe('WaitingServiceJudgesService', () => {
let service: WaitingServiceJudgesService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(WaitingServiceJudgesService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,161 @@
import { Injectable } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { db, Umpire } from 'db';
import { liveQuery } from 'dexie';
import { from } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class WaitingServiceJudgesService {
/**
* Reactive list sorted by order
*/
readonly waitingServiceJudges = toSignal(
from(liveQuery(() => db.waitingServiceJudges.orderBy('order').toArray())),
{
initialValue: []
}
);
/**
* Add new umpire to queue with max(order) + 1
*/
async add(serviceJudgeId: number, toPosition?: number): Promise<number> {
const id = await db.transaction('rw', db.waitingServiceJudges, async () => {
const last = await db.waitingServiceJudges.orderBy('order').last();
const nextOrder = last ? last.order + 1 : 1;
return db.waitingServiceJudges.add({
serviceJudgeId,
order: nextOrder
});
});
if (toPosition !== undefined) {
await this.moveToPosition(serviceJudgeId, toPosition);
}
return id;
}
/**
* Remove from queue
*/
async remove(id: number): Promise<void> {
await db.waitingServiceJudges.delete(id);
await this.reinitOrders();
}
async removeByUmpireId(umpireId: number): Promise<void> {
const item = await db.waitingServiceJudges
.where('serviceJudgeId')
.equals(umpireId)
.first();
if (!item?.id) {
return;
}
await this.remove(item.id);
}
/**
* Reorder (optional helper)
*/
async updateOrder(id: number, order: number): Promise<void> {
await db.waitingServiceJudges.update(id, { order });
}
/**
* Clear queue
*/
async clear(): Promise<void> {
await db.waitingServiceJudges.clear();
}
async moveToPosition(umpireId: number, newOrder: number): Promise<void> {
await db.transaction('rw', db.waitingServiceJudges, async () => {
const current = await db.waitingServiceJudges
.where('serviceJudgeId')
.equals(umpireId)
.first();
if (!current) {
return;
}
const oldOrder = current.order;
if (oldOrder === newOrder) {
return;
}
if (newOrder < oldOrder) {
// Moving up: shift affected records down
const affected = await db.waitingServiceJudges
.filter(
(item) =>
item.order >= newOrder &&
item.order < oldOrder &&
item.id !== current.id
)
.toArray();
for (const item of affected) {
await db.waitingServiceJudges.update(item.id!, {
order: item.order + 1
});
}
} else {
// Moving down: shift affected records up
const affected = await db.waitingServiceJudges
.filter(
(item) =>
item.order > oldOrder &&
item.order <= newOrder &&
item.id !== current.id
)
.toArray();
for (const item of affected) {
await db.waitingServiceJudges.update(item.id!, {
order: item.order - 1
});
}
}
await db.waitingServiceJudges.update(current.id!, {
order: newOrder
});
});
}
async reinitOrders(): Promise<void> {
await db.transaction('rw', db.waitingServiceJudges, async () => {
const items = await db.waitingServiceJudges.orderBy('order').toArray();
for (let i = 0; i < items.length; i++) {
const item = items[i];
const expectedOrder = i + 1;
if (item.order !== expectedOrder) {
await db.waitingServiceJudges.update(item.id!, {
order: expectedOrder
});
}
}
});
}
async getCurrentUmpire(): Promise<Umpire | undefined> {
const item = await db.waitingServiceJudges.where('order').equals(1).first();
return item ? db.umpires.get(item.serviceJudgeId) : undefined;
}
}
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { WaitingUmpiresService } from './waiting-umpires.service';
describe('WaitingUmpiresService', () => {
let service: WaitingUmpiresService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(WaitingUmpiresService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
+161
View File
@@ -0,0 +1,161 @@
import { Injectable } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { db, Umpire } from 'db';
import { liveQuery } from 'dexie';
import { from } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class WaitingUmpiresService {
/**
* Reactive list sorted by order
*/
readonly waitingUmpires = toSignal(
from(liveQuery(() => db.waitingUmpires.orderBy('order').toArray())),
{
initialValue: []
}
);
/**
* Add new umpire to queue with max(order) + 1
*/
async add(umpireId: number, toPosition?: number): Promise<number> {
const id = await db.transaction('rw', db.waitingUmpires, async () => {
const last = await db.waitingUmpires.orderBy('order').last();
const nextOrder = last ? last.order + 1 : 1;
return db.waitingUmpires.add({
umpireId,
order: nextOrder
});
});
if (toPosition !== undefined) {
await this.moveToPosition(umpireId, toPosition);
}
return id;
}
/**
* Remove from queue
*/
async remove(id: number): Promise<void> {
await db.waitingUmpires.delete(id);
await this.reinitOrders();
}
async removeByUmpireId(umpireId: number): Promise<void> {
const item = await db.waitingUmpires
.where('umpireId')
.equals(umpireId)
.first();
if (!item?.id) {
return;
}
await this.remove(item.id);
}
/**
* Reorder (optional helper)
*/
async updateOrder(id: number, order: number): Promise<void> {
await db.waitingUmpires.update(id, { order });
}
/**
* Clear queue
*/
async clear(): Promise<void> {
await db.waitingUmpires.clear();
}
async moveToPosition(umpireId: number, newOrder: number): Promise<void> {
await db.transaction('rw', db.waitingUmpires, async () => {
const current = await db.waitingUmpires
.where('umpireId')
.equals(umpireId)
.first();
if (!current) {
return;
}
const oldOrder = current.order;
if (oldOrder === newOrder) {
return;
}
if (newOrder < oldOrder) {
// Moving up: shift affected records down
const affected = await db.waitingUmpires
.filter(
(item) =>
item.order >= newOrder &&
item.order < oldOrder &&
item.id !== current.id
)
.toArray();
for (const item of affected) {
await db.waitingUmpires.update(item.id!, {
order: item.order + 1
});
}
} else {
// Moving down: shift affected records up
const affected = await db.waitingUmpires
.filter(
(item) =>
item.order > oldOrder &&
item.order <= newOrder &&
item.id !== current.id
)
.toArray();
for (const item of affected) {
await db.waitingUmpires.update(item.id!, {
order: item.order - 1
});
}
}
await db.waitingUmpires.update(current.id!, {
order: newOrder
});
});
}
async reinitOrders(): Promise<void> {
await db.transaction('rw', db.waitingUmpires, async () => {
const items = await db.waitingUmpires.orderBy('order').toArray();
for (let i = 0; i < items.length; i++) {
const item = items[i];
const expectedOrder = i + 1;
if (item.order !== expectedOrder) {
await db.waitingUmpires.update(item.id!, {
order: expectedOrder
});
}
}
});
}
async getCurrentUmpire(): Promise<Umpire | undefined> {
const item = await db.waitingUmpires.where('order').equals(1).first();
return item ? db.umpires.get(item.umpireId) : undefined;
}
}
+184 -6
View File
@@ -1,17 +1,195 @@
<ion-header [translucent]="true"> <ion-header [translucent]="true">
<ion-toolbar> <ion-toolbar>
<ion-title> <ion-title> {{ 'courts' | translate }} </ion-title>
Tab 1
</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content [fullscreen]="true"> <ion-content [fullscreen]="true" cdkDropListGroup>
<ion-header collapse="condense"> <ion-header collapse="condense">
<ion-toolbar> <ion-toolbar>
<ion-title size="large">Tab 1</ion-title> <ion-title size="large">{{ 'courts' | translate }}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<app-explore-container name="Tab 1 page"></app-explore-container> <ion-grid [fixed]="'fixed'" class="bottom-lists">
<ion-row>
<ion-col [size]="1" class="ion-display-none ion-display-xl-block">
<ion-item [lines]="'none'">{{ 'court' | translate }}</ion-item>
</ion-col>
<ion-col [size]="1" class="ion-display-xl-none">
<ion-item [lines]="'none'">{{ 'shortCourtName' | translate }}</ion-item>
</ion-col>
<ion-col>
<ion-item [lines]="'none'">
<ion-label class="ion-text-center"> {{ 'umpire' | translate }} </ion-label>
</ion-item>
</ion-col>
@if (serviceJudgeEnabled()) {
<ion-col>
<ion-item [lines]="'none'">
<ion-label class="ion-text-center"> {{ 'serviceJudge' | translate }} </ion-label>
</ion-item>
</ion-col>
}
<ion-col size="2" sizeLg="1"></ion-col>
</ion-row>
@for (item of [].constructor(settings()?.numberOfCourts); track $index) {
<ion-row>
<ion-col [size]="1">
<ion-list>
<ion-item [lines]="'none'"> {{ $index + 1 }}. </ion-item>
</ion-list>
</ion-col>
<ion-col>
<ion-list
[lines]="'none'"
cdkDropList
[id]="`court-umpire-${$index + 1}`"
[cdkDropListData]="umpireByCourt().get($index + 1) ? [umpireByCourt().get($index + 1)] : []"
[cdkDropListEnterPredicate]="canDropUmpire"
(cdkDropListDropped)="dropToUmpire($event, $index + 1)">
@if (umpireByCourt().get($index + 1); as umpire) {
<div class="custom-ion-item" cdkDrag [cdkDragData]="umpire">
<div *cdkDragPlaceholder class="custom-ion-item primary">
<ion-label>{{ umpire|fullname }}</ion-label>
</div>
<ion-label>{{ umpire|fullname }}</ion-label>
</div>
}
</ion-list>
</ion-col>
@if (serviceJudgeEnabled()) {
<ion-col>
<ion-list
[lines]="'none'"
cdkDropList
[id]="`court-service-judge-${$index + 1}`"
[cdkDropListData]="serviceJudgeByCourt().get($index + 1) ? [serviceJudgeByCourt().get($index + 1)] : []"
(cdkDropListDropped)="dropToServiceJudge($event, $index + 1)">
@if (serviceJudgeByCourt().get($index + 1); as umpire) {
<div class="custom-ion-item" cdkDrag [cdkDragData]="umpire">
<div *cdkDragPlaceholder class="custom-ion-item primary">
<ion-label>{{ umpire|fullname }}</ion-label>
</div>
<ion-label>{{ umpire|fullname }}</ion-label>
</div>
}
</ion-list>
</ion-col>
}
<ion-col size="2" sizeLg="1">
<ion-list>
<ion-item lines="none">
@if ( (umpireByCourt().get($index + 1) &&
serviceJudgeByCourt().get($index + 1)) ||
(umpireByCourt().get($index + 1) && !serviceJudgeEnabled()) ) {
<ion-button
[color]="'danger'"
(click)="showRemoveConfirmation($index + 1)">
<ion-icon
slot="icon-only"
size="large"
name="exit-outline"></ion-icon>
</ion-button>
} @else if ((!umpireByCourt().get($index + 1) &&
!serviceJudgeByCourt().get($index + 1) && waitingUmpires().length &&
waitingServiceJudges().length) || (!umpireByCourt().get($index + 1)
&& waitingUmpires().length && !serviceJudgeEnabled())) {
<ion-button
[color]="'success'"
[disabled]=""
(click)="showAddConfirmation($index + 1)">
<ion-icon
slot="icon-only"
size="large"
name="enter-outline"></ion-icon>
</ion-button>
}
</ion-item>
</ion-list>
</ion-col>
</ion-row>
}
</ion-grid>
<ion-grid [fixed]="'fixed'" class="ion-margin-top bottom-lists">
<ion-row>
<ion-col [size]="!serviceJudgeEnabled() ? 6 : 4">
<ion-item color="primary">
<ion-label class="ion-text-center"> {{ 'umpires' | translate }} </ion-label>
</ion-item>
</ion-col>
@if (serviceJudgeEnabled()) {
<ion-col [size]="4">
<ion-item color="primary">
<ion-label class="ion-text-center"> {{ 'serviceJudges' | translate }} </ion-label>
</ion-item>
</ion-col>
}
<ion-col [size]="!serviceJudgeEnabled() ? 6 : 4">
<ion-item color="medium">
<ion-label class="ion-text-center"> {{ 'notOnDuty' | translate }} </ion-label>
</ion-item>
</ion-col>
</ion-row>
<ion-row>
<ion-col>
<ion-list
[lines]="'none'"
cdkDropList
id="list-waiting-umpires"
[cdkDropListData]="waitingUmpires()"
(cdkDropListDropped)="dropToWaitingUmpire($event)">
@for (umpire of waitingUmpires(); track umpire.id) {
<div class="custom-ion-item" cdkDrag [cdkDragData]="umpire">
<div *cdkDragPlaceholder class="custom-ion-item primary">
<ion-label>{{ umpire|fullname }}</ion-label>
</div>
<ion-label>{{ umpire|fullname }}</ion-label>
</div>
}
</ion-list>
</ion-col>
@if (serviceJudgeEnabled()) {
<ion-col>
<ion-list
[lines]="'none'"
cdkDropList
id="list-waiting-service-judges"
[cdkDropListData]="waitingServiceJudges()"
(cdkDropListDropped)="dropToWaitingServiceJudge($event)">
@for (umpire of waitingServiceJudges(); track umpire.id) {
<div class="custom-ion-item" cdkDrag [cdkDragData]="umpire">
<div *cdkDragPlaceholder class="custom-ion-item primary">
<ion-label>{{ umpire|fullname }}</ion-label>
</div>
<ion-label>{{ umpire|fullname }}</ion-label>
</div>
}
</ion-list>
</ion-col>
}
<ion-col>
<ion-list
[lines]="'none'"
cdkDropList
cdkDropListSortingDisabled
id="list-on-rest"
(cdkDropListDropped)="dropToRest($event)"
[cdkDropListData]="onRest()">
@for (umpire of onRest(); track umpire.id) {
<div class="custom-ion-item" cdkDrag [cdkDragData]="umpire">
<div *cdkDragPlaceholder class="custom-ion-item primary">
<ion-label>{{ umpire|fullname }}</ion-label>
</div>
<ion-label>{{ umpire|fullname }}</ion-label>
</div>
}
</ion-list>
</ion-col>
</ion-row>
</ion-grid>
</ion-content> </ion-content>
+12
View File
@@ -0,0 +1,12 @@
.bottom-lists ion-list {
min-height: 64px;
}
.custom-ion-item {
cursor: move;
padding: 10px 15px;
&.primary {
background-color: var(--ion-color-primary);
}
}
+546 -5
View File
@@ -1,13 +1,554 @@
import { Component } from '@angular/core'; import { Component, computed, inject } from '@angular/core';
import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone'; import {
import { ExploreContainerComponent } from '../explore-container/explore-container.component'; IonHeader,
IonToolbar,
IonTitle,
IonContent,
IonGrid,
IonCol,
IonRow,
IonList,
IonItem,
IonLabel,
IonIcon,
IonButton,
AlertController
} from '@ionic/angular/standalone';
import { SettingsService } from '../services/settings-service';
import { UmpireService } from '../services/umpire.service';
import { Umpire } from 'db';
import { WaitingUmpiresService } from '../services/waiting-umpires.service';
import { WaitingServiceJudgesService } from '../services/waiting-service-judges.service';
import { FullnamePipe } from '../fullname-pipe';
import {
CdkDrag,
CdkDragDrop,
CdkDragPlaceholder,
CdkDropList,
CdkDropListGroup
} from '@angular/cdk/drag-drop';
import { CommonModule } from '@angular/common';
import { CourtUmpireService } from '../services/court.umpire.service';
import { CourtServiceJudgeService } from '../services/court.service.judge.service';
import { addIcons } from 'ionicons';
import { enterOutline, exitOutline } from 'ionicons/icons';
import { TranslatePipe } from '../i18n/translation.pipe';
import { TranslationService } from '../i18n/translation.service';
@Component({ @Component({
selector: 'app-tab1', selector: 'app-tab1',
templateUrl: 'tab1.page.html', templateUrl: 'tab1.page.html',
styleUrls: ['tab1.page.scss'], styleUrls: ['tab1.page.scss'],
imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent], providers: [FullnamePipe],
imports: [
IonButton,
IonIcon,
IonLabel,
IonItem,
IonList,
IonRow,
IonCol,
IonGrid,
IonHeader,
IonToolbar,
IonTitle,
IonContent,
FullnamePipe,
CdkDropList,
CdkDrag,
CommonModule,
CdkDragPlaceholder,
CdkDropListGroup,
TranslatePipe
]
}) })
export class Tab1Page { export class Tab1Page {
constructor() {} constructor() {
addIcons({ exitOutline, enterOutline });
}
readonly settingsService = inject(SettingsService);
readonly umpireService = inject(UmpireService);
readonly waitingUmpireService = inject(WaitingUmpiresService);
readonly waitingServiceJudgeService = inject(WaitingServiceJudgesService);
readonly courtUmpireService = inject(CourtUmpireService);
readonly courtServiceJudgeService = inject(CourtServiceJudgeService);
readonly translationService = inject(TranslationService);
private alertController = inject(AlertController);
private fullnamePipe = inject(FullnamePipe);
/**
* Raw signals from services
*/
readonly settings = this.settingsService.settings;
readonly _umpires = this.umpireService.umpires;
readonly _waitingUmpires = this.waitingUmpireService.waitingUmpires;
readonly _waitingServiceJudges =
this.waitingServiceJudgeService.waitingServiceJudges;
readonly _courtUmpires = this.courtUmpireService.umpires;
readonly _courtServiceJudges = this.courtServiceJudgeService.umpires;
/**
* Fast O(1) umpire lookup
*/
readonly umpireMap = computed(() => {
return new Map<number, Umpire>(this._umpires().map((u) => [u.id, u]));
});
/**
* Waiting umpire IDs
*/
readonly waitingUmpireIds = computed(() => {
return new Set(this._waitingUmpires().map((w) => w.umpireId));
});
/**
* Waiting service judge IDs
*/
readonly waitingServiceJudgeIds = computed(() => {
return new Set(this._waitingServiceJudges().map((w) => w.serviceJudgeId));
});
/**
* Court umpire IDs
*/
readonly courtUmpireIds = computed(() => {
return new Set(
this._courtUmpires()
.map((c) => c.umpireId)
.filter((id): id is number => id !== null)
);
});
/**
* Court service judge IDs
*/
readonly courtServiceJudgeIds = computed(() => {
return new Set(
this._courtServiceJudges()
.map((c) => c.umpireId)
.filter((id): id is number => id !== null)
);
});
/**
* Umpires waiting as umpire
*/
readonly waitingUmpires = computed(() => {
const umpireMap = this.umpireMap();
return this._waitingUmpires()
.map((wu) => umpireMap.get(wu.umpireId))
.filter((u): u is Umpire => !!u);
});
/**
* Umpires waiting as service judge
*/
readonly waitingServiceJudges = computed(() => {
const umpireMap = this.umpireMap();
return this._waitingServiceJudges()
.map((wsj) => umpireMap.get(wsj.serviceJudgeId))
.filter((u): u is Umpire => !!u);
});
/**
* Court umpires
*/
readonly courtUmpires = computed(() => {
const umpireMap = this.umpireMap();
return this._courtUmpires()
.map((cu) => {
if (cu.umpireId == null) {
return undefined;
}
return umpireMap.get(cu.umpireId);
})
.filter((u): u is Umpire => !!u);
});
/**
* Court service judges
*/
readonly courtServiceJudges = computed(() => {
const umpireMap = this.umpireMap();
return this._courtServiceJudges()
.map((csj) => {
if (csj.umpireId == null) {
return undefined;
}
return umpireMap.get(csj.umpireId);
})
.filter((u): u is Umpire => !!u);
});
/**
* Umpires resting
*/
readonly onRest = computed(() => {
const waitingUmpireIds = this.waitingUmpireIds();
const waitingServiceJudgeIds = this.waitingServiceJudgeIds();
const courtUmpireIds = this.courtUmpireIds();
const courtServiceJudgeIds = this.courtServiceJudgeIds();
return this._umpires().filter((umpire) => {
return (
!waitingUmpireIds.has(umpire.id) &&
!waitingServiceJudgeIds.has(umpire.id) &&
!courtUmpireIds.has(umpire.id) &&
!courtServiceJudgeIds.has(umpire.id)
);
});
});
/**
* Court -> umpire map
*/
readonly umpireByCourt = computed(() => {
const map = new Map<number, Umpire>();
const umpireMap = this.umpireMap();
for (const item of this._courtUmpires()) {
if (item.umpireId == null) {
continue;
}
const umpire = umpireMap.get(item.umpireId);
if (!umpire) {
continue;
}
map.set(item.courtNo, umpire);
}
return map;
});
/**
* Court -> service judge map
*/
readonly serviceJudgeByCourt = computed(() => {
const map = new Map<number, Umpire>();
const umpireMap = this.umpireMap();
for (const item of this._courtServiceJudges()) {
if (item.umpireId == null) {
continue;
}
const umpire = umpireMap.get(item.umpireId);
if (!umpire) {
continue;
}
map.set(item.courtNo, umpire);
}
return map;
});
/**
* Court indexes for template
*/
readonly courtIndexes = computed(() => {
const count = this.settings()?.numberOfCourts ?? 0;
return Array.from({ length: count }, (_, i) => i + 1);
});
/**
* Is service judge functionality enabled in settings.
*/
readonly serviceJudgeEnabled = computed(() => {
return this.settings()?.withServiceJudge ?? false;
});
dropToRest(event: CdkDragDrop<Umpire[]>) {
if (event.previousContainer === event.container) {
return;
} else {
const comingFrom = event.previousContainer.id;
const umpireToMove = event.item.data;
this.removeFromOriginalPlace(umpireToMove, comingFrom);
}
}
dropToWaitingServiceJudge(event: CdkDragDrop<Umpire[]>) {
const umpireToMove = event.item.data;
if (event.previousContainer === event.container) {
this.waitingServiceJudgeService.moveToPosition(
umpireToMove.id,
event.currentIndex + 1
);
return;
}
const comingFrom = event.previousContainer.id;
this.waitingServiceJudgeService.add(
umpireToMove.id,
event.currentIndex + 1
);
this.removeFromOriginalPlace(umpireToMove, comingFrom);
}
dropToWaitingUmpire(event: CdkDragDrop<Umpire[]>) {
const umpireToMove = event.item.data;
if (event.previousContainer === event.container) {
this.waitingUmpireService.moveToPosition(
umpireToMove.id,
event.currentIndex + 1
);
return;
} else {
const comingFrom = event.previousContainer.id;
this.waitingUmpireService.add(umpireToMove.id, event.currentIndex + 1);
this.removeFromOriginalPlace(umpireToMove, comingFrom);
}
}
dropToUmpire(event: CdkDragDrop<(Umpire | undefined)[]>, courtNo: number) {
const targetUmpires = event.container.data;
if (targetUmpires.length > 0) {
return;
}
const comingFrom = event.previousContainer.id;
const umpireToMove = event.item.data;
this.courtUmpireService.save({ umpireId: umpireToMove.id, courtNo });
this.removeFromOriginalPlace(umpireToMove, comingFrom);
}
dropToServiceJudge(
event: CdkDragDrop<(Umpire | undefined)[]>,
courtNo: number
) {
const targetUmpires = event.container.data;
if (targetUmpires.length > 0) {
return;
}
const comingFrom = event.previousContainer.id;
const umpireToMove = event.item.data;
this.courtServiceJudgeService.save({ umpireId: umpireToMove.id, courtNo });
this.removeFromOriginalPlace(umpireToMove, comingFrom);
}
private removeFromOriginalPlace(
umpireToMove: Umpire,
comingFrom: string
): void {
if ('list-waiting-service-judges' === comingFrom) {
this.waitingServiceJudgeService.removeByUmpireId(umpireToMove.id);
}
if ('list-waiting-umpires' === comingFrom) {
this.waitingUmpireService.removeByUmpireId(umpireToMove.id);
}
if (comingFrom.startsWith('court-umpire')) {
this.courtUmpireService.removeByUmpireId(umpireToMove.id);
}
if (comingFrom.startsWith('court-service-judge')) {
this.courtServiceJudgeService.removeByUmpireId(umpireToMove.id);
}
}
canDropUmpire = (
drag: CdkDrag<Umpire>,
drop: CdkDropList<Umpire[]>
): boolean => {
return drop.data.length === 0;
};
public async showRemoveConfirmation(courtNo: number) {
const umpire = this.umpireByCourt().get(courtNo);
const serviceJudge = this.serviceJudgeByCourt().get(courtNo);
if (
typeof umpire === 'undefined' ||
(typeof serviceJudge === 'undefined' && this.serviceJudgeEnabled())
) {
return;
}
if (!this.settings()?.showAlert) {
this.removeUmpiresFromCourt(courtNo);
return;
}
const alert = await this.alertController.create({
header: this.translationService.translate('courtConfirm', {
courtNo
}),
subHeader: this.translationService.translate(
serviceJudge ? 'removeUmpiresFromCourt' : 'removeUmpireFromCourt'
),
cssClass: 'wide',
message: `${this.fullnamePipe.transform(umpire)}${
serviceJudge ? ' - ' : ''
}${this.fullnamePipe.transform(serviceJudge)}`,
buttons: [
{
text: this.translationService.translate('no'),
role: 'cancel'
},
{
text: this.translationService.translate('yes'),
role: 'confirm',
handler: () => {
if (this.serviceJudgeEnabled()) {
this.removeUmpiresFromCourt(courtNo);
return;
}
this.removeUmpireFromCourt(courtNo);
}
}
]
});
await alert.present();
}
/**
* This is for removing both umpire and service judge from the court (when service judge is enabled).
*/
private async removeUmpiresFromCourt(courtNo: number) {
const umpire = this.umpireByCourt().get(courtNo);
const serviceJudge = this.serviceJudgeByCourt().get(courtNo);
if (typeof umpire === 'undefined' || typeof serviceJudge === 'undefined') {
return;
}
await this.courtUmpireService.removeByUmpireId(umpire.id);
await this.courtServiceJudgeService.removeByUmpireId(serviceJudge.id);
await this.waitingUmpireService.add(serviceJudge.id);
await this.waitingServiceJudgeService.add(umpire.id);
}
/**
* This is for removing 1 umpire only (when service judge is not enabled).
*/
private async removeUmpireFromCourt(courtNo: number) {
const umpire = this.umpireByCourt().get(courtNo);
if (typeof umpire === 'undefined') {
return;
}
await this.courtUmpireService.removeByUmpireId(umpire.id);
await this.waitingUmpireService.add(umpire.id);
}
public async showAddConfirmation(courtNo: number) {
const umpire = await this.waitingUmpireService.getCurrentUmpire();
const serviceJudge =
await this.waitingServiceJudgeService.getCurrentUmpire();
if (
typeof umpire === 'undefined' ||
(typeof serviceJudge === 'undefined' && this.serviceJudgeEnabled())
) {
return;
}
if (!this.settings()?.showAlert) {
if (this.serviceJudgeEnabled() && serviceJudge) {
this.addUmpiresToCourt(courtNo, umpire, serviceJudge);
return;
}
this.addUmpireToCourt(courtNo, umpire);
return;
}
const alert = await this.alertController.create({
header: this.translationService.translate('court', {
courtNo
}),
cssClass: 'wide',
subHeader: this.translationService.translate(
serviceJudge ? 'sendUmpiresToCourt' : 'sendUmpireToCourt'
),
message: this.translationService.translate(
serviceJudge ? 'sendUmpiresMessage' : 'sendUmpireMessage',
{
umpire: this.fullnamePipe.transform(umpire),
serviceJudge: this.fullnamePipe.transform(serviceJudge)
}
),
buttons: [
{
text: this.translationService.translate('no'),
role: 'cancel'
},
{
text: this.translationService.translate('yes'),
role: 'confirm',
handler: () => {
if (this.serviceJudgeEnabled() && serviceJudge) {
this.addUmpiresToCourt(courtNo, umpire, serviceJudge);
} else {
this.addUmpireToCourt(courtNo, umpire);
}
}
}
]
});
await alert.present();
}
private async addUmpiresToCourt(
courtNo: number,
umpire: Umpire,
serviceJudge: Umpire
) {
await this.courtUmpireService.save({ courtNo, umpireId: umpire.id });
await this.courtServiceJudgeService.save({
courtNo,
umpireId: serviceJudge.id
});
await this.waitingUmpireService.removeByUmpireId(umpire.id);
await this.waitingServiceJudgeService.removeByUmpireId(serviceJudge.id);
}
private async addUmpireToCourt(courtNo: number, umpire: Umpire) {
await this.courtUmpireService.save({ courtNo, umpireId: umpire.id });
await this.waitingUmpireService.removeByUmpireId(umpire.id);
}
} }
+92 -5
View File
@@ -1,17 +1,104 @@
<ion-header [translucent]="true"> <ion-header [translucent]="true">
<ion-toolbar> <ion-toolbar>
<ion-title> <ion-title> {{ 'courtOfficials' | translate }} </ion-title>
Tab 2 <ion-buttons [slot]="'end'">
</ion-title> <ion-button [color]="'primary'" [fill]="'solid'" id="open-modal">
<ion-icon slot="start" name="add"></ion-icon>
{{ 'add' | translate }}
</ion-button>
</ion-buttons>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content [fullscreen]="true"> <ion-content [fullscreen]="true">
<ion-header collapse="condense"> <ion-header collapse="condense">
<ion-toolbar> <ion-toolbar>
<ion-title size="large">Tab 2</ion-title> <ion-title size="large">Játékvezetők</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<app-explore-container name="Tab 2 page"></app-explore-container> <ion-grid>
<ion-row>
<ion-col size="12" sizeMd="6" pushMd="3">
<ion-list>
@for (umpire of umpires(); track umpire.id) {
<ion-item>
<ion-label> {{ umpire | fullname }} </ion-label>
<ion-button
[color]="'danger'"
slot="end"
(click)="showDeleteConfirmation(umpire)">
<ion-icon slot="icon-only" name="trash"></ion-icon>
</ion-button>
</ion-item>
} @empty {
<ion-item> {{ 'noCourtOfficials' | translate }} </ion-item>
}
</ion-list>
</ion-col>
</ion-row>
</ion-grid>
<ion-modal trigger="open-modal" (willDismiss)="onWillDismiss($event)">
<ng-template>
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button (click)="cancel()"
>{{ 'cancel' | translate }}</ion-button
>
</ion-buttons>
<ion-title>{{ 'newCourtOfficial' | translate }}</ion-title>
<ion-buttons slot="end">
<ion-button
[disabled]="!isValidUmpire()"
(click)="confirm()"
[strong]="true">
{{ 'save' | translate }}
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-list>
<ion-item>
<ion-input
[label]="'lastName' | translate"
labelPlacement="stacked"
type="text"
placeholder="{{ 'lastName' | translate }}"
[(ngModel)]="lastName" />
</ion-item>
<ion-item>
<ion-input
[label]="'firstName' | translate"
labelPlacement="stacked"
type="text"
placeholder="{{ 'firstName' | translate }}"
[(ngModel)]="firstName" />
</ion-item>
<ion-item>
<ion-input
[label]="'country' | translate"
labelPlacement="stacked"
type="text"
placeholder="{{ 'country' | translate }}"
[(ngModel)]="country" />
</ion-item>
<ion-item>
<ion-select
[label]="'gender' | translate"
placeholder="{{ 'gender' | translate }}"
[(ngModel)]="gender">
@for (item of genders; track item.code) {
<ion-select-option [value]="item.code">
{{item.label | translate }}
</ion-select-option>
}
</ion-select>
</ion-item>
</ion-list>
</ion-content>
</ng-template>
</ion-modal>
</ion-content> </ion-content>
+143 -5
View File
@@ -1,15 +1,153 @@
import { Component } from '@angular/core'; import { Component, inject, ViewChild } from '@angular/core';
import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone'; import {
import { ExploreContainerComponent } from '../explore-container/explore-container.component'; IonHeader,
IonToolbar,
IonTitle,
IonContent,
IonButtons,
IonButton,
IonIcon,
IonModal,
IonItem,
IonInput,
IonSelect,
IonSelectOption,
IonList,
IonGrid,
IonRow,
IonCol,
IonLabel,
AlertController
} from '@ionic/angular/standalone';
import { UmpireService } from '../services/umpire.service';
import { Umpire } from 'db';
import { addIcons } from 'ionicons';
import { add, trash } from 'ionicons/icons';
import { OverlayEventDetail } from '@ionic/core/components';
import { FormsModule } from '@angular/forms';
import { FullnamePipe } from '../fullname-pipe';
import { TranslatePipe } from '../i18n/translation.pipe';
import { TranslationKey } from '../i18n/translations';
import { TranslationService } from '../i18n/translation.service';
@Component({ @Component({
selector: 'app-tab2', selector: 'app-tab2',
templateUrl: 'tab2.page.html', templateUrl: 'tab2.page.html',
styleUrls: ['tab2.page.scss'], styleUrls: ['tab2.page.scss'],
imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent] providers: [FullnamePipe],
imports: [
IonLabel,
IonCol,
IonRow,
IonGrid,
IonInput,
IonItem,
IonModal,
IonIcon,
IonButton,
IonButtons,
IonHeader,
IonToolbar,
IonTitle,
IonContent,
FormsModule,
IonSelect,
IonSelectOption,
IonList,
FullnamePipe,
TranslatePipe
]
}) })
export class Tab2Page { export class Tab2Page {
readonly translationService = inject(TranslationService);
constructor() {} @ViewChild(IonModal) modal!: IonModal;
public readonly umpireService = inject(UmpireService);
public readonly fullnamePipe = inject(FullnamePipe);
public readonly alertController = inject(AlertController);
public readonly umpires = this.umpireService.umpires;
public lastName: string = '';
public firstName: string = '';
public country: string = '';
public gender: string = '';
public genders: { label: TranslationKey; code: string }[] = [
{ label: 'male', code: 'M' },
{ label: 'female', code: 'W' }
];
constructor() {
addIcons({ add, trash });
}
public async onWillDismiss(event: CustomEvent<OverlayEventDetail>) {
if (event.detail.role !== 'confirm') {
return;
}
const newUmpire: Umpire = event.detail.data;
await this.umpireService.create({
firstName: newUmpire.firstName,
lastName: newUmpire.lastName,
country: newUmpire.country,
gender: newUmpire.gender
});
}
cancel() {
this.modal.dismiss(null, 'cancel');
}
async confirm() {
await this.modal.dismiss(
{
lastName: this.lastName,
firstName: this.firstName,
country: this.country,
gender: this.gender
},
'confirm'
);
this.lastName = '';
this.firstName = '';
this.country = '';
this.gender = '';
}
isValidUmpire(): boolean {
return this.lastName !== '' && this.firstName !== '';
}
public async showDeleteConfirmation(umpire: Umpire): Promise<void> {
const alert = await this.alertController.create({
header: this.translationService.translate('confirmation'),
subHeader: this.translationService.translate('confirmDeleteUmpire'),
message: this.fullnamePipe.transform(umpire),
buttons: [
{
text: this.translationService.translate('no'),
role: 'cancel'
},
{
text: this.translationService.translate('yes'),
role: 'confirm',
handler: () => {
// TODO: also remove from first tab
this.removeUmpire(umpire);
}
}
]
});
await alert.present();
}
private removeUmpire(umpire: Umpire): void {
this.umpireService.delete(umpire.id);
}
} }
+65 -11
View File
@@ -1,17 +1,71 @@
<ion-header [translucent]="true"> <ion-header>
<ion-toolbar> <ion-toolbar>
<ion-title> <ion-title>{{ 'settings' | translate }}</ion-title>
Tab 3
</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content [fullscreen]="true"> <ion-content>
<ion-header collapse="condense"> <ion-grid [fixed]="true">
<ion-toolbar> <ion-row>
<ion-title size="large">Tab 3</ion-title> <ion-col sizeLg="6" pushLg="3">
</ion-toolbar> @if (settings(); as currentSettings) {
</ion-header>
<app-explore-container name="Tab 3 page"></app-explore-container> <ion-list>
<ion-item>
<ion-select
[label]="'language' | translate"
[value]="translationService.language()"
(ionChange)="updateLanguage($event)"
interface="popover">
<ion-select-option value="en">
{{ 'english' | translate }}
</ion-select-option>
<ion-select-option value="hu">
{{ 'hungarian' | translate }}
</ion-select-option>
</ion-select>
</ion-item>
<ion-item>
<ion-toggle
[checked]="currentSettings.withServiceJudge"
(ionChange)="updateWithServiceJudge($event)">
{{ 'serviceJudge' | translate }}
</ion-toggle>
</ion-item>
<ion-item>
<ion-toggle
[checked]="currentSettings.showAlert"
(ionChange)="updateShowAlert($event)">
{{ 'showAlerts' | translate }}
</ion-toggle>
</ion-item>
<ion-item>
<ion-input
[label]="'numberOfCourts' | translate"
type="number"
min="1"
labelPlacement="floating"
[value]="currentSettings.numberOfCourts"
(ionInput)="updateNumberOfCourts($event)">
</ion-input>
</ion-item>
</ion-list>
}
</ion-col>
</ion-row>
<ion-row>
<ion-col sizeLg="6" pushLg="3">
<ion-item>
<ion-label class="ion-text-center"
>{{ 'version' | translate }}: 1.1.4</ion-label
>
</ion-item>
</ion-col>
</ion-row>
</ion-grid>
</ion-content> </ion-content>
+99 -5
View File
@@ -1,13 +1,107 @@
import { Component } from '@angular/core'; import { Component, inject } from '@angular/core';
import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone'; import {
import { ExploreContainerComponent } from '../explore-container/explore-container.component'; IonHeader,
IonToolbar,
IonTitle,
IonContent,
IonList,
IonItem,
IonToggle,
IonInput,
IonGrid,
IonCol,
IonRow,
IonSelect,
IonSelectOption,
IonLabel
} from '@ionic/angular/standalone';
import { SettingsService } from '../services/settings-service';
import { CourtUmpireService } from '../services/court.umpire.service';
import { CourtServiceJudgeService } from '../services/court.service.judge.service';
import { CourtServiceJudge } from 'db';
import { WaitingServiceJudgesService } from '../services/waiting-service-judges.service';
import { TranslationService } from '../i18n/translation.service';
import { Language } from '../i18n/translations';
import { TranslatePipe } from '../i18n/translation.pipe';
@Component({ @Component({
selector: 'app-tab3', selector: 'app-tab3',
templateUrl: 'tab3.page.html', templateUrl: 'tab3.page.html',
styleUrls: ['tab3.page.scss'], styleUrls: ['tab3.page.scss'],
imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent], imports: [
IonRow,
IonCol,
IonGrid,
IonInput,
IonToggle,
IonItem,
IonList,
IonHeader,
IonToolbar,
IonTitle,
IonContent,
IonSelect,
IonSelectOption,
TranslatePipe,
IonLabel
]
}) })
export class Tab3Page { export class Tab3Page {
constructor() {} readonly settingsService = inject(SettingsService);
readonly courtUmpireService = inject(CourtUmpireService);
readonly courtServiceJudgeService = inject(CourtServiceJudgeService);
readonly waitingServiceJudgeService = inject(WaitingServiceJudgesService);
readonly translationService = inject(TranslationService);
readonly settings = this.settingsService.settings;
async updateNumberOfCourts(event: CustomEvent): Promise<void> {
this.courtUmpireService
.umpires()
.filter((cu) => {
return cu.courtNo > Number(event.detail.value);
})
.map((cu) => {
this.courtUmpireService.delete(cu.id);
});
this.courtServiceJudgeService
.umpires()
.filter((cu) => {
return cu.courtNo > Number(event.detail.value);
})
.map((cu) => {
this.courtServiceJudgeService.delete(cu.id);
});
await this.settingsService.update({
numberOfCourts: Number(event.detail.value)
});
}
async updateWithServiceJudge(event: CustomEvent): Promise<void> {
await this.settingsService.update({
withServiceJudge: event.detail.checked
});
if (!event.detail.checked) {
this.courtServiceJudgeService.umpires().map((csj: CourtServiceJudge) => {
this.courtServiceJudgeService.delete(csj.id);
});
this.waitingServiceJudgeService.waitingServiceJudges().map((wsj) => {
this.waitingServiceJudgeService.remove(wsj.id);
});
}
}
async updateShowAlert(event: CustomEvent): Promise<void> {
await this.settingsService.update({
showAlert: event.detail.checked
});
}
updateLanguage(event: CustomEvent): void {
this.translationService.setLanguage(event.detail.value as Language);
}
} }
+7 -7
View File
@@ -1,18 +1,18 @@
<ion-tabs> <ion-tabs>
<ion-tab-bar slot="bottom"> <ion-tab-bar slot="bottom">
<ion-tab-button tab="pilot" href="/tabs/pilot"> <ion-tab-button tab="pilot" href="/tabs/pilot">
<ion-icon aria-hidden="true" name="triangle"></ion-icon> <ion-icon aria-hidden="true" name="grid-outline"></ion-icon>
<ion-label>Tab 1</ion-label> <ion-label>{{ 'courts' | translate }}</ion-label>
</ion-tab-button> </ion-tab-button>
<ion-tab-button tab="umpires" href="/tabs/umpires"> <ion-tab-button tab="umpires" href="/tabs/umpires">
<ion-icon aria-hidden="true" name="ellipse"></ion-icon> <ion-icon aria-hidden="true" name="people-outline"></ion-icon>
<ion-label>Tab 2</ion-label> <ion-label>{{ 'courtOfficials' | translate }}</ion-label>
</ion-tab-button> </ion-tab-button>
<ion-tab-button tab="stats" href="/tabs/stats"> <ion-tab-button tab="settings" href="/tabs/settings">
<ion-icon aria-hidden="true" name="square"></ion-icon> <ion-icon aria-hidden="true" name="settings-outline"></ion-icon>
<ion-label>Tab 3</ion-label> <ion-label>{{ 'settings' | translate }}</ion-label>
</ion-tab-button> </ion-tab-button>
</ion-tab-bar> </ion-tab-bar>
</ion-tabs> </ion-tabs>
+11 -4
View File
@@ -1,18 +1,25 @@
import { Component, EnvironmentInjector, inject } from '@angular/core'; import { Component, EnvironmentInjector, inject } from '@angular/core';
import { IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel } from '@ionic/angular/standalone'; import {
IonTabs,
IonTabBar,
IonTabButton,
IonIcon,
IonLabel
} from '@ionic/angular/standalone';
import { addIcons } from 'ionicons'; import { addIcons } from 'ionicons';
import { triangle, ellipse, square } from 'ionicons/icons'; import { gridOutline, peopleOutline, settingsOutline } from 'ionicons/icons';
import { TranslatePipe } from '../i18n/translation.pipe';
@Component({ @Component({
selector: 'app-tabs', selector: 'app-tabs',
templateUrl: 'tabs.page.html', templateUrl: 'tabs.page.html',
styleUrls: ['tabs.page.scss'], styleUrls: ['tabs.page.scss'],
imports: [IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel], imports: [IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel, TranslatePipe]
}) })
export class TabsPage { export class TabsPage {
public environmentInjector = inject(EnvironmentInjector); public environmentInjector = inject(EnvironmentInjector);
constructor() { constructor() {
addIcons({ triangle, ellipse, square }); addIcons({ gridOutline, peopleOutline, settingsOutline });
} }
} }
+1 -1
View File
@@ -15,7 +15,7 @@ export const routes: Routes = [
loadComponent: () => import('../tab2/tab2.page').then((m) => m.Tab2Page) loadComponent: () => import('../tab2/tab2.page').then((m) => m.Tab2Page)
}, },
{ {
path: 'stats', path: 'settings',
loadComponent: () => import('../tab3/tab3.page').then((m) => m.Tab3Page) loadComponent: () => import('../tab3/tab3.page').then((m) => m.Tab3Page)
}, },
{ {

Some files were not shown because too many files have changed in this diff Show More