Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99cbbd7a07 | ||
|
|
7c5384621a | ||
|
|
848e12e2d6 | ||
|
|
2f7f5ba5af | ||
|
|
e7d9fe0efb |
No files matched your search
@@ -66,6 +66,15 @@ jobs:
|
|||||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||||
run: npm run tauri build
|
run: npm run tauri build
|
||||||
|
|
||||||
|
- name: Upload Windows packages as GitHub artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: court-pilot-windows
|
||||||
|
if-no-files-found: error
|
||||||
|
path: |
|
||||||
|
src-tauri/target/release/bundle/msi/*.msi
|
||||||
|
src-tauri/target/release/bundle/nsis/*.exe
|
||||||
|
|
||||||
- name: Upload release assets to CrabNebula
|
- name: Upload release assets to CrabNebula
|
||||||
uses: crabnebula-dev/cloud-release@v0
|
uses: crabnebula-dev/cloud-release@v0
|
||||||
with:
|
with:
|
||||||
@@ -116,6 +125,13 @@ jobs:
|
|||||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||||
run: npm run tauri build -- --target ${{ matrix.target }} --bundles app,dmg
|
run: npm run tauri build -- --target ${{ matrix.target }} --bundles app,dmg
|
||||||
|
|
||||||
|
- name: Upload macOS packages as GitHub artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: court-pilot-macos-${{ matrix.target }}
|
||||||
|
if-no-files-found: error
|
||||||
|
path: src-tauri/target/${{ matrix.target }}/release/bundle/dmg/*.dmg
|
||||||
|
|
||||||
- name: Upload release assets to CrabNebula
|
- name: Upload release assets to CrabNebula
|
||||||
uses: crabnebula-dev/cloud-release@v0
|
uses: crabnebula-dev/cloud-release@v0
|
||||||
with:
|
with:
|
||||||
@@ -228,3 +244,37 @@ jobs:
|
|||||||
api-key: ${{ secrets.CN_API_KEY }}
|
api-key: ${{ secrets.CN_API_KEY }}
|
||||||
path: ./src-tauri
|
path: ./src-tauri
|
||||||
working-directory: ./src-tauri
|
working-directory: ./src-tauri
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# Publish GitHub release after CrabNebula publication succeeds
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
github-release:
|
||||||
|
needs: publish
|
||||||
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Download release packages
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
pattern: court-pilot-*
|
||||||
|
path: release-assets
|
||||||
|
|
||||||
|
- name: Create GitHub release
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
GH_REPO: ${{ github.repository }}
|
||||||
|
RELEASE_TAG: ${{ github.ref_name }}
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
mapfile -d '' assets < <(find release-assets -type f -print0)
|
||||||
|
if [ "${#assets[@]}" -eq 0 ]; then
|
||||||
|
echo "No release packages found" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
gh release create "$RELEASE_TAG" "${assets[@]}" \
|
||||||
|
--verify-tag \
|
||||||
|
--title "$RELEASE_TAG" \
|
||||||
|
--generate-notes
|
||||||
@@ -99,7 +99,7 @@ Do not manually edit generated output or installed dependencies:
|
|||||||
- Run linting: `npm run lint`
|
- Run linting: `npm run lint`
|
||||||
- Run Tauri CLI commands: `npm run tauri -- <command>`
|
- Run Tauri CLI commands: `npm run tauri -- <command>`
|
||||||
|
|
||||||
Tauri development intentionally uses port `8103`. `src-tauri/tauri.conf.json` expects that port, so its `beforeDevCommand` must start Ionic on `8103`; the current `npm start` script does not explicitly select it. Verify the full `npm run tauri dev` flow before claiming it works.
|
Tauri development intentionally uses port `8103`. `src-tauri/tauri.conf.json` expects that port, so its `beforeDevCommand` starts the project's Angular CLI with `serve --host=0.0.0.0 --port=8103`. Keep the command aligned with `devUrl`. Verify the full `npm run tauri dev` flow before claiming it works.
|
||||||
|
|
||||||
## Release versioning
|
## Release versioning
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -122,7 +122,7 @@
|
|||||||
"lint": {
|
"lint": {
|
||||||
"builder": "@angular-eslint/builder:lint",
|
"builder": "@angular-eslint/builder:lint",
|
||||||
"options": {
|
"options": {
|
||||||
"lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
|
"lintFilePatterns": ["src/**/*.ts", "src/**/*.html", "db.ts"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import Dexie, { type EntityTable } from 'dexie';
|
import Dexie, { type EntityTable } from 'dexie';
|
||||||
|
|
||||||
|
export type WaitingRole = 'umpire' | 'serviceJudge';
|
||||||
|
|
||||||
export interface Umpire {
|
export interface Umpire {
|
||||||
id: number;
|
id: number;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
@@ -7,6 +9,7 @@ export interface Umpire {
|
|||||||
country: string;
|
country: string;
|
||||||
gender: string;
|
gender: string;
|
||||||
courtNo?: number;
|
courtNo?: number;
|
||||||
|
returnToQueue?: WaitingRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CourtUmpire {
|
export interface CourtUmpire {
|
||||||
@@ -38,6 +41,7 @@ export interface Settings {
|
|||||||
withServiceJudge: boolean;
|
withServiceJudge: boolean;
|
||||||
numberOfCourts: number;
|
numberOfCourts: number;
|
||||||
showAlert: boolean;
|
showAlert: boolean;
|
||||||
|
waitingListQuickButtons?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const db = new Dexie('CourtPilot') as Dexie & {
|
const db = new Dexie('CourtPilot') as Dexie & {
|
||||||
@@ -58,4 +62,20 @@ db.version(1).stores({
|
|||||||
waitingServiceJudges: '++id, order, serviceJudgeId'
|
waitingServiceJudges: '++id, order, serviceJudgeId'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
db.version(2)
|
||||||
|
.stores({
|
||||||
|
umpires: '++id, lastName',
|
||||||
|
courtUmpires: '++id, courtNo, umpireId',
|
||||||
|
courtServiceJudges: '++id, courtNo, umpireId',
|
||||||
|
settings: '++id',
|
||||||
|
waitingUmpires: '++id, order, umpireId',
|
||||||
|
waitingServiceJudges: '++id, order, serviceJudgeId'
|
||||||
|
})
|
||||||
|
.upgrade(async (transaction) => {
|
||||||
|
await transaction
|
||||||
|
.table('settings')
|
||||||
|
.toCollection()
|
||||||
|
.modify({ waitingListQuickButtons: false });
|
||||||
|
});
|
||||||
|
|
||||||
export { db };
|
export { db };
|
||||||
+21
-6
@@ -21,11 +21,11 @@ flowchart TD
|
|||||||
|
|
||||||
The root route lazy-loads the tab routes. The three primary routes are:
|
The root route lazy-loads the tab routes. The three primary routes are:
|
||||||
|
|
||||||
| Route | Source | Responsibility |
|
| Route | Source | Responsibility |
|
||||||
|---|---|---|
|
| ---------------- | --------------- | ---------------------------------------------------------------------- |
|
||||||
| `/tabs/pilot` | `src/app/tab1/` | Waiting queues, court assignments, drag and drop, and rotation actions |
|
| `/tabs/pilot` | `src/app/tab1/` | Waiting queues, court assignments, drag and drop, and rotation actions |
|
||||||
| `/tabs/umpires` | `src/app/tab2/` | Court-official roster management |
|
| `/tabs/umpires` | `src/app/tab2/` | Court-official roster management |
|
||||||
| `/tabs/settings` | `src/app/tab3/` | Court count, theme, service-judge mode, alerts, and language |
|
| `/tabs/settings` | `src/app/tab3/` | Court count, theme, service-judge mode, alerts, and language |
|
||||||
|
|
||||||
Pages are standalone Angular components and directly import the Ionic standalone components they use. Services are root-provided and injected with Angular's `inject()` function.
|
Pages are standalone Angular components and directly import the Ionic standalone components they use. Services are root-provided and injected with Angular's `inject()` function.
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ Persistence services use Dexie's `liveQuery()` and Angular's `toSignal()` to exp
|
|||||||
|
|
||||||
## Persistence model
|
## Persistence model
|
||||||
|
|
||||||
`db.ts` declares a `CourtPilot` Dexie database at schema version 1.
|
`db.ts` declares a `CourtPilot` Dexie database at schema version 2.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
erDiagram
|
erDiagram
|
||||||
@@ -85,6 +85,21 @@ component starts and toggles Ionic's `ion-palette-dark` class. Device theme chan
|
|||||||
apply while the app is open when device-preferred is selected. This does not
|
apply while the app is open when device-preferred is selected. This does not
|
||||||
change the Dexie schema or existing settings records.
|
change the Dexie schema or existing settings records.
|
||||||
|
|
||||||
|
### Waiting-list quick buttons
|
||||||
|
|
||||||
|
The optional waiting-list quick buttons default to disabled and require service
|
||||||
|
judges. Disabling service judges disables this setting. Both waiting lists have
|
||||||
|
an off-duty action; off-duty officials with a saved origin have a return action. Quick-button alerts
|
||||||
|
apply to both actions. Dragging an official from either waiting list to Not on Duty
|
||||||
|
also saves the origin, even when quick buttons are disabled. Returning appends to the saved role queue.
|
||||||
|
|
||||||
|
Schema version 2 adds `waitingListQuickButtons` to settings and optional
|
||||||
|
`returnToQueue` metadata to officials. The upgrade explicitly sets the setting to
|
||||||
|
false for existing settings rows. Existing officials need no transformation;
|
||||||
|
officials without return metadata have no return action. Queue indexes and
|
||||||
|
existing records are preserved. Older application versions do not understand the
|
||||||
|
new metadata; downgrading an IndexedDB database is not supported.
|
||||||
|
|
||||||
### Schema-change policy
|
### Schema-change policy
|
||||||
|
|
||||||
Once data may exist in released clients, do not modify schema version 1 in place. Add a new `db.version(n)` declaration and define upgrade behavior when existing records need transformation.
|
Once data may exist in released clients, do not modify schema version 1 in place. Add a new `db.version(n)` declaration and define upgrade behavior when existing records need transformation.
|
||||||
|
|||||||
+56
-2
@@ -126,6 +126,15 @@ uses Vite 8.3.5 or newer within v8, and Capacitor's Xcode parser uses UUID 11.1.
|
|||||||
or newer within v11. Xcode uses the compatible CommonJS `uuid.v4()` API.
|
or newer within v11. Xcode uses the compatible CommonJS `uuid.v4()` API.
|
||||||
Revisit these overrides when upstream packages include the patched versions.
|
Revisit these overrides when upstream packages include the patched versions.
|
||||||
|
|
||||||
|
The audit overrides also select patched `minimatch` 11 for modern consumers,
|
||||||
|
`glob` 14 for consumers of version 9 or newer, and `follow-redirects` 1.16.2 or
|
||||||
|
newer for Karma's HTTP proxy. These overrides retain Node.js 22 support. Keep
|
||||||
|
Karma's legacy `minimatch` 3 and `glob` 7 dependency paths intact: their callable
|
||||||
|
CommonJS APIs differ from the modern packages. npm still reports moderate
|
||||||
|
findings through those legacy test dependencies; resolving them requires an
|
||||||
|
upstream-compatible test-tooling update. Do not use `npm audit fix --force`,
|
||||||
|
which proposes incompatible downgrades of Angular and the test tooling.
|
||||||
|
|
||||||
## Tauri workflow
|
## Tauri workflow
|
||||||
|
|
||||||
Run Tauri commands through the npm script:
|
Run Tauri commands through the npm script:
|
||||||
@@ -137,10 +146,48 @@ nvm use lts/jod && npm run tauri dev
|
|||||||
nvm use lts/jod && npm run tauri build
|
nvm use lts/jod && npm run tauri build
|
||||||
```
|
```
|
||||||
|
|
||||||
Tauri development intentionally expects the frontend at `http://localhost:8103`. `devUrl` only tells Tauri where to connect; the Ionic command must also listen on port `8103`. The current `npm start` script does not explicitly select that port, so verify the complete development flow when changing Tauri or serve configuration.
|
Tauri development intentionally expects the frontend at `http://localhost:8103`. `devUrl` tells Tauri where to connect, and `beforeDevCommand` runs `npm run ng -- serve --host=0.0.0.0 --port=8103` to serve the frontend on the matching port without opening a browser. This uses the project's Angular CLI and does not require a global Ionic CLI installation. Keep these settings aligned when changing Tauri or serve configuration.
|
||||||
|
|
||||||
The desktop wrapper is under `src-tauri/`. Do not edit generated `src-tauri/target/` or `src-tauri/gen/` content.
|
The desktop wrapper is under `src-tauri/`. Do not edit generated `src-tauri/target/` or `src-tauri/gen/` content.
|
||||||
|
|
||||||
|
### Local updater signing
|
||||||
|
|
||||||
|
Tauri builds require the private key matching the updater public key in
|
||||||
|
`src-tauri/tauri.conf.json`. Set the key path in the build shell; Tauri does not
|
||||||
|
load these values from `.env` files. Keep private keys and passwords outside the
|
||||||
|
repository.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
|
||||||
|
. "$NVM_DIR/nvm.sh"
|
||||||
|
export TAURI_SIGNING_PRIVATE_KEY="$HOME/.tauri/myapp.key"
|
||||||
|
# For a key without a password:
|
||||||
|
export TAURI_SIGNING_PRIVATE_KEY_PASSWORD=""
|
||||||
|
nvm use lts/jod && npm run tauri build
|
||||||
|
```
|
||||||
|
|
||||||
|
For a password-protected key, replace the empty-password export with a hidden
|
||||||
|
prompt in your terminal:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
read -r -s -p "Signing key password: " TAURI_SIGNING_PRIVATE_KEY_PASSWORD
|
||||||
|
echo
|
||||||
|
export TAURI_SIGNING_PRIVATE_KEY_PASSWORD
|
||||||
|
```
|
||||||
|
|
||||||
|
If you keep the password in `$HOME/.tauri/signing-password`, restrict the file to
|
||||||
|
your account with `chmod 600 "$HOME/.tauri/signing-password"`, then load it for
|
||||||
|
each build without printing it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="$(cat "$HOME/.tauri/signing-password")"
|
||||||
|
```
|
||||||
|
|
||||||
|
This stores the password as plaintext outside the repository. The export applies
|
||||||
|
only to the current shell and its child processes.
|
||||||
|
|
||||||
|
See the [Tauri updater signing documentation](https://v2.tauri.app/plugin/updater/#building).
|
||||||
|
|
||||||
## Data storage
|
## Data storage
|
||||||
|
|
||||||
Court Pilot has no backend API. It stores domain data in IndexedDB using Dexie. The database and schema are declared in root-level `db.ts`.
|
Court Pilot has no backend API. It stores domain data in IndexedDB using Dexie. The database and schema are declared in root-level `db.ts`.
|
||||||
@@ -214,12 +261,19 @@ GIT_EDITOR=true git tag v1.2.0
|
|||||||
|
|
||||||
Replace `1.2.0` with the confirmed release version and verify the tag points to the intended commit. Do not overwrite or move an existing release tag.
|
Replace `1.2.0` with the confirmed release version and verify the tag points to the intended commit. Do not overwrite or move an existing release tag.
|
||||||
|
|
||||||
Creating the tag is local. Pushing it starts `.github/workflows/release.yml`, which builds Windows and macOS artifacts for CrabNebula and builds/publishes the Linux Snap. Push only after explicit confirmation:
|
Creating the tag is local. Pushing it starts `.github/workflows/release.yml`, which builds Windows and macOS artifacts for CrabNebula and builds/publishes the Linux Snap. After CrabNebula publication succeeds, it also creates a GitHub Release for the pushed tag with generated release notes and attaches the Windows installers, both macOS DMGs, and the Linux Snap. Push only after explicit confirmation:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git push origin v1.2.0
|
git push origin v1.2.0
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The GitHub Release job uses the built-in `GITHUB_TOKEN` with job-scoped
|
||||||
|
`contents: write` permission; no additional secret is required. Existing
|
||||||
|
CrabNebula, updater-signing, and Snap Store secrets are still required. Manual
|
||||||
|
workflow runs on branches skip the GitHub Release job; runs on `v*` tags include
|
||||||
|
it. Use a new version tag for each release: creation fails if a GitHub Release
|
||||||
|
already exists for that tag.
|
||||||
|
|
||||||
## Generated and local directories
|
## Generated and local directories
|
||||||
|
|
||||||
Do not manually edit or commit generated/dependency content unless a workflow explicitly requires an artifact:
|
Do not manually edit or commit generated/dependency content unless a workflow explicitly requires an artifact:
|
||||||
|
|||||||
+2
-2
@@ -4,11 +4,11 @@ const angular = require('angular-eslint');
|
|||||||
module.exports = defineConfig(
|
module.exports = defineConfig(
|
||||||
{ ignores: ['projects/**', 'www/**', 'coverage/**'] },
|
{ ignores: ['projects/**', 'www/**', 'coverage/**'] },
|
||||||
{
|
{
|
||||||
files: ['src/**/*.ts'],
|
files: ['src/**/*.ts', 'db.ts'],
|
||||||
extends: angular.configs.tsRecommended,
|
extends: angular.configs.tsRecommended,
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: ['./tsconfig.json'],
|
project: ['./tsconfig.eslint.json'],
|
||||||
tsconfigRootDir: __dirname
|
tsconfigRootDir: __dirname
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+26
-164
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "court-pilot",
|
"name": "court-pilot",
|
||||||
"version": "2.0.0",
|
"version": "2.1.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "court-pilot",
|
"name": "court-pilot",
|
||||||
"version": "2.0.0",
|
"version": "2.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/cdk": "^22.2.4",
|
"@angular/cdk": "^22.2.4",
|
||||||
"@angular/common": "^22.2.3",
|
"@angular/common": "^22.2.3",
|
||||||
@@ -4834,6 +4834,13 @@
|
|||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@isaacs/nonrecursive-glob-match": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@isaacs/nonrecursive-glob-match/-/nonrecursive-glob-match-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-SI7kyKHq4XXDRozw+r9GlRZkia1ZkFrpLW98r2IC2Bpr8YkFGE3fvjzmxCG0x78A++69HmDNrDvSMYBZmHGpKA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BlueOak-1.0.0"
|
||||||
|
},
|
||||||
"node_modules/@istanbuljs/schema": {
|
"node_modules/@istanbuljs/schema": {
|
||||||
"version": "0.1.6",
|
"version": "0.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz",
|
||||||
@@ -5737,9 +5744,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -5757,9 +5761,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -5777,9 +5778,6 @@
|
|||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -5797,9 +5795,6 @@
|
|||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -5817,9 +5812,6 @@
|
|||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -5837,9 +5829,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -5857,9 +5846,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6095,9 +6081,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6115,9 +6098,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6135,9 +6115,6 @@
|
|||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6155,9 +6132,6 @@
|
|||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6175,9 +6149,6 @@
|
|||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6195,9 +6166,6 @@
|
|||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6215,9 +6183,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6235,9 +6200,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6452,9 +6414,6 @@
|
|||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6476,9 +6435,6 @@
|
|||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6500,9 +6456,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6524,9 +6477,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6548,9 +6498,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6572,9 +6519,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6939,9 +6883,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6959,9 +6900,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6979,9 +6917,6 @@
|
|||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6999,9 +6934,6 @@
|
|||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -7019,9 +6951,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -7039,9 +6968,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -7142,9 +7068,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -7158,9 +7081,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -7174,9 +7094,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -7190,9 +7107,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -7379,9 +7293,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0 OR MIT",
|
"license": "Apache-2.0 OR MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -7399,9 +7310,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0 OR MIT",
|
"license": "Apache-2.0 OR MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -7419,9 +7327,6 @@
|
|||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0 OR MIT",
|
"license": "Apache-2.0 OR MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -7439,9 +7344,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0 OR MIT",
|
"license": "Apache-2.0 OR MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -7459,9 +7361,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0 OR MIT",
|
"license": "Apache-2.0 OR MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -10835,9 +10734,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/follow-redirects": {
|
"node_modules/follow-redirects": {
|
||||||
"version": "1.16.1",
|
"version": "1.16.2",
|
||||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.2.tgz",
|
||||||
"integrity": "sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg==",
|
"integrity": "sha512-/kVP2U8z596Ia0gwFrwyIeo74ta5aVmSeU1FfAUMAmcEP5EP7zydmag3SLiN64NaRKef3lxsq7i/g7ltJQDGow==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -13087,9 +12986,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -13111,9 +13007,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -13135,9 +13028,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -13159,9 +13049,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -13680,16 +13567,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "10.2.6",
|
"version": "11.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-11.0.4.tgz",
|
||||||
"integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==",
|
"integrity": "sha512-/UJ1dEoVQTjPNDh6SGlUo+ueLMmoHTas4q+8yaAR/5t6nLAIiBDJtoYUh20IYleix7ehiQkgczb84PE3uzHkxg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BlueOak-1.0.0",
|
"license": "BlueOak-1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^5.0.8"
|
"@isaacs/nonrecursive-glob-match": "^1.0.3",
|
||||||
|
"brace-expansion": "^5.0.12"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "18 || 20 || >=22"
|
"node": "20 || >=22"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
@@ -14514,9 +14402,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/path-scurry": {
|
"node_modules/path-scurry": {
|
||||||
"version": "2.0.2",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-3.0.0.tgz",
|
||||||
"integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
|
"integrity": "sha512-1pTbQqH1wIwMi/eleQ5leFTTCkzFsPcgXNjwOA8YGGW/W4/kHT3dssE7Z6NH+zuqNLOzOB0uaD61yl7cAcVhgg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BlueOak-1.0.0",
|
"license": "BlueOak-1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -15324,15 +15212,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rimraf/node_modules/glob": {
|
"node_modules/rimraf/node_modules/glob": {
|
||||||
"version": "13.0.6",
|
"version": "14.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-14.0.3.tgz",
|
||||||
"integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
|
"integrity": "sha512-A1vjIwC3qpdCso45Iw127gnp7sPhqgY8lUQhDrArIs+GbmfaaVPFuoyy7WyLT9EPZCRZ7j7aLdwUhQmHWesEow==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BlueOak-1.0.0",
|
"license": "BlueOak-1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimatch": "^10.2.2",
|
"minimatch": "^11.0.2",
|
||||||
"minipass": "^7.1.3",
|
"minipass": "^7.1.3",
|
||||||
"path-scurry": "^2.0.2"
|
"path-scurry": "^3.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "18 || 20 || >=22"
|
"node": "18 || 20 || >=22"
|
||||||
@@ -15660,7 +15548,6 @@
|
|||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": "glibc",
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -15678,7 +15565,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": "glibc",
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -15696,7 +15582,6 @@
|
|||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": "musl",
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -15714,7 +15599,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": "musl",
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -15732,7 +15616,6 @@
|
|||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": "musl",
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -15750,7 +15633,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": "musl",
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -15768,7 +15650,6 @@
|
|||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": "glibc",
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -15786,7 +15667,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": "glibc",
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -17457,9 +17337,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -17477,9 +17354,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -17497,9 +17371,6 @@
|
|||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -17517,9 +17388,6 @@
|
|||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -17537,9 +17405,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -17557,9 +17422,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|||||||
+6
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "court-pilot",
|
"name": "court-pilot",
|
||||||
"version": "2.0.0",
|
"version": "2.1.0",
|
||||||
"author": "Richard Toth",
|
"author": "Richard Toth",
|
||||||
"homepage": "https://github.com/tricsusz/court-pilot",
|
"homepage": "https://github.com/tricsusz/court-pilot",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -76,6 +76,11 @@
|
|||||||
},
|
},
|
||||||
"xcode": {
|
"xcode": {
|
||||||
"uuid": "^11.1.1"
|
"uuid": "^11.1.1"
|
||||||
|
},
|
||||||
|
"minimatch@>=9": "^11.0.1",
|
||||||
|
"glob@>=9": "^14.0.2",
|
||||||
|
"http-proxy": {
|
||||||
|
"follow-redirects": "^1.16.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -12,7 +12,7 @@ license: MIT
|
|||||||
base: core24
|
base: core24
|
||||||
icon: snap/gui/court-pilot.png
|
icon: snap/gui/court-pilot.png
|
||||||
|
|
||||||
version: '2.0.0'
|
version: '2.1.0'
|
||||||
|
|
||||||
summary: Badminton umpire rotation application
|
summary: Badminton umpire rotation application
|
||||||
|
|
||||||
|
|||||||
Generated
+1
-1
@@ -519,7 +519,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "court-pilot"
|
name = "court-pilot"
|
||||||
version = "2.0.0"
|
version = "2.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "court-pilot"
|
name = "court-pilot"
|
||||||
version = "2.0.0"
|
version = "2.1.0"
|
||||||
description = "Court Pilot"
|
description = "Court Pilot"
|
||||||
authors = ["Richard Toth"]
|
authors = ["Richard Toth"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"productName": "court-pilot",
|
"productName": "court-pilot",
|
||||||
"mainBinaryName": "court-pilot",
|
"mainBinaryName": "court-pilot",
|
||||||
"version": "2.0.0",
|
"version": "2.1.0",
|
||||||
"identifier": "com.courtpilot.dev",
|
"identifier": "com.courtpilot.dev",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../www",
|
"frontendDist": "../www",
|
||||||
"devUrl": "http://localhost:8103",
|
"devUrl": "http://localhost:8103",
|
||||||
"beforeDevCommand": "npm start",
|
"beforeDevCommand": "npm run ng -- serve --host=0.0.0.0 --port=8103",
|
||||||
"beforeBuildCommand": "ionic build"
|
"beforeBuildCommand": "ionic build"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
|
|||||||
@@ -9,6 +9,14 @@ export const translations = {
|
|||||||
themeLight: 'Light',
|
themeLight: 'Light',
|
||||||
themeDark: 'Dark',
|
themeDark: 'Dark',
|
||||||
themeSystem: 'Device-preferred',
|
themeSystem: 'Device-preferred',
|
||||||
|
waitingListQuickButtons: 'Quick buttons for waiting lists',
|
||||||
|
waitingListQuickButtonsHelp:
|
||||||
|
'Move waiting officials off duty and return them to the end of their previous list. Only officials with a saved list have a return button. Requires Service Judge to be enabled.',
|
||||||
|
moveOffDuty: 'Move off duty',
|
||||||
|
returnToQueue: 'Return to waiting list',
|
||||||
|
confirmMoveOffDuty: 'Move this official to Not on Duty?',
|
||||||
|
confirmReturnToQueue:
|
||||||
|
'Return this official to the bottom of the waiting list?',
|
||||||
showAlerts: 'Show alerts on quick buttons',
|
showAlerts: 'Show alerts on quick buttons',
|
||||||
serviceJudge: 'Service Judge',
|
serviceJudge: 'Service Judge',
|
||||||
language: 'Language',
|
language: 'Language',
|
||||||
@@ -62,6 +70,14 @@ export const translations = {
|
|||||||
themeLight: 'Világos',
|
themeLight: 'Világos',
|
||||||
themeDark: 'Sötét',
|
themeDark: 'Sötét',
|
||||||
themeSystem: 'Eszközbeállítás szerint',
|
themeSystem: 'Eszközbeállítás szerint',
|
||||||
|
waitingListQuickButtons: 'Gyors gombok a várólistákhoz',
|
||||||
|
waitingListQuickButtonsHelp:
|
||||||
|
'A várakozó tisztségviselőket pihenőre küldheted, majd visszaküldheted őket az előző várólistájuk végére. A visszaküldés gomb csak mentett várólista esetén jelenik meg. Az Adogatásbíró beállítás bekapcsolása szükséges.',
|
||||||
|
moveOffDuty: 'Pihenőre küldés',
|
||||||
|
returnToQueue: 'Vissza a várólistára',
|
||||||
|
confirmMoveOffDuty: 'Pihenőre küldöd ezt a tisztségviselőt?',
|
||||||
|
confirmReturnToQueue:
|
||||||
|
'Visszaküldöd ezt a tisztségviselőt a várólista végére?',
|
||||||
showAlerts: 'Figyelmeztetések megjelenítése a gyors gomboknál',
|
showAlerts: 'Figyelmeztetések megjelenítése a gyors gomboknál',
|
||||||
serviceJudge: 'Adogatásbíró',
|
serviceJudge: 'Adogatásbíró',
|
||||||
language: 'Nyelv',
|
language: 'Nyelv',
|
||||||
|
|||||||
@@ -29,7 +29,12 @@ export class SettingsService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.settings.update(1, changes);
|
await db.settings.update(1, {
|
||||||
|
...changes,
|
||||||
|
...(!(changes.withServiceJudge ?? current.withServiceJudge)
|
||||||
|
? { waitingListQuickButtons: false }
|
||||||
|
: {})
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ensureSettings() {
|
private async ensureSettings() {
|
||||||
@@ -40,7 +45,8 @@ export class SettingsService {
|
|||||||
id: 1,
|
id: 1,
|
||||||
numberOfCourts: 5,
|
numberOfCourts: 5,
|
||||||
withServiceJudge: true,
|
withServiceJudge: true,
|
||||||
showAlert: true
|
showAlert: true,
|
||||||
|
waitingListQuickButtons: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { db } from 'db';
|
||||||
|
import { WaitingDutyService } from './waiting-duty.service';
|
||||||
|
|
||||||
|
describe('WaitingDutyService', () => {
|
||||||
|
let service: WaitingDutyService;
|
||||||
|
beforeEach(async () => {
|
||||||
|
await Promise.all([
|
||||||
|
db.settings.clear(),
|
||||||
|
db.umpires.clear(),
|
||||||
|
db.waitingUmpires.clear(),
|
||||||
|
db.waitingServiceJudges.clear(),
|
||||||
|
db.courtUmpires.clear(),
|
||||||
|
db.courtServiceJudges.clear()
|
||||||
|
]);
|
||||||
|
await db.settings.put({
|
||||||
|
id: 1,
|
||||||
|
withServiceJudge: true,
|
||||||
|
waitingListQuickButtons: true,
|
||||||
|
showAlert: false,
|
||||||
|
numberOfCourts: 5
|
||||||
|
});
|
||||||
|
await db.umpires.put({
|
||||||
|
id: 1,
|
||||||
|
firstName: 'Alex',
|
||||||
|
lastName: 'Smith',
|
||||||
|
gender: 'M',
|
||||||
|
country: 'GB'
|
||||||
|
});
|
||||||
|
service = TestBed.inject(WaitingDutyService);
|
||||||
|
});
|
||||||
|
afterEach(async () => {
|
||||||
|
TestBed.resetTestingModule();
|
||||||
|
await Promise.all([
|
||||||
|
db.settings.clear(),
|
||||||
|
db.umpires.clear(),
|
||||||
|
db.waitingUmpires.clear(),
|
||||||
|
db.waitingServiceJudges.clear(),
|
||||||
|
db.courtUmpires.clear(),
|
||||||
|
db.courtServiceJudges.clear()
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
for (const role of ['umpire', 'serviceJudge'] as const) {
|
||||||
|
it(`remembers the ${role} queue and returns at its bottom only once`, async () => {
|
||||||
|
if (role === 'umpire')
|
||||||
|
await db.waitingUmpires.add({ umpireId: 1, order: 1 });
|
||||||
|
else await db.waitingServiceJudges.add({ serviceJudgeId: 1, order: 1 });
|
||||||
|
await service.move(1, 'rest');
|
||||||
|
expect((await db.umpires.get(1))?.returnToQueue).toBe(role);
|
||||||
|
expect(
|
||||||
|
(await db.waitingUmpires.count()) +
|
||||||
|
(await db.waitingServiceJudges.count())
|
||||||
|
).toBe(0);
|
||||||
|
if (role === 'umpire')
|
||||||
|
await db.waitingUmpires.add({ umpireId: 2, order: 1 });
|
||||||
|
else await db.waitingServiceJudges.add({ serviceJudgeId: 2, order: 1 });
|
||||||
|
await Promise.all([service.move(1, 'umpire'), service.move(1, 'umpire')]);
|
||||||
|
const items =
|
||||||
|
role === 'umpire'
|
||||||
|
? await db.waitingUmpires.orderBy('order').toArray()
|
||||||
|
: await db.waitingServiceJudges.orderBy('order').toArray();
|
||||||
|
expect(items.map((item) => item.order)).toEqual([1, 2]);
|
||||||
|
expect((await db.umpires.get(1))?.returnToQueue).toBeUndefined();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (const role of ['umpire', 'serviceJudge'] as const) {
|
||||||
|
it(`saves the ${role} origin on a drag move even with quick buttons disabled`, async () => {
|
||||||
|
await db.settings.update(1, { waitingListQuickButtons: false });
|
||||||
|
if (role === 'umpire')
|
||||||
|
await db.waitingUmpires.add({ umpireId: 1, order: 1 });
|
||||||
|
else await db.waitingServiceJudges.add({ serviceJudgeId: 1, order: 1 });
|
||||||
|
await service.moveToRest(1);
|
||||||
|
expect((await db.umpires.get(1))?.returnToQueue).toBe(role);
|
||||||
|
expect(await db.waitingUmpires.count()).toBe(0);
|
||||||
|
expect(await db.waitingServiceJudges.count()).toBe(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('does not return officials without a saved origin', async () => {
|
||||||
|
await service.move(1, 'umpire');
|
||||||
|
expect(await db.waitingUmpires.count()).toBe(0);
|
||||||
|
expect(await db.waitingServiceJudges.count()).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores stale return actions for assigned officials', async () => {
|
||||||
|
await db.courtUmpires.add({ umpireId: 1, courtNo: 1 });
|
||||||
|
await service.move(1, 'umpire');
|
||||||
|
expect(await db.waitingUmpires.count()).toBe(0);
|
||||||
|
});
|
||||||
|
it('does nothing when service judges are disabled', async () => {
|
||||||
|
await db.settings.update(1, { withServiceJudge: false });
|
||||||
|
await service.move(1, 'umpire');
|
||||||
|
expect(await db.waitingUmpires.count()).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import { Injectable, inject } from '@angular/core';
|
||||||
|
import { db, WaitingRole } from 'db';
|
||||||
|
import { WaitingUmpiresService } from './waiting-umpires.service';
|
||||||
|
import { WaitingServiceJudgesService } from './waiting-service-judges.service';
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class WaitingDutyService {
|
||||||
|
private readonly umpires = inject(WaitingUmpiresService);
|
||||||
|
private readonly judges = inject(WaitingServiceJudgesService);
|
||||||
|
|
||||||
|
async moveToRest(officialId: number): Promise<void> {
|
||||||
|
await this.moveOfficial(officialId, 'rest', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async move(
|
||||||
|
officialId: number,
|
||||||
|
destination: WaitingRole | 'rest'
|
||||||
|
): Promise<void> {
|
||||||
|
await this.moveOfficial(officialId, destination, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async moveOfficial(
|
||||||
|
officialId: number,
|
||||||
|
destination: WaitingRole | 'rest',
|
||||||
|
requireQuickButtons: boolean
|
||||||
|
): Promise<void> {
|
||||||
|
await db.transaction(
|
||||||
|
'rw',
|
||||||
|
[
|
||||||
|
db.settings,
|
||||||
|
db.umpires,
|
||||||
|
db.waitingUmpires,
|
||||||
|
db.waitingServiceJudges,
|
||||||
|
db.courtUmpires,
|
||||||
|
db.courtServiceJudges
|
||||||
|
],
|
||||||
|
async () => {
|
||||||
|
const settings = await db.settings.get(1);
|
||||||
|
const official = await db.umpires.get(officialId);
|
||||||
|
if (
|
||||||
|
!official ||
|
||||||
|
(requireQuickButtons &&
|
||||||
|
(!settings?.withServiceJudge || !settings.waitingListQuickButtons))
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
const umpire = await db.waitingUmpires
|
||||||
|
.where('umpireId')
|
||||||
|
.equals(officialId)
|
||||||
|
.first();
|
||||||
|
const judge = await db.waitingServiceJudges
|
||||||
|
.where('serviceJudgeId')
|
||||||
|
.equals(officialId)
|
||||||
|
.first();
|
||||||
|
const onCourt =
|
||||||
|
(await db.courtUmpires
|
||||||
|
.where('umpireId')
|
||||||
|
.equals(officialId)
|
||||||
|
.count()) ||
|
||||||
|
(await db.courtServiceJudges
|
||||||
|
.where('umpireId')
|
||||||
|
.equals(officialId)
|
||||||
|
.count());
|
||||||
|
if (onCourt) return;
|
||||||
|
if (destination === 'rest') {
|
||||||
|
if (!umpire && !judge) return;
|
||||||
|
await db.umpires.update(officialId, {
|
||||||
|
returnToQueue: umpire ? 'umpire' : 'serviceJudge'
|
||||||
|
});
|
||||||
|
await this.umpires.removeByUmpireId(officialId);
|
||||||
|
await this.judges.removeByUmpireId(officialId);
|
||||||
|
} else {
|
||||||
|
if (umpire || judge || !official.returnToQueue) return;
|
||||||
|
const role = official.returnToQueue;
|
||||||
|
if (role === 'serviceJudge') await this.judges.add(officialId);
|
||||||
|
else await this.umpires.add(officialId);
|
||||||
|
await db.umpires.update(officialId, { returnToQueue: undefined });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -161,6 +161,18 @@
|
|||||||
<ion-label>{{ umpire|fullname }}</ion-label>
|
<ion-label>{{ umpire|fullname }}</ion-label>
|
||||||
</div>
|
</div>
|
||||||
<ion-label>{{ umpire|fullname }}</ion-label>
|
<ion-label>{{ umpire|fullname }}</ion-label>
|
||||||
|
@if (waitingQuickButtonsEnabled()) {
|
||||||
|
<ion-button
|
||||||
|
class="waiting-duty-button"
|
||||||
|
size="small"
|
||||||
|
fill="solid"
|
||||||
|
[attr.aria-label]="('moveOffDuty' | translate) + ': ' + (umpire | fullname)"
|
||||||
|
[attr.title]="'moveOffDuty' | translate"
|
||||||
|
(pointerdown)="$event.stopPropagation()"
|
||||||
|
(click)="$event.stopPropagation(); changeWaitingDuty(umpire, true)">
|
||||||
|
<ion-icon slot="icon-only" name="exit-outline"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</ion-list>
|
</ion-list>
|
||||||
@@ -179,6 +191,18 @@
|
|||||||
<ion-label>{{ umpire|fullname }}</ion-label>
|
<ion-label>{{ umpire|fullname }}</ion-label>
|
||||||
</div>
|
</div>
|
||||||
<ion-label>{{ umpire|fullname }}</ion-label>
|
<ion-label>{{ umpire|fullname }}</ion-label>
|
||||||
|
@if (waitingQuickButtonsEnabled()) {
|
||||||
|
<ion-button
|
||||||
|
class="waiting-duty-button"
|
||||||
|
size="small"
|
||||||
|
fill="solid"
|
||||||
|
[attr.aria-label]="('moveOffDuty' | translate) + ': ' + (umpire | fullname)"
|
||||||
|
[attr.title]="'moveOffDuty' | translate"
|
||||||
|
(pointerdown)="$event.stopPropagation()"
|
||||||
|
(click)="$event.stopPropagation(); changeWaitingDuty(umpire, true)">
|
||||||
|
<ion-icon slot="icon-only" name="exit-outline"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</ion-list>
|
</ion-list>
|
||||||
@@ -198,6 +222,18 @@
|
|||||||
<ion-label>{{ umpire|fullname }}</ion-label>
|
<ion-label>{{ umpire|fullname }}</ion-label>
|
||||||
</div>
|
</div>
|
||||||
<ion-label>{{ umpire|fullname }}</ion-label>
|
<ion-label>{{ umpire|fullname }}</ion-label>
|
||||||
|
@if (waitingQuickButtonsEnabled() && umpire.returnToQueue) {
|
||||||
|
<ion-button
|
||||||
|
class="waiting-duty-button"
|
||||||
|
size="small"
|
||||||
|
fill="solid"
|
||||||
|
[attr.aria-label]="('returnToQueue' | translate) + ': ' + (umpire | fullname)"
|
||||||
|
[attr.title]="'returnToQueue' | translate"
|
||||||
|
(pointerdown)="$event.stopPropagation()"
|
||||||
|
(click)="$event.stopPropagation(); changeWaitingDuty(umpire, false)">
|
||||||
|
<ion-icon slot="icon-only" name="enter-outline"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
|||||||
@@ -38,3 +38,18 @@
|
|||||||
background-color: var(--ion-color-step-150, #d9d9d9);
|
background-color: var(--ion-color-step-150, #d9d9d9);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bottom-lists .custom-ion-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
ion-label {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.waiting-duty-button {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { CdkDragDrop } from '@angular/cdk/drag-drop';
|
||||||
import { signal } from '@angular/core';
|
import { signal } from '@angular/core';
|
||||||
import { AlertController } from '@ionic/angular';
|
import { AlertController } from '@ionic/angular';
|
||||||
import { CourtServiceJudge, CourtUmpire, Settings, Umpire } from 'db';
|
import { CourtServiceJudge, CourtUmpire, Settings, Umpire } from 'db';
|
||||||
|
import { WaitingDutyService } from '../services/waiting-duty.service';
|
||||||
import { Tab1Page } from './tab1.page';
|
import { Tab1Page } from './tab1.page';
|
||||||
import { SettingsService } from '../services/settings-service';
|
import { SettingsService } from '../services/settings-service';
|
||||||
import { UmpireService } from '../services/umpire.service';
|
import { UmpireService } from '../services/umpire.service';
|
||||||
@@ -34,6 +36,7 @@ describe('Tab1Page court rotation', () => {
|
|||||||
let waitingJudge: jasmine.SpyObj<WaitingServiceJudgesService>;
|
let waitingJudge: jasmine.SpyObj<WaitingServiceJudgesService>;
|
||||||
let umpireCourt: jasmine.SpyObj<CourtUmpireService>;
|
let umpireCourt: jasmine.SpyObj<CourtUmpireService>;
|
||||||
let judgeCourt: jasmine.SpyObj<CourtServiceJudgeService>;
|
let judgeCourt: jasmine.SpyObj<CourtServiceJudgeService>;
|
||||||
|
let duty: jasmine.SpyObj<WaitingDutyService>;
|
||||||
let alerts: jasmine.SpyObj<AlertController>;
|
let alerts: jasmine.SpyObj<AlertController>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -72,6 +75,9 @@ describe('Tab1Page court rotation', () => {
|
|||||||
['save', 'removeByUmpireId'],
|
['save', 'removeByUmpireId'],
|
||||||
{ umpires: courtJudges }
|
{ umpires: courtJudges }
|
||||||
);
|
);
|
||||||
|
duty = jasmine.createSpyObj('WaitingDutyService', ['move', 'moveToRest']);
|
||||||
|
duty.move.and.resolveTo();
|
||||||
|
duty.moveToRest.and.resolveTo();
|
||||||
alerts = jasmine.createSpyObj('AlertController', ['create']);
|
alerts = jasmine.createSpyObj('AlertController', ['create']);
|
||||||
waitingUmpire.getCurrentUmpire.and.resolveTo(official(1));
|
waitingUmpire.getCurrentUmpire.and.resolveTo(official(1));
|
||||||
waitingJudge.getCurrentUmpire.and.resolveTo(official(2));
|
waitingJudge.getCurrentUmpire.and.resolveTo(official(2));
|
||||||
@@ -85,6 +91,7 @@ describe('Tab1Page court rotation', () => {
|
|||||||
judgeCourt.removeByUmpireId.and.resolveTo();
|
judgeCourt.removeByUmpireId.and.resolveTo();
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
|
{ provide: WaitingDutyService, useValue: duty },
|
||||||
{ provide: SettingsService, useValue: { settings } },
|
{ provide: SettingsService, useValue: { settings } },
|
||||||
{ provide: UmpireService, useValue: { umpires } },
|
{ provide: UmpireService, useValue: { umpires } },
|
||||||
{ provide: WaitingUmpiresService, useValue: waitingUmpire },
|
{ provide: WaitingUmpiresService, useValue: waitingUmpire },
|
||||||
@@ -98,6 +105,74 @@ describe('Tab1Page court rotation', () => {
|
|||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for (const source of [
|
||||||
|
'list-waiting-umpires',
|
||||||
|
'list-waiting-service-judges'
|
||||||
|
]) {
|
||||||
|
it(`saves the waiting origin when dropped from ${source} to rest`, async () => {
|
||||||
|
await component.dropToRest({
|
||||||
|
previousContainer: { id: source },
|
||||||
|
container: { id: 'list-on-rest' },
|
||||||
|
item: { data: official(1) }
|
||||||
|
} as CdkDragDrop<Umpire[]>);
|
||||||
|
expect(duty.moveToRest).toHaveBeenCalledOnceWith(1);
|
||||||
|
expect(waitingUmpire.removeByUmpireId).not.toHaveBeenCalled();
|
||||||
|
expect(waitingJudge.removeByUmpireId).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('moves directly when quick-button alerts are disabled', async () => {
|
||||||
|
settings.update((value) => ({ ...value, waitingListQuickButtons: true }));
|
||||||
|
await component.changeWaitingDuty(official(1), true);
|
||||||
|
expect(duty.move).toHaveBeenCalledOnceWith(1, 'rest');
|
||||||
|
expect(alerts.create).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores return actions without a saved origin', async () => {
|
||||||
|
settings.update((value) => ({
|
||||||
|
...value,
|
||||||
|
waitingListQuickButtons: true,
|
||||||
|
showAlert: true
|
||||||
|
}));
|
||||||
|
await component.changeWaitingDuty(official(5), false);
|
||||||
|
expect(duty.move).not.toHaveBeenCalled();
|
||||||
|
expect(alerts.create).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('confirms returning to the saved service-judge queue', async () => {
|
||||||
|
settings.update((value) => ({
|
||||||
|
...value,
|
||||||
|
waitingListQuickButtons: true,
|
||||||
|
showAlert: true
|
||||||
|
}));
|
||||||
|
alerts.create.and.resolveTo({
|
||||||
|
present: async () => undefined
|
||||||
|
} as unknown as HTMLIonAlertElement);
|
||||||
|
await component.changeWaitingDuty(
|
||||||
|
{ ...official(5), returnToQueue: 'serviceJudge' },
|
||||||
|
false
|
||||||
|
);
|
||||||
|
expect(duty.move).not.toHaveBeenCalled();
|
||||||
|
const buttons = alerts.create.calls.mostRecent().args[0]?.buttons;
|
||||||
|
const confirm = buttons?.find(
|
||||||
|
(button) => typeof button !== 'string' && button.role === 'confirm'
|
||||||
|
);
|
||||||
|
if (typeof confirm === 'object' && typeof confirm.handler === 'function')
|
||||||
|
confirm.handler(undefined);
|
||||||
|
expect(duty.move).toHaveBeenCalledOnceWith(5, 'serviceJudge');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hides and ignores quick actions when service judges are disabled', async () => {
|
||||||
|
settings.update((value) => ({
|
||||||
|
...value,
|
||||||
|
waitingListQuickButtons: true,
|
||||||
|
withServiceJudge: false
|
||||||
|
}));
|
||||||
|
expect(component.waitingQuickButtonsEnabled()).toBeFalse();
|
||||||
|
await component.changeWaitingDuty(official(1), true);
|
||||||
|
expect(duty.move).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it('waits for confirmation before assigning officials', async () => {
|
it('waits for confirmation before assigning officials', async () => {
|
||||||
settings.update((value) => ({ ...value, showAlert: true }));
|
settings.update((value) => ({ ...value, showAlert: true }));
|
||||||
const present = jasmine.createSpy('present').and.resolveTo();
|
const present = jasmine.createSpy('present').and.resolveTo();
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
import { SettingsService } from '../services/settings-service';
|
import { SettingsService } from '../services/settings-service';
|
||||||
import { UmpireService } from '../services/umpire.service';
|
import { UmpireService } from '../services/umpire.service';
|
||||||
import { Umpire } from 'db';
|
import { Umpire } from 'db';
|
||||||
|
import { WaitingDutyService } from '../services/waiting-duty.service';
|
||||||
import { WaitingUmpiresService } from '../services/waiting-umpires.service';
|
import { WaitingUmpiresService } from '../services/waiting-umpires.service';
|
||||||
import { WaitingServiceJudgesService } from '../services/waiting-service-judges.service';
|
import { WaitingServiceJudgesService } from '../services/waiting-service-judges.service';
|
||||||
import { FullnamePipe } from '../fullname-pipe';
|
import { FullnamePipe } from '../fullname-pipe';
|
||||||
@@ -71,6 +72,8 @@ export class Tab1Page {
|
|||||||
addIcons({ exitOutline, enterOutline });
|
addIcons({ exitOutline, enterOutline });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readonly waitingDutyService = inject(WaitingDutyService);
|
||||||
|
|
||||||
readonly settingsService = inject(SettingsService);
|
readonly settingsService = inject(SettingsService);
|
||||||
readonly umpireService = inject(UmpireService);
|
readonly umpireService = inject(UmpireService);
|
||||||
readonly waitingUmpireService = inject(WaitingUmpiresService);
|
readonly waitingUmpireService = inject(WaitingUmpiresService);
|
||||||
@@ -285,12 +288,53 @@ export class Tab1Page {
|
|||||||
return this.settings()?.withServiceJudge ?? false;
|
return this.settings()?.withServiceJudge ?? false;
|
||||||
});
|
});
|
||||||
|
|
||||||
dropToRest(event: CdkDragDrop<Umpire[]>) {
|
readonly waitingQuickButtonsEnabled = computed(
|
||||||
|
() =>
|
||||||
|
this.serviceJudgeEnabled() && !!this.settings()?.waitingListQuickButtons
|
||||||
|
);
|
||||||
|
|
||||||
|
async changeWaitingDuty(umpire: Umpire, toRest: boolean): Promise<void> {
|
||||||
|
if (!this.waitingQuickButtonsEnabled()) return;
|
||||||
|
const destination = toRest ? 'rest' : umpire.returnToQueue;
|
||||||
|
if (!destination) return;
|
||||||
|
const move = () => this.waitingDutyService.move(umpire.id, destination);
|
||||||
|
if (!this.settings()?.showAlert) {
|
||||||
|
await move();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const alert = await this.alertController.create({
|
||||||
|
header: this.translationService.translate('confirmation'),
|
||||||
|
subHeader: this.translationService.translate(
|
||||||
|
toRest ? 'confirmMoveOffDuty' : 'confirmReturnToQueue'
|
||||||
|
),
|
||||||
|
message: this.fullnamePipe.transform(umpire),
|
||||||
|
buttons: [
|
||||||
|
{ text: this.translationService.translate('no'), role: 'cancel' },
|
||||||
|
{
|
||||||
|
text: this.translationService.translate('yes'),
|
||||||
|
role: 'confirm',
|
||||||
|
handler: () => {
|
||||||
|
void move();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
await alert.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
async dropToRest(event: CdkDragDrop<Umpire[]>): Promise<void> {
|
||||||
if (event.previousContainer === event.container) {
|
if (event.previousContainer === event.container) {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
const comingFrom = event.previousContainer.id;
|
const comingFrom = event.previousContainer.id;
|
||||||
const umpireToMove = event.item.data;
|
const umpireToMove = event.item.data;
|
||||||
|
if (
|
||||||
|
comingFrom === 'list-waiting-umpires' ||
|
||||||
|
comingFrom === 'list-waiting-service-judges'
|
||||||
|
) {
|
||||||
|
await this.waitingDutyService.moveToRest(umpireToMove.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.removeFromOriginalPlace(umpireToMove, comingFrom);
|
this.removeFromOriginalPlace(umpireToMove, comingFrom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,21 @@
|
|||||||
</ion-toggle>
|
</ion-toggle>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<div class="waiting-list-setting">
|
||||||
|
<ion-toggle
|
||||||
|
[checked]="currentSettings.waitingListQuickButtons ?? false"
|
||||||
|
[disabled]="!currentSettings.withServiceJudge"
|
||||||
|
aria-describedby="waiting-list-help"
|
||||||
|
(ionChange)="updateWaitingListQuickButtons($event)">
|
||||||
|
{{ 'waitingListQuickButtons' | translate }}
|
||||||
|
</ion-toggle>
|
||||||
|
<p id="waiting-list-help" class="setting-help">
|
||||||
|
{{ 'waitingListQuickButtonsHelp' | translate }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-toggle
|
<ion-toggle
|
||||||
[checked]="currentSettings.showAlert"
|
[checked]="currentSettings.showAlert"
|
||||||
@@ -79,7 +94,7 @@
|
|||||||
<ion-col sizeLg="6" pushLg="3">
|
<ion-col sizeLg="6" pushLg="3">
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label class="ion-text-center"
|
<ion-label class="ion-text-center"
|
||||||
>{{ 'version' | translate }}: 2.0.0</ion-label
|
>{{ 'version' | translate }}: 2.1.0</ion-label
|
||||||
>
|
>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-col>
|
</ion-col>
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
.waiting-list-setting {
|
||||||
|
width: 100%;
|
||||||
|
padding-block: 12px;
|
||||||
|
|
||||||
|
ion-toggle {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-help {
|
||||||
|
margin: 8px 0 0;
|
||||||
|
color: var(--ion-color-medium-shade);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
@@ -99,6 +99,14 @@ export class Tab3Page {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateWaitingListQuickButtons(
|
||||||
|
event: CustomEvent<{ checked: boolean }>
|
||||||
|
): Promise<void> {
|
||||||
|
await this.settingsService.update({
|
||||||
|
waitingListQuickButtons: event.detail.checked
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async updateShowAlert(event: CustomEvent): Promise<void> {
|
async updateShowAlert(event: CustomEvent): Promise<void> {
|
||||||
await this.settingsService.update({
|
await this.settingsService.update({
|
||||||
showAlert: event.detail.checked
|
showAlert: event.detail.checked
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"noEmit": true,
|
||||||
|
"types": ["jasmine"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "db.ts"]
|
||||||
|
}
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
{
|
{
|
||||||
"compileOnSave": false,
|
"compileOnSave": false,
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.spec.json" }
|
||||||
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"paths": { "db": ["./db.ts"] },
|
"paths": { "db": ["./db.ts"] },
|
||||||
"outDir": "./dist/out-tsc",
|
"outDir": "./dist/out-tsc",
|
||||||
|
|||||||
Reference in new issue
Block a user