From 6da58bbd65b829ac826ff1da3ab2ac2d70c283cb Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Sat, 22 Jun 2024 22:14:27 -0700 Subject: [PATCH 01/91] upgrade typescript, add partykit --- package.json | 8 +- yarn.lock | 735 +++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 661 insertions(+), 82 deletions(-) diff --git a/package.json b/package.json index eacb69b5a..5b7937f3f 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "normalize.css": "^8.0.1", "p-queue": "^8.0.1", "papaparse": "^5.4.1", + "partykit": "0.0.107", "peerjs": "^2.0.0-beta.3", "postcss": "^8.4.38", "postcss-loader": "^8.1.1", @@ -105,7 +106,7 @@ "sanitize-filename": "^1.6.3", "simfile-parser": "^0.7.2", "style-loader": "^4.0.0", - "typescript": "^5.4.5", + "typescript": "^5.5.2", "undici": "^6.19.0", "victory": "^37.0.2", "webpack": "^5.92.0", @@ -121,5 +122,8 @@ "semver": "^7.5.2", "xml2js": "^0.5.0" }, - "packageManager": "yarn@4.3.0" + "packageManager": "yarn@4.3.0", + "dependencies": { + "partysocket": "1.0.1" + } } diff --git a/yarn.lock b/yarn.lock index 5a9323061..addecec64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -43,14 +43,7 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.24.6": - version: 7.24.6 - resolution: "@babel/compat-data@npm:7.24.6" - checksum: 10/c355141e4649ef6efa413d71cfc1efb183be46b8fc945fc17e3c7f4313b4b566af575a4183450697916cd6b8c7f180e315986b5d7f07e7b7afd0786594754f7d - languageName: node - linkType: hard - -"@babel/compat-data@npm:^7.24.7": +"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.24.7": version: 7.24.7 resolution: "@babel/compat-data@npm:7.24.7" checksum: 10/6edc09152ca51a22c33741c441f33f9475598fa59edc53369edb74b49f4ea4bef1281f5b0ed2b9b67fb66faef2da2069e21c4eef83405d8326e524b301f4e7e2 @@ -92,7 +85,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-annotate-as-pure@npm:^7.24.6, @babel/helper-annotate-as-pure@npm:^7.24.7": +"@babel/helper-annotate-as-pure@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-annotate-as-pure@npm:7.24.7" dependencies: @@ -111,20 +104,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.22.6": - version: 7.24.6 - resolution: "@babel/helper-compilation-targets@npm:7.24.6" - dependencies: - "@babel/compat-data": "npm:^7.24.6" - "@babel/helper-validator-option": "npm:^7.24.6" - browserslist: "npm:^4.22.2" - lru-cache: "npm:^5.1.1" - semver: "npm:^6.3.1" - checksum: 10/28f34f2c9e0ec047360c4dca8d4fb99009e868f9c1acad0ca125f2f9990790897216155d44935209c6e4c4e0318f5a9a46304771d75823add7400e3079945314 - languageName: node - linkType: hard - -"@babel/helper-compilation-targets@npm:^7.24.7": +"@babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-compilation-targets@npm:7.24.7" dependencies: @@ -156,20 +136,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-create-regexp-features-plugin@npm:^7.18.6": - version: 7.24.6 - resolution: "@babel/helper-create-regexp-features-plugin@npm:7.24.6" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.6" - regexpu-core: "npm:^5.3.1" - semver: "npm:^6.3.1" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10/74e717c71d7c007cc81537566c70b28ac75403afb499db2b1b988904dcda0a09a958c4c4b7d74821d0932e73f1c56227f6371ed751b16ae679aa8a2e4a271d64 - languageName: node - linkType: hard - -"@babel/helper-create-regexp-features-plugin@npm:^7.24.7": +"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-create-regexp-features-plugin@npm:7.24.7" dependencies: @@ -345,13 +312,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-validator-option@npm:^7.24.6": - version: 7.24.6 - resolution: "@babel/helper-validator-option@npm:7.24.6" - checksum: 10/5defb2da74e1cac9497016f4e41698aeed75ec7a5e9dc07e777cdb67ef73cd2e27bd2bf8a3ab8d37e0b93a6a45524a9728f03e263afdef452436cf74794bde87 - languageName: node - linkType: hard - "@babel/helper-validator-option@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-validator-option@npm:7.24.7" @@ -1595,6 +1555,57 @@ __metadata: languageName: node linkType: hard +"@cloudflare/workerd-darwin-64@npm:1.20240610.1": + version: 1.20240610.1 + resolution: "@cloudflare/workerd-darwin-64@npm:1.20240610.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@cloudflare/workerd-darwin-arm64@npm:1.20240610.1": + version: 1.20240610.1 + resolution: "@cloudflare/workerd-darwin-arm64@npm:1.20240610.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@cloudflare/workerd-linux-64@npm:1.20240610.1": + version: 1.20240610.1 + resolution: "@cloudflare/workerd-linux-64@npm:1.20240610.1" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@cloudflare/workerd-linux-arm64@npm:1.20240610.1": + version: 1.20240610.1 + resolution: "@cloudflare/workerd-linux-arm64@npm:1.20240610.1" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@cloudflare/workerd-windows-64@npm:1.20240610.1": + version: 1.20240610.1 + resolution: "@cloudflare/workerd-windows-64@npm:1.20240610.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@cloudflare/workers-types@npm:4.20240614.0": + version: 4.20240614.0 + resolution: "@cloudflare/workers-types@npm:4.20240614.0" + checksum: 10/81e2f6c577a820f9f00a673eb9465afed6b9969c3e3c3d69861342fd175b61d30b499edfbb65050122deeda576f92e13606174893eaea3b3544d13363d5a40be + languageName: node + linkType: hard + +"@cspotcode/source-map-support@npm:0.8.1": + version: 0.8.1 + resolution: "@cspotcode/source-map-support@npm:0.8.1" + dependencies: + "@jridgewell/trace-mapping": "npm:0.3.9" + checksum: 10/b6e38a1712fab242c86a241c229cf562195aad985d0564bd352ac404be583029e89e93028ffd2c251d2c407ecac5fb0cbdca94a2d5c10f29ac806ede0508b3ff + languageName: node + linkType: hard + "@discoveryjs/json-ext@npm:^0.5.0": version: 0.5.7 resolution: "@discoveryjs/json-ext@npm:0.5.7" @@ -1611,6 +1622,167 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/aix-ppc64@npm:0.21.5" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-arm64@npm:0.21.5" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-arm@npm:0.21.5" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-x64@npm:0.21.5" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/darwin-arm64@npm:0.21.5" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/darwin-x64@npm:0.21.5" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/freebsd-arm64@npm:0.21.5" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/freebsd-x64@npm:0.21.5" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-arm64@npm:0.21.5" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-arm@npm:0.21.5" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-ia32@npm:0.21.5" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-loong64@npm:0.21.5" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-mips64el@npm:0.21.5" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-ppc64@npm:0.21.5" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-riscv64@npm:0.21.5" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-s390x@npm:0.21.5" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-x64@npm:0.21.5" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/netbsd-x64@npm:0.21.5" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/openbsd-x64@npm:0.21.5" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/sunos-x64@npm:0.21.5" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-arm64@npm:0.21.5" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-ia32@npm:0.21.5" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-x64@npm:0.21.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": version: 4.4.0 resolution: "@eslint-community/eslint-utils@npm:4.4.0" @@ -1653,6 +1825,13 @@ __metadata: languageName: node linkType: hard +"@fastify/busboy@npm:^2.0.0": + version: 2.1.1 + resolution: "@fastify/busboy@npm:2.1.1" + checksum: 10/2bb8a7eca8289ed14c9eb15239bc1019797454624e769b39a0b90ed204d032403adc0f8ed0d2aef8a18c772205fa7808cf5a1b91f21c7bfc7b6032150b1062c5 + languageName: node + linkType: hard + "@formatjs/ecma402-abstract@npm:2.0.0": version: 2.0.0 resolution: "@formatjs/ecma402-abstract@npm:2.0.0" @@ -2390,10 +2569,10 @@ __metadata: languageName: node linkType: hard -"@jridgewell/resolve-uri@npm:^3.1.0": - version: 3.1.1 - resolution: "@jridgewell/resolve-uri@npm:3.1.1" - checksum: 10/64d59df8ae1a4e74315eb1b61e012f1c7bc8aac47a3a1e683f6fe7008eab07bc512a742b7aa7c0405685d1421206de58c9c2e6adbfe23832f8bd69408ffc183e +"@jridgewell/resolve-uri@npm:^3.0.3, @jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10/97106439d750a409c22c8bff822d648f6a71f3aa9bc8e5129efdc36343cd3096ddc4eeb1c62d2fe48e9bdd4db37b05d4646a17114ecebd3bbcacfa2de51c3c1d languageName: node linkType: hard @@ -2421,6 +2600,16 @@ __metadata: languageName: node linkType: hard +"@jridgewell/trace-mapping@npm:0.3.9": + version: 0.3.9 + resolution: "@jridgewell/trace-mapping@npm:0.3.9" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.0.3" + "@jridgewell/sourcemap-codec": "npm:^1.4.10" + checksum: 10/83deafb8e7a5ca98993c2c6eeaa93c270f6f647a4c0dc00deb38c9cf9b2d3b7bf15e8839540155247ef034a052c0ec4466f980bf0c9e2ab63b97d16c0cedd3ff + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:^0.3.20, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.9": version: 0.3.25 resolution: "@jridgewell/trace-mapping@npm:0.3.25" @@ -3410,12 +3599,21 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.7.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0": - version: 8.11.3 - resolution: "acorn@npm:8.11.3" +"acorn-walk@npm:^8.2.0": + version: 8.3.3 + resolution: "acorn-walk@npm:8.3.3" + dependencies: + acorn: "npm:^8.11.0" + checksum: 10/59701dcb7070679622ba8e9c7f37577b4935565747ca0fd7c1c3ad30b3f1b1b008276282664e323b5495eb49f77fa12d3816fd06dc68e18f90fbebe759f71450 + languageName: node + linkType: hard + +"acorn@npm:^8.11.0, acorn@npm:^8.7.1, acorn@npm:^8.8.0, acorn@npm:^8.8.2, acorn@npm:^8.9.0": + version: 8.12.0 + resolution: "acorn@npm:8.12.0" bin: acorn: bin/acorn - checksum: 10/b688e7e3c64d9bfb17b596e1b35e4da9d50553713b3b3630cf5690f2b023a84eac90c56851e6912b483fe60e8b4ea28b254c07e92f17ef83d72d78745a8352dd + checksum: 10/550cc5033184eb98f7fbe2e9ddadd0f47f065734cc682f25db7a244f52314eb816801b64dec7174effd978045bd1754892731a90b1102b0ede9d17a15cfde138 languageName: node linkType: hard @@ -3623,6 +3821,15 @@ __metadata: languageName: node linkType: hard +"as-table@npm:^1.0.36": + version: 1.0.55 + resolution: "as-table@npm:1.0.55" + dependencies: + printable-characters: "npm:^1.0.42" + checksum: 10/8bbfbd7b6f240efb22f6553f756e89d1cae074e9f7a24580282e9d247c1bd9cf1fd9fb49056202a78a5e69907209d8bf032d8b6c3eaaab5fb6ad92da64a7894a + languageName: node + linkType: hard + "async@npm:^3.2.3": version: 3.2.5 resolution: "async@npm:3.2.5" @@ -4015,6 +4222,16 @@ __metadata: languageName: node linkType: hard +"capnp-ts@npm:^0.7.0": + version: 0.7.0 + resolution: "capnp-ts@npm:0.7.0" + dependencies: + debug: "npm:^4.3.1" + tslib: "npm:^2.2.0" + checksum: 10/186a76662e31ab16fe46fe0785ed2a511969d0c5198e2d7baec6b44f71c9b3bf8c05e7627036dc86c2d3ddc229c846559350c13f904fdd8da3590d7054715ba8 + languageName: node + linkType: hard + "chalk@npm:^2.4.2": version: 2.4.2 resolution: "chalk@npm:2.4.2" @@ -4187,6 +4404,17 @@ __metadata: languageName: node linkType: hard +"clipboardy@npm:4.0.0": + version: 4.0.0 + resolution: "clipboardy@npm:4.0.0" + dependencies: + execa: "npm:^8.0.1" + is-wsl: "npm:^3.1.0" + is64bit: "npm:^2.0.0" + checksum: 10/ec4ebe7e5c81d9c9cb994637e7b0e068c1c8fc272167ecd5519f967427271ec66e0e64da7268a2630b860eff42933aeabe25ba5e42bb80dbf1fae6362df059ed + languageName: node + linkType: hard + "clone-deep@npm:^4.0.1": version: 4.0.1 resolution: "clone-deep@npm:4.0.1" @@ -4401,6 +4629,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:^0.5.0": + version: 0.5.0 + resolution: "cookie@npm:0.5.0" + checksum: 10/aae7911ddc5f444a9025fbd979ad1b5d60191011339bce48e555cb83343d0f98b865ff5c4d71fecdfb8555a5cafdc65632f6fce172f32aaf6936830a883a0380 + languageName: node + linkType: hard + "copy-webpack-plugin@npm:^12.0.2": version: 12.0.2 resolution: "copy-webpack-plugin@npm:12.0.2" @@ -4805,6 +5040,13 @@ __metadata: languageName: node linkType: hard +"data-uri-to-buffer@npm:^2.0.0": + version: 2.0.2 + resolution: "data-uri-to-buffer@npm:2.0.2" + checksum: 10/152bec5e77513ee253a7c686700a1723246f582dad8b614e8eaaaba7fa45a15c8671ae4b8f4843f4f3a002dae1d3e7a20f852f7d7bdc8b4c15cfe7adfdfb07f8 + languageName: node + linkType: hard + "data-uri-to-buffer@npm:^4.0.0": version: 4.0.1 resolution: "data-uri-to-buffer@npm:4.0.1" @@ -4904,6 +5146,8 @@ __metadata: normalize.css: "npm:^8.0.1" p-queue: "npm:^8.0.1" papaparse: "npm:^5.4.1" + partykit: "npm:0.0.107" + partysocket: "npm:1.0.1" peerjs: "npm:^2.0.0-beta.3" postcss: "npm:^8.4.38" postcss-loader: "npm:^8.1.1" @@ -4916,7 +5160,7 @@ __metadata: sanitize-filename: "npm:^1.6.3" simfile-parser: "npm:^0.7.2" style-loader: "npm:^4.0.0" - typescript: "npm:^5.4.5" + typescript: "npm:^5.5.2" undici: "npm:^6.19.0" victory: "npm:^37.0.2" webpack: "npm:^5.92.0" @@ -5463,6 +5707,86 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:0.21.5": + version: 0.21.5 + resolution: "esbuild@npm:0.21.5" + dependencies: + "@esbuild/aix-ppc64": "npm:0.21.5" + "@esbuild/android-arm": "npm:0.21.5" + "@esbuild/android-arm64": "npm:0.21.5" + "@esbuild/android-x64": "npm:0.21.5" + "@esbuild/darwin-arm64": "npm:0.21.5" + "@esbuild/darwin-x64": "npm:0.21.5" + "@esbuild/freebsd-arm64": "npm:0.21.5" + "@esbuild/freebsd-x64": "npm:0.21.5" + "@esbuild/linux-arm": "npm:0.21.5" + "@esbuild/linux-arm64": "npm:0.21.5" + "@esbuild/linux-ia32": "npm:0.21.5" + "@esbuild/linux-loong64": "npm:0.21.5" + "@esbuild/linux-mips64el": "npm:0.21.5" + "@esbuild/linux-ppc64": "npm:0.21.5" + "@esbuild/linux-riscv64": "npm:0.21.5" + "@esbuild/linux-s390x": "npm:0.21.5" + "@esbuild/linux-x64": "npm:0.21.5" + "@esbuild/netbsd-x64": "npm:0.21.5" + "@esbuild/openbsd-x64": "npm:0.21.5" + "@esbuild/sunos-x64": "npm:0.21.5" + "@esbuild/win32-arm64": "npm:0.21.5" + "@esbuild/win32-ia32": "npm:0.21.5" + "@esbuild/win32-x64": "npm:0.21.5" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10/d2ff2ca84d30cce8e871517374d6c2290835380dc7cd413b2d49189ed170d45e407be14de2cb4794cf76f75cf89955c4714726ebd3de7444b3046f5cab23ab6b + languageName: node + linkType: hard + "escalade@npm:^3.1.1": version: 3.1.1 resolution: "escalade@npm:3.1.1" @@ -5685,6 +6009,13 @@ __metadata: languageName: node linkType: hard +"event-target-shim@npm:^6.0.2": + version: 6.0.2 + resolution: "event-target-shim@npm:6.0.2" + checksum: 10/aa69fc4193cad3f1e4dc0c2d3f2689ea2d477f5ff2fbee8b65f866035b15658e1985932b06ba2190c3d2cc9cc6802c26facd6c60487590c1a05f44545ec24f42 + languageName: node + linkType: hard + "eventemitter3@npm:^3.1.2": version: 3.1.2 resolution: "eventemitter3@npm:3.1.2" @@ -5730,6 +6061,23 @@ __metadata: languageName: node linkType: hard +"execa@npm:^8.0.1": + version: 8.0.1 + resolution: "execa@npm:8.0.1" + dependencies: + cross-spawn: "npm:^7.0.3" + get-stream: "npm:^8.0.1" + human-signals: "npm:^5.0.0" + is-stream: "npm:^3.0.0" + merge-stream: "npm:^2.0.0" + npm-run-path: "npm:^5.1.0" + onetime: "npm:^6.0.0" + signal-exit: "npm:^4.1.0" + strip-final-newline: "npm:^3.0.0" + checksum: 10/d2ab5fe1e2bb92b9788864d0713f1fce9a07c4594e272c0c97bc18c90569897ab262e4ea58d27a694d288227a2e24f16f5e2575b44224ad9983b799dc7f1098d + languageName: node + linkType: hard + "exif-parser@npm:^0.1.12": version: 0.1.12 resolution: "exif-parser@npm:0.1.12" @@ -5737,6 +6085,13 @@ __metadata: languageName: node linkType: hard +"exit-hook@npm:^2.2.1": + version: 2.2.1 + resolution: "exit-hook@npm:2.2.1" + checksum: 10/75835919e0aca624daa8d114c0014ae84506c4b79ac5806748cc7a86d1610a864ee974be58eec823c7757e5e6b07a5e332647e20ef84f6cc3dc3385c953c78c9 + languageName: node + linkType: hard + "expand-template@npm:^2.0.3": version: 2.0.3 resolution: "expand-template@npm:2.0.3" @@ -6183,7 +6538,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:~2.3.2": +"fsevents@npm:2.3.3, fsevents@npm:~2.3.2": version: 2.3.3 resolution: "fsevents@npm:2.3.3" dependencies: @@ -6193,7 +6548,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": +"fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" dependencies: @@ -6236,6 +6591,16 @@ __metadata: languageName: node linkType: hard +"get-source@npm:^2.0.12": + version: 2.0.12 + resolution: "get-source@npm:2.0.12" + dependencies: + data-uri-to-buffer: "npm:^2.0.0" + source-map: "npm:^0.6.1" + checksum: 10/6ba35ae0755046199b57d7fe254d50c6d7550d3b150e065a3607e3da8c55c617302f4c7cc3712252c7810954a04e2e56467ad02a0798c0841a5e980064bd3048 + languageName: node + linkType: hard + "get-stream@npm:^6.0.0": version: 6.0.1 resolution: "get-stream@npm:6.0.1" @@ -6243,6 +6608,13 @@ __metadata: languageName: node linkType: hard +"get-stream@npm:^8.0.1": + version: 8.0.1 + resolution: "get-stream@npm:8.0.1" + checksum: 10/dde5511e2e65a48e9af80fea64aff11b4921b14b6e874c6f8294c50975095af08f41bfb0b680c887f28b566dd6ec2cb2f960f9d36a323359be324ce98b766e9e + languageName: node + linkType: hard + "gifwrap@npm:^0.10.1": version: 0.10.1 resolution: "gifwrap@npm:0.10.1" @@ -6694,6 +7066,13 @@ __metadata: languageName: node linkType: hard +"human-signals@npm:^5.0.0": + version: 5.0.0 + resolution: "human-signals@npm:5.0.0" + checksum: 10/30f8870d831cdcd2d6ec0486a7d35d49384996742052cee792854273fa9dd9e7d5db06bb7985d4953e337e10714e994e0302e90dc6848069171b05ec836d65b0 + languageName: node + linkType: hard + "iconv-lite@npm:0.4.24, iconv-lite@npm:^0.4.24": version: 0.4.24 resolution: "iconv-lite@npm:0.4.24" @@ -7060,6 +7439,13 @@ __metadata: languageName: node linkType: hard +"is-stream@npm:^3.0.0": + version: 3.0.0 + resolution: "is-stream@npm:3.0.0" + checksum: 10/172093fe99119ffd07611ab6d1bcccfe8bc4aa80d864b15f43e63e54b7abc71e779acd69afdb854c4e2a67fdc16ae710e370eda40088d1cfc956a50ed82d8f16 + languageName: node + linkType: hard + "is-unicode-supported@npm:^0.1.0": version: 0.1.0 resolution: "is-unicode-supported@npm:0.1.0" @@ -7076,6 +7462,15 @@ __metadata: languageName: node linkType: hard +"is64bit@npm:^2.0.0": + version: 2.0.0 + resolution: "is64bit@npm:2.0.0" + dependencies: + system-architecture: "npm:^0.1.0" + checksum: 10/94dafd5f29bfb96c542e89ef8c33e811159ca7d07a2890ab83026fa87706612af4101308d9392e9ee68e046e8604a6b59a8f41091f8556f6235efbcfd9c5574c + languageName: node + linkType: hard + "isarray@npm:~1.0.0": version: 1.0.0 resolution: "isarray@npm:1.0.0" @@ -7739,6 +8134,13 @@ __metadata: languageName: node linkType: hard +"mimic-fn@npm:^4.0.0": + version: 4.0.0 + resolution: "mimic-fn@npm:4.0.0" + checksum: 10/995dcece15ee29aa16e188de6633d43a3db4611bcf93620e7e62109ec41c79c0f34277165b8ce5e361205049766e371851264c21ac64ca35499acb5421c2ba56 + languageName: node + linkType: hard + "mimic-response@npm:^3.1.0": version: 3.1.0 resolution: "mimic-response@npm:3.1.0" @@ -7767,6 +8169,28 @@ __metadata: languageName: node linkType: hard +"miniflare@npm:3.20240610.0": + version: 3.20240610.0 + resolution: "miniflare@npm:3.20240610.0" + dependencies: + "@cspotcode/source-map-support": "npm:0.8.1" + acorn: "npm:^8.8.0" + acorn-walk: "npm:^8.2.0" + capnp-ts: "npm:^0.7.0" + exit-hook: "npm:^2.2.1" + glob-to-regexp: "npm:^0.4.1" + stoppable: "npm:^1.1.0" + undici: "npm:^5.28.2" + workerd: "npm:1.20240610.1" + ws: "npm:^8.11.0" + youch: "npm:^3.2.2" + zod: "npm:^3.20.6" + bin: + miniflare: bootstrap.js + checksum: 10/0febb70dbcddd90059d0f57993f357cab5be9dec31a5f61010561b9f625a203cbc0bdfc4eaead26f9e26fcaba77aeef08f39b877e44ee407aaf6114e1368dda9 + languageName: node + linkType: hard + "minimalistic-assert@npm:^1.0.0": version: 1.0.1 resolution: "minimalistic-assert@npm:1.0.1" @@ -7957,6 +8381,15 @@ __metadata: languageName: node linkType: hard +"mustache@npm:^4.2.0": + version: 4.2.0 + resolution: "mustache@npm:4.2.0" + bin: + mustache: bin/mustache + checksum: 10/6e668bd5803255ab0779c3983b9412b5c4f4f90e822230e0e8f414f5449ed7a137eed29430e835aa689886f663385cfe05f808eb34b16e1f3a95525889b05cd3 + languageName: node + linkType: hard + "mute-stream@npm:1.0.0": version: 1.0.0 resolution: "mute-stream@npm:1.0.0" @@ -8161,6 +8594,15 @@ __metadata: languageName: node linkType: hard +"npm-run-path@npm:^5.1.0": + version: 5.3.0 + resolution: "npm-run-path@npm:5.3.0" + dependencies: + path-key: "npm:^4.0.0" + checksum: 10/ae8e7a89da9594fb9c308f6555c73f618152340dcaae423e5fb3620026fefbec463618a8b761920382d666fa7a2d8d240b6fe320e8a6cdd54dc3687e2b659d25 + languageName: node + linkType: hard + "nth-check@npm:^2.0.1": version: 2.1.1 resolution: "nth-check@npm:2.1.1" @@ -8239,6 +8681,15 @@ __metadata: languageName: node linkType: hard +"onetime@npm:^6.0.0": + version: 6.0.0 + resolution: "onetime@npm:6.0.0" + dependencies: + mimic-fn: "npm:^4.0.0" + checksum: 10/0846ce78e440841335d4e9182ef69d5762e9f38aa7499b19f42ea1c4cd40f0b4446094c455c713f9adac3f4ae86f613bb5e30c99e52652764d06a89f709b3788 + languageName: node + linkType: hard + "open@npm:^10.0.3": version: 10.0.3 resolution: "open@npm:10.0.3" @@ -8495,6 +8946,34 @@ __metadata: languageName: node linkType: hard +"partykit@npm:0.0.107": + version: 0.0.107 + resolution: "partykit@npm:0.0.107" + dependencies: + "@cloudflare/workers-types": "npm:4.20240614.0" + clipboardy: "npm:4.0.0" + esbuild: "npm:0.21.5" + fsevents: "npm:2.3.3" + miniflare: "npm:3.20240610.0" + yoga-wasm-web: "npm:0.3.3" + dependenciesMeta: + fsevents: + optional: true + bin: + partykit: dist/bin.mjs + checksum: 10/66054b0f5ccbe6d5c4b2ac0b1af3994bc2a81d621d24553e535d53093babed352e413fa7d4696676f40b57d23051bce764652b21a1825e864b5a77576a06f635 + languageName: node + linkType: hard + +"partysocket@npm:1.0.1": + version: 1.0.1 + resolution: "partysocket@npm:1.0.1" + dependencies: + event-target-shim: "npm:^6.0.2" + checksum: 10/07120a825a2006c54b4c3bd15a57372d35050c393f5f6ae64c779d6c73e349a2d85501d965dab6640673400a40fb34d82d08dc0585f824a31095e6051485ef21 + languageName: node + linkType: hard + "pascal-case@npm:^3.1.2": version: 3.1.2 resolution: "pascal-case@npm:3.1.2" @@ -8550,6 +9029,13 @@ __metadata: languageName: node linkType: hard +"path-key@npm:^4.0.0": + version: 4.0.0 + resolution: "path-key@npm:4.0.0" + checksum: 10/8e6c314ae6d16b83e93032c61020129f6f4484590a777eed709c4a01b50e498822b00f76ceaf94bc64dbd90b327df56ceadce27da3d83393790f1219e07721d7 + languageName: node + linkType: hard + "path-parse@npm:^1.0.7": version: 1.0.7 resolution: "path-parse@npm:1.0.7" @@ -9140,16 +9626,7 @@ __metadata: languageName: node linkType: hard -"prettier@npm:^3.2.5": - version: 3.3.0 - resolution: "prettier@npm:3.3.0" - bin: - prettier: bin/prettier.cjs - checksum: 10/e55233f8e4b5f96f52180dbfa424ae797a98a9b8a9a7a79de5004e522c02b423e71927ed99d855dbfcd00dc3b82e5f6fb304cfe117cc4e7c8477d883df2d8984 - languageName: node - linkType: hard - -"prettier@npm:^3.3.2": +"prettier@npm:^3.2.5, prettier@npm:^3.3.2": version: 3.3.2 resolution: "prettier@npm:3.3.2" bin: @@ -9168,6 +9645,13 @@ __metadata: languageName: node linkType: hard +"printable-characters@npm:^1.0.42": + version: 1.0.42 + resolution: "printable-characters@npm:1.0.42" + checksum: 10/5fd9f44f2b24c9d875a97642a72be27f53aaac7f0f8f2792f969f3082e4516878db21cfa999f827606b002a890e6afeac0e0cc8dcb0d2d7965252975e634c6b2 + languageName: node + linkType: hard + "proc-log@npm:^3.0.0": version: 3.0.0 resolution: "proc-log@npm:3.0.0" @@ -10099,7 +10583,7 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^4.0.1": +"signal-exit@npm:^4.0.1, signal-exit@npm:^4.1.0": version: 4.1.0 resolution: "signal-exit@npm:4.1.0" checksum: 10/c9fa63bbbd7431066174a48ba2dd9986dfd930c3a8b59de9c29d7b6854ec1c12a80d15310869ea5166d413b99f041bfa3dd80a7947bcd44ea8e6eb3ffeabfa1f @@ -10229,7 +10713,7 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.6.0, source-map@npm:~0.6.0": +"source-map@npm:^0.6.0, source-map@npm:^0.6.1, source-map@npm:~0.6.0": version: 0.6.1 resolution: "source-map@npm:0.6.1" checksum: 10/59ef7462f1c29d502b3057e822cdbdae0b0e565302c4dd1a95e11e793d8d9d62006cdc10e0fd99163ca33ff2071360cf50ee13f90440806e7ed57d81cba2f7ff @@ -10286,6 +10770,16 @@ __metadata: languageName: node linkType: hard +"stacktracey@npm:^2.1.8": + version: 2.1.8 + resolution: "stacktracey@npm:2.1.8" + dependencies: + as-table: "npm:^1.0.36" + get-source: "npm:^2.0.12" + checksum: 10/c87f708b639636788c4b46ecc6e503c27b6124bec724bcdc3180d7cdddfab0dee370225009e3b407adaedf847362cfc77af64f01c805516e39a28d16c6d40df8 + languageName: node + linkType: hard + "statuses@npm:2.0.1": version: 2.0.1 resolution: "statuses@npm:2.0.1" @@ -10300,6 +10794,13 @@ __metadata: languageName: node linkType: hard +"stoppable@npm:^1.1.0": + version: 1.1.0 + resolution: "stoppable@npm:1.1.0" + checksum: 10/63104fcbdece130bc4906fd982061e763d2ef48065ed1ab29895e5ad00552c625f8a4c50c9cd2e3bfa805c8a2c3bfdda0f07c5ae39694bd2d5cb0bee1618d1e9 + languageName: node + linkType: hard + "string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" @@ -10365,6 +10866,13 @@ __metadata: languageName: node linkType: hard +"strip-final-newline@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-final-newline@npm:3.0.0" + checksum: 10/23ee263adfa2070cd0f23d1ac14e2ed2f000c9b44229aec9c799f1367ec001478469560abefd00c5c99ee6f0b31c137d53ec6029c53e9f32a93804e18c201050 + languageName: node + linkType: hard + "strip-json-comments@npm:^3.1.1": version: 3.1.1 resolution: "strip-json-comments@npm:3.1.1" @@ -10478,6 +10986,13 @@ __metadata: languageName: node linkType: hard +"system-architecture@npm:^0.1.0": + version: 0.1.0 + resolution: "system-architecture@npm:0.1.0" + checksum: 10/ca0dd793c45c354ab57dd7fc8ce7dc9923a6e07382bd3b22eb5b08f55ddb0217c390d00767549c5155fd4ce7ef23ffdd8cfb33dd4344cbbd37837d085a50f6f0 + languageName: node + linkType: hard + "tapable@npm:^2.0.0, tapable@npm:^2.1.1, tapable@npm:^2.2.0, tapable@npm:^2.2.1": version: 2.2.1 resolution: "tapable@npm:2.2.1" @@ -10704,10 +11219,10 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.0, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.6.2, tslib@npm:~2.6.2": - version: 2.6.2 - resolution: "tslib@npm:2.6.2" - checksum: 10/bd26c22d36736513980091a1e356378e8b662ded04204453d353a7f34a4c21ed0afc59b5f90719d4ba756e581a162ecbf93118dc9c6be5acf70aa309188166ca +"tslib@npm:^2.0.0, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.2.0, tslib@npm:^2.4.0, tslib@npm:^2.6.2, tslib@npm:~2.6.2": + version: 2.6.3 + resolution: "tslib@npm:2.6.3" + checksum: 10/52109bb681f8133a2e58142f11a50e05476de4f075ca906d13b596ae5f7f12d30c482feb0bff167ae01cfc84c5803e575a307d47938999246f5a49d174fc558c languageName: node linkType: hard @@ -10767,23 +11282,23 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.4.5": - version: 5.4.5 - resolution: "typescript@npm:5.4.5" +"typescript@npm:^5.5.2": + version: 5.5.2 + resolution: "typescript@npm:5.5.2" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/d04a9e27e6d83861f2126665aa8d84847e8ebabcea9125b9ebc30370b98cb38b5dff2508d74e2326a744938191a83a69aa9fddab41f193ffa43eabfdf3f190a5 + checksum: 10/9118b20f248e76b0dbff8737fef65dfa89d02668d4e633d2c5ceac99033a0ca5e8a1c1a53bc94da68e8f67677a88f318663dde859c9e9a09c1e116415daec2ba languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.4.5#optional!builtin": - version: 5.4.5 - resolution: "typescript@patch:typescript@npm%3A5.4.5#optional!builtin::version=5.4.5&hash=5adc0c" +"typescript@patch:typescript@npm%3A^5.5.2#optional!builtin": + version: 5.5.2 + resolution: "typescript@patch:typescript@npm%3A5.5.2#optional!builtin::version=5.5.2&hash=b45daf" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/760f7d92fb383dbf7dee2443bf902f4365db2117f96f875cf809167f6103d55064de973db9f78fe8f31ec08fff52b2c969aee0d310939c0a3798ec75d0bca2e1 + checksum: 10/28b3de2ddaf63a7620e7ddbe5d377af71ce93ecc558c41bf0e3d88661d8e6e7aa6c7739164fef98055f69819e41faca49252938ef3633a3dff2734cca6a9042e languageName: node linkType: hard @@ -10794,6 +11309,15 @@ __metadata: languageName: node linkType: hard +"undici@npm:^5.28.2": + version: 5.28.4 + resolution: "undici@npm:5.28.4" + dependencies: + "@fastify/busboy": "npm:^2.0.0" + checksum: 10/a666a9f5ac4270c659fafc33d78b6b5039a0adbae3e28f934774c85dcc66ea91da907896f12b414bd6f578508b44d5dc206fa636afa0e49a4e1c9e99831ff065 + languageName: node + linkType: hard + "undici@npm:^6.19.0": version: 6.19.0 resolution: "undici@npm:6.19.0" @@ -11715,6 +12239,32 @@ __metadata: languageName: node linkType: hard +"workerd@npm:1.20240610.1": + version: 1.20240610.1 + resolution: "workerd@npm:1.20240610.1" + dependencies: + "@cloudflare/workerd-darwin-64": "npm:1.20240610.1" + "@cloudflare/workerd-darwin-arm64": "npm:1.20240610.1" + "@cloudflare/workerd-linux-64": "npm:1.20240610.1" + "@cloudflare/workerd-linux-arm64": "npm:1.20240610.1" + "@cloudflare/workerd-windows-64": "npm:1.20240610.1" + dependenciesMeta: + "@cloudflare/workerd-darwin-64": + optional: true + "@cloudflare/workerd-darwin-arm64": + optional: true + "@cloudflare/workerd-linux-64": + optional: true + "@cloudflare/workerd-linux-arm64": + optional: true + "@cloudflare/workerd-windows-64": + optional: true + bin: + workerd: bin/workerd + checksum: 10/c37cd30c25fbdc7f97a296cf4b1a8ad2dbb65621a9c4b4bb24cb8fc00d9674c6610c5d2e5dc87c6cda83aa7b3b23a63d84b924ec1d9a3881622a9284590723f6 + languageName: node + linkType: hard + "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0" @@ -11755,7 +12305,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.16.0, ws@npm:^8.17.0": +"ws@npm:^8.11.0, ws@npm:^8.16.0, ws@npm:^8.17.0": version: 8.17.1 resolution: "ws@npm:8.17.1" peerDependencies: @@ -11864,6 +12414,24 @@ __metadata: languageName: node linkType: hard +"yoga-wasm-web@npm:0.3.3": + version: 0.3.3 + resolution: "yoga-wasm-web@npm:0.3.3" + checksum: 10/7a60a7dbb6dc0383395f2b40c9fac6e4f7b322f934ebb07cefc92997baca597f3ef01f67fe37e5e61c128de917760aa7639980ed2f8d94c2dfbef83bd5f61e36 + languageName: node + linkType: hard + +"youch@npm:^3.2.2": + version: 3.3.3 + resolution: "youch@npm:3.3.3" + dependencies: + cookie: "npm:^0.5.0" + mustache: "npm:^4.2.0" + stacktracey: "npm:^2.1.8" + checksum: 10/d13fb6f1e756823397ba02dd0c6f8a1b060a21327238872b46677ea1e1570b6b90fe216560a2ff9f34ba2eead6c442403b70df44354c6224bbfe31cc9a182c38 + languageName: node + linkType: hard + "zip-webpack-plugin@npm:^4.0.0": version: 4.0.1 resolution: "zip-webpack-plugin@npm:4.0.1" @@ -11876,6 +12444,13 @@ __metadata: languageName: node linkType: hard +"zod@npm:^3.20.6": + version: 3.23.8 + resolution: "zod@npm:3.23.8" + checksum: 10/846fd73e1af0def79c19d510ea9e4a795544a67d5b34b7e1c4d0425bf6bfd1c719446d94cdfa1721c1987d891321d61f779e8236fde517dc0e524aa851a6eff1 + languageName: node + linkType: hard + "zustand@npm:^4.3.9": version: 4.4.7 resolution: "zustand@npm:4.4.7" From 6636fe7c4ed8b638f67612f2f64ec986375b67c9 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Sat, 22 Jun 2024 22:18:25 -0700 Subject: [PATCH 02/91] initial working-ish prototype --- .gitignore | 1 + partykit.json | 6 + src/config-persistence.ts | 55 ++++- src/config-state.ts | 9 +- src/controls/controls-drawer.tsx | 24 +- src/draw-state.tsx | 28 +-- src/drawn-set.tsx | 2 - src/models/Drawing.ts | 3 - src/party/README.md | 1 + src/party/client.ts | 40 ++++ src/party/server.ts | 77 ++++++ src/party/types.ts | 31 +++ src/tournament-mode/drawing-actions.tsx | 73 +----- src/tournament-mode/remote-peer-menu.tsx | 146 ------------ src/tournament-mode/remote-peers.ts | 288 ----------------------- src/tournament-mode/sync-with-peers.ts | 26 -- src/zustand/contextual-zustand.ts | 10 +- 17 files changed, 223 insertions(+), 597 deletions(-) create mode 100644 partykit.json create mode 100644 src/party/README.md create mode 100644 src/party/client.ts create mode 100644 src/party/server.ts create mode 100644 src/party/types.ts delete mode 100644 src/tournament-mode/remote-peer-menu.tsx delete mode 100644 src/tournament-mode/remote-peers.ts delete mode 100644 src/tournament-mode/sync-with-peers.ts diff --git a/.gitignore b/.gitignore index 9eb97107e..6210c7177 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ !.yarn/releases !.yarn/sdks !.yarn/versions +.partykit dist node_modules diff --git a/partykit.json b/partykit.json new file mode 100644 index 000000000..ff146517e --- /dev/null +++ b/partykit.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://www.partykit.io/schema.json", + "name": "ddr-card-draw-party", + "main": "src/party/server.ts", + "compatibilityDate": "2024-06-21" +} diff --git a/src/config-persistence.ts b/src/config-persistence.ts index 3a6d87ebf..9ae2fdaa5 100644 --- a/src/config-persistence.ts +++ b/src/config-persistence.ts @@ -1,8 +1,13 @@ import { ConfigState, useConfigState } from "./config-state"; import { useDrawState } from "./draw-state"; +import { Roomstate } from "./party/types"; import { toaster } from "./toaster"; import { buildDataUri, dateForFilename, shareData } from "./utils/share"; +/** Mark specific fields in T optional, keeping others unchanged */ +type Optional = Partial> & + Omit; + interface PersistedConfigV1 { version: 1; dataSetName: string; @@ -21,7 +26,7 @@ type NonFunctionKeys = keyof { /** * Strips mutations from an object, and converts sets to arrays, maps to arrays of entry pairs */ -type Serialized = { +export type Serialized = { [K in NonFunctionKeys]: T[K] extends ReadonlyMap ? Array<[K, V]> : T[K] extends ReadonlySet @@ -95,14 +100,21 @@ export function loadConfig() { return resolution; } -function buildPersistedConfig(): PersistedConfigV1 { - const { ...configState } = useConfigState.getState(); - const serializedState: PersistedConfigV1["configState"] = { - ...configState, - difficulties: Array.from(configState.difficulties), - flags: Array.from(configState.flags), - folders: Array.from(configState.folders), +export function serializeConfig( + cfg: Optional, +): Serialized { + return { + ...cfg, + difficulties: Array.from(cfg.difficulties), + flags: Array.from(cfg.flags), + folders: Array.from(cfg.folders), }; +} + +function buildPersistedConfig(): PersistedConfigV1 { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { update, ...configState } = useConfigState.getState(); + const serializedState = serializeConfig(configState); const ret: PersistedConfigV1 = { version: 1, dataSetName: useDrawState.getState().dataSetName, @@ -111,7 +123,18 @@ function buildPersistedConfig(): PersistedConfigV1 { return ret; } -async function loadPersistedConfig(saved: PersistedConfigV1) { +export async function loadFromRoomstate(roomstate: Roomstate) { + await loadPersistedConfig({ + version: 1, + dataSetName: roomstate.dataSetName || useDrawState.getState().dataSetName, + configState: roomstate.config, + }); + useDrawState.setState({ drawings: roomstate.drawings }); +} + +async function loadPersistedConfig( + saved: Optional, +) { if (saved.version !== 1) { return false; } @@ -130,11 +153,17 @@ async function loadPersistedConfig(saved: PersistedConfigV1) { await nextConfigChange; } + if (saved.configState) { + applySerializedConfig(saved.configState); + } +} + +export function applySerializedConfig(config: Serialized) { useConfigState.setState({ - ...migrateOldNames(saved.configState), - difficulties: new Set(saved.configState.difficulties), - flags: new Set(saved.configState.flags), - folders: new Set(saved.configState.folders), + ...migrateOldNames(config), + difficulties: new Set(config.difficulties), + flags: new Set(config.flags), + folders: new Set(config.folders), }); } diff --git a/src/config-state.ts b/src/config-state.ts index b24d7ebe7..be0450379 100644 --- a/src/config-state.ts +++ b/src/config-state.ts @@ -1,5 +1,7 @@ import type { StoreApi } from "zustand"; import { createWithEqualityFn } from "zustand/traditional"; +import { sendToParty } from "./party/client"; +import { serializeConfig } from "./config-persistence"; export interface ConfigState { chartCount: number; @@ -29,7 +31,7 @@ export interface ConfigState { } export const useConfigState = createWithEqualityFn( - (set) => ({ + (set, get) => ({ chartCount: 5, playerPicks: 0, upperBound: 0, @@ -61,7 +63,10 @@ export const useConfigState = createWithEqualityFn( sortByLevel: false, defaultPlayersPerDraw: 2, useGranularLevels: false, - update: set, + update: (input) => { + set(input); + sendToParty({ type: "config", config: serializeConfig(get()) }); + }, }), Object.is, ); diff --git a/src/controls/controls-drawer.tsx b/src/controls/controls-drawer.tsx index 060287503..1356b6084 100644 --- a/src/controls/controls-drawer.tsx +++ b/src/controls/controls-drawer.tsx @@ -8,14 +8,11 @@ import { Divider, FormGroup, HTMLSelect, - Icon, NumericInput, Tab, Tabs, } from "@blueprintjs/core"; import { - ThirdParty, - GlobeNetwork, Settings, People, CaretDown, @@ -35,8 +32,6 @@ import { EligibleChartsListFilter } from "../eligible-charts/filter"; import { useIntl } from "../hooks/useIntl"; import { useIsNarrow } from "../hooks/useMediaQuery"; import { GameData } from "../models/SongData"; -import { RemotePeerControls } from "../tournament-mode/remote-peer-menu"; -import { useRemotePeers } from "../tournament-mode/remote-peers"; import { WeightsControls } from "./controls-weights"; import styles from "./controls.css"; import { PlayerNamesControls } from "./player-names"; @@ -84,8 +79,6 @@ function getDiffsAndRangeForNewStyle( export default function ControlsDrawer() { const { t } = useIntl(); - const isConnected = useRemotePeers((r) => !!r.thisPeer); - const hasPeers = useRemotePeers((r) => !!r.remotePeers.size); return (
@@ -96,24 +89,13 @@ export default function ControlsDrawer() { > {t("controls.tabs.general")} - - ) : ( - - ) - } - intent={isConnected ? "success" : "none"} - /> - } + icon={} />} panel={} > {t("controls.tabs.networking")} - + */} } diff --git a/src/draw-state.tsx b/src/draw-state.tsx index 9503911ae..a28025a00 100644 --- a/src/draw-state.tsx +++ b/src/draw-state.tsx @@ -13,7 +13,7 @@ import { IntlProvider } from "./intl-provider"; import type { StoreApi } from "zustand"; import { createWithEqualityFn } from "zustand/traditional"; import { shallow } from "zustand/shallow"; -import { DataConnection } from "peerjs"; +import { sendToParty } from "./party/client"; interface DrawState { importedData: Map; @@ -27,7 +27,6 @@ interface DrawState { /** returns false if no songs could be drawn */ drawSongs(config: ConfigState): boolean; clearDrawings(): void; - injectRemoteDrawing(d: Drawing, syncWithPeer?: DataConnection): void; } function applyNewData(data: GameData, set: StoreApi["setState"]) { @@ -110,6 +109,10 @@ export const useDrawState = createWithEqualityFn( dataSetName, drawings: [], }); + sendToParty({ + type: "dataSet", + data: dataSetName, + }); writeDataSetToUrl(dataSetName); // Attempt to look up a local data file first @@ -147,28 +150,9 @@ export const useDrawState = createWithEqualityFn( lastDrawFailed: false, }; }); + sendToParty({ type: "drawings", drawings: get().drawings }); return true; }, - injectRemoteDrawing(drawing, syncWithPeer) { - set((prevState) => { - const currentDrawing = prevState.drawings.find( - (d) => d.id === drawing.id, - ); - const newDrawings = prevState.drawings.filter( - (d) => d.id !== drawing.id, - ); - newDrawings.unshift(drawing); - if (currentDrawing) { - drawing.__syncPeer = currentDrawing.__syncPeer; - } - if (syncWithPeer) { - drawing.__syncPeer = syncWithPeer; - } - return { - drawings: newDrawings, - }; - }); - }, }), Object.is, ); diff --git a/src/drawn-set.tsx b/src/drawn-set.tsx index 36b553056..82355047a 100644 --- a/src/drawn-set.tsx +++ b/src/drawn-set.tsx @@ -6,7 +6,6 @@ import { Drawing } from "./models/Drawing"; import { SetLabels } from "./tournament-mode/drawing-labels"; import { DrawingProvider, useDrawing } from "./drawing-context"; import { DrawingActions } from "./tournament-mode/drawing-actions"; -import { SyncWithPeers } from "./tournament-mode/sync-with-peers"; import { useConfigState } from "./config-state"; import { ErrorFallback } from "./utils/error-fallback"; @@ -90,7 +89,6 @@ const DrawnSet = memo(function DrawnSet({ drawing }) {
} > -
; pocketPicks: Array; priorityPlayer?: number; - /** __ prefix avoids serializing this field during sync */ - __syncPeer?: DataConnection; } diff --git a/src/party/README.md b/src/party/README.md new file mode 100644 index 000000000..113b8b895 --- /dev/null +++ b/src/party/README.md @@ -0,0 +1 @@ +This is where the partykit server is implemented. See: https://docs.partykit.io/reference/partyserver-api/ diff --git a/src/party/client.ts b/src/party/client.ts new file mode 100644 index 000000000..7227eb84c --- /dev/null +++ b/src/party/client.ts @@ -0,0 +1,40 @@ +import PartySocket from "partysocket"; +import type { Broadcast, ClientMsg } from "./types"; +import { + applySerializedConfig, + loadFromRoomstate, +} from "../config-persistence"; +import { useDrawState } from "../draw-state"; + +const socket = new PartySocket({ + room: "default", // TODO picked off the path? + host: "localhost:43735", // TODO determine this after deploy, or local? +}); + +socket.addEventListener("message", (evt) => { + try { + const data: Broadcast = JSON.parse(evt.data); + switch (data.type) { + case "roomstate": + loadFromRoomstate(data); + break; + case "config": + applySerializedConfig(data.config); + break; + case "dataSet": + if (typeof data.data === "string") { + useDrawState.getState().loadGameData(data.data); + } + break; + case "drawings": + useDrawState.setState({ drawings: data.drawings }); + break; + } + } catch (e) { + console.warn("failed to handle party socket message", e); + } +}); + +export function sendToParty(message: ClientMsg) { + socket.send(JSON.stringify(message)); +} diff --git a/src/party/server.ts b/src/party/server.ts new file mode 100644 index 000000000..9f5dbb6a2 --- /dev/null +++ b/src/party/server.ts @@ -0,0 +1,77 @@ +import type * as Party from "partykit/server"; +import type { Drawing } from "../models/Drawing"; +import type { ConfigState } from "../config-state"; +import type { Serialized } from "../config-persistence"; +import type { ClientMsg, Roomstate } from "./types"; + +export default class Server implements Party.Server { + private dataSetName?: string; + private drawings: Drawing[] = []; + private config?: Serialized; + + constructor(readonly room: Party.Room) {} + + onConnect(conn: Party.Connection, ctx: Party.ConnectionContext) { + // A websocket just connected! + console.log( + `Connected: + id: ${conn.id} + room: ${this.room.id} + url: ${new URL(ctx.request.url).pathname}`, + ); + + // send the initial state to this client + conn.send(this.getRoomState()); + } + + onMessage(message: string, sender: Party.Connection) { + // let's log the message + console.log(`connection ${sender.id} sent message: ${message}`); + // as well as broadcast it to all the other connections in the room... + this.room.broadcast( + message, + // ...except for the connection it came from + [sender.id], + ); + + const parsed = JSON.parse(message) as ClientMsg; + switch (parsed.type) { + case "config": + this.config = parsed.config; + this.persistConfig(); + break; + case "drawings": + this.drawings = parsed.drawings; + this.room.storage.put("allDrawings", this.drawings); + break; + case "dataSet": + if (typeof parsed.data === "string") { + this.dataSetName = parsed.data; + } else { + console.error(`party server does not yet support custom data`); + } + break; + } + } + + private getRoomState() { + return JSON.stringify({ + type: "roomstate", + drawings: this.drawings, + config: this.config, + dataSetName: this.dataSetName, + } satisfies Roomstate); + } + + private persistConfig() { + this.room.storage.put("config", this.config); + } + + private async restoreFromStorage() { + this.config = await this.room.storage.get("config"); + this.drawings = + (await this.room.storage.get(`allDrawings`)) || []; + } +} + +Server satisfies Party.Worker; diff --git a/src/party/types.ts b/src/party/types.ts new file mode 100644 index 000000000..339ef555a --- /dev/null +++ b/src/party/types.ts @@ -0,0 +1,31 @@ +import type { Serialized } from "../config-persistence"; +import type { ConfigState } from "../config-state"; +import type { Drawing } from "../models/Drawing"; +import type { GameData } from "../models/SongData"; + +export interface Roomstate { + type: "roomstate"; + dataSetName?: string; + drawings: Drawing[]; + config?: Serialized; +} + +/** All messages possibly sent by the server to clients */ +export type Broadcast = Roomstate | ClientMsg; + +export interface DrawingUpdate { + type: "drawings"; + drawings: Drawing[]; +} + +export interface ConfigChange { + type: "config"; + config: Serialized; +} + +export interface DataSetChange { + type: "dataSet"; + data: string | GameData; +} + +export type ClientMsg = DrawingUpdate | ConfigChange | DataSetChange; diff --git a/src/tournament-mode/drawing-actions.tsx b/src/tournament-mode/drawing-actions.tsx index 7086238d7..f881b3a31 100644 --- a/src/tournament-mode/drawing-actions.tsx +++ b/src/tournament-mode/drawing-actions.tsx @@ -1,28 +1,15 @@ +import { Button, Tooltip } from "@blueprintjs/core"; import { - Button, - Icon, - Menu, - MenuItem, - Popover, - Tooltip, -} from "@blueprintjs/core"; -import { - SendMessage, - Changes, - Share, Camera, Refresh, NewPerson, BlockedPerson, Error, } from "@blueprintjs/icons"; -import { useDrawing, useDrawingStore } from "../drawing-context"; +import { useDrawing } from "../drawing-context"; import styles from "./drawing-actions.css"; -import { CurrentPeersMenu } from "./remote-peer-menu"; -import { displayFromPeerId, useRemotePeers } from "./remote-peers"; import { domToPng } from "modern-screenshot"; import { shareImage } from "../utils/share"; -import { firstOf } from "../utils"; import { useConfigState } from "../config-state"; import { useErrorBoundary } from "react-error-boundary"; @@ -33,67 +20,11 @@ export function DrawingActions() { const updateDrawing = useDrawing((s) => s.updateDrawing); const redrawAllCharts = useDrawing((s) => s.redrawAllCharts); const hasPlayers = useDrawing((s) => !!s.players.length); - const syncPeer = useDrawing((s) => s.__syncPeer); - const isConnected = useRemotePeers((s) => !!s.thisPeer); - const remotePeers = useRemotePeers((s) => s.remotePeers); - const sendDrawing = useRemotePeers((s) => s.sendDrawing); - const syncDrawing = useRemotePeers((s) => s.beginSyncWithPeer); - const drawingStore = useDrawingStore(); const showLabels = useConfigState((s) => s.showPlayerAndRoundLabels); const { showBoundary } = useErrorBoundary(); - let remoteActions: JSX.Element | undefined = undefined; - - const onlyRemote = firstOf(remotePeers.values()); - if (remotePeers.size === 1 && onlyRemote) { - const peerId = displayFromPeerId(onlyRemote.peer); - remoteActions = ( - - } - text={`Send to ${peerId}`} - onClick={() => sendDrawing(getDrawing())} - /> - } - text={`Start sync with ${peerId}`} - onClick={() => syncDrawing(drawingStore)} - /> - - ); - } else if (remotePeers.size > 1) { - remoteActions = ( - - } text="Send to..."> - sendDrawing(getDrawing(), peerId)} - /> - - } text="Start sync with..."> - syncDrawing(drawingStore, peerId)} - /> - - - ); - } - - const button = ( - @@ -234,7 +234,7 @@ function FolderSettings() { key={`${dataSetName}:${idx}`} label={folder} value={folder} - checked={selectedFolders.has(folder)} + checked={selectedFolders.includes(folder)} onChange={() => updateState((s) => { const newFolders = new Set(s.folders); @@ -243,7 +243,7 @@ function FolderSettings() { } else { newFolders.add(folder); } - return { folders: newFolders }; + return { folders: Array.from(newFolders) }; }) } /> @@ -345,7 +345,9 @@ function GeneralSettings() { - + @@ -463,7 +465,7 @@ function GeneralSettings() { next.style, ); if (diffs.length === 1) { - next.difficulties = new Set(diffs.map((d) => d.key)); + next.difficulties = diffs.map((d) => d.key); } if (lvlRange.low > next.upperBound) { next.upperBound = lvlRange.low; @@ -489,7 +491,7 @@ function GeneralSettings() { key={`${dif.key}`} name="difficulties" value={dif.key} - checked={selectedDifficulties.has(dif.key)} + checked={selectedDifficulties.includes(dif.key)} onChange={(e) => { const { checked, value } = e.currentTarget; updateState((s) => { @@ -499,7 +501,7 @@ function GeneralSettings() { } else { difficulties.delete(value); } - return { difficulties }; + return { difficulties: Array.from(difficulties) }; }); }} label={t("meta." + dif.key)} diff --git a/src/eligible-charts/index.tsx b/src/eligible-charts/index.tsx index 9faef5967..9bdd1b17e 100644 --- a/src/eligible-charts/index.tsx +++ b/src/eligible-charts/index.tsx @@ -67,7 +67,7 @@ export default function EligibleChartsList() { {charts.length} eligible charts from {songs.size} songs (of{" "} {gameData.songs.length} total) - {configState.flags.size > 0 && !isNarrow && ( + {configState.flags.length > 0 && !isNarrow && ( diff --git a/src/state/config.slice.ts b/src/state/config.slice.ts index 105dccb4b..d723c364a 100644 --- a/src/state/config.slice.ts +++ b/src/state/config.slice.ts @@ -6,6 +6,7 @@ import { gameDataSlice } from "./game-data.slice"; import { store, useAppDispatch, useAppState } from "./store"; import { EqualityFn } from "react-redux"; import { addPlayerNameToDrawing } from "./central"; +import { useCallback } from "react"; function createPartialFromDefaults(gameData: GameData) { const { lowerLvlBound, upperLvlBound, flags, difficulties, folders, style } = @@ -13,9 +14,9 @@ function createPartialFromDefaults(gameData: GameData) { const ret: Partial = { lowerBound: lowerLvlBound, upperBound: upperLvlBound, - flags: new Set(flags), - difficulties: new Set(difficulties), - folders: new Set(folders), + flags, + difficulties, + folders, style, cutoffDate: "", }; @@ -70,15 +71,18 @@ export function useConfigState( export function useUpdateConfig() { const dispatch = useAppDispatch(); - return ( - patch: - | Partial - | ((state: ConfigState) => Partial), - ) => { - if (typeof patch === "function") { - const state = configSlice.selectSlice(store.getState()); - patch = patch(state); - } - dispatch(actions.update(patch)); - }; + return useCallback( + ( + patch: + | Partial + | ((state: ConfigState) => Partial), + ) => { + if (typeof patch === "function") { + const state = configSlice.selectSlice(store.getState()); + patch = patch(state); + } + dispatch(actions.update(patch)); + }, + [dispatch], + ); } From 18305be2cdb863f041b83e0ace1f2855ba350d90 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Wed, 3 Jul 2024 17:26:09 -0700 Subject: [PATCH 08/91] speculative wireup for new partykit networking --- src/config-persistence.ts | 158 ++++++------------------------- src/controls/index.tsx | 8 +- src/party/client.ts | 81 +++++++++------- src/party/server.ts | 61 +++++------- src/party/types.ts | 32 ++----- src/state/drawings.slice.ts | 20 ++-- src/state/listener-middleware.ts | 9 ++ src/state/root-reducer.ts | 22 +++++ src/state/store.ts | 11 ++- 9 files changed, 155 insertions(+), 247 deletions(-) create mode 100644 src/state/listener-middleware.ts create mode 100644 src/state/root-reducer.ts diff --git a/src/config-persistence.ts b/src/config-persistence.ts index 0603b43b0..5bd278dfb 100644 --- a/src/config-persistence.ts +++ b/src/config-persistence.ts @@ -1,24 +1,16 @@ -import { getDefaultStore } from "jotai"; -import { - ConfigState, - showEligibleCharts, - showPlayerAndRoundLabels, -} from "./config-state"; -import { useDrawState } from "./draw-state"; -import { Roomstate } from "./party/types"; -import { store } from "./state/store"; +// import { ConfigState } from "./config-state"; import { toaster } from "./toaster"; -import { buildDataUri, dateForFilename, shareData } from "./utils/share"; +// import { buildDataUri, dateForFilename, shareData } from "./utils/share"; /** Mark specific fields in T optional, keeping others unchanged */ -type Optional = Partial> & - Omit; +// type Optional = Partial> & +// Omit; -interface PersistedConfigV1 { - version: 1; - dataSetName: string; - configState: Serialized & OldSettings; -} +// interface PersistedConfigV1 { +// version: 1; +// dataSetName: string; +// configState: Serialized; +// } /** * Returns a union of all property names in T which do not contain a function value. @@ -40,22 +32,22 @@ export type Serialized = { : T[K]; }; -export function saveConfig() { - const persistedObj = buildPersistedConfig(store.getState().config); - const dataUri = buildDataUri( - JSON.stringify(persistedObj, undefined, 2), - "application/json", - "url", - ); - - return shareData(dataUri, { - filename: `ddr-tools-config-${persistedObj.dataSetName}-${dateForFilename()}.json`, - methods: [ - { type: "nativeShare", allowDesktop: true }, - { type: "download" }, - ], - }); -} +// export function saveConfig() { +// const persistedObj = buildPersistedConfig(store.getState().config); +// const dataUri = buildDataUri( +// JSON.stringify(persistedObj, undefined, 2), +// "application/json", +// "url", +// ); + +// return shareData(dataUri, { +// filename: `ddr-tools-config-${persistedObj.dataSetName}-${dateForFilename()}.json`, +// methods: [ +// { type: "nativeShare", allowDesktop: true }, +// { type: "download" }, +// ], +// }); +// } export function loadConfig() { const fileInput = document.createElement("input"); @@ -80,8 +72,8 @@ export function loadConfig() { reject(); throw new Error("file type is " + f.type); } - const contents: PersistedConfigV1 = JSON.parse(await f.text()); - await loadPersistedConfig(contents); + // const contents: PersistedConfigV1 = JSON.parse(await f.text()); + // await loadPersistedConfig(contents); resolve(); toaster.show({ message: "Successfully loaded draw settings", @@ -105,99 +97,3 @@ export function loadConfig() { fileInput.click(); return resolution; } - -export function serializeConfig(cfg: ConfigState): Serialized { - return { - ...cfg, - difficulties: Array.from(cfg.difficulties), - flags: Array.from(cfg.flags), - folders: Array.from(cfg.folders), - }; -} - -function buildPersistedConfig(cfg: ConfigState): PersistedConfigV1 { - const serializedState = serializeConfig(cfg); - const ret: PersistedConfigV1 = { - version: 1, - dataSetName: useDrawState.getState().dataSetName, - configState: serializedState, - }; - return ret; -} - -export async function loadFromRoomstate(roomstate: Roomstate) { - await loadPersistedConfig({ - version: 1, - dataSetName: roomstate.dataSetName || useDrawState.getState().dataSetName, - configState: roomstate.config, - }); - useDrawState.setState({ drawings: roomstate.drawings }); -} - -async function loadPersistedConfig( - saved: Optional, -) { - if (saved.version !== 1) { - return false; - } - const drawState = useDrawState.getState(); - if (drawState.dataSetName !== saved.dataSetName) { - const nextConfigChange = new Promise((resolve) => { - const unsub = useConfigState.subscribe(() => { - unsub(); - resolve(); - }); - }); - await drawState.loadGameData(saved.dataSetName); - // the ApplyDefaultConfig component will kick in - // to overwrite config in response to this change - // so we have to wait for that to happen before continuing - await nextConfigChange; - } - - if (saved.configState) { - applySerializedConfig(saved.configState); - } -} - -export function applySerializedConfig(config: Serialized) { - useConfigState.setState({ - ...migrateOldNames(config), - difficulties: new Set(config.difficulties), - flags: new Set(config.flags), - folders: new Set(config.folders), - }); -} - -interface OldSettings { - /** renamed to `showEligibleCharts` */ - showPool?: boolean; - /** renamed to `showPlayerAndRoundLabels` */ - showLabels?: boolean; -} - -function migrateOldNames( - config: PersistedConfigV1["configState"], -): Serialized { - const { showPool, showLabels, ...modernConfig } = config; - - const jotaiStore = getDefaultStore(); - if (showPool) { - jotaiStore.set(showEligibleCharts, showPool); - } - - if (showLabels) { - jotaiStore.set(showPlayerAndRoundLabels, showLabels); - } - - const maybeOldWeights = modernConfig.weights as unknown as - | Array<[number, number]> - | Array; - if (Array.isArray(maybeOldWeights[0])) { - modernConfig.weights = maybeOldWeights.map((pair) => - Array.isArray(pair) ? pair[1] : pair, - ); - } - - return modernConfig; -} diff --git a/src/controls/index.tsx b/src/controls/index.tsx index a43d0f1ff..b62ad0ff9 100644 --- a/src/controls/index.tsx +++ b/src/controls/index.tsx @@ -9,11 +9,11 @@ import { Spinner, Tooltip, } from "@blueprintjs/core"; -import { NewLayers, Cog, FloppyDisk, Import } from "@blueprintjs/icons"; +import { NewLayers, Cog } from "@blueprintjs/icons"; import { useState, lazy, Suspense } from "react"; import { FormattedMessage } from "react-intl"; import { useIsNarrow } from "../hooks/useMediaQuery"; -import { loadConfig, saveConfig } from "../config-persistence"; +// import { loadConfig, saveConfig } from "../config-persistence"; import { ErrorBoundary } from "react-error-boundary"; import { ErrorFallback } from "../utils/error-fallback"; import { ShowChartsToggle } from "./show-charts-toggle"; @@ -59,14 +59,14 @@ export function HeaderControls() { title={ <> - + {/* - + */} } > diff --git a/src/party/client.ts b/src/party/client.ts index 7227eb84c..704dadb15 100644 --- a/src/party/client.ts +++ b/src/party/client.ts @@ -1,40 +1,49 @@ -import PartySocket from "partysocket"; -import type { Broadcast, ClientMsg } from "./types"; -import { - applySerializedConfig, - loadFromRoomstate, -} from "../config-persistence"; -import { useDrawState } from "../draw-state"; +import usePartySocket from "partysocket/react"; +import type { Broadcast, ReduxAction } from "./types"; +import { useAppDispatch } from "../state/store"; +import { receivePartyState } from "../state/central"; +import { startAppListening } from "../state/listener-middleware"; +import { useEffect } from "react"; -const socket = new PartySocket({ - room: "default", // TODO picked off the path? - host: "localhost:43735", // TODO determine this after deploy, or local? -}); - -socket.addEventListener("message", (evt) => { - try { - const data: Broadcast = JSON.parse(evt.data); - switch (data.type) { - case "roomstate": - loadFromRoomstate(data); - break; - case "config": - applySerializedConfig(data.config); - break; - case "dataSet": - if (typeof data.data === "string") { - useDrawState.getState().loadGameData(data.data); +export function PartySocketManager(props: { roomName?: string }) { + const dispatch = useAppDispatch(); + const socket = usePartySocket({ + room: props.roomName, + host: "localhost:43735", // TODO determine this based on build type, other stuff + onMessage(evt) { + try { + const data: Broadcast = JSON.parse(evt.data); + switch (data.type) { + case "roomstate": + dispatch(receivePartyState(data)); + break; + case "action": + const foreignAction = { + ...data.action, + meta: { source: "partykit" }, + }; + dispatch(foreignAction); + break; } - break; - case "drawings": - useDrawState.setState({ drawings: data.drawings }); - break; - } - } catch (e) { - console.warn("failed to handle party socket message", e); - } -}); + } catch (e) { + console.warn("failed to handle party socket message", e); + } + }, + }); -export function sendToParty(message: ClientMsg) { - socket.send(JSON.stringify(message)); + useEffect(() => { + return startAppListening({ + predicate(action) { + // @ts-expect-error i don't know how to type action meta properties yet + return action.meta.source !== "partykit"; + }, + effect(action) { + const message: ReduxAction = { + type: "action", + action, + }; + socket.send(JSON.stringify(message)); + }, + }); + }, [socket]); } diff --git a/src/party/server.ts b/src/party/server.ts index 9f5dbb6a2..33c85988c 100644 --- a/src/party/server.ts +++ b/src/party/server.ts @@ -1,16 +1,19 @@ import type * as Party from "partykit/server"; -import type { Drawing } from "../models/Drawing"; -import type { ConfigState } from "../config-state"; -import type { Serialized } from "../config-persistence"; -import type { ClientMsg, Roomstate } from "./types"; +import type { ReduxAction, Roomstate } from "./types"; +import { Action, configureStore } from "@reduxjs/toolkit"; +import { reducer } from "../state/root-reducer"; +import { AppState } from "../state/store"; +import { receivePartyState } from "../state/central"; export default class Server implements Party.Server { - private dataSetName?: string; - private drawings: Drawing[] = []; - private config?: Serialized; + private store = configureStore({ reducer }); constructor(readonly room: Party.Room) {} + onStart(): void | Promise { + return this.restoreFromStorage(); + } + onConnect(conn: Party.Connection, ctx: Party.ConnectionContext) { // A websocket just connected! console.log( @@ -34,43 +37,31 @@ export default class Server implements Party.Server { [sender.id], ); - const parsed = JSON.parse(message) as ClientMsg; - switch (parsed.type) { - case "config": - this.config = parsed.config; - this.persistConfig(); - break; - case "drawings": - this.drawings = parsed.drawings; - this.room.storage.put("allDrawings", this.drawings); - break; - case "dataSet": - if (typeof parsed.data === "string") { - this.dataSetName = parsed.data; - } else { - console.error(`party server does not yet support custom data`); - } - break; - } + const parsed = JSON.parse(message) as ReduxAction; + this.dispatchAndPersist(parsed); } private getRoomState() { - return JSON.stringify({ + return JSON.stringify({ type: "roomstate", - drawings: this.drawings, - config: this.config, - dataSetName: this.dataSetName, - } satisfies Roomstate); + state: this.store.getState(), + }); } - private persistConfig() { - this.room.storage.put("config", this.config); + private async dispatchAndPersist(action: Action) { + this.store.dispatch(action); + await this.room.storage.put("currentState", this.store.getState()); } private async restoreFromStorage() { - this.config = await this.room.storage.get("config"); - this.drawings = - (await this.room.storage.get(`allDrawings`)) || []; + const preloadedState = + await this.room.storage.get("currentState"); + if (preloadedState) { + this.store.dispatch( + receivePartyState({ type: "roomstate", state: preloadedState }), + ); + } + // this.store = configureStore({ reducer, preloadedState }); } } diff --git a/src/party/types.ts b/src/party/types.ts index 339ef555a..7aec1910e 100644 --- a/src/party/types.ts +++ b/src/party/types.ts @@ -1,31 +1,15 @@ -import type { Serialized } from "../config-persistence"; -import type { ConfigState } from "../config-state"; -import type { Drawing } from "../models/Drawing"; -import type { GameData } from "../models/SongData"; +import { Action } from "@reduxjs/toolkit"; +import { AppState } from "../state/store"; export interface Roomstate { type: "roomstate"; - dataSetName?: string; - drawings: Drawing[]; - config?: Serialized; + state: AppState; } -/** All messages possibly sent by the server to clients */ -export type Broadcast = Roomstate | ClientMsg; - -export interface DrawingUpdate { - type: "drawings"; - drawings: Drawing[]; -} - -export interface ConfigChange { - type: "config"; - config: Serialized; +export interface ReduxAction { + type: "action"; + action: Action; } -export interface DataSetChange { - type: "dataSet"; - data: string | GameData; -} - -export type ClientMsg = DrawingUpdate | ConfigChange | DataSetChange; +/** All messages possibly sent by the server to clients */ +export type Broadcast = Roomstate | ReduxAction; diff --git a/src/state/drawings.slice.ts b/src/state/drawings.slice.ts index 7a7305267..811e928a7 100644 --- a/src/state/drawings.slice.ts +++ b/src/state/drawings.slice.ts @@ -9,7 +9,7 @@ import { EligibleChart, PlayerActionOnChart, } from "../models/Drawing"; -import { addPlayerNameToDrawing, receivePartyState } from "./central"; +import { addPlayerNameToDrawing } from "./central"; import { AppState } from "./store"; import { draw } from "../card-draw"; @@ -154,17 +154,13 @@ export const drawingsSlice = createSlice({ }, }, extraReducers(builder) { - builder - .addCase(receivePartyState, (state, action) => { - return drawingsAdapter.addMany(state, action.payload.drawings); - }) - .addCase(addPlayerNameToDrawing, (state, action) => { - const drawing = state.entities[action.payload.drawingId]; - if (!drawing) { - return; - } - drawing.players[action.payload.asPlayerNo - 1] = action.payload.name; - }); + builder.addCase(addPlayerNameToDrawing, (state, action) => { + const drawing = state.entities[action.payload.drawingId]; + if (!drawing) { + return; + } + drawing.players[action.payload.asPlayerNo - 1] = action.payload.name; + }); }, selectors: { haveDrawings(state) { diff --git a/src/state/listener-middleware.ts b/src/state/listener-middleware.ts new file mode 100644 index 000000000..9184fa02c --- /dev/null +++ b/src/state/listener-middleware.ts @@ -0,0 +1,9 @@ +import { createListenerMiddleware } from "@reduxjs/toolkit"; +import type { AppState, AppDispatch } from "./store"; + +export const listenerMiddleware = createListenerMiddleware(); + +export const startAppListening = listenerMiddleware.startListening.withTypes< + AppState, + AppDispatch +>(); diff --git a/src/state/root-reducer.ts b/src/state/root-reducer.ts new file mode 100644 index 000000000..9a8954b81 --- /dev/null +++ b/src/state/root-reducer.ts @@ -0,0 +1,22 @@ +import { combineSlices } from "@reduxjs/toolkit"; +import { configSlice } from "./config.slice"; +import { drawingsSlice } from "./drawings.slice"; +import { gameDataSlice } from "./game-data.slice"; +import { receivePartyState } from "./central"; + +const combinedReducer = combineSlices( + drawingsSlice, + configSlice, + gameDataSlice, +); + +export const reducer: typeof combinedReducer = (state, action) => { + if (receivePartyState.match(action)) { + return action.payload.state; + } + return combinedReducer(state, action); +}; + +reducer.inject = combinedReducer.inject; +reducer.withLazyLoadedSlices = combinedReducer.withLazyLoadedSlices; +reducer.selector = combinedReducer.selector; diff --git a/src/state/store.ts b/src/state/store.ts index ea03cde5b..058c3f6b4 100644 --- a/src/state/store.ts +++ b/src/state/store.ts @@ -1,11 +1,12 @@ -import { configureStore, combineSlices } from "@reduxjs/toolkit"; +import { configureStore } from "@reduxjs/toolkit"; import { useDispatch, useSelector, useStore } from "react-redux"; -import { drawingsSlice } from "./drawings.slice"; -import { configSlice } from "./config.slice"; -import { gameDataSlice } from "./game-data.slice"; +import { reducer } from "./root-reducer"; +import { listenerMiddleware } from "./listener-middleware"; export const store = configureStore({ - reducer: combineSlices(drawingsSlice, configSlice, gameDataSlice), + reducer, + middleware: (getDefaults) => + getDefaults().concat(listenerMiddleware.middleware), }); export type AppState = ReturnType; From 82a22c9b116519422da88ef8612651a6f37e9bef Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Wed, 3 Jul 2024 18:56:55 -0700 Subject: [PATCH 09/91] reorganize further so server can build --- src/app.tsx | 2 ++ src/controls/controls-drawer.tsx | 2 +- src/controls/controls-weights.tsx | 2 +- src/controls/player-names.tsx | 2 +- src/eligible-charts/filter.tsx | 2 +- src/eligible-charts/histogram.tsx | 2 +- src/eligible-charts/index.tsx | 2 +- src/party/client.ts | 21 +++++++++++++--- src/party/server.ts | 26 +++++++++----------- src/song-card/chart-level.tsx | 2 +- src/song-card/song-card.tsx | 2 +- src/song-search/index.tsx | 2 +- src/state/central.ts | 4 +-- src/state/config.slice.ts | 34 -------------------------- src/state/hooks.ts | 34 ++++++++++++++++++++++++++ src/state/root-reducer.ts | 2 +- src/tournament-mode/drawing-labels.tsx | 2 +- src/tournament-mode/round-select.tsx | 2 +- src/utils/index.ts | 4 ++- 19 files changed, 83 insertions(+), 66 deletions(-) create mode 100644 src/state/hooks.ts diff --git a/src/app.tsx b/src/app.tsx index 57aa98933..390b2f8e7 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -16,10 +16,12 @@ import { ThemeSyncWidget } from "./theme-toggle"; import { DropHandler } from "./drop-handler"; import { Provider } from "react-redux"; import { store } from "./state/store"; +import { PartySocketManager } from "./party/client"; export function App() { return ( + diff --git a/src/controls/controls-drawer.tsx b/src/controls/controls-drawer.tsx index 542b62f57..3fe4fd30a 100644 --- a/src/controls/controls-drawer.tsx +++ b/src/controls/controls-drawer.tsx @@ -36,7 +36,7 @@ import { getAvailableLevels } from "../game-data-utils"; import { ShowChartsToggle } from "./show-charts-toggle"; import { Fraction } from "../utils/fraction"; import { detectedLanguage } from "../utils"; -import { useConfigState, useUpdateConfig } from "../state/config.slice"; +import { useConfigState, useUpdateConfig } from "../state/hooks"; import { useAtomValue } from "jotai"; import { showEligibleCharts } from "../config-state"; import { useAppState } from "../state/store"; diff --git a/src/controls/controls-weights.tsx b/src/controls/controls-weights.tsx index 2dfc767f5..8c30260f1 100644 --- a/src/controls/controls-weights.tsx +++ b/src/controls/controls-weights.tsx @@ -2,7 +2,7 @@ import { shallow } from "zustand/shallow"; import styles from "./controls-weights.css"; import { zeroPad } from "../utils"; import { useMemo } from "react"; -import { useConfigState, useUpdateConfig } from "../state/config.slice"; +import { useConfigState, useUpdateConfig } from "../state/hooks"; import { useIntl } from "../hooks/useIntl"; import { NumericInput, Checkbox, Classes } from "@blueprintjs/core"; import { getAvailableLevels } from "../game-data-utils"; diff --git a/src/controls/player-names.tsx b/src/controls/player-names.tsx index 29780ca94..359a16735 100644 --- a/src/controls/player-names.tsx +++ b/src/controls/player-names.tsx @@ -6,7 +6,7 @@ import { TagInput, } from "@blueprintjs/core"; import { ReactNode } from "react"; -import { useConfigState, useUpdateConfig } from "../state/config.slice"; +import { useConfigState, useUpdateConfig } from "../state/hooks"; import { useIntl } from "../hooks/useIntl"; import { DiagramTree, Person } from "@blueprintjs/icons"; import { useAtom } from "jotai"; diff --git a/src/eligible-charts/filter.tsx b/src/eligible-charts/filter.tsx index 2b469aff7..e34e666cd 100644 --- a/src/eligible-charts/filter.tsx +++ b/src/eligible-charts/filter.tsx @@ -1,6 +1,6 @@ import { HTMLSelect } from "@blueprintjs/core"; import { atom, useAtom } from "jotai"; -import { useConfigState } from "../state/config.slice"; +import { useConfigState } from "../state/hooks"; import { useIntl } from "../hooks/useIntl"; export const currentTabAtom = atom("all"); diff --git a/src/eligible-charts/histogram.tsx b/src/eligible-charts/histogram.tsx index 50d87f777..2f1025c16 100644 --- a/src/eligible-charts/histogram.tsx +++ b/src/eligible-charts/histogram.tsx @@ -18,7 +18,7 @@ import { } from "../game-data-utils"; import { Theme, useTheme } from "../theme-toggle"; import { useIsNarrow } from "../hooks/useMediaQuery"; -import { useConfigState } from "../state/config.slice"; +import { useConfigState } from "../state/hooks"; import { useAppState } from "../state/store"; interface Props { diff --git a/src/eligible-charts/index.tsx b/src/eligible-charts/index.tsx index 9bdd1b17e..64d9f6801 100644 --- a/src/eligible-charts/index.tsx +++ b/src/eligible-charts/index.tsx @@ -1,5 +1,5 @@ import { eligibleCharts } from "../card-draw"; -import { useConfigState } from "../state/config.slice"; +import { useConfigState } from "../state/hooks"; import { SongCard } from "../song-card"; import styles from "../drawing-list.css"; import { EligibleChart } from "../models/Drawing"; diff --git a/src/party/client.ts b/src/party/client.ts index 704dadb15..bfcc7f1fa 100644 --- a/src/party/client.ts +++ b/src/party/client.ts @@ -4,18 +4,19 @@ import { useAppDispatch } from "../state/store"; import { receivePartyState } from "../state/central"; import { startAppListening } from "../state/listener-middleware"; import { useEffect } from "react"; +import { loadGameDataByName } from "../state/thunks"; export function PartySocketManager(props: { roomName?: string }) { const dispatch = useAppDispatch(); const socket = usePartySocket({ room: props.roomName, - host: "localhost:43735", // TODO determine this based on build type, other stuff + host: "localhost:1999", // TODO determine this based on build type, other stuff onMessage(evt) { try { const data: Broadcast = JSON.parse(evt.data); switch (data.type) { case "roomstate": - dispatch(receivePartyState(data)); + dispatch(receivePartyState(data.state)); break; case "action": const foreignAction = { @@ -35,7 +36,19 @@ export function PartySocketManager(props: { roomName?: string }) { return startAppListening({ predicate(action) { // @ts-expect-error i don't know how to type action meta properties yet - return action.meta.source !== "partykit"; + if (action.meta?.source === "partykit") { + return false; + } + + if (receivePartyState.match(action)) { + return false; + } + + // don't try sending the loaded game data through + if (action.type.startsWith(loadGameDataByName.typePrefix)) { + return false; + } + return true; }, effect(action) { const message: ReduxAction = { @@ -46,4 +59,6 @@ export function PartySocketManager(props: { roomName?: string }) { }, }); }, [socket]); + + return null; } diff --git a/src/party/server.ts b/src/party/server.ts index 33c85988c..f57281859 100644 --- a/src/party/server.ts +++ b/src/party/server.ts @@ -1,6 +1,6 @@ import type * as Party from "partykit/server"; import type { ReduxAction, Roomstate } from "./types"; -import { Action, configureStore } from "@reduxjs/toolkit"; +import { configureStore } from "@reduxjs/toolkit"; import { reducer } from "../state/root-reducer"; import { AppState } from "../state/store"; import { receivePartyState } from "../state/central"; @@ -8,10 +8,16 @@ import { receivePartyState } from "../state/central"; export default class Server implements Party.Server { private store = configureStore({ reducer }); - constructor(readonly room: Party.Room) {} + constructor(readonly room: Party.Room) { + console.log("constructor start"); + } - onStart(): void | Promise { - return this.restoreFromStorage(); + async onStart() { + console.log("onStart", typeof reducer); + await this.restoreFromStorage(); + this.store.subscribe(() => { + this.room.storage.put("currentState", this.store.getState()); + }); } onConnect(conn: Party.Connection, ctx: Party.ConnectionContext) { @@ -38,7 +44,7 @@ export default class Server implements Party.Server { ); const parsed = JSON.parse(message) as ReduxAction; - this.dispatchAndPersist(parsed); + this.store.dispatch(parsed.action); } private getRoomState() { @@ -48,20 +54,12 @@ export default class Server implements Party.Server { }); } - private async dispatchAndPersist(action: Action) { - this.store.dispatch(action); - await this.room.storage.put("currentState", this.store.getState()); - } - private async restoreFromStorage() { const preloadedState = await this.room.storage.get("currentState"); if (preloadedState) { - this.store.dispatch( - receivePartyState({ type: "roomstate", state: preloadedState }), - ); + this.store.dispatch(receivePartyState(preloadedState)); } - // this.store = configureStore({ reducer, preloadedState }); } } diff --git a/src/song-card/chart-level.tsx b/src/song-card/chart-level.tsx index 6336b5108..ea52f3ada 100644 --- a/src/song-card/chart-level.tsx +++ b/src/song-card/chart-level.tsx @@ -1,4 +1,4 @@ -import { useConfigState } from "../state/config.slice"; +import { useConfigState } from "../state/hooks"; import { formatLevel } from "../game-data-utils"; import { CHART_PLACEHOLDER, diff --git a/src/song-card/song-card.tsx b/src/song-card/song-card.tsx index 0ba725718..086b0db64 100644 --- a/src/song-card/song-card.tsx +++ b/src/song-card/song-card.tsx @@ -1,7 +1,7 @@ import { Popover } from "@blueprintjs/core"; import classNames from "classnames"; import { useCallback, useMemo, useState } from "react"; -import { useConfigState } from "../state/config.slice"; +import { useConfigState } from "../state/hooks"; import { useDrawing } from "../drawing-context"; import { CHART_PLACEHOLDER, diff --git a/src/song-search/index.tsx b/src/song-search/index.tsx index 525914508..ec3e58a96 100644 --- a/src/song-search/index.tsx +++ b/src/song-search/index.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { chartIsValid, getDrawnChart, songIsValid } from "../card-draw"; -import { useConfigState } from "../state/config.slice"; +import { useConfigState } from "../state/hooks"; import { EligibleChart } from "../models/Drawing"; import { Song } from "../models/SongData"; import { SearchResult, SearchResultData } from "./search-result"; diff --git a/src/state/central.ts b/src/state/central.ts index 69de34f0c..6096443a1 100644 --- a/src/state/central.ts +++ b/src/state/central.ts @@ -1,10 +1,10 @@ import { createAction } from "@reduxjs/toolkit"; import { withPayload } from "./util"; -import { Roomstate } from "../party/types"; +import type { AppState } from "./store"; export const receivePartyState = createAction( "party/supplyState", - withPayload(), + withPayload(), ); export const addPlayerNameToDrawing = createAction( diff --git a/src/state/config.slice.ts b/src/state/config.slice.ts index d723c364a..1ce348945 100644 --- a/src/state/config.slice.ts +++ b/src/state/config.slice.ts @@ -3,10 +3,7 @@ import { ConfigState, initialState } from "../config-state"; import { loadGameDataByName } from "./thunks"; import { GameData } from "../models/SongData"; import { gameDataSlice } from "./game-data.slice"; -import { store, useAppDispatch, useAppState } from "./store"; -import { EqualityFn } from "react-redux"; import { addPlayerNameToDrawing } from "./central"; -import { useCallback } from "react"; function createPartialFromDefaults(gameData: GameData) { const { lowerLvlBound, upperLvlBound, flags, difficulties, folders, style } = @@ -55,34 +52,3 @@ export const configSlice = createSlice({ }); }, }); - -export const { actions } = configSlice; - -export function useConfigState( - selector?: (state: ConfigState) => T, - equalityFn?: EqualityFn, -) { - return useAppState((state) => { - const sliceState = configSlice.selectSlice(state); - if (!selector) return sliceState as T; - return selector(sliceState); - }, equalityFn); -} - -export function useUpdateConfig() { - const dispatch = useAppDispatch(); - return useCallback( - ( - patch: - | Partial - | ((state: ConfigState) => Partial), - ) => { - if (typeof patch === "function") { - const state = configSlice.selectSlice(store.getState()); - patch = patch(state); - } - dispatch(actions.update(patch)); - }, - [dispatch], - ); -} diff --git a/src/state/hooks.ts b/src/state/hooks.ts new file mode 100644 index 000000000..e15d142bf --- /dev/null +++ b/src/state/hooks.ts @@ -0,0 +1,34 @@ +import { store, useAppDispatch, useAppState } from "./store"; +import { EqualityFn } from "react-redux"; +import { useCallback } from "react"; +import { ConfigState } from "../config-state"; +import { configSlice } from "./config.slice"; + +export function useConfigState( + selector?: (state: ConfigState) => T, + equalityFn?: EqualityFn, +) { + return useAppState((state) => { + const sliceState = configSlice.selectSlice(state); + if (!selector) return sliceState as T; + return selector(sliceState); + }, equalityFn); +} + +export function useUpdateConfig() { + const dispatch = useAppDispatch(); + return useCallback( + ( + patch: + | Partial + | ((state: ConfigState) => Partial), + ) => { + if (typeof patch === "function") { + const state = configSlice.selectSlice(store.getState()); + patch = patch(state); + } + dispatch(configSlice.actions.update(patch)); + }, + [dispatch], + ); +} diff --git a/src/state/root-reducer.ts b/src/state/root-reducer.ts index 9a8954b81..b0af54bc6 100644 --- a/src/state/root-reducer.ts +++ b/src/state/root-reducer.ts @@ -12,7 +12,7 @@ const combinedReducer = combineSlices( export const reducer: typeof combinedReducer = (state, action) => { if (receivePartyState.match(action)) { - return action.payload.state; + return action.payload; } return combinedReducer(state, action); }; diff --git a/src/tournament-mode/drawing-labels.tsx b/src/tournament-mode/drawing-labels.tsx index 94cd7e757..b9b2bbb82 100644 --- a/src/tournament-mode/drawing-labels.tsx +++ b/src/tournament-mode/drawing-labels.tsx @@ -1,5 +1,5 @@ import { useCallback } from "react"; -import { useConfigState } from "../state/config.slice"; +import { useConfigState } from "../state/hooks"; import { useDrawing } from "../drawing-context"; import styles from "./drawing-labels.css"; import { AutoCompleteSelect, RoundSelect } from "./round-select"; diff --git a/src/tournament-mode/round-select.tsx b/src/tournament-mode/round-select.tsx index e5d96c3df..3b323b85c 100644 --- a/src/tournament-mode/round-select.tsx +++ b/src/tournament-mode/round-select.tsx @@ -5,7 +5,7 @@ import { filterRoundLabel, renderRoundLabel } from "./round-label"; import { useDrawing, useUpdateDrawing } from "../drawing-context"; import { useIntl } from "../hooks/useIntl"; import { useMemo } from "react"; -import { useConfigState } from "../state/config.slice"; +import { useConfigState } from "../state/hooks"; import { Add } from "@blueprintjs/icons"; function identity(i: T) { diff --git a/src/utils/index.ts b/src/utils/index.ts index af0a60968..1779fb57e 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -72,7 +72,9 @@ interface GameDataParent { /** ordered list of all available game data files */ export const availableGameData = ( - process.env.DATA_FILES as Array> + (process.env.DATA_FILES as Array< + Omit + >) || [] ).sort((a, b) => { const parentDiff = a.parent.localeCompare(b.parent); if (parentDiff) { From 8c763ebfdcf2f11e1f04f1d6ebebe07a802669eb Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Thu, 11 Jul 2024 12:11:31 -0700 Subject: [PATCH 10/91] move game data loading out of redux --- src/controls/controls-drawer.tsx | 12 +++--- src/controls/controls-weights.tsx | 5 ++- src/controls/degrs-tester.tsx | 8 ++-- src/controls/index.tsx | 8 ++-- src/draw-state.tsx | 48 ++++++++------------- src/drop-handler.tsx | 17 ++++++-- src/eligible-charts/histogram.tsx | 5 ++- src/eligible-charts/index.tsx | 6 +-- src/hooks/useDataSets.ts | 37 ++++++++-------- src/hooks/useFuzzySearch.ts | 5 ++- src/last-update.tsx | 4 +- src/party/client.ts | 5 --- src/song-search/index.tsx | 6 +-- src/state/config.slice.ts | 70 ++++++++++++++----------------- src/state/drawings.slice.ts | 16 +++++-- src/state/game-data.atoms.ts | 68 ++++++++++++++++++++++++++++++ src/state/game-data.slice.ts | 31 ++------------ src/state/thunks.ts | 25 ----------- src/version-select.tsx | 27 ++++++++---- 19 files changed, 216 insertions(+), 187 deletions(-) create mode 100644 src/state/game-data.atoms.ts delete mode 100644 src/state/thunks.ts diff --git a/src/controls/controls-drawer.tsx b/src/controls/controls-drawer.tsx index 3fe4fd30a..6ec0bf63b 100644 --- a/src/controls/controls-drawer.tsx +++ b/src/controls/controls-drawer.tsx @@ -40,6 +40,7 @@ import { useConfigState, useUpdateConfig } from "../state/hooks"; import { useAtomValue } from "jotai"; import { showEligibleCharts } from "../config-state"; import { useAppState } from "../state/store"; +import { gameDataAtom } from "../state/game-data.atoms"; function getAvailableDifficulties(gameData: GameData, selectedStyle: string) { const s = new Set(); @@ -112,7 +113,7 @@ export default function ControlsDrawer() { const dateFormat = "yyyy-MM-dd"; function ReleaseDateFilter() { const { t } = useIntl(); - const gameData = useAppState((s) => s.gameData.gameData); + const gameData = useAtomValue(gameDataAtom); const updateState = useUpdateConfig(); const cutoffDate = useConfigState((s) => s.cutoffDate); const mostRecentRelease = useMemo( @@ -160,7 +161,7 @@ function ReleaseDateFilter() { function FlagSettings() { const { t } = useIntl(); const dataSetName = useAppState((s) => s.gameData.dataSetName); - const gameData = useAppState((s) => s.gameData.gameData); + const gameData = useAtomValue(gameDataAtom); const hasFlags = !!gameData?.meta.flags.length; const updateState = useUpdateConfig(); const selectedFlags = useConfigState((s) => s.flags); @@ -197,9 +198,8 @@ function FlagSettings() { /** Renders the checkboxes for each individual folder that exists in the data file's meta.folders */ function FolderSettings() { const { t } = useIntl(); - const availableFolders = useAppState( - (s) => s.gameData.gameData?.meta.folders, - ); + const gameData = useAtomValue(gameDataAtom); + const availableFolders = gameData?.meta.folders; const dataSetName = useAppState((s) => s.gameData.dataSetName); const updateState = useUpdateConfig(); const selectedFolders = useConfigState((s) => s.folders); @@ -254,7 +254,7 @@ function FolderSettings() { function GeneralSettings() { const { t } = useIntl(); - const gameData = useAppState((s) => s.gameData.gameData); + const gameData = useAtomValue(gameDataAtom); const updateState = useUpdateConfig(); const showingEligibleCharts = useAtomValue(showEligibleCharts); const configState = useConfigState(); diff --git a/src/controls/controls-weights.tsx b/src/controls/controls-weights.tsx index 8c30260f1..9abdfa4cf 100644 --- a/src/controls/controls-weights.tsx +++ b/src/controls/controls-weights.tsx @@ -7,7 +7,8 @@ import { useIntl } from "../hooks/useIntl"; import { NumericInput, Checkbox, Classes } from "@blueprintjs/core"; import { getAvailableLevels } from "../game-data-utils"; import { LevelRangeBucket, getBuckets } from "../card-draw"; -import { useAppState } from "../state/store"; +import { useAtomValue } from "jotai"; +import { gameDataAtom } from "../state/game-data.atoms"; interface Props { usesTiers: boolean; @@ -52,7 +53,7 @@ export function WeightsControls({ usesTiers, high, low }: Props) { }), shallow, ); - const gameData = useAppState((s) => s.gameData.gameData); + const gameData = useAtomValue(gameDataAtom); const groups = useMemo(() => { const availableLevels = getAvailableLevels(gameData, useGranularLevels); return Array.from( diff --git a/src/controls/degrs-tester.tsx b/src/controls/degrs-tester.tsx index 753c38640..dc5bd2de7 100644 --- a/src/controls/degrs-tester.tsx +++ b/src/controls/degrs-tester.tsx @@ -6,7 +6,7 @@ import { ProgressBar, } from "@blueprintjs/core"; import { draw } from "../card-draw"; -import { useAtom } from "jotai"; +import { getDefaultStore, useAtom } from "jotai"; import { configSlice } from "../state/config.slice"; import { requestIdleCallback } from "../utils/idle-callback"; import { @@ -20,8 +20,9 @@ import { SongCard, SongCardProps } from "../song-card/song-card"; import { useState } from "react"; import { Rain, Repeat, WarningSign } from "@blueprintjs/icons"; import { EligibleChart, PlayerPickPlaceholder } from "../models/Drawing"; -import { store, useAppStore } from "../state/store"; +import { store } from "../state/store"; import { GameData } from "../models/SongData"; +import { gameDataAtom } from "../state/game-data.atoms"; export function isDegrs(thing: EligibleChart | PlayerPickPlaceholder) { return "name" in thing && thing.name.startsWith('DEAD END("GROOVE'); @@ -63,14 +64,13 @@ export function DegrsTestButton() { const [isTesting, setIsTesting] = useAtom(degrsIsTesting); const [progress, setProgress] = useAtom(degrsTestProgress); const [results, setResults] = useAtom(degrsTestResults); - const store = useAppStore(); async function startTest() { setIsTesting(true); setProgress(0); setResults(undefined); await nextIdleCycle(); - const tester = degrsTester(store.getState().gameData.gameData!); + const tester = degrsTester(getDefaultStore().get(gameDataAtom)!); let report = tester.next(); while (!report.done) { setProgress(report.value); diff --git a/src/controls/index.tsx b/src/controls/index.tsx index b62ad0ff9..dce5cc59e 100644 --- a/src/controls/index.tsx +++ b/src/controls/index.tsx @@ -13,14 +13,14 @@ import { NewLayers, Cog } from "@blueprintjs/icons"; import { useState, lazy, Suspense } from "react"; import { FormattedMessage } from "react-intl"; import { useIsNarrow } from "../hooks/useMediaQuery"; -// import { loadConfig, saveConfig } from "../config-persistence"; import { ErrorBoundary } from "react-error-boundary"; import { ErrorFallback } from "../utils/error-fallback"; import { ShowChartsToggle } from "./show-charts-toggle"; import { createDraw } from "../state/drawings.slice"; -import { useAppDispatch, useAppState, useAppStore } from "../state/store"; -import { useSetAtom } from "jotai"; +import { useAppDispatch, useAppStore } from "../state/store"; +import { useAtomValue, useSetAtom } from "jotai"; import { showEligibleCharts } from "../config-state"; +import { gameDataLoadingStatus } from "../state/game-data.atoms"; const ControlsDrawer = lazy(() => import("./controls-drawer")); @@ -28,7 +28,7 @@ export function HeaderControls() { const setShowEligibleCharts = useSetAtom(showEligibleCharts); const [settingsOpen, setSettingsOpen] = useState(false); const [lastDrawFailed, setLastDrawFailed] = useState(false); - const hasGameData = useAppState((s) => !!s.gameData.gameData); + const hasGameData = useAtomValue(gameDataLoadingStatus) === "available"; const isNarrow = useIsNarrow(); const dispatch = useAppDispatch(); const store = useAppStore(); diff --git a/src/draw-state.tsx b/src/draw-state.tsx index d66cec711..7a1e27a96 100644 --- a/src/draw-state.tsx +++ b/src/draw-state.tsx @@ -1,36 +1,19 @@ -import { ReactNode, useEffect } from "react"; +import { ReactNode } from "react"; import { UnloadHandler } from "./unload-handler"; -import { requestIdleCallback, cancelIdleCallback } from "./utils/idle-callback"; import { I18NDict } from "./models/SongData"; import i18nData from "./assets/i18n.json"; -import { availableGameData, detectedLanguage } from "./utils"; +import { detectedLanguage } from "./utils"; import { IntlProvider } from "./intl-provider"; -import { useAppDispatch, useAppState } from "./state/store"; -import { loadGameDataByName } from "./state/thunks"; +import { useAppState } from "./state/store"; import { drawingsSlice } from "./state/drawings.slice"; +import { useAtomValue } from "jotai"; +import { gameDataAtom } from "./state/game-data.atoms"; interface Props { defaultDataSet: string; children: ReactNode; } -function getInitialDataSet(defaultDataName: string) { - const hash = window.location.hash.slice(1); - if (hash.startsWith("game-")) { - const targetData = hash.slice(5); - if (availableGameData.some((d) => d.name === targetData)) { - return targetData; - } - } - if ( - defaultDataName && - availableGameData.some((d) => d.name === defaultDataName) - ) { - return defaultDataName; - } - return availableGameData[0].name; -} - // function writeDataSetToUrl(game: string) { // const nextHash = `game-${game}`; // if ("#" + nextHash !== window.location.hash) { @@ -41,15 +24,20 @@ function getInitialDataSet(defaultDataName: string) { // } export function DrawStateManager(props: Props) { - const gameData = useAppState((state) => state.gameData.gameData); + const gameData = useAtomValue(gameDataAtom); const hasDrawings = useAppState(drawingsSlice.selectors.haveDrawings); - const dispatch = useAppDispatch(); - useEffect(() => { - const idleHandle = requestIdleCallback(() => - dispatch(loadGameDataByName(getInitialDataSet(props.defaultDataSet))), - ); - return () => cancelIdleCallback(idleHandle); - }, [dispatch, props.defaultDataSet]); + // const dispatch = useAppDispatch(); + // useEffect(() => { + // const idleHandle = requestIdleCallback(() => + // dispatch( + // gameDataSlice.actions.selectGameData({ + // dataSetName: getInitialDataSet(props.defaultDataSet), + // dataType: "stock", + // }), + // ), + // ); + // return () => cancelIdleCallback(idleHandle); + // }, [dispatch, props.defaultDataSet]); return ( { + return { + ...prev, + [parsedPack.name]: derivedData, + }; + }); dispatch( - gameDataSlice.actions.selectCustomData({ - name: parsedPack.name, - gameData: derivedData, + gameDataSlice.actions.selectGameData({ + dataSetName: parsedPack.name, + dataType: "custom", }), ); pause(500).then(() => { setSaving(false); onSave(); }); - }, [parsedPack, derivedData, dispatch, onSave]); + }, [parsedPack, derivedData, setCustomData, dispatch, onSave]); const maybeSkeleton = derivedData ? "" : Classes.SKELETON; diff --git a/src/eligible-charts/histogram.tsx b/src/eligible-charts/histogram.tsx index 2f1025c16..aecc6d77a 100644 --- a/src/eligible-charts/histogram.tsx +++ b/src/eligible-charts/histogram.tsx @@ -19,7 +19,8 @@ import { import { Theme, useTheme } from "../theme-toggle"; import { useIsNarrow } from "../hooks/useMediaQuery"; import { useConfigState } from "../state/hooks"; -import { useAppState } from "../state/store"; +import { useAtomValue } from "jotai"; +import { gameDataAtom } from "../state/game-data.atoms"; interface Props { charts: EligibleChart[]; @@ -29,7 +30,7 @@ export function DiffHistogram({ charts }: Props) { const { t } = useIntl(); const fgColor = useTheme() === Theme.Dark ? "white" : undefined; const isNarrow = useIsNarrow(); - const gameData = useAppState((s) => s.gameData.gameData); + const gameData = useAtomValue(gameDataAtom); const allDiffs = gameData?.meta.difficulties; const useGranularLevels = useConfigState((s) => s.useGranularLevels); const availableLevels = getAvailableLevels(gameData, useGranularLevels); diff --git a/src/eligible-charts/index.tsx b/src/eligible-charts/index.tsx index 64d9f6801..4c91e0455 100644 --- a/src/eligible-charts/index.tsx +++ b/src/eligible-charts/index.tsx @@ -11,21 +11,21 @@ import { Button, } from "@blueprintjs/core"; import { useIsNarrow } from "../hooks/useMediaQuery"; -import { useAtom } from "jotai"; +import { useAtom, useAtomValue } from "jotai"; import { useCallback, useDeferredValue, useMemo } from "react"; import { currentTabAtom, EligibleChartsListFilter } from "./filter"; import { DiffHistogram } from "./histogram"; import { isDegrs, TesterCard } from "../controls/degrs-tester"; import { Export } from "@blueprintjs/icons"; import { shareCharts } from "../utils/share"; -import { useAppState } from "../state/store"; +import { gameDataAtom } from "../state/game-data.atoms"; function songKeyFromChart(chart: EligibleChart) { return `${chart.name}:${chart.artist}`; } export default function EligibleChartsList() { - const gameData = useAppState((s) => s.gameData.gameData); + const gameData = useAtomValue(gameDataAtom); const [currentTab] = useDeferredValue(useAtom(currentTabAtom)); const configState = useDeferredValue(useConfigState()); const isNarrow = useIsNarrow(); diff --git a/src/hooks/useDataSets.ts b/src/hooks/useDataSets.ts index 1a00e783b..c77c4e07d 100644 --- a/src/hooks/useDataSets.ts +++ b/src/hooks/useDataSets.ts @@ -2,33 +2,34 @@ import { useCallback, useMemo } from "react"; import { availableGameData } from "../utils"; import { useAppDispatch, useAppState } from "../state/store"; import { gameDataSlice } from "../state/game-data.slice"; -import { GameData } from "../models/SongData"; -import { loadGameDataByName } from "../state/thunks"; +import { + customDataCache, + gameDataLoadingStatus, +} from "../state/game-data.atoms"; +import { useAtomValue } from "jotai"; export function useDataSets() { const dataSetName = useAppState((s) => s.gameData.dataSetName); const dispatch = useAppDispatch(); - const dataIsLoaded = useAppState((s) => !!s.gameData); - const importedData = useAppState((s) => s.gameData.uploadCache); + const importedData = useAtomValue(customDataCache); + const dataLoadingState = useAtomValue(gameDataLoadingStatus); const loadGameData = useCallback( - (name: string, gameData?: GameData) => { - if (gameData) { + (name: string) => { + if (importedData[name]) { dispatch( - gameDataSlice.actions.selectCustomData({ - name, - gameData, + gameDataSlice.actions.selectGameData({ + dataSetName: name, + dataType: "custom", }), ); - } else if (importedData[name]) { + } else { dispatch( - gameDataSlice.actions.selectCustomData({ - name, - gameData: importedData[name], + gameDataSlice.actions.selectGameData({ + dataSetName: name, + dataType: "stock", }), ); - } else { - dispatch(loadGameDataByName(name)); } }, [dispatch, importedData], @@ -45,12 +46,14 @@ export function useDataSets() { ]; }, [importedData]); - const current = available.find((s) => s.name === dataSetName) || available[0]; + const current: (typeof availableGameData)[0] = available.find( + (s) => s.name === dataSetName, + ) || { display: "Select game data", name: "", parent: "" }; return { available, current, loadData: loadGameData, - dataIsLoaded, + dataIsLoaded: dataLoadingState === "available", }; } diff --git a/src/hooks/useFuzzySearch.ts b/src/hooks/useFuzzySearch.ts index 95a0313fe..250efef33 100644 --- a/src/hooks/useFuzzySearch.ts +++ b/src/hooks/useFuzzySearch.ts @@ -1,8 +1,9 @@ import FuzzySearch from "fuzzy-search"; -import { useAppState } from "../state/store"; +import { useAtomValue } from "jotai"; +import { gameDataAtom } from "../state/game-data.atoms"; export function useFuzzySearch() { - const gameData = useAppState((s) => s.gameData.gameData); + const gameData = useAtomValue(gameDataAtom); if (!gameData) return null; return new FuzzySearch( gameData.songs, diff --git a/src/last-update.tsx b/src/last-update.tsx index 4d6f7bf16..e3ed638ec 100644 --- a/src/last-update.tsx +++ b/src/last-update.tsx @@ -3,10 +3,12 @@ import cn from "classnames"; import { FormattedMessage } from "react-intl"; import { detectedLanguage } from "./utils"; import { useAppState } from "./state/store"; +import { useAtomValue } from "jotai"; +import { gameDataAtom } from "./state/game-data.atoms"; export function LastUpdate() { const dataSetName = useAppState((s) => s.gameData.dataSetName); - const gameData = useAppState((s) => s.gameData.gameData); + const gameData = useAtomValue(gameDataAtom); if (!gameData) { return null; } diff --git a/src/party/client.ts b/src/party/client.ts index bfcc7f1fa..3e52326e9 100644 --- a/src/party/client.ts +++ b/src/party/client.ts @@ -4,7 +4,6 @@ import { useAppDispatch } from "../state/store"; import { receivePartyState } from "../state/central"; import { startAppListening } from "../state/listener-middleware"; import { useEffect } from "react"; -import { loadGameDataByName } from "../state/thunks"; export function PartySocketManager(props: { roomName?: string }) { const dispatch = useAppDispatch(); @@ -44,10 +43,6 @@ export function PartySocketManager(props: { roomName?: string }) { return false; } - // don't try sending the loaded game data through - if (action.type.startsWith(loadGameDataByName.typePrefix)) { - return false; - } return true; }, effect(action) { diff --git a/src/song-search/index.tsx b/src/song-search/index.tsx index ec3e58a96..88b9c916b 100644 --- a/src/song-search/index.tsx +++ b/src/song-search/index.tsx @@ -6,8 +6,9 @@ import { Song } from "../models/SongData"; import { SearchResult, SearchResultData } from "./search-result"; import { Omnibar } from "@blueprintjs/select"; import styles from "./song-search.css"; -import { useAppStore } from "../state/store"; import { useFuzzySearch } from "../hooks/useFuzzySearch"; +import { getDefaultStore } from "jotai"; +import { gameDataAtom } from "../state/game-data.atoms"; interface Props { isOpen: boolean; @@ -20,7 +21,6 @@ export function SongSearch(props: Props) { const [searchTerm, updateSearchTerm] = useState(""); const config = useConfigState(); const fuzzySearch = useFuzzySearch(); - const store = useAppStore(); let items: SearchResultData[] = []; if (fuzzySearch) { @@ -54,7 +54,7 @@ export function SongSearch(props: Props) { item.chart === "none" || !item.chart ? undefined : getDrawnChart( - store.getState().gameData.gameData!, + getDefaultStore().get(gameDataAtom)!, item.song, item.chart, ), diff --git a/src/state/config.slice.ts b/src/state/config.slice.ts index 1ce348945..65e075517 100644 --- a/src/state/config.slice.ts +++ b/src/state/config.slice.ts @@ -1,54 +1,46 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { ConfigState, initialState } from "../config-state"; -import { loadGameDataByName } from "./thunks"; import { GameData } from "../models/SongData"; -import { gameDataSlice } from "./game-data.slice"; import { addPlayerNameToDrawing } from "./central"; -function createPartialFromDefaults(gameData: GameData) { - const { lowerLvlBound, upperLvlBound, flags, difficulties, folders, style } = - gameData.defaults; - const ret: Partial = { - lowerBound: lowerLvlBound, - upperBound: upperLvlBound, - flags, - difficulties, - folders, - style, - cutoffDate: "", - }; - if (!gameData.meta.granularTierResolution) { - ret.useGranularLevels = false; - } - return ret; -} - export const configSlice = createSlice({ name: "config", initialState, reducers: { - update: (state, action: PayloadAction>) => { + update(state, action: PayloadAction>) { Object.assign(state, action.payload); }, + applyDefaults( + state, + action: PayloadAction< + GameData["defaults"] & { supportsGranular: boolean } + >, + ) { + const { flags, difficulties, folders, style } = action.payload; + const patch: Partial = { + lowerBound: action.payload.lowerLvlBound, + upperBound: action.payload.upperLvlBound, + flags, + difficulties, + folders, + style, + cutoffDate: "", + }; + if (!action.payload.supportsGranular) { + patch.useGranularLevels = false; + } + Object.assign(state, patch); + }, }, extraReducers(builder) { - builder - .addCase(loadGameDataByName.fulfilled, (state, action) => { - const patch = createPartialFromDefaults(action.payload); - Object.assign(state, patch); - }) - .addCase(gameDataSlice.actions.selectCustomData, (state, action) => { - const patch = createPartialFromDefaults(action.payload.gameData); - Object.assign(state, patch); - }) - .addCase(addPlayerNameToDrawing, (state, action) => { - if (!action.payload.name) { - return; - } - if (state.playerNames.includes(action.payload.name)) { - return; - } - state.playerNames.push(action.payload.name); - }); + builder.addCase(addPlayerNameToDrawing, (state, action) => { + if (!action.payload.name) { + return; + } + if (state.playerNames.includes(action.payload.name)) { + return; + } + state.playerNames.push(action.payload.name); + }); }, }); diff --git a/src/state/drawings.slice.ts b/src/state/drawings.slice.ts index 811e928a7..48d6e4924 100644 --- a/src/state/drawings.slice.ts +++ b/src/state/drawings.slice.ts @@ -12,6 +12,8 @@ import { import { addPlayerNameToDrawing } from "./central"; import { AppState } from "./store"; import { draw } from "../card-draw"; +import { getDefaultStore } from "jotai"; +import { gameDataAtom } from "./game-data.atoms"; export const drawingsAdapter = createEntityAdapter({}); @@ -38,12 +40,14 @@ function trackDraw(count: number | null, game?: string) { * addDrawing action in the default successful case */ export function createDraw(state: AppState) { - if (!state.gameData.gameData) { + const jotaiStore = getDefaultStore(); + const gameData = jotaiStore.get(gameDataAtom); + if (!gameData) { trackDraw(null); return false; } - const drawing = draw(state.gameData.gameData, state.config); + const drawing = draw(gameData, state.config); trackDraw(drawing.charts.length, state.gameData.dataSetName); if (!drawing.charts.length) { return true; @@ -180,7 +184,9 @@ export function createRedrawAll(state: AppState, drawingId: string) { ...state.config, chartCount: drawing.charts.length, }; - const drawResult = draw(state.gameData.gameData!, drawConfig); + const jotaiStore = getDefaultStore(); + const gameData = jotaiStore.get(gameDataAtom); + const drawResult = draw(gameData!, drawConfig); return drawingsSlice.actions.updateOne({ id: drawingId, changes: { @@ -202,7 +208,9 @@ export function createRedrawChart( ...state.config, chartCount: 1, }; - const drawResult = draw(state.gameData.gameData!, drawConfig); + const jotaiStore = getDefaultStore(); + const gameData = jotaiStore.get(gameDataAtom); + const drawResult = draw(gameData!, drawConfig); const chart = drawResult.charts.find((c) => c.type === "DRAWN"); if (!chart) { return; diff --git a/src/state/game-data.atoms.ts b/src/state/game-data.atoms.ts new file mode 100644 index 000000000..8a543c1f7 --- /dev/null +++ b/src/state/game-data.atoms.ts @@ -0,0 +1,68 @@ +import { atom, getDefaultStore } from "jotai"; +import { GameData } from "../models/SongData"; +import { startAppListening } from "./listener-middleware"; +import { configSlice } from "./config.slice"; + +export const gameDataAtom = atom(null); + +export const gameDataLoadingStatus = atom< + "loading" | "failed" | "pending" | "available" +>("pending"); + +export const customDataCache = atom>({}); + +async function loadStockGamedataByName(name: string) { + return ( + await import(/* webpackChunkName: "songData" */ `../songs/${name}.json`) + ).default as GameData; +} + +startAppListening({ + predicate(action, currentState, originalState) { + if (currentState.gameData !== originalState.gameData) { + return true; + } + return false; + }, + async effect(action, api) { + console.debug("running game-data.atoms effect"); + const newState = api.getState().gameData; + if (!newState.dataSetName) { + console.debug("no data selected, not loading anything"); + return; + } + const jotaiStore = getDefaultStore(); + let newData: GameData | undefined; + if (newState.dataType === "stock") { + console.debug("stock data"); + jotaiStore.set(gameDataLoadingStatus, "loading"); + try { + newData = await loadStockGamedataByName(newState.dataSetName); + } catch (e) { + console.error("stock data fetch failed", { cause: e }); + jotaiStore.set(gameDataLoadingStatus, "failed"); + return; + } + } else { + console.debug("custom data"); + const customData = jotaiStore.get(customDataCache); + if (customData[newState.dataSetName]) { + newData = customData[newState.dataSetName]; + } + } + if (newData) { + console.log("data arrived", { newData }); + jotaiStore.set(gameDataAtom, newData); + api.dispatch( + configSlice.actions.applyDefaults({ + supportsGranular: !!newData.meta.granularTierResolution, + ...newData.defaults, + }), + ); + jotaiStore.set(gameDataLoadingStatus, "available"); + } else { + console.log("data did not arrive"); + jotaiStore.set(gameDataLoadingStatus, "failed"); + } + }, +}); diff --git a/src/state/game-data.slice.ts b/src/state/game-data.slice.ts index f1db9d176..f83337aa5 100644 --- a/src/state/game-data.slice.ts +++ b/src/state/game-data.slice.ts @@ -1,44 +1,21 @@ import { PayloadAction, createSlice } from "@reduxjs/toolkit"; -import { GameData } from "../models/SongData"; -import { loadGameDataByName } from "./thunks"; interface GameDataState { - gameData: GameData | null; dataSetName: string; - uploadCache: Record; + dataType: "stock" | "custom"; } const initialState: GameDataState = { - gameData: null, dataSetName: "", - uploadCache: {}, + dataType: "stock", }; export const gameDataSlice = createSlice({ name: "gameData", initialState, reducers: { - selectCustomData( - state, - action: PayloadAction<{ name: string; gameData: GameData }>, - ) { - state.dataSetName = action.payload.name; - state.gameData = action.payload.gameData; - if (state.uploadCache[action.payload.name] !== action.payload.gameData) { - state.uploadCache[action.payload.name] = action.payload.gameData; - } + selectGameData(state, action: PayloadAction) { + return action.payload; }, }, - extraReducers(builder) { - builder - .addCase(loadGameDataByName.fulfilled, (state, action) => { - state.gameData = action.payload; - }) - .addCase(loadGameDataByName.pending, (state, action) => { - state.dataSetName = action.meta.arg; - state.gameData = null; - }); - }, }); - -export const { actions } = gameDataSlice; diff --git a/src/state/thunks.ts b/src/state/thunks.ts deleted file mode 100644 index a46f07563..000000000 --- a/src/state/thunks.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { createAsyncThunk } from "@reduxjs/toolkit"; -import { GameData } from "../models/SongData"; -import { AppState, AppDispatch } from "./store"; - -const createTypedThunk = createAsyncThunk.withTypes<{ - state: AppState; - dispatch: AppDispatch; -}>(); - -export const loadGameDataByName = createTypedThunk( - "gameData/load", - async (name: string) => { - return ( - await import(/* webpackChunkName: "songData" */ `../songs/${name}.json`) - ).default as GameData; - }, - { - condition(arg, api) { - const state = api.getState(); - if (arg === state.gameData.dataSetName) { - return false; - } - }, - }, -); diff --git a/src/version-select.tsx b/src/version-select.tsx index daf9fd331..896f99990 100644 --- a/src/version-select.tsx +++ b/src/version-select.tsx @@ -5,13 +5,15 @@ import { Spinner, SpinnerSize, } from "@blueprintjs/core"; +import { Error } from "@blueprintjs/icons"; import { Select } from "@blueprintjs/select"; import { ReactNode, useEffect, useState } from "react"; import { useDataSets } from "./hooks/useDataSets"; import { groupGameData } from "./utils"; import { useIntl } from "./hooks/useIntl"; import { DoubleCaretVertical, FolderOpen } from "@blueprintjs/icons"; -import { useAppState } from "./state/store"; +import { useAtomValue } from "jotai"; +import { gameDataLoadingStatus } from "./state/game-data.atoms"; export function VersionSelect() { const { t } = useIntl(); @@ -70,15 +72,22 @@ export function VersionSelect() { } export function DataLoadingSpinner() { - const dataIsLoading = useAppState((s) => !s.gameData.gameData); - if (!dataIsLoading) { - return null; + const loadingStatus = useAtomValue(gameDataLoadingStatus); + if (loadingStatus === "failed") { + return ( + <> + Couldn't load game! + + ); } - return ( - - Loading game... - - ); + if (loadingStatus === "loading") { + return ( + + Loading game... + + ); + } + return null; } interface DelayProps { From 0da3104c941984087062e87f9e4f32c3508edd03 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Thu, 11 Jul 2024 17:43:22 -0700 Subject: [PATCH 11/91] add client-side routing, pull room from url --- package.json | 1 + src/app.tsx | 53 ++++++++++++++++++++++++++++++------- src/draw-state.tsx | 52 ------------------------------------ src/intl-provider.tsx | 40 ++++++++++++++-------------- src/party/client.ts | 15 ++++++++--- src/song-card/song-card.tsx | 2 +- webpack.config.js | 2 ++ yarn.lock | 32 ++++++++++++++++++++++ 8 files changed, 111 insertions(+), 86 deletions(-) delete mode 100644 src/draw-state.tsx diff --git a/package.json b/package.json index 954b1e101..1b42fb3e8 100644 --- a/package.json +++ b/package.json @@ -106,6 +106,7 @@ "react-intl": "^6.6.8", "react-redux": "^9.1.2", "react-refresh": "^0.14.2", + "react-router-dom": "^6.24.1", "sanitize-filename": "^1.6.3", "simfile-parser": "^0.7.2", "style-loader": "^4.0.0", diff --git a/src/app.tsx b/src/app.tsx index 390b2f8e7..11f3f5237 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -10,7 +10,7 @@ FocusStyleManager.onlyShowFocusOnTabs(); import { DrawingList } from "./drawing-list"; import { UpdateManager } from "./update-manager"; -import { DrawStateManager } from "./draw-state"; +import { IntlProvider } from "./intl-provider"; import { Header } from "./header"; import { ThemeSyncWidget } from "./theme-toggle"; import { DropHandler } from "./drop-handler"; @@ -18,17 +18,50 @@ import { Provider } from "react-redux"; import { store } from "./state/store"; import { PartySocketManager } from "./party/client"; -export function App() { +import { + createBrowserRouter, + RouterProvider, + useParams, +} from "react-router-dom"; + +const router = createBrowserRouter([ + { + path: "/", + element: ( + <> +

+ You need to pick an event first. How about this one:{" "} + Default Event +

+ + ), + }, + { + path: "e/:roomName", + element: , + }, +]); + +function AppForRoom() { + const params = useParams<"roomName">(); + if (!params.roomName) { + return null; + } return ( - - - - -
- - - + + + + +
+ + + + ); } + +export function App() { + return ; +} diff --git a/src/draw-state.tsx b/src/draw-state.tsx deleted file mode 100644 index 7a1e27a96..000000000 --- a/src/draw-state.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { ReactNode } from "react"; -import { UnloadHandler } from "./unload-handler"; -import { I18NDict } from "./models/SongData"; -import i18nData from "./assets/i18n.json"; -import { detectedLanguage } from "./utils"; -import { IntlProvider } from "./intl-provider"; -import { useAppState } from "./state/store"; -import { drawingsSlice } from "./state/drawings.slice"; -import { useAtomValue } from "jotai"; -import { gameDataAtom } from "./state/game-data.atoms"; - -interface Props { - defaultDataSet: string; - children: ReactNode; -} - -// function writeDataSetToUrl(game: string) { -// const nextHash = `game-${game}`; -// if ("#" + nextHash !== window.location.hash) { -// const nextUrl = new URL(window.location.href); -// nextUrl.hash = encodeURIComponent(nextHash); -// window.history.replaceState(undefined, "", nextUrl); -// } -// } - -export function DrawStateManager(props: Props) { - const gameData = useAtomValue(gameDataAtom); - const hasDrawings = useAppState(drawingsSlice.selectors.haveDrawings); - // const dispatch = useAppDispatch(); - // useEffect(() => { - // const idleHandle = requestIdleCallback(() => - // dispatch( - // gameDataSlice.actions.selectGameData({ - // dataSetName: getInitialDataSet(props.defaultDataSet), - // dataType: "stock", - // }), - // ), - // ); - // return () => cancelIdleCallback(idleHandle); - // }, [dispatch, props.defaultDataSet]); - - return ( - } - mergeTranslations={gameData?.i18n} - > - - {props.children} - - ); -} diff --git a/src/intl-provider.tsx b/src/intl-provider.tsx index 58b1623ad..1c3ae767d 100644 --- a/src/intl-provider.tsx +++ b/src/intl-provider.tsx @@ -1,43 +1,43 @@ +import { useAtomValue } from "jotai"; +import { ReactNode, useMemo } from "react"; import { IntlProvider as UpstreamProvider } from "react-intl"; -import { PropsWithChildren, useMemo } from "react"; -import { flattenedKeys } from "./utils"; +import translations from "./assets/i18n.json"; import { I18NDict } from "./models/SongData"; +import { gameDataAtom } from "./state/game-data.atoms"; +import { detectedLanguage, flattenedKeys } from "./utils"; const FALLBACK_LOCALE = "en"; -interface Props { - locale: string; - translations: Record; - mergeTranslations?: Record; -} +const typedTranslations = translations as Record; + +export function IntlProvider({ children }: { children: ReactNode }) { + const gameSpecificTranslations = useAtomValue(gameDataAtom)?.i18n; -export function IntlProvider({ - locale, - translations, - mergeTranslations, - children, -}: PropsWithChildren) { const messages = useMemo(() => { const ret: Record = {}; - for (const [k, v] of flattenedKeys(translations[FALLBACK_LOCALE])) { + for (const [k, v] of flattenedKeys(typedTranslations[FALLBACK_LOCALE])) { ret[k] = v; } - for (const [k, v] of flattenedKeys(translations[locale])) { + for (const [k, v] of flattenedKeys(typedTranslations[detectedLanguage])) { ret[k] = v; } - if (mergeTranslations) { - for (const [k, v] of flattenedKeys(mergeTranslations[FALLBACK_LOCALE])) { + if (gameSpecificTranslations) { + for (const [k, v] of flattenedKeys( + gameSpecificTranslations[FALLBACK_LOCALE], + )) { ret[`meta.${k}`] = v; } - for (const [k, v] of flattenedKeys(mergeTranslations[locale])) { + for (const [k, v] of flattenedKeys( + gameSpecificTranslations[detectedLanguage], + )) { ret[`meta.${k}`] = v; } } return ret; - }, [translations, mergeTranslations, locale]); + }, [gameSpecificTranslations]); return ( - + {children} ); diff --git a/src/party/client.ts b/src/party/client.ts index 3e52326e9..98a5db88a 100644 --- a/src/party/client.ts +++ b/src/party/client.ts @@ -3,10 +3,14 @@ import type { Broadcast, ReduxAction } from "./types"; import { useAppDispatch } from "../state/store"; import { receivePartyState } from "../state/central"; import { startAppListening } from "../state/listener-middleware"; -import { useEffect } from "react"; +import React, { useEffect, useState } from "react"; -export function PartySocketManager(props: { roomName?: string }) { +export function PartySocketManager(props: { + roomName?: string; + children: React.ReactNode; +}) { const dispatch = useAppDispatch(); + const [ready, setReady] = useState(false); const socket = usePartySocket({ room: props.roomName, host: "localhost:1999", // TODO determine this based on build type, other stuff @@ -16,6 +20,7 @@ export function PartySocketManager(props: { roomName?: string }) { switch (data.type) { case "roomstate": dispatch(receivePartyState(data.state)); + setReady(true); break; case "action": const foreignAction = { @@ -55,5 +60,9 @@ export function PartySocketManager(props: { roomName?: string }) { }); }, [socket]); - return null; + if (!ready) { + // TODO move this into redux state, provide UI + return null; + } + return props.children; } diff --git a/src/song-card/song-card.tsx b/src/song-card/song-card.tsx index 086b0db64..3170bc892 100644 --- a/src/song-card/song-card.tsx +++ b/src/song-card/song-card.tsx @@ -152,7 +152,7 @@ export function SongCard(props: Props) { let jacketBg = {}; if (jacket) { - const prefix = jacket.startsWith("blob:") ? "" : "jackets/"; + const prefix = jacket.startsWith("blob:") ? "" : "/jackets/"; jacketBg = { backgroundImage: `url("${prefix}${jacket}")`, }; diff --git a/webpack.config.js b/webpack.config.js index 8a1cfe1d2..8b268403c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -33,11 +33,13 @@ module.exports = function (env = {}, argv = {}) { static: "./dist", hot: true, host: "0.0.0.0", + historyApiFallback: true, }, entry: "./src/index.tsx", output: { filename: "[name].[chunkhash:5].js", path: resolve(__dirname, "./dist"), + publicPath: "/", }, optimization: { minimize: isProd, diff --git a/yarn.lock b/yarn.lock index cb35d38e5..c054442e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2783,6 +2783,13 @@ __metadata: languageName: node linkType: hard +"@remix-run/router@npm:1.17.1": + version: 1.17.1 + resolution: "@remix-run/router@npm:1.17.1" + checksum: 10/5efc598626cd81688ac26e0abd08204b980831ead8cd2c4b8a27e0c169ee4777fc609fa289c093b93efc3a1e335304698c6961276c2309348444ec7209836c83 + languageName: node + linkType: hard + "@sinclair/typebox@npm:^0.27.8": version: 0.27.8 resolution: "@sinclair/typebox@npm:0.27.8" @@ -5186,6 +5193,7 @@ __metadata: react-intl: "npm:^6.6.8" react-redux: "npm:^9.1.2" react-refresh: "npm:^0.14.2" + react-router-dom: "npm:^6.24.1" sanitize-filename: "npm:^1.6.3" simfile-parser: "npm:^0.7.2" style-loader: "npm:^4.0.0" @@ -9968,6 +9976,30 @@ __metadata: languageName: node linkType: hard +"react-router-dom@npm:^6.24.1": + version: 6.24.1 + resolution: "react-router-dom@npm:6.24.1" + dependencies: + "@remix-run/router": "npm:1.17.1" + react-router: "npm:6.24.1" + peerDependencies: + react: ">=16.8" + react-dom: ">=16.8" + checksum: 10/98eeeec3d36695b3d6d8000d8373ba3cefa9afc49ee44f646f3b721e8b47a80f5ce3737ad1f752cccf19caf01e370918750a4bc86a06a99e491731b454d5ec55 + languageName: node + linkType: hard + +"react-router@npm:6.24.1": + version: 6.24.1 + resolution: "react-router@npm:6.24.1" + dependencies: + "@remix-run/router": "npm:1.17.1" + peerDependencies: + react: ">=16.8" + checksum: 10/18ac968171dee286a2f067dc8568faf73c759f833e88e09f1b34ff6e9376d1fd5eade8697a86be83093225956b256b398d935ce2f681c1bf711fb3c058c19839 + languageName: node + linkType: hard + "react-transition-group@npm:^4.4.5": version: 4.4.5 resolution: "react-transition-group@npm:4.4.5" From 6cee1a40a007db3151af13d949b164621770ef65 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Thu, 11 Jul 2024 17:57:19 -0700 Subject: [PATCH 12/91] nicer UI while connecting --- src/app.tsx | 8 ++++++-- src/party/{client.ts => client.tsx} | 18 +++++++++++++++--- src/utils/delay-render.tsx | 25 +++++++++++++++++++++++++ src/version-select.tsx | 20 +------------------- 4 files changed, 47 insertions(+), 24 deletions(-) rename src/party/{client.ts => client.tsx} (75%) create mode 100644 src/utils/delay-render.tsx diff --git a/src/app.tsx b/src/app.tsx index 11f3f5237..9fafbde97 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -51,7 +51,6 @@ function AppForRoom() { -
@@ -63,5 +62,10 @@ function AppForRoom() { } export function App() { - return ; + return ( + <> + + + + ); } diff --git a/src/party/client.ts b/src/party/client.tsx similarity index 75% rename from src/party/client.ts rename to src/party/client.tsx index 98a5db88a..019805fda 100644 --- a/src/party/client.ts +++ b/src/party/client.tsx @@ -4,16 +4,19 @@ import { useAppDispatch } from "../state/store"; import { receivePartyState } from "../state/central"; import { startAppListening } from "../state/listener-middleware"; import React, { useEffect, useState } from "react"; +import { Card, NonIdealState, Spinner } from "@blueprintjs/core"; +import { DelayRender } from "../utils/delay-render"; export function PartySocketManager(props: { roomName?: string; children: React.ReactNode; }) { const dispatch = useAppDispatch(); + // TODO move this state to redux??? const [ready, setReady] = useState(false); const socket = usePartySocket({ room: props.roomName, - host: "localhost:1999", // TODO determine this based on build type, other stuff + host: process.env.NODE_ENV === "development" ? "localhost:1999" : "", onMessage(evt) { try { const data: Broadcast = JSON.parse(evt.data); @@ -61,8 +64,17 @@ export function PartySocketManager(props: { }, [socket]); if (!ready) { - // TODO move this into redux state, provide UI - return null; + return ( +
+ + + } title="Connecting..." /> + + +
+ ); } return props.children; } diff --git a/src/utils/delay-render.tsx b/src/utils/delay-render.tsx new file mode 100644 index 000000000..b377d6aa0 --- /dev/null +++ b/src/utils/delay-render.tsx @@ -0,0 +1,25 @@ +import { ReactNode, useState, useEffect } from "react"; + +interface DelayProps { + delayMs?: number; + children: ReactNode; +} + +/** + * delays rendering children until 200ms has passed. + * helps to hide flickering loading spinners when loads + * are fast + **/ +export function DelayRender(props: DelayProps) { + const [display, setDisplay] = useState(false); + useEffect(() => { + const handle = setTimeout(() => { + setDisplay(true); + }, props.delayMs || 200); + return () => clearTimeout(handle); + }, [props.delayMs]); + if (display) { + return <>{props.children}; + } + return null; +} diff --git a/src/version-select.tsx b/src/version-select.tsx index 896f99990..2c9391c28 100644 --- a/src/version-select.tsx +++ b/src/version-select.tsx @@ -7,13 +7,13 @@ import { } from "@blueprintjs/core"; import { Error } from "@blueprintjs/icons"; import { Select } from "@blueprintjs/select"; -import { ReactNode, useEffect, useState } from "react"; import { useDataSets } from "./hooks/useDataSets"; import { groupGameData } from "./utils"; import { useIntl } from "./hooks/useIntl"; import { DoubleCaretVertical, FolderOpen } from "@blueprintjs/icons"; import { useAtomValue } from "jotai"; import { gameDataLoadingStatus } from "./state/game-data.atoms"; +import { DelayRender } from "./utils/delay-render"; export function VersionSelect() { const { t } = useIntl(); @@ -89,21 +89,3 @@ export function DataLoadingSpinner() { } return null; } - -interface DelayProps { - children: ReactNode; -} - -function DelayRender(props: DelayProps) { - const [display, setDisplay] = useState(false); - useEffect(() => { - const handle = setTimeout(() => { - setDisplay(true); - }, 200); - return () => clearTimeout(handle); - }, []); - if (display) { - return <>{props.children}; - } - return null; -} From 9e05f4e420042134050ecc41c6bffaf874560205 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Thu, 11 Jul 2024 17:59:08 -0700 Subject: [PATCH 13/91] add deployed partykit host --- src/party/client.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/party/client.tsx b/src/party/client.tsx index 019805fda..ebef75964 100644 --- a/src/party/client.tsx +++ b/src/party/client.tsx @@ -16,7 +16,10 @@ export function PartySocketManager(props: { const [ready, setReady] = useState(false); const socket = usePartySocket({ room: props.roomName, - host: process.env.NODE_ENV === "development" ? "localhost:1999" : "", + host: + process.env.NODE_ENV === "development" + ? "localhost:1999" + : "ddr-card-draw-party.noahm.partykit.dev", onMessage(evt) { try { const data: Broadcast = JSON.parse(evt.data); From 6ac6102ec328e787688f850a1e00e1ab18c9c816 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Thu, 11 Jul 2024 18:01:02 -0700 Subject: [PATCH 14/91] restore behavior of newest drawings at the top --- src/drawing-list.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/drawing-list.tsx b/src/drawing-list.tsx index 9de50cf81..7187e4ad5 100644 --- a/src/drawing-list.tsx +++ b/src/drawing-list.tsx @@ -24,9 +24,9 @@ const ScrollableDrawings = memo(() => { const drawingIds = useDeferredValue(useAppState((s) => s.drawings.ids)); return (
- {drawingIds.map((did) => ( - - ))} + {drawingIds + .map((did) => ) + .reverse()}
); }); From a36c85349170d998eeaf8a23f5226ae532926da0 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Thu, 11 Jul 2024 18:09:59 -0700 Subject: [PATCH 15/91] add vercel.json for client-side routing support --- vercel.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 vercel.json diff --git a/vercel.json b/vercel.json new file mode 100644 index 000000000..1323cdac3 --- /dev/null +++ b/vercel.json @@ -0,0 +1,8 @@ +{ + "rewrites": [ + { + "source": "/(.*)", + "destination": "/index.html" + } + ] +} From acb594ca4ddbcfa60b5f2ab2fe740202ad3d4534 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Fri, 12 Jul 2024 14:53:21 -0700 Subject: [PATCH 16/91] add event state slice --- src/state/event.slice.ts | 43 +++++++++++++++++++++++++++++++++++++++ src/state/root-reducer.ts | 2 ++ 2 files changed, 45 insertions(+) create mode 100644 src/state/event.slice.ts diff --git a/src/state/event.slice.ts b/src/state/event.slice.ts new file mode 100644 index 000000000..60d7e982a --- /dev/null +++ b/src/state/event.slice.ts @@ -0,0 +1,43 @@ +import { PayloadAction, createSlice } from "@reduxjs/toolkit"; +import { nanoid } from "nanoid"; + +interface CabInfo { + /** drawing id if active */ + activeMatch: string | null; + name: string; + id: string; +} + +interface EventState { + eventName: string; + cabs: Record; +} + +const initialState: EventState = { + eventName: "", + cabs: {}, +}; + +export const eventSlice = createSlice({ + name: "event", + initialState, + reducers: { + /** add a cab with its name */ + addCab(state, action: PayloadAction) { + const newCab: CabInfo = { + id: nanoid(5), + name: action.payload, + activeMatch: null, + }; + state.cabs[newCab.id] = newCab; + }, + assignMatchToCab( + state, + action: PayloadAction<{ cabId: string; matchId: string | null }>, + ) { + const cab = state.cabs[action.payload.cabId]; + if (!cab) return; + cab.activeMatch = action.payload.matchId; + }, + }, +}); diff --git a/src/state/root-reducer.ts b/src/state/root-reducer.ts index b0af54bc6..e39238659 100644 --- a/src/state/root-reducer.ts +++ b/src/state/root-reducer.ts @@ -3,11 +3,13 @@ import { configSlice } from "./config.slice"; import { drawingsSlice } from "./drawings.slice"; import { gameDataSlice } from "./game-data.slice"; import { receivePartyState } from "./central"; +import { eventSlice } from "./event.slice"; const combinedReducer = combineSlices( drawingsSlice, configSlice, gameDataSlice, + eventSlice, ); export const reducer: typeof combinedReducer = (state, action) => { From 1774363391e4bb96f9fa5ce20d655ca16336e609 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Fri, 12 Jul 2024 20:07:48 -0700 Subject: [PATCH 17/91] add persisted webpack cache --- webpack.config.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/webpack.config.js b/webpack.config.js index 8b268403c..a7f7ac13a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -25,6 +25,9 @@ module.exports = function (env = {}, argv = {}) { return { target: "web", + cache: { + type: "filesystem", + }, mode: isProd ? "production" : "development", devtool: isProd ? "source-map" : "inline-cheap-module-source-map", devServer: !serve From 7e6eaea9a49ff9396bee70c2edefce96267b9456 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Fri, 12 Jul 2024 20:08:12 -0700 Subject: [PATCH 18/91] add basic cab management --- src/app.tsx | 14 ++- src/cab-management.tsx | 133 ++++++++++++++++++++++++ src/config-state.ts | 2 +- src/drawing-list.tsx | 2 +- src/drawn-set.css | 5 + src/drawn-set.tsx | 1 + src/state/event.slice.ts | 10 +- src/tournament-mode/drawing-actions.tsx | 33 +++++- 8 files changed, 194 insertions(+), 6 deletions(-) create mode 100644 src/cab-management.tsx diff --git a/src/app.tsx b/src/app.tsx index 9fafbde97..3b08254fb 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -23,6 +23,7 @@ import { RouterProvider, useParams, } from "react-router-dom"; +import { CabManagement } from "./cab-management"; const router = createBrowserRouter([ { @@ -53,7 +54,18 @@ function AppForRoom() {
- +
+ + +
diff --git a/src/cab-management.tsx b/src/cab-management.tsx new file mode 100644 index 000000000..92cca57ba --- /dev/null +++ b/src/cab-management.tsx @@ -0,0 +1,133 @@ +import { Button, Card, ControlGroup, InputGroup } from "@blueprintjs/core"; +import { useAppDispatch, useAppState } from "./state/store"; +import { useCallback, useState } from "react"; +import { CabInfo, eventSlice } from "./state/event.slice"; +import { Add, Cross, Remove } from "@blueprintjs/icons"; +import { detectedLanguage } from "./utils"; + +export function CabManagement() { + const cabs = Object.values(useAppState((s) => s.event.cabs)); + return ( +
+
+ +
+
+ {cabs.map((cab) => ( + + ))} +
+
+ ); +} + +function AddCabControl() { + const [name, setName] = useState(""); + const dispatch = useAppDispatch(); + const addCab = useCallback(() => { + dispatch(eventSlice.actions.addCab(name)); + setName(""); + }, [dispatch, name]); + return ( +
{ + e.preventDefault(); + addCab(); + }} + > + + setName(e.currentTarget.value)} + placeholder="Cab name" + /> + + +
+ ); +} + +function CabSummary({ cab }: { cab: CabInfo }) { + const dispatch = useAppDispatch(); + const removeCab = useCallback( + () => dispatch(eventSlice.actions.removeCab(cab.id)), + [dispatch, cab.id], + ); + return ( +
+

+ {cab.name}

+ +
+ ); +} + +const listFormatter = new Intl.ListFormat(detectedLanguage, { + style: "short", + type: "unit", +}); + +function CurrentMatch(props: { cab: CabInfo }) { + const dispatch = useAppDispatch(); + const removeCab = useCallback( + () => + dispatch( + eventSlice.actions.assignMatchToCab({ + cabId: props.cab.id, + matchId: null, + }), + ), + [dispatch, props.cab.id], + ); + const drawing = useAppState((s) => { + if (!props.cab.activeMatch) return null; + return s.drawings.entities[props.cab.activeMatch] || null; + }); + + const scrollToDrawing = useCallback(() => { + if (!drawing) { + return; + } + const el = document.getElementById(`drawing:${drawing.id}`); + if (!el) { + return; + } + const priorFocus = document.querySelector( + "[data-focused]", + ) as HTMLElement | null; + if (priorFocus) { + delete priorFocus.dataset.focused; + } + el.scrollIntoView({ behavior: "smooth" }); + el.dataset.focused = ""; + }, [drawing]); + + if (!drawing) { + return

No match

; + } + const filledPlayers = drawing.players.map( + (p, idx) => p || `Player ${idx + 1}`, + ); + return ( + + +
diff --git a/src/drawn-set.tsx b/src/drawn-set.tsx index f1b594cae..1bed3891e 100644 --- a/src/drawn-set.tsx +++ b/src/drawn-set.tsx @@ -32,6 +32,14 @@ function ChartList() { ); } +export function ChartsOnly({ drawingId }: Props) { + return ( + + + + ); +} + function ChartFromContext({ chartId }: { chartId: string }) { const chart = useDrawing((d) => d.charts.find((c) => c.id === chartId)); const veto = useDrawing((d) => d.bans[chartId]); diff --git a/src/obs-sources/cards.tsx b/src/obs-sources/cards.tsx new file mode 100644 index 000000000..9a930e0ce --- /dev/null +++ b/src/obs-sources/cards.tsx @@ -0,0 +1,12 @@ +import { useParams } from "react-router-dom"; +import { ChartsOnly } from "../drawn-set"; +import { useAppState } from "../state/store"; + +export function CabCards() { + const params = useParams<"roomName" | "cabId">(); + const drawingId = useAppState((s) => s.event.cabs[params.cabId!].activeMatch); + if (!drawingId) { + return null; + } + return ; +} diff --git a/src/obs-sources/text.tsx b/src/obs-sources/text.tsx new file mode 100644 index 000000000..3cbc31020 --- /dev/null +++ b/src/obs-sources/text.tsx @@ -0,0 +1,23 @@ +import { useParams } from "react-router-dom"; +import { drawingSelectors } from "../state/drawings.slice"; +import { useAppState } from "../state/store"; + +export function CabTitle() { + const params = useParams<"roomName" | "cabId">(); + const text = useAppState((s) => { + const drawingId = s.event.cabs[params.cabId!].activeMatch; + if (!drawingId) return null; + return drawingSelectors.selectById(s, drawingId).title; + }); + return

{text}

; +} + +export function CabPlayer(props: { p: number }) { + const params = useParams<"roomName" | "cabId">(); + const text = useAppState((s) => { + const drawingId = s.event.cabs[params.cabId!].activeMatch; + if (!drawingId) return null; + return drawingSelectors.selectById(s, drawingId).players[props.p - 1]; + }); + return

{text}

; +} diff --git a/src/utils/share.ts b/src/utils/share.ts index 170ba9633..9c5454318 100644 --- a/src/utils/share.ts +++ b/src/utils/share.ts @@ -186,6 +186,22 @@ export function copyToClipboard(blob: Blob) { ]); } +export async function copyPlainTextToClipboard( + text: string, + toastMessage?: string, +) { + await navigator.clipboard.writeText(text); + if (toastMessage) { + toaster.show( + { + message: toastMessage, + icon: "paperclip", + }, + "copied-data", + ); + } +} + function dataUriToBlob(dataUri: string) { const headerIndex = dataUri.indexOf(","); const header = dataUri.slice(0, headerIndex); From 2acb83b4de789b71b965d45f0b10ee2b3c160236 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Sat, 13 Jul 2024 15:34:41 -0700 Subject: [PATCH 20/91] allow new slices to retain their default state --- src/state/root-reducer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/state/root-reducer.ts b/src/state/root-reducer.ts index e39238659..d0ffe07a6 100644 --- a/src/state/root-reducer.ts +++ b/src/state/root-reducer.ts @@ -14,7 +14,7 @@ const combinedReducer = combineSlices( export const reducer: typeof combinedReducer = (state, action) => { if (receivePartyState.match(action)) { - return action.payload; + return Object.assign({}, state, action.payload); } return combinedReducer(state, action); }; From 326c40bac34b71295a7ea1f45660dffe898c0b30 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Sat, 13 Jul 2024 15:36:53 -0700 Subject: [PATCH 21/91] prepopulate default cab --- src/state/event.slice.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/state/event.slice.ts b/src/state/event.slice.ts index 9c9cab44e..97a07b4e8 100644 --- a/src/state/event.slice.ts +++ b/src/state/event.slice.ts @@ -18,6 +18,13 @@ const initialState: EventState = { cabs: {}, }; +const defaultCab: CabInfo = { + activeMatch: null, + name: "Primary Cab", + id: nanoid(5), +}; +initialState.cabs[defaultCab.id] = defaultCab; + export const eventSlice = createSlice({ name: "event", initialState, From 07a51097e6eb6af182af23a4788a5076baa3a5de Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Sat, 13 Jul 2024 15:42:09 -0700 Subject: [PATCH 22/91] create default cab via lazy initialization --- src/state/event.slice.ts | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/state/event.slice.ts b/src/state/event.slice.ts index 97a07b4e8..73aa715dc 100644 --- a/src/state/event.slice.ts +++ b/src/state/event.slice.ts @@ -13,17 +13,19 @@ interface EventState { cabs: Record; } -const initialState: EventState = { - eventName: "", - cabs: {}, -}; - -const defaultCab: CabInfo = { - activeMatch: null, - name: "Primary Cab", - id: nanoid(5), -}; -initialState.cabs[defaultCab.id] = defaultCab; +function initialState(): EventState { + const defaultCab: CabInfo = { + activeMatch: null, + name: "Primary Cab", + id: nanoid(5), + }; + return { + eventName: "", + cabs: { + [defaultCab.id]: defaultCab, + }, + }; +} export const eventSlice = createSlice({ name: "event", From a9682541799bdc47796579e38de1b84b9f0fd5b6 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Sat, 13 Jul 2024 15:50:32 -0700 Subject: [PATCH 23/91] non random initial state --- src/party/server.ts | 17 +++++------------ src/state/event.slice.ts | 11 +++++------ 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/party/server.ts b/src/party/server.ts index f57281859..028384087 100644 --- a/src/party/server.ts +++ b/src/party/server.ts @@ -3,18 +3,19 @@ import type { ReduxAction, Roomstate } from "./types"; import { configureStore } from "@reduxjs/toolkit"; import { reducer } from "../state/root-reducer"; import { AppState } from "../state/store"; -import { receivePartyState } from "../state/central"; export default class Server implements Party.Server { - private store = configureStore({ reducer }); + // @ts-expect-error I assign this for sure + private store: ReturnType; constructor(readonly room: Party.Room) { console.log("constructor start"); } async onStart() { - console.log("onStart", typeof reducer); - await this.restoreFromStorage(); + const preloadedState = + await this.room.storage.get("currentState"); + this.store = configureStore({ reducer, preloadedState }); this.store.subscribe(() => { this.room.storage.put("currentState", this.store.getState()); }); @@ -53,14 +54,6 @@ export default class Server implements Party.Server { state: this.store.getState(), }); } - - private async restoreFromStorage() { - const preloadedState = - await this.room.storage.get("currentState"); - if (preloadedState) { - this.store.dispatch(receivePartyState(preloadedState)); - } - } } Server satisfies Party.Worker; diff --git a/src/state/event.slice.ts b/src/state/event.slice.ts index 73aa715dc..1f2601d46 100644 --- a/src/state/event.slice.ts +++ b/src/state/event.slice.ts @@ -14,15 +14,14 @@ interface EventState { } function initialState(): EventState { - const defaultCab: CabInfo = { - activeMatch: null, - name: "Primary Cab", - id: nanoid(5), - }; return { eventName: "", cabs: { - [defaultCab.id]: defaultCab, + default: { + id: "default", + name: "Primary Cab", + activeMatch: null, + }, }, }; } From 566d29195fd5ef8a8b5855a46a03545b0f8671dc Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Sun, 14 Jul 2024 10:59:52 -0700 Subject: [PATCH 24/91] add very basic gql+codegen setup --- .env.template | 5 + .gitignore | 1 + codegen.ts | 14 + graphql.config.ts | 14 + package.json | 5 + src/startgg-gql/generated/fragment-masking.ts | 87 + src/startgg-gql/generated/gql.ts | 42 + src/startgg-gql/generated/graphql.ts | 2413 ++++++++++++++++ src/startgg-gql/generated/index.ts | 2 + src/startgg-gql/index.ts | 16 + src/startgg-gql/queries.ts | 10 + webpack.config.js | 2 + yarn.lock | 2433 ++++++++++++++++- 13 files changed, 4899 insertions(+), 145 deletions(-) create mode 100644 .env.template create mode 100644 codegen.ts create mode 100644 graphql.config.ts create mode 100644 src/startgg-gql/generated/fragment-masking.ts create mode 100644 src/startgg-gql/generated/gql.ts create mode 100644 src/startgg-gql/generated/graphql.ts create mode 100644 src/startgg-gql/generated/index.ts create mode 100644 src/startgg-gql/index.ts create mode 100644 src/startgg-gql/queries.ts diff --git a/.env.template b/.env.template new file mode 100644 index 000000000..a580f6777 --- /dev/null +++ b/.env.template @@ -0,0 +1,5 @@ +# Generate one of your own for free at +# https://start.gg/admin/profile/developer +# Read more about start.gg auth here: +# https://developer.start.gg/docs/authentication +STARTGG_TOKEN=YOUR_TOKEN_HERE diff --git a/.gitignore b/.gitignore index 6210c7177..d4e36307b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ !.yarn/releases !.yarn/sdks !.yarn/versions +.env .partykit dist diff --git a/codegen.ts b/codegen.ts new file mode 100644 index 000000000..490e23ec6 --- /dev/null +++ b/codegen.ts @@ -0,0 +1,14 @@ +import { CodegenConfig } from "@graphql-codegen/cli"; +import baseConfig from "./graphql.config"; + +const config: CodegenConfig = { + ...baseConfig, + ignoreNoDocuments: false, // for better experience with the watcher + generates: { + "./src/startgg-gql/generated/": { + preset: "client", + }, + }, +}; + +export default config; diff --git a/graphql.config.ts b/graphql.config.ts new file mode 100644 index 000000000..56375f723 --- /dev/null +++ b/graphql.config.ts @@ -0,0 +1,14 @@ +import "dotenv/config"; + +export default { + schema: [ + { + "https://api.start.gg/gql/alpha": { + headers: { + Authorization: `Bearer ${process.env.STARTGG_TOKEN}`, + }, + }, + }, + ], + documents: ["./src/startgg-gql/queries.ts"], +}; diff --git a/package.json b/package.json index aa992e9b1..ce34e3fc3 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,8 @@ "@blueprintjs/datetime2": "^2.3.5", "@blueprintjs/icons": "^5.1.0", "@blueprintjs/select": "^5.1.5", + "@graphql-codegen/cli": "^5.0.2", + "@graphql-codegen/client-preset": "^4.3.2", "@lcdp/offline-plugin": "^5.0.7", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", "@reduxjs/toolkit": "^2.2.5", @@ -57,6 +59,7 @@ "@types/react-dom": "^18.3.0", "@typescript-eslint/eslint-plugin": "^7.13.0", "@typescript-eslint/parser": "^7.13.0", + "@urql/core": "^5.0.4", "autoprefixer": "^10.4.19", "axios": "^1.7.2", "babel-loader": "^9.1.3", @@ -69,6 +72,7 @@ "css-loader": "^7.1.2", "css-minimizer-webpack-plugin": "^7.0.0", "date-fns": "^2.28.0", + "dotenv": "^16.4.5", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", @@ -77,6 +81,7 @@ "favicons-webpack-plugin": "^6.0.0", "fork-ts-checker-webpack-plugin": "^9.0.2", "fuzzy-search": "^3.0.2", + "graphql": "^16.9.0", "he": "^1.2.0", "html-entities": "^2.5.2", "html-loader": "^5.0.0", diff --git a/src/startgg-gql/generated/fragment-masking.ts b/src/startgg-gql/generated/fragment-masking.ts new file mode 100644 index 000000000..aca71b135 --- /dev/null +++ b/src/startgg-gql/generated/fragment-masking.ts @@ -0,0 +1,87 @@ +/* eslint-disable */ +import { ResultOf, DocumentTypeDecoration, TypedDocumentNode } from '@graphql-typed-document-node/core'; +import { FragmentDefinitionNode } from 'graphql'; +import { Incremental } from './graphql'; + + +export type FragmentType> = TDocumentType extends DocumentTypeDecoration< + infer TType, + any +> + ? [TType] extends [{ ' $fragmentName'?: infer TKey }] + ? TKey extends string + ? { ' $fragmentRefs'?: { [key in TKey]: TType } } + : never + : never + : never; + +// return non-nullable if `fragmentType` is non-nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> +): TType; +// return nullable if `fragmentType` is undefined +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | undefined +): TType | undefined; +// return nullable if `fragmentType` is nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null +): TType | null; +// return nullable if `fragmentType` is nullable or undefined +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null | undefined +): TType | null | undefined; +// return array of non-nullable if `fragmentType` is array of non-nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> +): Array; +// return array of nullable if `fragmentType` is array of nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> | null | undefined +): Array | null | undefined; +// return readonly array of non-nullable if `fragmentType` is array of non-nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> +): ReadonlyArray; +// return readonly array of nullable if `fragmentType` is array of nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> | null | undefined +): ReadonlyArray | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | Array>> | ReadonlyArray>> | null | undefined +): TType | Array | ReadonlyArray | null | undefined { + return fragmentType as any; +} + + +export function makeFragmentData< + F extends DocumentTypeDecoration, + FT extends ResultOf +>(data: FT, _fragment: F): FragmentType { + return data as FragmentType; +} +export function isFragmentReady( + queryNode: DocumentTypeDecoration, + fragmentNode: TypedDocumentNode, + data: FragmentType, any>> | null | undefined +): data is FragmentType { + const deferredFields = (queryNode as { __meta__?: { deferredFields: Record } }).__meta__ + ?.deferredFields; + + if (!deferredFields) return true; + + const fragDef = fragmentNode.definitions[0] as FragmentDefinitionNode | undefined; + const fragName = fragDef?.name?.value; + + const fields = (fragName && deferredFields[fragName]) || []; + return fields.length > 0 && fields.every(field => data && field in data); +} diff --git a/src/startgg-gql/generated/gql.ts b/src/startgg-gql/generated/gql.ts new file mode 100644 index 000000000..bf3542bfc --- /dev/null +++ b/src/startgg-gql/generated/gql.ts @@ -0,0 +1,42 @@ +/* eslint-disable */ +import * as types from './graphql'; +import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'; + +/** + * Map of all GraphQL operations in the project. + * + * This map has several performance disadvantages: + * 1. It is not tree-shakeable, so it will include all operations in the project. + * 2. It is not minifiable, so the string of a GraphQL query will be multiple times inside the bundle. + * 3. It does not support dead code elimination, so it will add unused operations. + * + * Therefore it is highly recommended to use the babel or swc plugin for production. + */ +const documents = { + "\n query EventEntrants($eventSlug: String) {\n event(slug: $eventSlug) {\n id\n name\n }\n }\n": types.EventEntrantsDocument, +}; + +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + * + * + * @example + * ```ts + * const query = graphql(`query GetUser($id: ID!) { user(id: $id) { name } }`); + * ``` + * + * The query argument is unknown! + * Please regenerate the types. + */ +export function graphql(source: string): unknown; + +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query EventEntrants($eventSlug: String) {\n event(slug: $eventSlug) {\n id\n name\n }\n }\n"): (typeof documents)["\n query EventEntrants($eventSlug: String) {\n event(slug: $eventSlug) {\n id\n name\n }\n }\n"]; + +export function graphql(source: string) { + return (documents as any)[source] ?? {}; +} + +export type DocumentType> = TDocumentNode extends DocumentNode< infer TType, any> ? TType : never; \ No newline at end of file diff --git a/src/startgg-gql/generated/graphql.ts b/src/startgg-gql/generated/graphql.ts new file mode 100644 index 000000000..e77ac7332 --- /dev/null +++ b/src/startgg-gql/generated/graphql.ts @@ -0,0 +1,2413 @@ +/* eslint-disable */ +import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'; +export type Maybe = T | null; +export type InputMaybe = Maybe; +export type Exact = { [K in keyof T]: T[K] }; +export type MakeOptional = Omit & { [SubKey in K]?: Maybe }; +export type MakeMaybe = Omit & { [SubKey in K]: Maybe }; +export type MakeEmpty = { [_ in K]?: never }; +export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; +/** All built-in and custom scalars, mapped to their actual values */ +export type Scalars = { + ID: { input: string; output: string; } + String: { input: string; output: string; } + Boolean: { input: boolean; output: boolean; } + Int: { input: number; output: number; } + Float: { input: number; output: number; } + /** + * The `JSON` scalar type represents JSON values as specified by + * [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). + */ + JSON: { input: any; output: any; } + /** + * Represents a Unix Timestamp. Supports up to 53 bit int values, + * as that is JavaScript's internal memory allocation for integer values. + */ + Timestamp: { input: any; output: any; } +}; + +/** A set of actions available for an entity to take */ +export type ActionSet = { + id?: Maybe; +}; + +/** Represents the state of an activity */ +export enum ActivityState { + /** Activity is active or in progress */ + Active = 'ACTIVE', + /** Activity, like a set, has been called to start */ + Called = 'CALLED', + /** Activity is done */ + Completed = 'COMPLETED', + /** Activity is created */ + Created = 'CREATED', + /** Activity is invalid */ + Invalid = 'INVALID', + /** Activity is queued to run */ + Queued = 'QUEUED', + /** Activity is ready to be started */ + Ready = 'READY' +} + +/** A user's address */ +export type Address = { + __typename?: 'Address'; + city?: Maybe; + country?: Maybe; + countryId?: Maybe; + id?: Maybe; + state?: Maybe; + stateId?: Maybe; +}; + +/** Represents the name of the third-party service (e.g Twitter) for OAuth */ +export enum AuthorizationType { + Battlenet = 'BATTLENET', + Discord = 'DISCORD', + Epic = 'EPIC', + Mixer = 'MIXER', + Steam = 'STEAM', + Twitch = 'TWITCH', + Twitter = 'TWITTER', + Xbox = 'XBOX' +} + +/** Bracket-specific configuration */ +export type BracketConfig = { + bracketType?: Maybe; + id?: Maybe; +}; + +/** Game specific H2H set data such as character, stage, and stock info */ +export type BracketSetGameDataInput = { + /** Score for entrant 1 (if applicable). For smash, this is stocks remaining. */ + entrant1Score?: InputMaybe; + /** Score for entrant 2 (if applicable). For smash, this is stocks remaining. */ + entrant2Score?: InputMaybe; + /** Game number */ + gameNum: Scalars['Int']['input']; + /** List of selections for the game, typically character selections. */ + selections?: InputMaybe>>; + /** ID of the stage that was played for this game (if applicable) */ + stageId?: InputMaybe; + /** Entrant ID of game winner */ + winnerId?: InputMaybe; +}; + +/** Game specific H2H selections made by the entrants, such as character info */ +export type BracketSetGameSelectionInput = { + /** Character selected by this entrant for this game. */ + characterId?: InputMaybe; + /** Entrant ID that made selection */ + entrantId: Scalars['ID']['input']; +}; + +/** The type of Bracket format that a Phase is configured with. */ +export enum BracketType { + Circuit = 'CIRCUIT', + CustomSchedule = 'CUSTOM_SCHEDULE', + DoubleElimination = 'DOUBLE_ELIMINATION', + EliminationRounds = 'ELIMINATION_ROUNDS', + Exhibition = 'EXHIBITION', + Matchmaking = 'MATCHMAKING', + Race = 'RACE', + RoundRobin = 'ROUND_ROBIN', + SingleElimination = 'SINGLE_ELIMINATION', + Swiss = 'SWISS' +} + +/** A character in a videogame */ +export type Character = { + __typename?: 'Character'; + id?: Maybe; + images?: Maybe>>; + /** Name of Character */ + name?: Maybe; +}; + + +/** A character in a videogame */ +export type CharacterImagesArgs = { + type?: InputMaybe; +}; + +/** Comparison operator */ +export enum Comparator { + Equal = 'EQUAL', + GreaterThan = 'GREATER_THAN', + GreaterThanOrEqual = 'GREATER_THAN_OR_EQUAL', + LessThan = 'LESS_THAN', + LessThanOrEqual = 'LESS_THAN_OR_EQUAL' +} + +/** Name, address, etc */ +export type ContactInfo = { + __typename?: 'ContactInfo'; + /** Participant City Name */ + city?: Maybe; + /** Participant Country Name */ + country?: Maybe; + /** Participant Country (region) id */ + countryId?: Maybe; + id?: Maybe; + name?: Maybe; + /** First Name */ + nameFirst?: Maybe; + /** Last Name */ + nameLast?: Maybe; + /** Participant State Name */ + state?: Maybe; + /** Participant State (region) id */ + stateId?: Maybe; + /** Zip or Postal Code */ + zipcode?: Maybe; +}; + +/** An entrant in an event */ +export type Entrant = { + __typename?: 'Entrant'; + event?: Maybe; + id?: Maybe; + /** Entrant's seed number in the first phase of the event. */ + initialSeedNum?: Maybe; + isDisqualified?: Maybe; + /** The entrant name as it appears in bracket: gamerTag of the participant or team name */ + name?: Maybe; + /** Paginated sets for this entrant */ + paginatedSets?: Maybe; + participants?: Maybe>>; + seeds?: Maybe>>; + skill?: Maybe; + /** Standing for this entrant given an event. All entrants queried must be in the same event (for now). */ + standing?: Maybe; + /** @deprecated DEPRECATED. Use streams instead, which supports multiple stream types and teams. */ + stream?: Maybe; + streams?: Maybe>>; + /** Team linked to this entrant, if one exists */ + team?: Maybe; +}; + + +/** An entrant in an event */ +export type EntrantPaginatedSetsArgs = { + filters?: InputMaybe; + page?: InputMaybe; + perPage?: InputMaybe; + sortType?: InputMaybe; +}; + +export type EntrantConnection = { + __typename?: 'EntrantConnection'; + nodes?: Maybe>>; + pageInfo?: Maybe; +}; + +/** An event in a tournament */ +export type Event = { + __typename?: 'Event'; + /** How long before the event start will the check-in end (in seconds) */ + checkInBuffer?: Maybe; + /** How long the event check-in will last (in seconds) */ + checkInDuration?: Maybe; + /** Whether check-in is enabled for this event */ + checkInEnabled?: Maybe; + /** Rough categorization of event tier, denoting relative importance in the competitive scene */ + competitionTier?: Maybe; + /** When the event was created (unix timestamp) */ + createdAt?: Maybe; + /** Last date attendees are able to create teams for team events */ + deckSubmissionDeadline?: Maybe; + /** + * Maximum number of participants each Entrant can have + * @deprecated Migrate to teamRosterSize + */ + entrantSizeMax?: Maybe; + /** + * Minimum number of participants each Entrant can have + * @deprecated Migrate to teamRosterSize + */ + entrantSizeMin?: Maybe; + /** The entrants that belong to an event, paginated by filter criteria */ + entrants?: Maybe; + /** Whether the event has decks */ + hasDecks?: Maybe; + /** Are player tasks enabled for this event */ + hasTasks?: Maybe; + id?: Maybe; + images?: Maybe>>; + /** Whether the event is an online event or not */ + isOnline?: Maybe; + league?: Maybe; + /** Markdown field for match rules/instructions */ + matchRulesMarkdown?: Maybe; + /** Title of event set by organizer */ + name?: Maybe; + /** Gets the number of entrants in this event */ + numEntrants?: Maybe; + /** The phase groups that belong to an event. */ + phaseGroups?: Maybe>>; + /** The phases that belong to an event. */ + phases?: Maybe>>; + /** TO settings for prizing */ + prizingInfo?: Maybe; + publishing?: Maybe; + /** Markdown field for event rules/instructions */ + rulesMarkdown?: Maybe; + /** Id of the event ruleset */ + rulesetId?: Maybe; + /** + * Settings pulled from the event ruleset, if one exists + * @deprecated Use ruleset + */ + rulesetSettings?: Maybe; + /** Paginated sets for this Event */ + sets?: Maybe; + slug?: Maybe; + /** Paginated list of standings */ + standings?: Maybe; + /** When does this event start? */ + startAt?: Maybe; + /** The state of the Event. */ + state?: Maybe; + /** Paginated stations on this event */ + stations?: Maybe; + /** Last date attendees are able to create teams for team events */ + teamManagementDeadline?: Maybe; + /** If this is a teams event, returns whether or not teams can set custom names */ + teamNameAllowed?: Maybe; + /** Team roster size requirements */ + teamRosterSize?: Maybe; + tournament?: Maybe; + /** The type of the event, whether an entrant will have one participant or multiple */ + type?: Maybe; + /** When the event was last modified (unix timestamp) */ + updatedAt?: Maybe; + /** Whether the event uses the new EventSeeds for seeding */ + useEventSeeds?: Maybe; + /** The entrant (if applicable) for a given user in this event */ + userEntrant?: Maybe; + videogame?: Maybe; + /** The waves being used by the event */ + waves?: Maybe>>; +}; + + +/** An event in a tournament */ +export type EventEntrantsArgs = { + query?: InputMaybe; +}; + + +/** An event in a tournament */ +export type EventImagesArgs = { + type?: InputMaybe; +}; + + +/** An event in a tournament */ +export type EventPhasesArgs = { + phaseId?: InputMaybe; + state?: InputMaybe; +}; + + +/** An event in a tournament */ +export type EventSetsArgs = { + filters?: InputMaybe; + page?: InputMaybe; + perPage?: InputMaybe; + sortType?: InputMaybe; +}; + + +/** An event in a tournament */ +export type EventStandingsArgs = { + query: StandingPaginationQuery; +}; + + +/** An event in a tournament */ +export type EventStationsArgs = { + query?: InputMaybe; +}; + + +/** An event in a tournament */ +export type EventUserEntrantArgs = { + userId?: InputMaybe; +}; + + +/** An event in a tournament */ +export type EventWavesArgs = { + phaseId?: InputMaybe; +}; + +export type EventConnection = { + __typename?: 'EventConnection'; + nodes?: Maybe>>; + pageInfo?: Maybe; +}; + +export type EventEntrantPageQuery = { + filter?: InputMaybe; + page?: InputMaybe; + perPage?: InputMaybe; + sortBy?: InputMaybe; +}; + +export type EventEntrantPageQueryFilter = { + name?: InputMaybe; +}; + +export type EventFilter = { + fantasyEventId?: InputMaybe; + fantasyRosterHash?: InputMaybe; + id?: InputMaybe; + ids?: InputMaybe>>; + published?: InputMaybe; + slug?: InputMaybe; + type?: InputMaybe>>; + videogameId?: InputMaybe>>; +}; + +/** Name and Gamertag of the owner of an event in a league */ +export type EventOwner = { + __typename?: 'EventOwner'; + email?: Maybe; + eventId?: Maybe; + fullName?: Maybe; + gamerTag?: Maybe; +}; + +export type EventOwnerConnection = { + __typename?: 'EventOwnerConnection'; + nodes?: Maybe>>; + pageInfo?: Maybe; +}; + +export type EventOwnersQuery = { + page?: InputMaybe; + /** How many nodes to return for the page. Maximum value of 500 */ + perPage?: InputMaybe; + sortBy?: InputMaybe; +}; + +/** An event-level Team, in the context of some competition */ +export type EventTeam = Team & { + __typename?: 'EventTeam'; + /** Uniquely identifying token for team. Same as the hashed part of the slug */ + discriminator?: Maybe; + /** @deprecated Use the entrant field off the EventTeam type */ + entrant?: Maybe; + /** @deprecated Use the event field off the EventTeam type */ + event?: Maybe; + globalTeam?: Maybe; + id?: Maybe; + images?: Maybe>>; + members?: Maybe>>; + name?: Maybe; +}; + + +/** An event-level Team, in the context of some competition */ +export type EventTeamImagesArgs = { + type?: InputMaybe; +}; + + +/** An event-level Team, in the context of some competition */ +export type EventTeamMembersArgs = { + status?: InputMaybe>>; +}; + +export type EventTeamConnection = { + __typename?: 'EventTeamConnection'; + nodes?: Maybe>>; + pageInfo?: Maybe; +}; + +/** Used for league application tiers */ +export type EventTier = { + __typename?: 'EventTier'; + id?: Maybe; + /** Name of this tier */ + name?: Maybe; +}; + +/** A game represents a single game within a set. */ +export type Game = { + __typename?: 'Game'; + /** Score of entrant 1. For smash, this is equivalent to stocks remaining. */ + entrant1Score?: Maybe; + /** Score of entrant 2. For smash, this is equivalent to stocks remaining. */ + entrant2Score?: Maybe; + id?: Maybe; + images?: Maybe>>; + orderNum?: Maybe; + /** Selections for this game such as character, etc. */ + selections?: Maybe>>; + /** The stage that this game was played on (if applicable) */ + stage?: Maybe; + state?: Maybe; + winnerId?: Maybe; +}; + + +/** A game represents a single game within a set. */ +export type GameImagesArgs = { + type?: InputMaybe; +}; + +/** A selection for this game. i.e. character/stage selection, etc */ +export type GameSelection = { + __typename?: 'GameSelection'; + /** If this is a character selection, returns the selected character. */ + character?: Maybe; + /** The entrant who this selection is for */ + entrant?: Maybe; + id?: Maybe; + orderNum?: Maybe; + /** + * The participant who this selection is for. This is only populated if there are + * selections for multiple participants of a single entrant + */ + participant?: Maybe; + selectionType?: Maybe; + selectionValue?: Maybe; +}; + +/** The type of selection i.e. is it for a character or something else */ +export enum GameSelectionType { + /** Character selection */ + Character = 'CHARACTER' +} + +/** Global Team */ +export type GlobalTeam = Team & { + __typename?: 'GlobalTeam'; + /** Uniquely identifying token for team. Same as the hashed part of the slug */ + discriminator?: Maybe; + /** @deprecated Use the entrant field off the EventTeam type */ + entrant?: Maybe; + /** @deprecated Use the event field off the EventTeam type */ + event?: Maybe; + eventTeams?: Maybe; + id?: Maybe; + images?: Maybe>>; + /** Leagues-level teams for leagues this team is competing in */ + leagueTeams?: Maybe; + members?: Maybe>>; + name?: Maybe; +}; + + +/** Global Team */ +export type GlobalTeamEventTeamsArgs = { + query?: InputMaybe; +}; + + +/** Global Team */ +export type GlobalTeamImagesArgs = { + type?: InputMaybe; +}; + + +/** Global Team */ +export type GlobalTeamLeagueTeamsArgs = { + query?: InputMaybe; +}; + + +/** Global Team */ +export type GlobalTeamMembersArgs = { + status?: InputMaybe>>; +}; + +/** An image */ +export type Image = { + __typename?: 'Image'; + height?: Maybe; + id?: Maybe; + ratio?: Maybe; + type?: Maybe; + url?: Maybe; + width?: Maybe; +}; + +/** A league */ +export type League = { + __typename?: 'League'; + addrState?: Maybe; + city?: Maybe; + countryCode?: Maybe; + /** When the tournament was created (unix timestamp) */ + createdAt?: Maybe; + currency?: Maybe; + /** When the tournament ends */ + endAt?: Maybe; + entrantCount?: Maybe; + eventOwners?: Maybe; + /** When does event registration close */ + eventRegistrationClosesAt?: Maybe; + /** Paginated list of events in a league */ + events?: Maybe; + /** + * Hacked "progression" into this final event + * @deprecated No longer used + */ + finalEventId?: Maybe; + /** True if tournament has at least one offline event */ + hasOfflineEvents?: Maybe; + hasOnlineEvents?: Maybe; + hashtag?: Maybe; + id?: Maybe; + images?: Maybe>>; + /** True if tournament has at least one online event */ + isOnline?: Maybe; + lat?: Maybe; + links?: Maybe; + lng?: Maybe; + mapsPlaceId?: Maybe; + /** The tournament name */ + name?: Maybe; + /** + * Top X number of people in the standings who progress to final event + * @deprecated No longer used + */ + numProgressingToFinalEvent?: Maybe; + numUniquePlayers?: Maybe; + postalCode?: Maybe; + primaryContact?: Maybe; + primaryContactType?: Maybe; + /** Publishing settings for this tournament */ + publishing?: Maybe; + /** When does registration for the tournament end */ + registrationClosesAt?: Maybe; + rules?: Maybe; + /** The short slug used to form the url */ + shortSlug?: Maybe; + /** Whether standings for this league should be visible */ + showStandings?: Maybe; + slug?: Maybe; + /** Paginated list of standings */ + standings?: Maybe; + /** When the tournament Starts */ + startAt?: Maybe; + /** State of the tournament, can be ActivityState::CREATED, ActivityState::ACTIVE, or ActivityState::COMPLETED */ + state?: Maybe; + /** When is the team creation deadline */ + teamCreationClosesAt?: Maybe; + tiers?: Maybe>>; + /** The timezone of the tournament */ + timezone?: Maybe; + /** The type of tournament from TournamentType */ + tournamentType?: Maybe; + /** When the tournament was last modified (unix timestamp) */ + updatedAt?: Maybe; + /** Build Tournament URL */ + url?: Maybe; + venueAddress?: Maybe; + venueName?: Maybe; + videogames?: Maybe>>; +}; + + +/** A league */ +export type LeagueEventOwnersArgs = { + query?: InputMaybe; +}; + + +/** A league */ +export type LeagueEventsArgs = { + query?: InputMaybe; +}; + + +/** A league */ +export type LeagueImagesArgs = { + type?: InputMaybe; +}; + + +/** A league */ +export type LeagueStandingsArgs = { + query?: InputMaybe; +}; + + +/** A league */ +export type LeagueUrlArgs = { + relative?: InputMaybe; + tab?: InputMaybe; +}; + +export type LeagueConnection = { + __typename?: 'LeagueConnection'; + nodes?: Maybe>>; + pageInfo?: Maybe; +}; + +export type LeagueEventsFilter = { + leagueEntrantId?: InputMaybe; + pointMappingGroupIds?: InputMaybe>>; + search?: InputMaybe; + tierIds?: InputMaybe>>; + upcoming?: InputMaybe; + userId?: InputMaybe; +}; + +export type LeagueEventsQuery = { + filter?: InputMaybe; + page?: InputMaybe; + /** How many nodes to return for the page. Maximum value of 500 */ + perPage?: InputMaybe; + sortBy?: InputMaybe; +}; + +export type LeaguePageFilter = { + activeShops?: InputMaybe; + afterDate?: InputMaybe; + beforeDate?: InputMaybe; + computedUpdatedAt?: InputMaybe; + hasBannerImages?: InputMaybe; + id?: InputMaybe; + ids?: InputMaybe>>; + isFeatured?: InputMaybe; + name?: InputMaybe; + /** ID of the user that owns this league. */ + ownerId?: InputMaybe; + past?: InputMaybe; + publiclySearchable?: InputMaybe; + published?: InputMaybe; + upcoming?: InputMaybe; + videogameIds?: InputMaybe>>; +}; + +export type LeagueQuery = { + filter?: InputMaybe; + page?: InputMaybe; + /** How many nodes to return for the page. Maximum value of 500 */ + perPage?: InputMaybe; + sort?: InputMaybe; + sortBy?: InputMaybe; +}; + +export type LocationFilterType = { + city?: InputMaybe; + countryCode?: InputMaybe; + state?: InputMaybe; +}; + +/** Match-level configuration */ +export type MatchConfig = { + bracketType?: Maybe; + id?: Maybe; +}; + +/** Different options available for verifying player-reported match results */ +export enum MatchConfigVerificationMethod { + Any = 'ANY', + Mixer = 'MIXER', + StreamMe = 'STREAM_ME', + Twitch = 'TWITCH', + Youtube = 'YOUTUBE' +} + +export type Mutation = { + __typename?: 'Mutation'; + /** Delete a phase by id */ + deletePhase?: Maybe; + /** Delete a station by id */ + deleteStation?: Maybe; + /** Delete a wave by id */ + deleteWave?: Maybe; + /** Generate tournament registration Token on behalf of user */ + generateRegistrationToken?: Maybe; + /** Update a set to called state */ + markSetCalled?: Maybe; + /** Update a set to called state */ + markSetInProgress?: Maybe; + /** Register for tournament */ + registerForTournament?: Maybe; + /** + * Report set winner or game stats for a H2H bracket set. If winnerId is + * supplied, mark set as complete. gameData parameter will overwrite any existing + * reported game data. + */ + reportBracketSet?: Maybe>>; + /** Resets set to initial state, can affect other sets and phase groups */ + resetSet?: Maybe; + /** Automatically attempt to resolve all schedule conflicts. Returns a list of changed seeds */ + resolveScheduleConflicts?: Maybe>>; + /** Swap two seed ids in a phase */ + swapSeeds?: Maybe>>; + /** + * Update game stats for a H2H bracket set. Set winner cannot be changed with + * this function, use the resetSet mutation instead. + */ + updateBracketSet?: Maybe; + /** Update set of phase groups in a phase */ + updatePhaseGroups?: Maybe>>; + /** Update the seeding for a phase */ + updatePhaseSeeding?: Maybe; + /** Create or update a Phase */ + upsertPhase?: Maybe; + /** Add or update a station by id */ + upsertStation?: Maybe; + /** Add or update a wave by id */ + upsertWave?: Maybe; +}; + + +export type MutationDeletePhaseArgs = { + phaseId: Scalars['ID']['input']; +}; + + +export type MutationDeleteStationArgs = { + stationId: Scalars['ID']['input']; +}; + + +export type MutationDeleteWaveArgs = { + waveId: Scalars['ID']['input']; +}; + + +export type MutationGenerateRegistrationTokenArgs = { + registration: TournamentRegistrationInput; + userId: Scalars['ID']['input']; +}; + + +export type MutationMarkSetCalledArgs = { + setId: Scalars['ID']['input']; +}; + + +export type MutationMarkSetInProgressArgs = { + setId: Scalars['ID']['input']; +}; + + +export type MutationRegisterForTournamentArgs = { + registration?: InputMaybe; + registrationToken?: InputMaybe; +}; + + +export type MutationReportBracketSetArgs = { + gameData?: InputMaybe>>; + isDQ?: InputMaybe; + setId: Scalars['ID']['input']; + winnerId?: InputMaybe; +}; + + +export type MutationResetSetArgs = { + resetDependentSets?: InputMaybe; + setId: Scalars['ID']['input']; +}; + + +export type MutationResolveScheduleConflictsArgs = { + options?: InputMaybe; + tournamentId: Scalars['ID']['input']; +}; + + +export type MutationSwapSeedsArgs = { + phaseId: Scalars['ID']['input']; + seed1Id: Scalars['ID']['input']; + seed2Id: Scalars['ID']['input']; +}; + + +export type MutationUpdateBracketSetArgs = { + gameData?: InputMaybe>>; + isDQ?: InputMaybe; + setId: Scalars['ID']['input']; + winnerId?: InputMaybe; +}; + + +export type MutationUpdatePhaseGroupsArgs = { + groupConfigs: Array>; +}; + + +export type MutationUpdatePhaseSeedingArgs = { + options?: InputMaybe; + phaseId: Scalars['ID']['input']; + seedMapping: Array>; +}; + + +export type MutationUpsertPhaseArgs = { + eventId?: InputMaybe; + payload: PhaseUpsertInput; + phaseId?: InputMaybe; +}; + + +export type MutationUpsertStationArgs = { + fields: StationUpsertInput; + stationId?: InputMaybe; + tournamentId?: InputMaybe; +}; + + +export type MutationUpsertWaveArgs = { + fields: WaveUpsertInput; + tournamentId?: InputMaybe; + waveId?: InputMaybe; +}; + +export type PageInfo = { + __typename?: 'PageInfo'; + filter?: Maybe; + page?: Maybe; + perPage?: Maybe; + sortBy?: Maybe; + total?: Maybe; + totalPages?: Maybe; +}; + +export type PaginationSearchType = { + fieldsToSearch?: InputMaybe>>; + searchString?: InputMaybe; +}; + +/** A participant of a tournament; either a spectator or competitor */ +export type Participant = { + __typename?: 'Participant'; + /** If this participant was checked-in by admin */ + checkedIn?: Maybe; + /** The time this participant was checked-in by admin */ + checkedInAt?: Maybe; + /** Info for connected accounts to external services. */ + connectedAccounts?: Maybe; + /** + * Contact Info selected during registration. Falls back to User.location and/or + * User.name if necessary. These fields are for admin use only. If you are not a + * tournament admin or the participant being queried, these fields will be null. + * Do not display this information publicly. + */ + contactInfo?: Maybe; + /** Email of the user, only available to admins within 18 months of tournament completion for tournament administrators. */ + email?: Maybe; + /** Entrants associated with this Participant, if applicable */ + entrants?: Maybe>>; + /** The events this participant registered for within a Tournament. */ + events?: Maybe>>; + /** The tag that was used when the participant registered, e.g. Mang0 */ + gamerTag?: Maybe; + id?: Maybe; + images?: Maybe>>; + player?: Maybe; + /** The prefix that the user set for this Tournament, e.g. C9 */ + prefix?: Maybe; + /** Tournament Admin viewable field. Shows details for required social connections */ + requiredConnections?: Maybe>>; + /** The user this participant is associated to. */ + user?: Maybe; + /** If this participant is verified as actually being in the tournament */ + verified?: Maybe; +}; + + +/** A participant of a tournament; either a spectator or competitor */ +export type ParticipantImagesArgs = { + type?: InputMaybe; +}; + +export type ParticipantConnection = { + __typename?: 'ParticipantConnection'; + nodes?: Maybe>>; + pageInfo?: Maybe; +}; + +export type ParticipantPageFilter = { + checkedIn?: InputMaybe; + eventIds?: InputMaybe>>; + gamerTag?: InputMaybe; + id?: InputMaybe; + ids?: InputMaybe>>; + incompleteTeam?: InputMaybe; + missingDeck?: InputMaybe; + notCheckedIn?: InputMaybe; + search?: InputMaybe; + unpaid?: InputMaybe; +}; + +export type ParticipantPaginationQuery = { + filter?: InputMaybe; + page?: InputMaybe; + /** How many nodes to return for the page. Maximum value of 500 */ + perPage?: InputMaybe; + sortBy?: InputMaybe; +}; + +/** A phase in an event */ +export type Phase = { + __typename?: 'Phase'; + /** The bracket type of this phase. */ + bracketType?: Maybe; + /** The Event that this phase belongs to */ + event?: Maybe; + /** Number of phase groups in this phase */ + groupCount?: Maybe; + id?: Maybe; + /** Is the phase an exhibition or not. */ + isExhibition?: Maybe; + /** Name of phase e.g. Round 1 Pools */ + name?: Maybe; + /** The number of seeds this phase contains. */ + numSeeds?: Maybe; + /** @deprecated Please use 'seeds' instead */ + paginatedSeeds?: Maybe; + /** Phase groups under this phase, paginated */ + phaseGroups?: Maybe; + /** The relative order of this phase within an event */ + phaseOrder?: Maybe; + /** Paginated seeds for this phase */ + seeds?: Maybe; + /** Paginated sets for this Phase */ + sets?: Maybe; + /** State of the phase */ + state?: Maybe; + waves?: Maybe>>; +}; + + +/** A phase in an event */ +export type PhasePaginatedSeedsArgs = { + eventId?: InputMaybe; + query: SeedPaginationQuery; +}; + + +/** A phase in an event */ +export type PhasePhaseGroupsArgs = { + query?: InputMaybe; +}; + + +/** A phase in an event */ +export type PhaseSeedsArgs = { + eventId?: InputMaybe; + query: SeedPaginationQuery; +}; + + +/** A phase in an event */ +export type PhaseSetsArgs = { + filters?: InputMaybe; + page?: InputMaybe; + perPage?: InputMaybe; + sortType?: InputMaybe; +}; + +/** A group within a phase */ +export type PhaseGroup = { + __typename?: 'PhaseGroup'; + /** The bracket type of this group's phase. */ + bracketType?: Maybe; + /** URL for this phase groups's bracket. */ + bracketUrl?: Maybe; + /** Unique identifier for this group within the context of its phase */ + displayIdentifier?: Maybe; + /** For the given phase group, this is the start time of the first round that occurs in the group. */ + firstRoundTime?: Maybe; + id?: Maybe; + numRounds?: Maybe; + /** @deprecated Please use 'seeds', which is now paginated */ + paginatedSeeds?: Maybe; + /** + * Paginated sets on this phaseGroup + * @deprecated Please use 'sets', which is now paginated + */ + paginatedSets?: Maybe; + /** The phase associated with this phase group */ + phase?: Maybe; + /** The progressions out of this phase group */ + progressionsOut?: Maybe>>; + rounds?: Maybe>>; + seedMap?: Maybe; + /** Paginated seeds for this phase group */ + seeds?: Maybe; + /** Paginated sets on this phaseGroup */ + sets?: Maybe; + /** Paginated list of standings */ + standings?: Maybe; + /** Unix time the group is scheduled to start. This info could also be on the wave instead. */ + startAt?: Maybe; + state?: Maybe; + tiebreakOrder?: Maybe; + wave?: Maybe; +}; + + +/** A group within a phase */ +export type PhaseGroupPaginatedSeedsArgs = { + eventId?: InputMaybe; + query: SeedPaginationQuery; +}; + + +/** A group within a phase */ +export type PhaseGroupPaginatedSetsArgs = { + filters?: InputMaybe; + page?: InputMaybe; + perPage?: InputMaybe; + sortType?: InputMaybe; +}; + + +/** A group within a phase */ +export type PhaseGroupSeedsArgs = { + eventId?: InputMaybe; + query: SeedPaginationQuery; +}; + + +/** A group within a phase */ +export type PhaseGroupSetsArgs = { + filters?: InputMaybe; + page?: InputMaybe; + perPage?: InputMaybe; + sortType?: InputMaybe; +}; + + +/** A group within a phase */ +export type PhaseGroupStandingsArgs = { + query?: InputMaybe; +}; + +export type PhaseGroupConnection = { + __typename?: 'PhaseGroupConnection'; + nodes?: Maybe>>; + pageInfo?: Maybe; +}; + +export type PhaseGroupPageQuery = { + entrantIds?: InputMaybe>>; + filter?: InputMaybe; + page?: InputMaybe; + perPage?: InputMaybe; + sortBy?: InputMaybe; +}; + +export type PhaseGroupPageQueryFilter = { + id?: InputMaybe>>; + waveId?: InputMaybe; +}; + +export type PhaseGroupUpdateInput = { + phaseGroupId: Scalars['ID']['input']; + stationId?: InputMaybe; + waveId?: InputMaybe; +}; + +export type PhaseUpsertInput = { + bracketType?: InputMaybe; + /** The number of pools to configure for the Phase. Only applies to brackets that support pools */ + groupCount?: InputMaybe; + /** The name of the Phase. For example, "Top 8" or "Pools" */ + name?: InputMaybe; +}; + +/** A player */ +export type Player = { + __typename?: 'Player'; + gamerTag?: Maybe; + id?: Maybe; + prefix?: Maybe; + /** Most recent active & published rankings */ + rankings?: Maybe>>; + /** + * Recent sets for this player. + * @deprecated Use the sets field instead. + */ + recentSets?: Maybe>>; + /** Recent standings */ + recentStandings?: Maybe>>; + /** Set history for this player. */ + sets?: Maybe; + user?: Maybe; +}; + + +/** A player */ +export type PlayerRankingsArgs = { + limit?: InputMaybe; + videogameId?: InputMaybe; +}; + + +/** A player */ +export type PlayerRecentSetsArgs = { + opponentId?: InputMaybe; +}; + + +/** A player */ +export type PlayerRecentStandingsArgs = { + limit?: InputMaybe; + videogameId?: InputMaybe; +}; + + +/** A player */ +export type PlayerSetsArgs = { + filters?: InputMaybe; + page?: InputMaybe; + perPage?: InputMaybe; +}; + +/** A player's ranks */ +export type PlayerRank = { + __typename?: 'PlayerRank'; + id?: Maybe; + /** The player's placement on the ranking */ + rank?: Maybe; + title?: Maybe; +}; + +/** An OAuth ProfileAuthorization object */ +export type ProfileAuthorization = { + __typename?: 'ProfileAuthorization'; + /** The id given by the external service */ + externalId?: Maybe; + /** The username given by the external service (including discriminator if discord) */ + externalUsername?: Maybe; + id?: Maybe; + stream?: Maybe; + /** The name of the external service providing this auth i.e. "twitch" */ + type?: Maybe; + url?: Maybe; +}; + +/** A connection between a placement in an origin phase group to a destination seed. */ +export type Progression = { + __typename?: 'Progression'; + id?: Maybe; + originOrder?: Maybe; + originPhase?: Maybe; + originPhaseGroup?: Maybe; + originPlacement?: Maybe; +}; + +export type Query = { + __typename?: 'Query'; + /** Returns the authenticated user */ + currentUser?: Maybe; + /** Returns an entrant given its id */ + entrant?: Maybe; + /** Returns an event given its id or slug */ + event?: Maybe; + /** Returns a league given its id or slug */ + league?: Maybe; + /** Paginated, filterable list of leagues */ + leagues?: Maybe; + /** Returns a participant given its id */ + participant?: Maybe; + /** Returns a phase given its id */ + phase?: Maybe; + /** Returns a phase group given its id */ + phaseGroup?: Maybe; + /** Returns a player given an id */ + player?: Maybe; + /** Returns a phase seed given its id */ + seed?: Maybe; + /** Returns a set given its id */ + set?: Maybe; + /** A shop entity */ + shop?: Maybe; + /** Returns an stream given its id */ + stream?: Maybe; + /** Returns all the stream queues for a given tournament */ + streamQueue?: Maybe>>; + /** Returns a team given its id */ + team?: Maybe; + /** Returns a tournament given its id or slug */ + tournament?: Maybe; + /** Paginated, filterable list of tournaments */ + tournaments?: Maybe; + /** Returns a user given a user slug of the form user/abc123, or id */ + user?: Maybe; + /** Returns a videogame given its id */ + videogame?: Maybe; + /** Returns paginated list of videogames matching the search criteria. */ + videogames?: Maybe; +}; + + +export type QueryEntrantArgs = { + id: Scalars['ID']['input']; +}; + + +export type QueryEventArgs = { + id?: InputMaybe; + slug?: InputMaybe; +}; + + +export type QueryLeagueArgs = { + id?: InputMaybe; + slug?: InputMaybe; +}; + + +export type QueryLeaguesArgs = { + query: LeagueQuery; +}; + + +export type QueryParticipantArgs = { + id: Scalars['ID']['input']; + isAdmin?: InputMaybe; +}; + + +export type QueryPhaseArgs = { + id?: InputMaybe; +}; + + +export type QueryPhaseGroupArgs = { + id?: InputMaybe; +}; + + +export type QueryPlayerArgs = { + id: Scalars['ID']['input']; +}; + + +export type QuerySeedArgs = { + id?: InputMaybe; +}; + + +export type QuerySetArgs = { + id: Scalars['ID']['input']; +}; + + +export type QueryShopArgs = { + id?: InputMaybe; + slug?: InputMaybe; +}; + + +export type QueryStreamArgs = { + id: Scalars['ID']['input']; +}; + + +export type QueryStreamQueueArgs = { + includePlayerStreams?: InputMaybe; + tournamentId: Scalars['ID']['input']; +}; + + +export type QueryTeamArgs = { + id?: InputMaybe; + inviteCode?: InputMaybe; + slug?: InputMaybe; +}; + + +export type QueryTournamentArgs = { + id?: InputMaybe; + slug?: InputMaybe; +}; + + +export type QueryTournamentsArgs = { + query: TournamentQuery; +}; + + +export type QueryUserArgs = { + id?: InputMaybe; + slug?: InputMaybe; +}; + + +export type QueryVideogameArgs = { + id?: InputMaybe; + slug?: InputMaybe; +}; + + +export type QueryVideogamesArgs = { + query: VideogameQuery; +}; + +/** Race specific bracket configuration */ +export type RaceBracketConfig = BracketConfig & { + __typename?: 'RaceBracketConfig'; + automaticEndTime?: Maybe; + automaticStartTime?: Maybe; + bracketType?: Maybe; + goalTargetComparator?: Maybe; + goalTargetValue?: Maybe; + id?: Maybe; + limitMode?: Maybe; + limitValue?: Maybe; + raceType?: Maybe; +}; + +/** Enforces limits on the amount of allowable Race submissions */ +export enum RaceLimitMode { + BestAll = 'BEST_ALL', + FirstAll = 'FIRST_ALL', + Playtime = 'PLAYTIME' +} + +/** Race specific match configuration */ +export type RaceMatchConfig = MatchConfig & { + __typename?: 'RaceMatchConfig'; + bracketType?: Maybe; + id?: Maybe; + /** Can players report results? */ + playerReportingEnabled?: Maybe; + /** Accepted methods of verification that players can use */ + verificationMethods?: Maybe>>; + /** Are players required to submit verification of their reported results? */ + verificationRequired?: Maybe; +}; + +/** Race type */ +export enum RaceType { + Goals = 'GOALS', + Timed = 'TIMED' +} + +export type ResetAffectedData = { + __typename?: 'ResetAffectedData'; + affectedPhaseGroupCount?: Maybe; + affectedSetCount?: Maybe; + affectedSets?: Maybe>>; +}; + +export type ResolveConflictsLockedSeedConfig = { + eventId: Scalars['ID']['input']; + numSeeds: Scalars['Int']['input']; +}; + +export type ResolveConflictsOptions = { + lockedSeeds?: InputMaybe>>; +}; + +/** A round within a phase group */ +export type Round = { + __typename?: 'Round'; + /** + * If applicable, bestOf is the number of games + * one must win a majority out of to win a set in this round + */ + bestOf?: Maybe; + id?: Maybe; + /** Indicates this round's order in the phase group */ + number?: Maybe; + /** The time that this round is scheduled to start at */ + startAt?: Maybe; +}; + +/** + * The score that led to this standing being awarded. The meaning of this field can + * vary by standing type and is not used for some standing types. + */ +export type Score = { + __typename?: 'Score'; + /** Like value, but formatted for race format events. Formatted according to the race config for the front end to use. */ + displayValue?: Maybe; + /** The name of this score. e.g. "Kills" or "Stocks" */ + label?: Maybe; + /** The raw score value */ + value?: Maybe; +}; + +/** A seed for an entrant */ +export type Seed = { + __typename?: 'Seed'; + /** Map of Participant ID to checked in boolean */ + checkedInParticipants?: Maybe; + entrant?: Maybe; + groupSeedNum?: Maybe; + id?: Maybe; + isBye?: Maybe; + phase?: Maybe; + phaseGroup?: Maybe; + placeholderName?: Maybe; + placement?: Maybe; + /** The player(s) associated with this seed's entrant */ + players?: Maybe>>; + progressionSeedId?: Maybe; + /** Source progression information */ + progressionSource?: Maybe; + seedNum?: Maybe; + /** Entrant's win/loss record for this standing. Scores do not include byes. */ + setRecordWithoutByes?: Maybe; + standings?: Maybe>>; +}; + + +/** A seed for an entrant */ +export type SeedSetRecordWithoutByesArgs = { + phaseGroupId: Scalars['ID']['input']; +}; + + +/** A seed for an entrant */ +export type SeedStandingsArgs = { + containerType?: InputMaybe; +}; + +export type SeedConnection = { + __typename?: 'SeedConnection'; + nodes?: Maybe>>; + pageInfo?: Maybe; +}; + +export type SeedPageFilter = { + checkInState?: InputMaybe>>; + entrantName?: InputMaybe; + eventCheckInGroupId?: InputMaybe; + eventId?: InputMaybe; + id?: InputMaybe; + phaseGroupId?: InputMaybe>>; + phaseId?: InputMaybe>>; + search?: InputMaybe; +}; + +export type SeedPaginationQuery = { + filter?: InputMaybe; + page?: InputMaybe; + /** How many nodes to return for the page. Maximum value of 500 */ + perPage?: InputMaybe; + sortBy?: InputMaybe; +}; + +/** A set */ +export type Set = { + __typename?: 'Set'; + /** The time this set was marked as completed */ + completedAt?: Maybe; + /** The time this set was created */ + createdAt?: Maybe; + displayScore?: Maybe; + /** Event that this set belongs to. */ + event?: Maybe; + /** Full round text of this set. */ + fullRoundText?: Maybe; + game?: Maybe; + games?: Maybe>>; + /** Whether this set contains a placeholder entrant */ + hasPlaceholder?: Maybe; + id?: Maybe; + /** The letters that describe a unique identifier within the pool. Eg. F, AT */ + identifier?: Maybe; + images?: Maybe>>; + lPlacement?: Maybe; + /** Phase group that this Set belongs to. */ + phaseGroup?: Maybe; + /** The sets that are affected from resetting this set */ + resetAffectedData?: Maybe; + /** The round number of the set. Negative numbers are losers bracket */ + round?: Maybe; + /** + * Indicates whether the set is in best of or total games mode. This instructs + * which field is used to figure out how many games are in this set. + */ + setGamesType?: Maybe; + /** A possible spot in a set. Use this to get all entrants in a set. Use this for all bracket types (FFA, elimination, etc) */ + slots?: Maybe>>; + /** The start time of the Set. If there is no startAt time on the Set, will pull it from phaseGroup rounds configuration. */ + startAt?: Maybe; + startedAt?: Maybe; + state?: Maybe; + /** Tournament event station for a set */ + station?: Maybe; + /** Tournament event stream for a set */ + stream?: Maybe; + /** If setGamesType is in total games mode, this defined the number of games in the set. */ + totalGames?: Maybe; + /** Url of a VOD for this set */ + vodUrl?: Maybe; + wPlacement?: Maybe; + winnerId?: Maybe; +}; + + +/** A set */ +export type SetDisplayScoreArgs = { + mainEntrantId?: InputMaybe; +}; + + +/** A set */ +export type SetGameArgs = { + orderNum: Scalars['Int']['input']; +}; + + +/** A set */ +export type SetImagesArgs = { + type?: InputMaybe; +}; + + +/** A set */ +export type SetSlotsArgs = { + includeByes?: InputMaybe; +}; + +export type SetConnection = { + __typename?: 'SetConnection'; + nodes?: Maybe>>; + pageInfo?: Maybe; +}; + +/** Filter Sets by geographical constraints. */ +export type SetFilterLocation = { + /** Only return Sets in this country. Expects a valid two-letter country code */ + country?: InputMaybe; + distanceFrom?: InputMaybe; + /** Only return Sets in this state. Only applicable to US states */ + state?: InputMaybe; +}; + +/** Only return Sets that are a certain distance away from a specified point */ +export type SetFilterLocationDistanceFrom = { + /** Point at which to perform distance calculation */ + point?: InputMaybe; + /** Distance from the point to include results in */ + radius?: InputMaybe; +}; + +export type SetFilterLocationDistanceFromPoint = { + lat?: InputMaybe; + lon?: InputMaybe; +}; + +export type SetFilters = { + /** Only return Sets for these Entrants */ + entrantIds?: InputMaybe>>; + /** Only return Sets for this Entrant size. For example, to fetch 1v1 Sets only, filter by an entrantSize of 1 */ + entrantSize?: InputMaybe>>; + /** Only return Sets in these Events */ + eventIds?: InputMaybe>>; + /** Only return Sets that have an attached VOD */ + hasVod?: InputMaybe; + /** Do not return empty Sets. For example, set this to true to filter out sets that are waiting for progressions. */ + hideEmpty?: InputMaybe; + /** Only return Sets that are in an Online event. If omitted, Sets for both online and offline Events are returned */ + isEventOnline?: InputMaybe; + /** Only return Sets in certain geographical areas. */ + location?: InputMaybe; + /** Only return Sets for these Participants */ + participantIds?: InputMaybe>>; + /** Only return Sets in these PhaseGroups */ + phaseGroupIds?: InputMaybe>>; + /** Only return Sets in these Phases */ + phaseIds?: InputMaybe>>; + /** Only return Sets for these Players */ + playerIds?: InputMaybe>>; + /** Only return Sets for these Rounds */ + roundNumber?: InputMaybe; + /** Return sets that contain a bye */ + showByes?: InputMaybe; + /** Only returns Sets that are in these states */ + state?: InputMaybe>>; + /** Only return Sets that are assigned to these Station IDs */ + stationIds?: InputMaybe>>; + /** Only return Sets that are assigned to these Station numbers */ + stationNumbers?: InputMaybe>>; + /** Only return Sets in these Tournaments */ + tournamentIds?: InputMaybe>>; + /** Only return sets created or updated since this timestamp */ + updatedAfter?: InputMaybe; +}; + +/** A slot in a set where a seed currently or will eventually exist in order to participate in the set. */ +export type SetSlot = { + __typename?: 'SetSlot'; + entrant?: Maybe; + id?: Maybe; + /** Pairs with prereqType, is the ID of the prereq. */ + prereqId?: Maybe; + /** Given a set prereq type, defines the placement required in the origin set to end up in this slot. */ + prereqPlacement?: Maybe; + /** Describes where the entity in this slot comes from. */ + prereqType?: Maybe; + seed?: Maybe; + /** The index of the slot. Unique per set. */ + slotIndex?: Maybe; + /** The standing within this set for the seed currently assigned to this slot. */ + standing?: Maybe; +}; + +/** Different sort type configurations used when displaying multiple sets */ +export enum SetSortType { + /** Sets are sorted in the suggested order that they be called to be played. The order of completed sets is reversed. */ + CallOrder = 'CALL_ORDER', + /** Sets are sorted by relevancy dependent on the state and progress of the event. */ + Magic = 'MAGIC', + /** Sets will not be sorted. */ + None = 'NONE', + /** Sets are sorted in the order that they were started. */ + Recent = 'RECENT', + /** Sets sorted by round and identifier */ + Round = 'ROUND', + /** Deprecated. This is equivalent to CALL_ORDER */ + Standard = 'STANDARD' +} + +/** A shop */ +export type Shop = { + __typename?: 'Shop'; + id?: Maybe; + levels?: Maybe; + messages?: Maybe; + name?: Maybe; + slug?: Maybe; + url?: Maybe; +}; + + +/** A shop */ +export type ShopLevelsArgs = { + query?: InputMaybe; +}; + + +/** A shop */ +export type ShopMessagesArgs = { + query?: InputMaybe; +}; + +/** A shop level */ +export type ShopLevel = { + __typename?: 'ShopLevel'; + currAmount?: Maybe; + description?: Maybe; + goalAmount?: Maybe; + id?: Maybe; + images?: Maybe>>; + name?: Maybe; +}; + + +/** A shop level */ +export type ShopLevelImagesArgs = { + type?: InputMaybe; +}; + +export type ShopLevelConnection = { + __typename?: 'ShopLevelConnection'; + nodes?: Maybe>>; + pageInfo?: Maybe; +}; + +export type ShopLevelsQuery = { + page?: InputMaybe; + /** How many nodes to return for the page. Maximum value of 500 */ + perPage?: InputMaybe; + sortBy?: InputMaybe; +}; + +/** The message and player info for a shop order */ +export type ShopOrderMessage = { + __typename?: 'ShopOrderMessage'; + /** The player's gamertag. Returns null if anonymous message type */ + gamertag?: Maybe; + id?: Maybe; + /** The order message */ + message?: Maybe; + /** The player's name. Returns null unless name & tag display is selected */ + name?: Maybe; + /** The player who left the comment */ + player?: Maybe; + /** The total order amount */ + total?: Maybe; +}; + +export type ShopOrderMessageConnection = { + __typename?: 'ShopOrderMessageConnection'; + nodes?: Maybe>>; + pageInfo?: Maybe; +}; + +export type ShopOrderMessagesQuery = { + page?: InputMaybe; + /** How many nodes to return for the page. Maximum value of 500 */ + perPage?: InputMaybe; + sortBy?: InputMaybe; +}; + +/** Represents the name of the third-party social service (e.g Twitter) for OAuth */ +export enum SocialConnectionType { + Discord = 'DISCORD', + Mixer = 'MIXER', + Twitch = 'TWITCH', + Twitter = 'TWITTER', + Xbox = 'XBOX' +} + +/** Video Stage */ +export type Stage = { + __typename?: 'Stage'; + id?: Maybe; + /** Stage name */ + name?: Maybe; +}; + +/** A standing indicates the placement of something within a container. */ +export type Standing = { + __typename?: 'Standing'; + /** + * The containing entity that contextualizes this standing. Event standings, for + * example, represent an entrant's standing in the entire event vs. Set standings + * which is an entrant's standing in only a single set within an event. + */ + container?: Maybe; + /** If the entity this standing is assigned to can be resolved into an entrant, this will provide the entrant. */ + entrant?: Maybe; + id?: Maybe; + isFinal?: Maybe; + /** Metadata that goes along with this standing. Can take on different forms based on standing group type and settings. */ + metadata?: Maybe; + placement?: Maybe; + /** The player(s) tied to this standing's entity */ + player?: Maybe; + /** @deprecated The "placement" field is identical and will eventually replace "standing" */ + standing?: Maybe; + stats?: Maybe; + totalPoints?: Maybe; +}; + +export type StandingConnection = { + __typename?: 'StandingConnection'; + nodes?: Maybe>>; + pageInfo?: Maybe; +}; + +/** The containing entity that this standing is for */ +export type StandingContainer = Event | PhaseGroup | Set | Tournament; + +export type StandingGroupStandingPageFilter = { + page?: InputMaybe; + perPage?: InputMaybe; + sortBy?: InputMaybe; +}; + +export type StandingPageFilter = { + id?: InputMaybe; + ids?: InputMaybe>>; + search?: InputMaybe; +}; + +export type StandingPaginationQuery = { + filter?: InputMaybe; + page?: InputMaybe; + /** How many nodes to return for the page. Maximum value of 500 */ + perPage?: InputMaybe; + sortBy?: InputMaybe; +}; + +/** Any stats related to this standing. This type is experimental and very likely to change in the future. */ +export type StandingStats = { + __typename?: 'StandingStats'; + score?: Maybe; +}; + +export type StationFilter = { + page?: InputMaybe; + perPage?: InputMaybe; +}; + +export type StationUpsertInput = { + clusterId?: InputMaybe; + number: Scalars['Int']['input']; +}; + +/** Stations, such as a stream setup, at an event */ +export type Stations = { + __typename?: 'Stations'; + canAutoAssign?: Maybe; + clusterNumber?: Maybe; + clusterPrefix?: Maybe; + enabled?: Maybe; + id?: Maybe; + identifier?: Maybe; + numSetups?: Maybe; + number?: Maybe; + prefix?: Maybe; + queue?: Maybe; + queueDepth?: Maybe; + state?: Maybe; + updatedAt?: Maybe; +}; + +export type StationsConnection = { + __typename?: 'StationsConnection'; + nodes?: Maybe>>; + pageInfo?: Maybe; +}; + +/** A Stream object */ +export type Stream = { + __typename?: 'Stream'; + id?: Maybe; + /** Whether the stream is currently live. May be slightly delayed. */ + isOnline?: Maybe; + /** The name of the stream */ + name?: Maybe; + /** The name of the external service providing this auth i.e. "twitch" */ + type?: Maybe; +}; + +/** A Stream queue object */ +export type StreamQueue = { + __typename?: 'StreamQueue'; + id?: Maybe; + /** The sets on the stream */ + sets?: Maybe>>; + /** The stream on the queue */ + stream?: Maybe; +}; + +/** Represents the source of a stream */ +export enum StreamSource { + /** Stream is on smashcast.tv channel */ + Hitbox = 'HITBOX', + /** Stream is on a mixer.com channel */ + Mixer = 'MIXER', + /** Stream is on a stream.me channel */ + Streamme = 'STREAMME', + /** Stream is on twitch.tv channel */ + Twitch = 'TWITCH', + /** Stream is on a youtube.com channel */ + Youtube = 'YOUTUBE' +} + +/** Represents the type of stream service */ +export enum StreamType { + Mixer = 'MIXER', + Twitch = 'TWITCH', + Youtube = 'YOUTUBE' +} + +/** Tournament Stream */ +export type Streams = { + __typename?: 'Streams'; + enabled?: Maybe; + followerCount?: Maybe; + id?: Maybe; + isOnline?: Maybe; + numSetups?: Maybe; + parentStreamId?: Maybe; + streamGame?: Maybe; + streamId?: Maybe; + streamLogo?: Maybe; + streamName?: Maybe; + streamSource?: Maybe; + streamStatus?: Maybe; + streamType?: Maybe; + streamTypeId?: Maybe; +}; + +/** A team, either at the global level or within the context of an event */ +export type Team = { + /** Uniquely identifying token for team. Same as the hashed part of the slug */ + discriminator?: Maybe; + /** @deprecated Use the entrant field off the EventTeam type */ + entrant?: Maybe; + /** @deprecated Use the event field off the EventTeam type */ + event?: Maybe; + id?: Maybe; + images?: Maybe>>; + members?: Maybe>>; + name?: Maybe; +}; + + +/** A team, either at the global level or within the context of an event */ +export type TeamImagesArgs = { + type?: InputMaybe; +}; + + +/** A team, either at the global level or within the context of an event */ +export type TeamMembersArgs = { + status?: InputMaybe>>; +}; + +/** A set of actions available for a team to take */ +export type TeamActionSet = ActionSet & { + __typename?: 'TeamActionSet'; + id?: Maybe; +}; + +export type TeamConnection = { + __typename?: 'TeamConnection'; + nodes?: Maybe>>; + pageInfo?: Maybe; +}; + +/** A member of a team */ +export type TeamMember = { + __typename?: 'TeamMember'; + id?: Maybe; + isAlternate?: Maybe; + isCaptain?: Maybe; + /** The type of the team member */ + memberType?: Maybe; + participant?: Maybe; + player?: Maybe; + /** The status of the team member */ + status?: Maybe; +}; + +/** Membership status of a team member */ +export enum TeamMemberStatus { + Accepted = 'ACCEPTED', + Alum = 'ALUM', + Hiatus = 'HIATUS', + Invited = 'INVITED', + OpenSpot = 'OPEN_SPOT', + Request = 'REQUEST', + Unknown = 'UNKNOWN' +} + +/** Membership type of a team member */ +export enum TeamMemberType { + Player = 'PLAYER', + Staff = 'STAFF' +} + +export type TeamPaginationFilter = { + eventId?: InputMaybe; + eventIds?: InputMaybe>>; + eventState?: InputMaybe; + globalTeamId?: InputMaybe; + isLeague?: InputMaybe; + maxEntrantCount?: InputMaybe; + memberStatus?: InputMaybe>>; + minEntrantCount?: InputMaybe; + past?: InputMaybe; + rosterComplete?: InputMaybe; + rosterIncomplete?: InputMaybe; + search?: InputMaybe; + tournamentId?: InputMaybe; + type?: InputMaybe; + upcoming?: InputMaybe; + videogameId?: InputMaybe>>; +}; + +export type TeamPaginationQuery = { + filter?: InputMaybe; + page?: InputMaybe; + /** How many nodes to return for the page. Maximum value of 500 */ + perPage?: InputMaybe; + sortBy?: InputMaybe; +}; + +/** Team roster size requirements */ +export type TeamRosterSize = { + __typename?: 'TeamRosterSize'; + maxAlternates?: Maybe; + maxPlayers?: Maybe; + minAlternates?: Maybe; + minPlayers?: Maybe; +}; + +export type TopGameFilter = { + /** Array of which # top game you want to filter on.e.g. [2, 3] will filter on the 2nd and 3rd top games */ + gameNums?: InputMaybe>>; +}; + +/** A tournament */ +export type Tournament = { + __typename?: 'Tournament'; + addrState?: Maybe; + /** Admin-only view of admins for this tournament */ + admins?: Maybe>>; + city?: Maybe; + countryCode?: Maybe; + /** When the tournament was created (unix timestamp) */ + createdAt?: Maybe; + currency?: Maybe; + /** When the tournament ends */ + endAt?: Maybe; + /** When does event registration close */ + eventRegistrationClosesAt?: Maybe; + events?: Maybe>>; + /** True if tournament has at least one offline event */ + hasOfflineEvents?: Maybe; + hasOnlineEvents?: Maybe; + hashtag?: Maybe; + id?: Maybe; + images?: Maybe>>; + /** True if tournament has at least one online event */ + isOnline?: Maybe; + /** Is tournament registration open */ + isRegistrationOpen?: Maybe; + lat?: Maybe; + links?: Maybe; + lng?: Maybe; + mapsPlaceId?: Maybe; + /** The tournament name */ + name?: Maybe; + /** Number of attendees including spectators, if public */ + numAttendees?: Maybe; + /** The user who created the tournament */ + owner?: Maybe; + /** Paginated, queryable list of participants */ + participants?: Maybe; + postalCode?: Maybe; + primaryContact?: Maybe; + primaryContactType?: Maybe; + /** Publishing settings for this tournament */ + publishing?: Maybe; + /** When does registration for the tournament end */ + registrationClosesAt?: Maybe; + rules?: Maybe; + /** The short slug used to form the url */ + shortSlug?: Maybe; + /** The slug used to form the url */ + slug?: Maybe; + /** When the tournament Starts */ + startAt?: Maybe; + /** State of the tournament, can be ActivityState::CREATED, ActivityState::ACTIVE, or ActivityState::COMPLETED */ + state?: Maybe; + stations?: Maybe; + streamQueue?: Maybe>>; + streams?: Maybe>>; + /** When is the team creation deadline */ + teamCreationClosesAt?: Maybe; + /** Paginated, queryable list of teams */ + teams?: Maybe; + /** The timezone of the tournament */ + timezone?: Maybe; + /** The type of tournament from TournamentType */ + tournamentType?: Maybe; + /** When the tournament was last modified (unix timestamp) */ + updatedAt?: Maybe; + /** Build Tournament URL */ + url?: Maybe; + venueAddress?: Maybe; + venueName?: Maybe; + /** List of all waves in this tournament */ + waves?: Maybe>>; +}; + + +/** A tournament */ +export type TournamentAdminsArgs = { + roles?: InputMaybe>>; +}; + + +/** A tournament */ +export type TournamentEventsArgs = { + filter?: InputMaybe; + limit?: InputMaybe; +}; + + +/** A tournament */ +export type TournamentImagesArgs = { + type?: InputMaybe; +}; + + +/** A tournament */ +export type TournamentParticipantsArgs = { + isAdmin?: InputMaybe; + query: ParticipantPaginationQuery; +}; + + +/** A tournament */ +export type TournamentStationsArgs = { + page?: InputMaybe; + perPage?: InputMaybe; +}; + + +/** A tournament */ +export type TournamentTeamsArgs = { + query: TeamPaginationQuery; +}; + + +/** A tournament */ +export type TournamentUrlArgs = { + relative?: InputMaybe; + tab?: InputMaybe; +}; + +export type TournamentConnection = { + __typename?: 'TournamentConnection'; + nodes?: Maybe>>; + pageInfo?: Maybe; +}; + +export type TournamentLinks = { + __typename?: 'TournamentLinks'; + discord?: Maybe; + facebook?: Maybe; +}; + +export type TournamentLocationFilter = { + /** e.g. 50mi */ + distance?: InputMaybe; + /** Latitude, Longitude */ + distanceFrom?: InputMaybe; +}; + +export type TournamentPageFilter = { + activeShops?: InputMaybe; + addrState?: InputMaybe; + afterDate?: InputMaybe; + beforeDate?: InputMaybe; + computedUpdatedAt?: InputMaybe; + countryCode?: InputMaybe; + hasBannerImages?: InputMaybe; + hasOnlineEvents?: InputMaybe; + id?: InputMaybe; + ids?: InputMaybe>>; + /** If true, filter to only tournaments the currently authed user is an admin of */ + isCurrentUserAdmin?: InputMaybe; + isFeatured?: InputMaybe; + isLeague?: InputMaybe; + location?: InputMaybe; + name?: InputMaybe; + /** ID of the user that owns this tournament. */ + ownerId?: InputMaybe; + past?: InputMaybe; + publiclySearchable?: InputMaybe; + published?: InputMaybe; + regOpen?: InputMaybe; + sortByScore?: InputMaybe; + staffPicks?: InputMaybe; + topGames?: InputMaybe; + upcoming?: InputMaybe; + venueName?: InputMaybe; + videogameIds?: InputMaybe>>; +}; + +export enum TournamentPaginationSort { + ComputedUpdatedAt = 'computedUpdatedAt', + EndAt = 'endAt', + EventRegistrationClosesAt = 'eventRegistrationClosesAt', + StartAt = 'startAt' +} + +export type TournamentQuery = { + filter?: InputMaybe; + page?: InputMaybe; + /** How many nodes to return for the page. Maximum value of 500 */ + perPage?: InputMaybe; + sort?: InputMaybe; + sortBy?: InputMaybe; +}; + +export type TournamentRegistrationInput = { + eventIds?: InputMaybe>>; +}; + +export type UpdatePhaseSeedInfo = { + phaseGroupId?: InputMaybe; + seedId: Scalars['ID']['input']; + seedNum: Scalars['ID']['input']; +}; + +export type UpdatePhaseSeedingOptions = { + /** Validate that seedMapping exactly accounts for all entrants in the phase */ + strictMode?: InputMaybe; +}; + +/** A user */ +export type User = { + __typename?: 'User'; + /** Authorizations to external services (i.e. Twitch, Twitter) */ + authorizations?: Maybe>>; + bio?: Maybe; + /** Public facing user birthday that respects user publishing settings */ + birthday?: Maybe; + /** Uniquely identifying token for user. Same as the hashed part of the slug */ + discriminator?: Maybe; + email?: Maybe; + /** Events this user has competed in */ + events?: Maybe; + genderPronoun?: Maybe; + id?: Maybe; + images?: Maybe>>; + /** Leagues this user has competed in */ + leagues?: Maybe; + /** Public location info for this user */ + location?: Maybe
; + /** Public facing user name that respects user publishing settings */ + name?: Maybe; + /** player for user */ + player?: Maybe; + slug?: Maybe; + /** Tournaments this user is organizing or competing in */ + tournaments?: Maybe; +}; + + +/** A user */ +export type UserAuthorizationsArgs = { + types?: InputMaybe>>; +}; + + +/** A user */ +export type UserEventsArgs = { + query?: InputMaybe; +}; + + +/** A user */ +export type UserImagesArgs = { + type?: InputMaybe; +}; + + +/** A user */ +export type UserLeaguesArgs = { + query?: InputMaybe; +}; + + +/** A user */ +export type UserTournamentsArgs = { + query?: InputMaybe; +}; + +export type UserEventsPaginationFilter = { + eventType?: InputMaybe; + location?: InputMaybe; + maxEntrantCount?: InputMaybe; + minEntrantCount?: InputMaybe; + search?: InputMaybe; + videogameId?: InputMaybe>>; +}; + +export type UserEventsPaginationQuery = { + filter?: InputMaybe; + page?: InputMaybe; + /** How many nodes to return for the page. Maximum value of 500 */ + perPage?: InputMaybe; + sortBy?: InputMaybe; +}; + +export type UserLeaguesPaginationFilter = { + past?: InputMaybe; + search?: InputMaybe; + upcoming?: InputMaybe; + videogameId?: InputMaybe>>; +}; + +export type UserLeaguesPaginationQuery = { + filter?: InputMaybe; + page?: InputMaybe; + /** How many nodes to return for the page. Maximum value of 500 */ + perPage?: InputMaybe; + sortBy?: InputMaybe; +}; + +export type UserTournamentsPaginationFilter = { + excludeId?: InputMaybe>>; + past?: InputMaybe; + search?: InputMaybe; + tournamentView?: InputMaybe; + upcoming?: InputMaybe; + videogameId?: InputMaybe>>; +}; + +export type UserTournamentsPaginationQuery = { + filter?: InputMaybe; + page?: InputMaybe; + /** How many nodes to return for the page. Maximum value of 500 */ + perPage?: InputMaybe; + sortBy?: InputMaybe; +}; + +/** A videogame */ +export type Videogame = { + __typename?: 'Videogame'; + /** All characters for this videogame */ + characters?: Maybe>>; + displayName?: Maybe; + id?: Maybe; + images?: Maybe>>; + name?: Maybe; + slug?: Maybe; + /** All stages for this videogame */ + stages?: Maybe>>; +}; + + +/** A videogame */ +export type VideogameImagesArgs = { + type?: InputMaybe; +}; + +export type VideogameConnection = { + __typename?: 'VideogameConnection'; + nodes?: Maybe>>; + pageInfo?: Maybe; +}; + +export type VideogamePageFilter = { + forUser?: InputMaybe; + id?: InputMaybe>>; + name?: InputMaybe; +}; + +export type VideogameQuery = { + filter?: InputMaybe; + page?: InputMaybe; + /** How many nodes to return for the page. Maximum value of 500 */ + perPage?: InputMaybe; + sortBy?: InputMaybe; +}; + +/** A wave in a tournament */ +export type Wave = { + __typename?: 'Wave'; + id?: Maybe; + /** The Wave Identifier */ + identifier?: Maybe; + /** Unix time the wave is scheduled to start. */ + startAt?: Maybe; +}; + +export type WaveUpsertInput = { + endAt: Scalars['Timestamp']['input']; + identifier: Scalars['String']['input']; + startAt: Scalars['Timestamp']['input']; +}; + +export type EventEntrantsQueryVariables = Exact<{ + eventSlug?: InputMaybe; +}>; + + +export type EventEntrantsQuery = { __typename?: 'Query', event?: { __typename?: 'Event', id?: string | null, name?: string | null } | null }; + + +export const EventEntrantsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EventEntrants"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"eventSlug"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"event"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"eventSlug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file diff --git a/src/startgg-gql/generated/index.ts b/src/startgg-gql/generated/index.ts new file mode 100644 index 000000000..f51599168 --- /dev/null +++ b/src/startgg-gql/generated/index.ts @@ -0,0 +1,2 @@ +export * from "./fragment-masking"; +export * from "./gql"; \ No newline at end of file diff --git a/src/startgg-gql/index.ts b/src/startgg-gql/index.ts new file mode 100644 index 000000000..deb490bf5 --- /dev/null +++ b/src/startgg-gql/index.ts @@ -0,0 +1,16 @@ +import { EventEntrantsDocument } from "./generated/graphql"; +import { Client, cacheExchange, fetchExchange } from "@urql/core"; + +const client = new Client({ + url: "https://api.start.gg/gql/alpha", + fetchOptions: { + headers: { + Authorization: `Bearer ${process.env.STARTGG_TOKEN}`, + }, + }, + exchanges: [cacheExchange, fetchExchange], +}); + +export function getEventEntrants(slug: string) { + return client.query(EventEntrantsDocument, { eventSlug: slug }); +} diff --git a/src/startgg-gql/queries.ts b/src/startgg-gql/queries.ts new file mode 100644 index 000000000..9f6e66085 --- /dev/null +++ b/src/startgg-gql/queries.ts @@ -0,0 +1,10 @@ +import { gql } from "@urql/core"; + +gql` + query EventEntrants($eventSlug: String) { + event(slug: $eventSlug) { + id + name + } + } +`; diff --git a/webpack.config.js b/webpack.config.js index a7f7ac13a..0c43b5464 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,3 +1,4 @@ +require("dotenv").setup(); const fs = require("fs"); const { resolve, basename } = require("path"); @@ -191,6 +192,7 @@ module.exports = function (env = {}, argv = {}) { }; }), ), + "process.env.STARTGG_TOKEN": JSON.stringify(process.env.STARTGG_TOKEN), }), new MiniCssExtractPlugin({ filename: "[name].[chunkhash:5].css", diff --git a/yarn.lock b/yarn.lock index e5c19943e..2fae8661c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,6 +5,18 @@ __metadata: version: 8 cacheKey: 10 +"@0no-co/graphql.web@npm:^1.0.5": + version: 1.0.7 + resolution: "@0no-co/graphql.web@npm:1.0.7" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 + peerDependenciesMeta: + graphql: + optional: true + checksum: 10/8be5d80ba57df759a11f905ba7ac4bc6e9bc75034804bb679db4fa4eed3396f97b0191421031ba2d94af259fa7d55042a152e3f94294eba62ae81591d41bad87 + languageName: node + linkType: hard + "@aashutoshrathi/word-wrap@npm:^1.2.3": version: 1.2.6 resolution: "@aashutoshrathi/word-wrap@npm:1.2.6" @@ -33,6 +45,44 @@ __metadata: languageName: node linkType: hard +"@ardatan/relay-compiler@npm:12.0.0": + version: 12.0.0 + resolution: "@ardatan/relay-compiler@npm:12.0.0" + dependencies: + "@babel/core": "npm:^7.14.0" + "@babel/generator": "npm:^7.14.0" + "@babel/parser": "npm:^7.14.0" + "@babel/runtime": "npm:^7.0.0" + "@babel/traverse": "npm:^7.14.0" + "@babel/types": "npm:^7.0.0" + babel-preset-fbjs: "npm:^3.4.0" + chalk: "npm:^4.0.0" + fb-watchman: "npm:^2.0.0" + fbjs: "npm:^3.0.0" + glob: "npm:^7.1.1" + immutable: "npm:~3.7.6" + invariant: "npm:^2.2.4" + nullthrows: "npm:^1.1.1" + relay-runtime: "npm:12.0.0" + signedsource: "npm:^1.0.0" + yargs: "npm:^15.3.1" + peerDependencies: + graphql: "*" + bin: + relay-compiler: bin/relay-compiler + checksum: 10/60896560fd282ccc9e705fa18c685d23783f97670fa44be287beaf9d49acfd1a6bbc19daf3e55d9cffdf385ef883be36f7acf5bdcf61c46483e31db9e4e71884 + languageName: node + linkType: hard + +"@ardatan/sync-fetch@npm:^0.0.1": + version: 0.0.1 + resolution: "@ardatan/sync-fetch@npm:0.0.1" + dependencies: + node-fetch: "npm:^2.6.1" + checksum: 10/ee21741badecb18fb9a18a404275e25272f67ade914f98885de79ccecba3403b8a6357e6b033a028e24f0d902197dd541655309d7789ebacd7ad981bf1f12618 + languageName: node + linkType: hard + "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.24.7": version: 7.24.7 resolution: "@babel/code-frame@npm:7.24.7" @@ -43,6 +93,13 @@ __metadata: languageName: node linkType: hard +"@babel/compat-data@npm:^7.20.5, @babel/compat-data@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/compat-data@npm:7.24.8" + checksum: 10/6989b8a61782d9c6c7a1fc58b4efd4fb68e5f5a5b6be3463a3de3752f39a30d21438b8b4485c18cb6b8d7f29e07f79d79639caa08737fae57838e81d7da055c0 + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.24.7": version: 7.24.7 resolution: "@babel/compat-data@npm:7.24.7" @@ -50,6 +107,29 @@ __metadata: languageName: node linkType: hard +"@babel/core@npm:^7.14.0, @babel/core@npm:^7.22.9": + version: 7.24.8 + resolution: "@babel/core@npm:7.24.8" + dependencies: + "@ampproject/remapping": "npm:^2.2.0" + "@babel/code-frame": "npm:^7.24.7" + "@babel/generator": "npm:^7.24.8" + "@babel/helper-compilation-targets": "npm:^7.24.8" + "@babel/helper-module-transforms": "npm:^7.24.8" + "@babel/helpers": "npm:^7.24.8" + "@babel/parser": "npm:^7.24.8" + "@babel/template": "npm:^7.24.7" + "@babel/traverse": "npm:^7.24.8" + "@babel/types": "npm:^7.24.8" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10/79818e6e8ecd5f50ffbfb8dfb1748928e6e17b198bd8da0d6f725bf67aece5141020cd3aba56dc425a33e118c0e80e5569ad6fa615897e49726087dd875279d7 + languageName: node + linkType: hard + "@babel/core@npm:^7.24": version: 7.24.7 resolution: "@babel/core@npm:7.24.7" @@ -73,6 +153,18 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.14.0, @babel/generator@npm:^7.18.13, @babel/generator@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/generator@npm:7.24.8" + dependencies: + "@babel/types": "npm:^7.24.8" + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.25" + jsesc: "npm:^2.5.1" + checksum: 10/dc1bd931120f93e7a5b35fdf66c13ca56b966b07ee9ba124f7e24b1905cbcf7d7891cc7c281961876eff9fcff67c46652cce89847665e263bc04d283d4343164 + languageName: node + linkType: hard + "@babel/generator@npm:^7.24.7": version: 7.24.7 resolution: "@babel/generator@npm:7.24.7" @@ -104,6 +196,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-compilation-targets@npm:^7.20.7, @babel/helper-compilation-targets@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helper-compilation-targets@npm:7.24.8" + dependencies: + "@babel/compat-data": "npm:^7.24.8" + "@babel/helper-validator-option": "npm:^7.24.8" + browserslist: "npm:^4.23.1" + lru-cache: "npm:^5.1.1" + semver: "npm:^6.3.1" + checksum: 10/3489280d07b871af565b32f9b11946ff9a999fac0db9bec5df960760f6836c7a4b52fccb9d64229ccce835d37a43afb85659beb439ecedde04dcea7eb062a143 + languageName: node + linkType: hard + "@babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-compilation-targets@npm:7.24.7" @@ -117,6 +222,25 @@ __metadata: languageName: node linkType: hard +"@babel/helper-create-class-features-plugin@npm:^7.18.6": + version: 7.24.8 + resolution: "@babel/helper-create-class-features-plugin@npm:7.24.8" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.24.7" + "@babel/helper-environment-visitor": "npm:^7.24.7" + "@babel/helper-function-name": "npm:^7.24.7" + "@babel/helper-member-expression-to-functions": "npm:^7.24.8" + "@babel/helper-optimise-call-expression": "npm:^7.24.7" + "@babel/helper-replace-supers": "npm:^7.24.7" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" + "@babel/helper-split-export-declaration": "npm:^7.24.7" + semver: "npm:^6.3.1" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10/a779c5356fcc4881e807d85d973fd37e99e773fe95837b0f6582ca9a89331f84e5f26b0b6aa9a101181325b73cf3f54081d178b657a79819b8abadc53b0ea8ec + languageName: node + linkType: hard + "@babel/helper-create-class-features-plugin@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-create-class-features-plugin@npm:7.24.7" @@ -202,6 +326,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-member-expression-to-functions@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helper-member-expression-to-functions@npm:7.24.8" + dependencies: + "@babel/traverse": "npm:^7.24.8" + "@babel/types": "npm:^7.24.8" + checksum: 10/ac878761cfd0a46c081cda0da75cc186f922cf16e8ecdd0c4fb6dca4330d9fe4871b41a9976224cf9669c9e7fe0421b5c27349f2e99c125fa0be871b327fa770 + languageName: node + linkType: hard + "@babel/helper-module-imports@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-module-imports@npm:7.24.7" @@ -227,6 +361,21 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-transforms@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helper-module-transforms@npm:7.24.8" + dependencies: + "@babel/helper-environment-visitor": "npm:^7.24.7" + "@babel/helper-module-imports": "npm:^7.24.7" + "@babel/helper-simple-access": "npm:^7.24.7" + "@babel/helper-split-export-declaration": "npm:^7.24.7" + "@babel/helper-validator-identifier": "npm:^7.24.7" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10/912ad994da126c3150d8f8702030380849608094a7a352523ffa8e697080da9358d63af2582d38902c929839f394bbc6f1ae4921ba132ba3f65f27f0696aa2c7 + languageName: node + linkType: hard + "@babel/helper-optimise-call-expression@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-optimise-call-expression@npm:7.24.7" @@ -243,6 +392,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-plugin-utils@npm:^7.20.2, @babel/helper-plugin-utils@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helper-plugin-utils@npm:7.24.8" + checksum: 10/adbc9fc1142800a35a5eb0793296924ee8057fe35c61657774208670468a9fbfbb216f2d0bc46c680c5fefa785e5ff917cc1674b10bd75cdf9a6aa3444780630 + languageName: node + linkType: hard + "@babel/helper-remap-async-to-generator@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-remap-async-to-generator@npm:7.24.7" @@ -305,6 +461,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helper-string-parser@npm:7.24.8" + checksum: 10/6d1bf8f27dd725ce02bdc6dffca3c95fb9ab8a06adc2edbd9c1c9d68500274230d1a609025833ed81981eff560045b6b38f7b4c6fb1ab19fc90e5004e3932535 + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-validator-identifier@npm:7.24.7" @@ -319,6 +482,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-option@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helper-validator-option@npm:7.24.8" + checksum: 10/a52442dfa74be6719c0608fee3225bd0493c4057459f3014681ea1a4643cd38b68ff477fe867c4b356da7330d085f247f0724d300582fa4ab9a02efaf34d107c + languageName: node + linkType: hard + "@babel/helper-wrap-function@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-wrap-function@npm:7.24.7" @@ -341,6 +511,16 @@ __metadata: languageName: node linkType: hard +"@babel/helpers@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helpers@npm:7.24.8" + dependencies: + "@babel/template": "npm:^7.24.7" + "@babel/types": "npm:^7.24.8" + checksum: 10/61c08a2baa87382a87c7110e9b5574c782603e247b7e6267769ee0e8b7b54b70ff05f16466f05bb318622b7ac28e79b449edff565abf5adcb1adb1b0f42fee9c + languageName: node + linkType: hard + "@babel/highlight@npm:^7.24.7": version: 7.24.7 resolution: "@babel/highlight@npm:7.24.7" @@ -353,6 +533,15 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.14.0, @babel/parser@npm:^7.16.8, @babel/parser@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/parser@npm:7.24.8" + bin: + parser: ./bin/babel-parser.js + checksum: 10/e44b8327da46e8659bc9fb77f66e2dc4364dd66495fb17d046b96a77bf604f0446f1e9a89cf2f011d78fc3f5cdfbae2e9e0714708e1c985988335683b2e781ef + languageName: node + linkType: hard + "@babel/parser@npm:^7.24.7": version: 7.24.7 resolution: "@babel/parser@npm:7.24.7" @@ -410,6 +599,33 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-proposal-class-properties@npm:^7.0.0": + version: 7.18.6 + resolution: "@babel/plugin-proposal-class-properties@npm:7.18.6" + dependencies: + "@babel/helper-create-class-features-plugin": "npm:^7.18.6" + "@babel/helper-plugin-utils": "npm:^7.18.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/49a78a2773ec0db56e915d9797e44fd079ab8a9b2e1716e0df07c92532f2c65d76aeda9543883916b8e0ff13606afeffa67c5b93d05b607bc87653ad18a91422 + languageName: node + linkType: hard + +"@babel/plugin-proposal-object-rest-spread@npm:^7.0.0": + version: 7.20.7 + resolution: "@babel/plugin-proposal-object-rest-spread@npm:7.20.7" + dependencies: + "@babel/compat-data": "npm:^7.20.5" + "@babel/helper-compilation-targets": "npm:^7.20.7" + "@babel/helper-plugin-utils": "npm:^7.20.2" + "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" + "@babel/plugin-transform-parameters": "npm:^7.20.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/cb0f8f2ff98d7bb64ee91c28b20e8ab15d9bc7043f0932cbb9e51e1bbfb623b12f206a1171e070299c9cf21948c320b710d6d72a42f68a5bfd2702354113a1c5 + languageName: node + linkType: hard + "@babel/plugin-proposal-private-property-in-object@npm:7.21.0-placeholder-for-preset-env.2": version: 7.21.0-placeholder-for-preset-env.2 resolution: "@babel/plugin-proposal-private-property-in-object@npm:7.21.0-placeholder-for-preset-env.2" @@ -430,7 +646,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-class-properties@npm:^7.12.13": +"@babel/plugin-syntax-class-properties@npm:^7.0.0, @babel/plugin-syntax-class-properties@npm:^7.12.13": version: 7.12.13 resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" dependencies: @@ -474,7 +690,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-import-assertions@npm:^7.24.7": +"@babel/plugin-syntax-flow@npm:^7.0.0, @babel/plugin-syntax-flow@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-syntax-flow@npm:7.24.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.24.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/0a83bde6736110d68f3b20eda44ca020a6d34c336a342f84369207f5514e17779b9c3d3ebc2f1c94b595c13819f46bf7af367c4b1382bda182e1764655fd6a5a + languageName: node + linkType: hard + +"@babel/plugin-syntax-import-assertions@npm:^7.20.0, @babel/plugin-syntax-import-assertions@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-syntax-import-assertions@npm:7.24.7" dependencies: @@ -518,7 +745,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-jsx@npm:^7.24.7": +"@babel/plugin-syntax-jsx@npm:^7.0.0, @babel/plugin-syntax-jsx@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-syntax-jsx@npm:7.24.7" dependencies: @@ -562,7 +789,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-object-rest-spread@npm:^7.8.3": +"@babel/plugin-syntax-object-rest-spread@npm:^7.0.0, @babel/plugin-syntax-object-rest-spread@npm:^7.8.3": version: 7.8.3 resolution: "@babel/plugin-syntax-object-rest-spread@npm:7.8.3" dependencies: @@ -640,7 +867,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-arrow-functions@npm:^7.24.7": +"@babel/plugin-transform-arrow-functions@npm:^7.0.0, @babel/plugin-transform-arrow-functions@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-arrow-functions@npm:7.24.7" dependencies: @@ -678,7 +905,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-block-scoped-functions@npm:^7.24.7": +"@babel/plugin-transform-block-scoped-functions@npm:^7.0.0, @babel/plugin-transform-block-scoped-functions@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.24.7" dependencies: @@ -689,7 +916,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-block-scoping@npm:^7.24.7": +"@babel/plugin-transform-block-scoping@npm:^7.0.0, @babel/plugin-transform-block-scoping@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-block-scoping@npm:7.24.7" dependencies: @@ -725,6 +952,24 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-classes@npm:^7.0.0": + version: 7.24.8 + resolution: "@babel/plugin-transform-classes@npm:7.24.8" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.24.7" + "@babel/helper-compilation-targets": "npm:^7.24.8" + "@babel/helper-environment-visitor": "npm:^7.24.7" + "@babel/helper-function-name": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/helper-replace-supers": "npm:^7.24.7" + "@babel/helper-split-export-declaration": "npm:^7.24.7" + globals: "npm:^11.1.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/3d586018691423ed1fbcb4589cc29001226c96e5e060932bf99379568c684a4a230cca7871e7c825335336ef0326066ba6e3bf5e6d0209425b0f5ceeda3eaed2 + languageName: node + linkType: hard + "@babel/plugin-transform-classes@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-classes@npm:7.24.7" @@ -743,7 +988,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-computed-properties@npm:^7.24.7": +"@babel/plugin-transform-computed-properties@npm:^7.0.0, @babel/plugin-transform-computed-properties@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-computed-properties@npm:7.24.7" dependencies: @@ -755,6 +1000,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-destructuring@npm:^7.0.0": + version: 7.24.8 + resolution: "@babel/plugin-transform-destructuring@npm:7.24.8" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.24.8" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/e3bba0bb050592615fbf062ea07ae94f99e9cf22add006eaa66ed672d67ff7051b578a5ea68a7d79f9184fb3c27c65333d86b0b8ea04f9810bcccbeea2ffbe76 + languageName: node + linkType: hard + "@babel/plugin-transform-destructuring@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-destructuring@npm:7.24.7" @@ -825,7 +1081,19 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-for-of@npm:^7.24.7": +"@babel/plugin-transform-flow-strip-types@npm:^7.0.0": + version: 7.24.7 + resolution: "@babel/plugin-transform-flow-strip-types@npm:7.24.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/plugin-syntax-flow": "npm:^7.24.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/234390eb09f0c1d5a2001c9e48c6440c30f9f188939004e07aa8c0cb946f04793a2e058fa1737b1c56041a7d3ea1510593c39220cc43bba85a017bfcc1c89c4d + languageName: node + linkType: hard + +"@babel/plugin-transform-for-of@npm:^7.0.0, @babel/plugin-transform-for-of@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-for-of@npm:7.24.7" dependencies: @@ -837,7 +1105,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-function-name@npm:^7.24.7": +"@babel/plugin-transform-function-name@npm:^7.0.0, @babel/plugin-transform-function-name@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-function-name@npm:7.24.7" dependencies: @@ -862,7 +1130,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-literals@npm:^7.24.7": +"@babel/plugin-transform-literals@npm:^7.0.0, @babel/plugin-transform-literals@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-literals@npm:7.24.7" dependencies: @@ -885,7 +1153,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-member-expression-literals@npm:^7.24.7": +"@babel/plugin-transform-member-expression-literals@npm:^7.0.0, @babel/plugin-transform-member-expression-literals@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-member-expression-literals@npm:7.24.7" dependencies: @@ -908,6 +1176,19 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-modules-commonjs@npm:^7.0.0": + version: 7.24.8 + resolution: "@babel/plugin-transform-modules-commonjs@npm:7.24.8" + dependencies: + "@babel/helper-module-transforms": "npm:^7.24.8" + "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/helper-simple-access": "npm:^7.24.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/18e5d229767c7b5b6ff0cbf1a8d2d555965b90201839d0ac2dc043b56857624ea344e59f733f028142a8c1d54923b82e2a0185694ef36f988d797bfbaf59819c + languageName: node + linkType: hard + "@babel/plugin-transform-modules-commonjs@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-modules-commonjs@npm:7.24.7" @@ -1008,7 +1289,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-object-super@npm:^7.24.7": +"@babel/plugin-transform-object-super@npm:^7.0.0, @babel/plugin-transform-object-super@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-object-super@npm:7.24.7" dependencies: @@ -1045,7 +1326,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-parameters@npm:^7.24.7": +"@babel/plugin-transform-parameters@npm:^7.0.0, @babel/plugin-transform-parameters@npm:^7.20.7, @babel/plugin-transform-parameters@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-parameters@npm:7.24.7" dependencies: @@ -1082,7 +1363,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-property-literals@npm:^7.24.7": +"@babel/plugin-transform-property-literals@npm:^7.0.0, @babel/plugin-transform-property-literals@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-property-literals@npm:7.24.7" dependencies: @@ -1093,6 +1374,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-react-display-name@npm:^7.0.0": + version: 7.24.7 + resolution: "@babel/plugin-transform-react-display-name@npm:7.24.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.24.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/f5d34903680ca358c5a3ccb83421df259e5142be95dde51dc4a62ec79fd6558599b3b92b4afd37329d2567a4ba4c338f1c817f8ce0c56ddf20cd3d051498649e + languageName: node + linkType: hard + "@babel/plugin-transform-react-jsx-source@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-react-jsx-source@npm:7.24.7" @@ -1104,7 +1396,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-react-jsx@npm:^7.24": +"@babel/plugin-transform-react-jsx@npm:^7.0.0, @babel/plugin-transform-react-jsx@npm:^7.24": version: 7.24.7 resolution: "@babel/plugin-transform-react-jsx@npm:7.24.7" dependencies: @@ -1142,7 +1434,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-shorthand-properties@npm:^7.24.7": +"@babel/plugin-transform-shorthand-properties@npm:^7.0.0, @babel/plugin-transform-shorthand-properties@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-shorthand-properties@npm:7.24.7" dependencies: @@ -1153,7 +1445,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-spread@npm:^7.24.7": +"@babel/plugin-transform-spread@npm:^7.0.0, @babel/plugin-transform-spread@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-spread@npm:7.24.7" dependencies: @@ -1176,7 +1468,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-template-literals@npm:^7.24.7": +"@babel/plugin-transform-template-literals@npm:^7.0.0, @babel/plugin-transform-template-literals@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-template-literals@npm:7.24.7" dependencies: @@ -1385,6 +1677,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.0.0": + version: 7.24.8 + resolution: "@babel/runtime@npm:7.24.8" + dependencies: + regenerator-runtime: "npm:^0.14.0" + checksum: 10/e6f335e472a8a337379effc15815dd0eddf6a7d0c00b50deb4f9e9585819b45431d0ff3c2d3d0fa58c227a9b04dcc4a85e7245fb57493adb2863b5208c769cbd + languageName: node + linkType: hard + "@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7": version: 7.24.4 resolution: "@babel/runtime@npm:7.24.4" @@ -1394,7 +1695,7 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.24.7": +"@babel/template@npm:^7.18.10, @babel/template@npm:^7.20.7, @babel/template@npm:^7.24.7": version: 7.24.7 resolution: "@babel/template@npm:7.24.7" dependencies: @@ -1405,6 +1706,24 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.14.0, @babel/traverse@npm:^7.16.8, @babel/traverse@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/traverse@npm:7.24.8" + dependencies: + "@babel/code-frame": "npm:^7.24.7" + "@babel/generator": "npm:^7.24.8" + "@babel/helper-environment-visitor": "npm:^7.24.7" + "@babel/helper-function-name": "npm:^7.24.7" + "@babel/helper-hoist-variables": "npm:^7.24.7" + "@babel/helper-split-export-declaration": "npm:^7.24.7" + "@babel/parser": "npm:^7.24.8" + "@babel/types": "npm:^7.24.8" + debug: "npm:^4.3.1" + globals: "npm:^11.1.0" + checksum: 10/47d8ecf8cfff58fe621fc4d8454b82c97c407816d8f9c435caa0c849ea7c357b91119a06f3c69f21a0228b5d06ac0b44f49d1f78cff032d6266317707f1fe615 + languageName: node + linkType: hard + "@babel/traverse@npm:^7.24.7": version: 7.24.7 resolution: "@babel/traverse@npm:7.24.7" @@ -1423,6 +1742,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.16.8, @babel/types@npm:^7.18.13, @babel/types@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/types@npm:7.24.8" + dependencies: + "@babel/helper-string-parser": "npm:^7.24.8" + "@babel/helper-validator-identifier": "npm:^7.24.7" + to-fast-properties: "npm:^2.0.0" + checksum: 10/29b080b2753c22ee5e2455ff767a971443245d945dea4d1b3130e036dcdf0949a89539a581753c68d03d2f2f2325244ee0f91fb83dabee1cbac5db5246838137 + languageName: node + linkType: hard + "@babel/types@npm:^7.24.7, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": version: 7.24.7 resolution: "@babel/types@npm:7.24.7" @@ -1787,139 +2117,728 @@ __metadata: version: 4.4.0 resolution: "@eslint-community/eslint-utils@npm:4.4.0" dependencies: - eslint-visitor-keys: "npm:^3.3.0" + eslint-visitor-keys: "npm:^3.3.0" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + checksum: 10/8d70bcdcd8cd279049183aca747d6c2ed7092a5cf0cf5916faac1ef37ffa74f0c245c2a3a3d3b9979d9dfdd4ca59257b4c5621db699d637b847a2c5e02f491c2 + languageName: node + linkType: hard + +"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.6.1": + version: 4.10.0 + resolution: "@eslint-community/regexpp@npm:4.10.0" + checksum: 10/8c36169c815fc5d726078e8c71a5b592957ee60d08c6470f9ce0187c8046af1a00afbda0a065cc40ff18d5d83f82aed9793c6818f7304a74a7488dc9f3ecbd42 + languageName: node + linkType: hard + +"@eslint/eslintrc@npm:^2.1.4": + version: 2.1.4 + resolution: "@eslint/eslintrc@npm:2.1.4" + dependencies: + ajv: "npm:^6.12.4" + debug: "npm:^4.3.2" + espree: "npm:^9.6.0" + globals: "npm:^13.19.0" + ignore: "npm:^5.2.0" + import-fresh: "npm:^3.2.1" + js-yaml: "npm:^4.1.0" + minimatch: "npm:^3.1.2" + strip-json-comments: "npm:^3.1.1" + checksum: 10/7a3b14f4b40fc1a22624c3f84d9f467a3d9ea1ca6e9a372116cb92507e485260359465b58e25bcb6c9981b155416b98c9973ad9b796053fd7b3f776a6946bce8 + languageName: node + linkType: hard + +"@eslint/js@npm:8.57.0": + version: 8.57.0 + resolution: "@eslint/js@npm:8.57.0" + checksum: 10/3c501ce8a997cf6cbbaf4ed358af5492875e3550c19b9621413b82caa9ae5382c584b0efa79835639e6e0ddaa568caf3499318e5bdab68643ef4199dce5eb0a0 + languageName: node + linkType: hard + +"@fastify/busboy@npm:^2.0.0": + version: 2.1.1 + resolution: "@fastify/busboy@npm:2.1.1" + checksum: 10/2bb8a7eca8289ed14c9eb15239bc1019797454624e769b39a0b90ed204d032403adc0f8ed0d2aef8a18c772205fa7808cf5a1b91f21c7bfc7b6032150b1062c5 + languageName: node + linkType: hard + +"@formatjs/ecma402-abstract@npm:2.0.0": + version: 2.0.0 + resolution: "@formatjs/ecma402-abstract@npm:2.0.0" + dependencies: + "@formatjs/intl-localematcher": "npm:0.5.4" + tslib: "npm:^2.4.0" + checksum: 10/41543ba509ea3c7d6530d57b888115f7ca242f13462a951fae4d1d1f28bae10c999f4dea28a71d2f08366d4889a3f5276cae3a16c6f6417b841a84fd314c2234 + languageName: node + linkType: hard + +"@formatjs/fast-memoize@npm:2.2.0": + version: 2.2.0 + resolution: "@formatjs/fast-memoize@npm:2.2.0" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/8697fe72a7ece252d600a7d08105f2a2f758e2dd96f54ac0a4c508b1205a559fc08835635e1f8e5ca9dcc3ee61ce1fca4a0e7047b402f29fc96051e293a280ff + languageName: node + linkType: hard + +"@formatjs/icu-messageformat-parser@npm:2.7.8": + version: 2.7.8 + resolution: "@formatjs/icu-messageformat-parser@npm:2.7.8" + dependencies: + "@formatjs/ecma402-abstract": "npm:2.0.0" + "@formatjs/icu-skeleton-parser": "npm:1.8.2" + tslib: "npm:^2.4.0" + checksum: 10/292fd36268ad84337c0e798fc73b58e8f3cf3f362cea031f710fd78053d29b420526ab766a95745e162ae4a11bf846bc2f7ae5c2c0a3288d3bc9daa97a3be8c1 + languageName: node + linkType: hard + +"@formatjs/icu-skeleton-parser@npm:1.8.2": + version: 1.8.2 + resolution: "@formatjs/icu-skeleton-parser@npm:1.8.2" + dependencies: + "@formatjs/ecma402-abstract": "npm:2.0.0" + tslib: "npm:^2.4.0" + checksum: 10/a06b61cf6c298bbbc23349e391bad8a1cf0a6a32dc4928a4681a3aa6f38dd8c6a181dc4067e228f67584d4dc181d862704095e65c38cfac077c984dc24ba54d3 + languageName: node + linkType: hard + +"@formatjs/intl-displaynames@npm:6.6.8": + version: 6.6.8 + resolution: "@formatjs/intl-displaynames@npm:6.6.8" + dependencies: + "@formatjs/ecma402-abstract": "npm:2.0.0" + "@formatjs/intl-localematcher": "npm:0.5.4" + tslib: "npm:^2.4.0" + checksum: 10/d305787fe2a35f9725b1293f70a2ccc454310c5df48ad62613d674a648dd318bec2a3ccd8656047443d1e438bb1cf8140e354d9c4a4101ec38df00614e0244b7 + languageName: node + linkType: hard + +"@formatjs/intl-listformat@npm:7.5.7": + version: 7.5.7 + resolution: "@formatjs/intl-listformat@npm:7.5.7" + dependencies: + "@formatjs/ecma402-abstract": "npm:2.0.0" + "@formatjs/intl-localematcher": "npm:0.5.4" + tslib: "npm:^2.4.0" + checksum: 10/ba7e1ee9c29f2a8cfc66d51c5f273f279ced048b427533aa3d502cf7d12d510e79965d0471a158fd41d7cc7314f688c56d863c3ebec8e5e550d687f64d210794 + languageName: node + linkType: hard + +"@formatjs/intl-localematcher@npm:0.5.4": + version: 0.5.4 + resolution: "@formatjs/intl-localematcher@npm:0.5.4" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/780cb29b42e1ea87f2eb5db268577fcdc53da52d9f096871f3a1bb78603b4ba81d208ea0b0b9bc21548797c941ce435321f62d2522795b83b740f90b0ceb5778 + languageName: node + linkType: hard + +"@formatjs/intl@npm:2.10.4": + version: 2.10.4 + resolution: "@formatjs/intl@npm:2.10.4" + dependencies: + "@formatjs/ecma402-abstract": "npm:2.0.0" + "@formatjs/fast-memoize": "npm:2.2.0" + "@formatjs/icu-messageformat-parser": "npm:2.7.8" + "@formatjs/intl-displaynames": "npm:6.6.8" + "@formatjs/intl-listformat": "npm:7.5.7" + intl-messageformat: "npm:10.5.14" + tslib: "npm:^2.4.0" + peerDependencies: + typescript: ^4.7 || 5 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10/1d4e60cfb3edecb51a5f8dd2c2b1c784c1a9bfcbc1b6240323d8009e5f375e5c526c9eb19149b42e0e50f6a473e9709378071658ed25a62dc3923dd7fc6a2843 + languageName: node + linkType: hard + +"@graphql-codegen/add@npm:^5.0.3": + version: 5.0.3 + resolution: "@graphql-codegen/add@npm:5.0.3" + dependencies: + "@graphql-codegen/plugin-helpers": "npm:^5.0.3" + tslib: "npm:~2.6.0" + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 10/5e4ea9b5d76e6f472015185d0d007abf38ba7b27fb613b163ef1ca6c732695951111721896ee36894d098b27a67c55a84d000b503db93b9d9e6713e34d2fa5a6 + languageName: node + linkType: hard + +"@graphql-codegen/cli@npm:^5.0.2": + version: 5.0.2 + resolution: "@graphql-codegen/cli@npm:5.0.2" + dependencies: + "@babel/generator": "npm:^7.18.13" + "@babel/template": "npm:^7.18.10" + "@babel/types": "npm:^7.18.13" + "@graphql-codegen/client-preset": "npm:^4.2.2" + "@graphql-codegen/core": "npm:^4.0.2" + "@graphql-codegen/plugin-helpers": "npm:^5.0.3" + "@graphql-tools/apollo-engine-loader": "npm:^8.0.0" + "@graphql-tools/code-file-loader": "npm:^8.0.0" + "@graphql-tools/git-loader": "npm:^8.0.0" + "@graphql-tools/github-loader": "npm:^8.0.0" + "@graphql-tools/graphql-file-loader": "npm:^8.0.0" + "@graphql-tools/json-file-loader": "npm:^8.0.0" + "@graphql-tools/load": "npm:^8.0.0" + "@graphql-tools/prisma-loader": "npm:^8.0.0" + "@graphql-tools/url-loader": "npm:^8.0.0" + "@graphql-tools/utils": "npm:^10.0.0" + "@whatwg-node/fetch": "npm:^0.8.0" + chalk: "npm:^4.1.0" + cosmiconfig: "npm:^8.1.3" + debounce: "npm:^1.2.0" + detect-indent: "npm:^6.0.0" + graphql-config: "npm:^5.0.2" + inquirer: "npm:^8.0.0" + is-glob: "npm:^4.0.1" + jiti: "npm:^1.17.1" + json-to-pretty-yaml: "npm:^1.2.2" + listr2: "npm:^4.0.5" + log-symbols: "npm:^4.0.0" + micromatch: "npm:^4.0.5" + shell-quote: "npm:^1.7.3" + string-env-interpolation: "npm:^1.0.1" + ts-log: "npm:^2.2.3" + tslib: "npm:^2.4.0" + yaml: "npm:^2.3.1" + yargs: "npm:^17.0.0" + peerDependencies: + "@parcel/watcher": ^2.1.0 + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + peerDependenciesMeta: + "@parcel/watcher": + optional: true + bin: + gql-gen: cjs/bin.js + graphql-code-generator: cjs/bin.js + graphql-codegen: cjs/bin.js + graphql-codegen-esm: esm/bin.js + checksum: 10/24f5a4d441e4af2f0cae1818c8643a5400718cc1f08ca829a9110a35d99cb5529b567991ce826544b5a2aab36d0be3b10309dc112343bab1232d7c6f2fa14008 + languageName: node + linkType: hard + +"@graphql-codegen/client-preset@npm:^4.2.2, @graphql-codegen/client-preset@npm:^4.3.2": + version: 4.3.2 + resolution: "@graphql-codegen/client-preset@npm:4.3.2" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.20.2" + "@babel/template": "npm:^7.20.7" + "@graphql-codegen/add": "npm:^5.0.3" + "@graphql-codegen/gql-tag-operations": "npm:4.0.9" + "@graphql-codegen/plugin-helpers": "npm:^5.0.4" + "@graphql-codegen/typed-document-node": "npm:^5.0.9" + "@graphql-codegen/typescript": "npm:^4.0.9" + "@graphql-codegen/typescript-operations": "npm:^4.2.3" + "@graphql-codegen/visitor-plugin-common": "npm:^5.3.1" + "@graphql-tools/documents": "npm:^1.0.0" + "@graphql-tools/utils": "npm:^10.0.0" + "@graphql-typed-document-node/core": "npm:3.2.0" + tslib: "npm:~2.6.0" + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 10/52c66f1f0de4a1537726d2d6915f307aa650ab425a9b6ba5e24718a412f5b2aa7b27c706074d7fa086cbede7c0a0fb74ff1016ba378cc729a40e301d207a42f8 + languageName: node + linkType: hard + +"@graphql-codegen/core@npm:^4.0.2": + version: 4.0.2 + resolution: "@graphql-codegen/core@npm:4.0.2" + dependencies: + "@graphql-codegen/plugin-helpers": "npm:^5.0.3" + "@graphql-tools/schema": "npm:^10.0.0" + "@graphql-tools/utils": "npm:^10.0.0" + tslib: "npm:~2.6.0" + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 10/09aa9d5b3215b7c8a81e07d6c826fa9697e4d20c7fa4333905aa89afe88044ce5c733633a59c6590fc997f03a6f62f9aecf76d6c1efa4f1a16c5ad2b0b6f665b + languageName: node + linkType: hard + +"@graphql-codegen/gql-tag-operations@npm:4.0.9": + version: 4.0.9 + resolution: "@graphql-codegen/gql-tag-operations@npm:4.0.9" + dependencies: + "@graphql-codegen/plugin-helpers": "npm:^5.0.4" + "@graphql-codegen/visitor-plugin-common": "npm:5.3.1" + "@graphql-tools/utils": "npm:^10.0.0" + auto-bind: "npm:~4.0.0" + tslib: "npm:~2.6.0" + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 10/90eaecbe0742ee65f0e25df471760664d615bb6396d7e35c2d33615128849c1bdb1c0479c75de589479b7b1a0ee99b34684a2faeeff6245e01ffa7dcfff4b0c6 + languageName: node + linkType: hard + +"@graphql-codegen/plugin-helpers@npm:^5.0.3, @graphql-codegen/plugin-helpers@npm:^5.0.4": + version: 5.0.4 + resolution: "@graphql-codegen/plugin-helpers@npm:5.0.4" + dependencies: + "@graphql-tools/utils": "npm:^10.0.0" + change-case-all: "npm:1.0.15" + common-tags: "npm:1.8.2" + import-from: "npm:4.0.0" + lodash: "npm:~4.17.0" + tslib: "npm:~2.6.0" + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 10/8162bffc76bf0d6cd9ff83c98b8a5e5eadbb1bc0de2d273480af937a27ca8fbf74aae72a617303a9d4121b9914eb9af065858f07c0ac13cd169b53a9bcead799 + languageName: node + linkType: hard + +"@graphql-codegen/schema-ast@npm:^4.0.2": + version: 4.1.0 + resolution: "@graphql-codegen/schema-ast@npm:4.1.0" + dependencies: + "@graphql-codegen/plugin-helpers": "npm:^5.0.3" + "@graphql-tools/utils": "npm:^10.0.0" + tslib: "npm:~2.6.0" + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 10/cddec7723d708990ac8e33eb8935e72545b60ed7b772452ba45b60e577af950d23503de83f0919d1730f7d52dcb970900d3587d9a54202032164ba3c246d4c10 + languageName: node + linkType: hard + +"@graphql-codegen/typed-document-node@npm:^5.0.9": + version: 5.0.9 + resolution: "@graphql-codegen/typed-document-node@npm:5.0.9" + dependencies: + "@graphql-codegen/plugin-helpers": "npm:^5.0.4" + "@graphql-codegen/visitor-plugin-common": "npm:5.3.1" + auto-bind: "npm:~4.0.0" + change-case-all: "npm:1.0.15" + tslib: "npm:~2.6.0" + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 10/0cac39bf2d8304660dc99b46b9c6ed811be79aca8a2cd1f58d9a16ff83fb644e985d872456eed5fdba27fa1727e76c3d3190e251c0171d83aff1af58cbcbc3c8 + languageName: node + linkType: hard + +"@graphql-codegen/typescript-operations@npm:^4.2.3": + version: 4.2.3 + resolution: "@graphql-codegen/typescript-operations@npm:4.2.3" + dependencies: + "@graphql-codegen/plugin-helpers": "npm:^5.0.4" + "@graphql-codegen/typescript": "npm:^4.0.9" + "@graphql-codegen/visitor-plugin-common": "npm:5.3.1" + auto-bind: "npm:~4.0.0" + tslib: "npm:~2.6.0" + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 10/b501b43c5a686847e12812690e0aea3842c6efa10d53703db063d82f2a4f3c469bb02c430a29ac1d1b450ffc82cf97c0ca1b9caa8e8979279f72646f010301bf + languageName: node + linkType: hard + +"@graphql-codegen/typescript@npm:^4.0.9": + version: 4.0.9 + resolution: "@graphql-codegen/typescript@npm:4.0.9" + dependencies: + "@graphql-codegen/plugin-helpers": "npm:^5.0.4" + "@graphql-codegen/schema-ast": "npm:^4.0.2" + "@graphql-codegen/visitor-plugin-common": "npm:5.3.1" + auto-bind: "npm:~4.0.0" + tslib: "npm:~2.6.0" + peerDependencies: + graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 10/304026adfe622530b8a2827569dd5bbd390177051be8c214fb79873ec64ef21793635c91657703bfd229a3d06f1a8a6f1addd8ae7eab20d1eff2efe6fb044df7 + languageName: node + linkType: hard + +"@graphql-codegen/visitor-plugin-common@npm:5.3.1, @graphql-codegen/visitor-plugin-common@npm:^5.3.1": + version: 5.3.1 + resolution: "@graphql-codegen/visitor-plugin-common@npm:5.3.1" + dependencies: + "@graphql-codegen/plugin-helpers": "npm:^5.0.4" + "@graphql-tools/optimize": "npm:^2.0.0" + "@graphql-tools/relay-operation-optimizer": "npm:^7.0.0" + "@graphql-tools/utils": "npm:^10.0.0" + auto-bind: "npm:~4.0.0" + change-case-all: "npm:1.0.15" + dependency-graph: "npm:^0.11.0" + graphql-tag: "npm:^2.11.0" + parse-filepath: "npm:^1.0.2" + tslib: "npm:~2.6.0" + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 10/6dd0464d9099d5aeabeb766515fc8dd2fc84bcae4cb0e3653d7f38aea716d6622d35d7cbb57a1954e6bc1cde10f4dd8c4a75ceb4e8bb8cdbba16219615666a5f + languageName: node + linkType: hard + +"@graphql-tools/apollo-engine-loader@npm:^8.0.0": + version: 8.0.1 + resolution: "@graphql-tools/apollo-engine-loader@npm:8.0.1" + dependencies: + "@ardatan/sync-fetch": "npm:^0.0.1" + "@graphql-tools/utils": "npm:^10.0.13" + "@whatwg-node/fetch": "npm:^0.9.0" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/4baef5a8fd85787568188f852446e4f2453a21026c60b9391641093bff0a88172df8beb8bbfe2b134e177acad90eeb613387a1185699d1e7718e1dfa701c6fd7 + languageName: node + linkType: hard + +"@graphql-tools/batch-execute@npm:^9.0.4": + version: 9.0.4 + resolution: "@graphql-tools/batch-execute@npm:9.0.4" + dependencies: + "@graphql-tools/utils": "npm:^10.0.13" + dataloader: "npm:^2.2.2" + tslib: "npm:^2.4.0" + value-or-promise: "npm:^1.0.12" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/955647a094f787138bccd6cf33e06cf5bb5bf451cfad66a613f8c54ffd5a1f1b48342136e4e3d32fcf559ee1e2c438c98f5fd42cf99b19b3e5017449bea8a707 + languageName: node + linkType: hard + +"@graphql-tools/code-file-loader@npm:^8.0.0": + version: 8.1.2 + resolution: "@graphql-tools/code-file-loader@npm:8.1.2" + dependencies: + "@graphql-tools/graphql-tag-pluck": "npm:8.3.1" + "@graphql-tools/utils": "npm:^10.0.13" + globby: "npm:^11.0.3" + tslib: "npm:^2.4.0" + unixify: "npm:^1.0.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/691a58c8c7584127a5c1c7a162b3cfde0597f422adfa757bbb790c7ccda5116c7ca606860e929958c2368416adce7b23ac4234a8f8df59c2a28736540b31feab + languageName: node + linkType: hard + +"@graphql-tools/delegate@npm:^10.0.4": + version: 10.0.13 + resolution: "@graphql-tools/delegate@npm:10.0.13" + dependencies: + "@graphql-tools/batch-execute": "npm:^9.0.4" + "@graphql-tools/executor": "npm:^1.2.8" + "@graphql-tools/schema": "npm:^10.0.4" + "@graphql-tools/utils": "npm:^10.2.3" + dataloader: "npm:^2.2.2" + tslib: "npm:^2.5.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/fb200442d12271dc4368a9b5ff30f8f801f6b42af68fa31ff1c6f77514a9d459f24ed8fc387ff943bc4864e623751d2406692c1cadf5df877f5bd5e9c41ac359 + languageName: node + linkType: hard + +"@graphql-tools/documents@npm:^1.0.0": + version: 1.0.1 + resolution: "@graphql-tools/documents@npm:1.0.1" + dependencies: + lodash.sortby: "npm:^4.7.0" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/6af5cc1a5ab88fc2ef08d97c1190c4857ea894ea41672f9f94889ed817664524972c8f234bed023b0746fd2f358b96ca1cc753f0af127d0b8076fa7c6f3e27e5 + languageName: node + linkType: hard + +"@graphql-tools/executor-graphql-ws@npm:^1.1.2": + version: 1.2.0 + resolution: "@graphql-tools/executor-graphql-ws@npm:1.2.0" + dependencies: + "@graphql-tools/utils": "npm:^10.3.0" + "@types/ws": "npm:^8.0.0" + graphql-ws: "npm:^5.14.0" + isomorphic-ws: "npm:^5.0.0" + tslib: "npm:^2.4.0" + ws: "npm:^8.17.1" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/501824d3608c17109ab3505639215ed46b416a53329352b60ef63e39611be2e33d19f3ad882eb427ca27c9c65330d94a477cd1fd45f1098957b51d221d0a57b2 + languageName: node + linkType: hard + +"@graphql-tools/executor-http@npm:^1.0.9": + version: 1.1.4 + resolution: "@graphql-tools/executor-http@npm:1.1.4" + dependencies: + "@graphql-tools/utils": "npm:^10.3.2" + "@repeaterjs/repeater": "npm:^3.0.4" + "@whatwg-node/fetch": "npm:^0.9.0" + extract-files: "npm:^11.0.0" + meros: "npm:^1.2.1" + tslib: "npm:^2.4.0" + value-or-promise: "npm:^1.0.12" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/6a1296cde5acf4e021bf5ffbb8123b6deb1995198657b662eb9fef3d3c8f0c04a1d90f934c864f5877002906daa5af2da895960b51269d1c905ddafee5411e0c + languageName: node + linkType: hard + +"@graphql-tools/executor-legacy-ws@npm:^1.0.6": + version: 1.1.0 + resolution: "@graphql-tools/executor-legacy-ws@npm:1.1.0" + dependencies: + "@graphql-tools/utils": "npm:^10.3.0" + "@types/ws": "npm:^8.0.0" + isomorphic-ws: "npm:^5.0.0" + tslib: "npm:^2.4.0" + ws: "npm:^8.17.1" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/b7bfe6c1e2229beef35fb175102d0a5ab45e679834c45d579c3d0efe8b7d1e22836d3773e03b0c920bbc94a58d490d54d62f1484e043eef6a54cc426b4b92f98 + languageName: node + linkType: hard + +"@graphql-tools/executor@npm:^1.2.8": + version: 1.2.8 + resolution: "@graphql-tools/executor@npm:1.2.8" + dependencies: + "@graphql-tools/utils": "npm:^10.2.3" + "@graphql-typed-document-node/core": "npm:3.2.0" + "@repeaterjs/repeater": "npm:^3.0.4" + tslib: "npm:^2.4.0" + value-or-promise: "npm:^1.0.12" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/d51f3b830bf3bcb84f06ca1b90324ae36a468df68e595699d4856615d3309e76fa6ff6aeb90584572cc7f1c7c18e9fffa3ccc30d8d1e40c94de3488da80899df + languageName: node + linkType: hard + +"@graphql-tools/git-loader@npm:^8.0.0": + version: 8.0.6 + resolution: "@graphql-tools/git-loader@npm:8.0.6" + dependencies: + "@graphql-tools/graphql-tag-pluck": "npm:8.3.1" + "@graphql-tools/utils": "npm:^10.0.13" + is-glob: "npm:4.0.3" + micromatch: "npm:^4.0.4" + tslib: "npm:^2.4.0" + unixify: "npm:^1.0.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/c5995bf6b96309e6603a51e71208d1f610d3f270a9b03ac537004ae1ec12d53368716debe77dbaf94a22a68144db2edfbff944211b1d17e755682fdd3eac67eb + languageName: node + linkType: hard + +"@graphql-tools/github-loader@npm:^8.0.0": + version: 8.0.1 + resolution: "@graphql-tools/github-loader@npm:8.0.1" + dependencies: + "@ardatan/sync-fetch": "npm:^0.0.1" + "@graphql-tools/executor-http": "npm:^1.0.9" + "@graphql-tools/graphql-tag-pluck": "npm:^8.0.0" + "@graphql-tools/utils": "npm:^10.0.13" + "@whatwg-node/fetch": "npm:^0.9.0" + tslib: "npm:^2.4.0" + value-or-promise: "npm:^1.0.12" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/d309e8330bafc4ef9796e7223f195e391011bf491f652857abb427faef59bc559228a0d288f6b765bb49a0cfeae1db055863a81bd49db9d720ae2c25622160f1 + languageName: node + linkType: hard + +"@graphql-tools/graphql-file-loader@npm:^8.0.0": + version: 8.0.1 + resolution: "@graphql-tools/graphql-file-loader@npm:8.0.1" + dependencies: + "@graphql-tools/import": "npm:7.0.1" + "@graphql-tools/utils": "npm:^10.0.13" + globby: "npm:^11.0.3" + tslib: "npm:^2.4.0" + unixify: "npm:^1.0.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/fceb035cacf481a03b242e3cca912c998b23ab3ce77b1f7b7ca05b3f3ff9f09d117aeefca2b219d09c3f920506e3780fa1efcba47fee9615cc63281e49ee2caa + languageName: node + linkType: hard + +"@graphql-tools/graphql-tag-pluck@npm:8.3.1, @graphql-tools/graphql-tag-pluck@npm:^8.0.0": + version: 8.3.1 + resolution: "@graphql-tools/graphql-tag-pluck@npm:8.3.1" + dependencies: + "@babel/core": "npm:^7.22.9" + "@babel/parser": "npm:^7.16.8" + "@babel/plugin-syntax-import-assertions": "npm:^7.20.0" + "@babel/traverse": "npm:^7.16.8" + "@babel/types": "npm:^7.16.8" + "@graphql-tools/utils": "npm:^10.0.13" + tslib: "npm:^2.4.0" peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - checksum: 10/8d70bcdcd8cd279049183aca747d6c2ed7092a5cf0cf5916faac1ef37ffa74f0c245c2a3a3d3b9979d9dfdd4ca59257b4c5621db699d637b847a2c5e02f491c2 + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/ec784009dece05632df0e01f6f1ab57f137736d16457c7e6a8faa745bfc0b4f9e85557bb8cb0ab738cd1f5387e7ab0b7b0d2d7316ccc19f183f5149fe1f8e066 languageName: node linkType: hard -"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.6.1": - version: 4.10.0 - resolution: "@eslint-community/regexpp@npm:4.10.0" - checksum: 10/8c36169c815fc5d726078e8c71a5b592957ee60d08c6470f9ce0187c8046af1a00afbda0a065cc40ff18d5d83f82aed9793c6818f7304a74a7488dc9f3ecbd42 +"@graphql-tools/import@npm:7.0.1": + version: 7.0.1 + resolution: "@graphql-tools/import@npm:7.0.1" + dependencies: + "@graphql-tools/utils": "npm:^10.0.13" + resolve-from: "npm:5.0.0" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/ff578aad5e6f65728e658895e7e6be6866c0a713efe528cd420dd84c31a672ee6c6a6956bdff11c3cb2d3d56a90382d78868c2b90798577e6b087c08e3cf4d2b languageName: node linkType: hard -"@eslint/eslintrc@npm:^2.1.4": - version: 2.1.4 - resolution: "@eslint/eslintrc@npm:2.1.4" +"@graphql-tools/json-file-loader@npm:^8.0.0": + version: 8.0.1 + resolution: "@graphql-tools/json-file-loader@npm:8.0.1" dependencies: - ajv: "npm:^6.12.4" - debug: "npm:^4.3.2" - espree: "npm:^9.6.0" - globals: "npm:^13.19.0" - ignore: "npm:^5.2.0" - import-fresh: "npm:^3.2.1" - js-yaml: "npm:^4.1.0" - minimatch: "npm:^3.1.2" - strip-json-comments: "npm:^3.1.1" - checksum: 10/7a3b14f4b40fc1a22624c3f84d9f467a3d9ea1ca6e9a372116cb92507e485260359465b58e25bcb6c9981b155416b98c9973ad9b796053fd7b3f776a6946bce8 + "@graphql-tools/utils": "npm:^10.0.13" + globby: "npm:^11.0.3" + tslib: "npm:^2.4.0" + unixify: "npm:^1.0.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/db19f579d845d8ee41101c938f9921644939830df97447a797b3bffc59a914f9d987b0002df912b766fb7a623718367274d292db218424dca4ae00b1c927c613 languageName: node linkType: hard -"@eslint/js@npm:8.57.0": - version: 8.57.0 - resolution: "@eslint/js@npm:8.57.0" - checksum: 10/3c501ce8a997cf6cbbaf4ed358af5492875e3550c19b9621413b82caa9ae5382c584b0efa79835639e6e0ddaa568caf3499318e5bdab68643ef4199dce5eb0a0 +"@graphql-tools/load@npm:^8.0.0": + version: 8.0.2 + resolution: "@graphql-tools/load@npm:8.0.2" + dependencies: + "@graphql-tools/schema": "npm:^10.0.3" + "@graphql-tools/utils": "npm:^10.0.13" + p-limit: "npm:3.1.0" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/ca291b6790ed34b9ec4ebb56fbebe0be208712b7d6d4cf54283a0d1bee65b49da7b2b254d6fc14e56fedd865270536551a2d09545ec91c6fa4d5097aa8f12aae languageName: node linkType: hard -"@fastify/busboy@npm:^2.0.0": - version: 2.1.1 - resolution: "@fastify/busboy@npm:2.1.1" - checksum: 10/2bb8a7eca8289ed14c9eb15239bc1019797454624e769b39a0b90ed204d032403adc0f8ed0d2aef8a18c772205fa7808cf5a1b91f21c7bfc7b6032150b1062c5 +"@graphql-tools/merge@npm:^9.0.0, @graphql-tools/merge@npm:^9.0.3": + version: 9.0.4 + resolution: "@graphql-tools/merge@npm:9.0.4" + dependencies: + "@graphql-tools/utils": "npm:^10.0.13" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/9f60577237f43e66cabb2de01552a0231286b8a32877d0b5bcde31c2352867c63e55a236a3a1ae07e313f42ac070695a4f79ed6d04e6bbb8842e8e75d8276dd8 languageName: node linkType: hard -"@formatjs/ecma402-abstract@npm:2.0.0": +"@graphql-tools/optimize@npm:^2.0.0": version: 2.0.0 - resolution: "@formatjs/ecma402-abstract@npm:2.0.0" + resolution: "@graphql-tools/optimize@npm:2.0.0" dependencies: - "@formatjs/intl-localematcher": "npm:0.5.4" tslib: "npm:^2.4.0" - checksum: 10/41543ba509ea3c7d6530d57b888115f7ca242f13462a951fae4d1d1f28bae10c999f4dea28a71d2f08366d4889a3f5276cae3a16c6f6417b841a84fd314c2234 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/7f79c0e1852abc571308e887d27d613da5b797256c8c6eb6c5fe7ca77f09e61524778ae281cebc0b698c51d4fe1074e2b8e0d0627b8e2dcf505aa6ed09b49a2f languageName: node linkType: hard -"@formatjs/fast-memoize@npm:2.2.0": - version: 2.2.0 - resolution: "@formatjs/fast-memoize@npm:2.2.0" +"@graphql-tools/prisma-loader@npm:^8.0.0": + version: 8.0.4 + resolution: "@graphql-tools/prisma-loader@npm:8.0.4" dependencies: + "@graphql-tools/url-loader": "npm:^8.0.2" + "@graphql-tools/utils": "npm:^10.0.13" + "@types/js-yaml": "npm:^4.0.0" + "@whatwg-node/fetch": "npm:^0.9.0" + chalk: "npm:^4.1.0" + debug: "npm:^4.3.1" + dotenv: "npm:^16.0.0" + graphql-request: "npm:^6.0.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.0" + jose: "npm:^5.0.0" + js-yaml: "npm:^4.0.0" + lodash: "npm:^4.17.20" + scuid: "npm:^1.1.0" tslib: "npm:^2.4.0" - checksum: 10/8697fe72a7ece252d600a7d08105f2a2f758e2dd96f54ac0a4c508b1205a559fc08835635e1f8e5ca9dcc3ee61ce1fca4a0e7047b402f29fc96051e293a280ff + yaml-ast-parser: "npm:^0.0.43" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/f0e20d1d7442ede2103bdb067f9fd9d551ee996e993be5caee33a39d85fda0eb7a02404d8fe6b12eb9ba5537a2a42fa7345dc48ab68e9debd8b3039acacd4f40 languageName: node linkType: hard -"@formatjs/icu-messageformat-parser@npm:2.7.8": - version: 2.7.8 - resolution: "@formatjs/icu-messageformat-parser@npm:2.7.8" +"@graphql-tools/relay-operation-optimizer@npm:^7.0.0": + version: 7.0.1 + resolution: "@graphql-tools/relay-operation-optimizer@npm:7.0.1" dependencies: - "@formatjs/ecma402-abstract": "npm:2.0.0" - "@formatjs/icu-skeleton-parser": "npm:1.8.2" + "@ardatan/relay-compiler": "npm:12.0.0" + "@graphql-tools/utils": "npm:^10.0.13" tslib: "npm:^2.4.0" - checksum: 10/292fd36268ad84337c0e798fc73b58e8f3cf3f362cea031f710fd78053d29b420526ab766a95745e162ae4a11bf846bc2f7ae5c2c0a3288d3bc9daa97a3be8c1 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/24a29aea91a0028d7624ae38fed1bfd387ecf5a0e297dbbca23c0f73c51956765db7517b2a75a5b3a6003ea5e3f025f0fc4f527eec22b05e7e25216dc6318797 languageName: node linkType: hard -"@formatjs/icu-skeleton-parser@npm:1.8.2": - version: 1.8.2 - resolution: "@formatjs/icu-skeleton-parser@npm:1.8.2" +"@graphql-tools/schema@npm:^10.0.0, @graphql-tools/schema@npm:^10.0.3, @graphql-tools/schema@npm:^10.0.4": + version: 10.0.4 + resolution: "@graphql-tools/schema@npm:10.0.4" dependencies: - "@formatjs/ecma402-abstract": "npm:2.0.0" + "@graphql-tools/merge": "npm:^9.0.3" + "@graphql-tools/utils": "npm:^10.2.1" tslib: "npm:^2.4.0" - checksum: 10/a06b61cf6c298bbbc23349e391bad8a1cf0a6a32dc4928a4681a3aa6f38dd8c6a181dc4067e228f67584d4dc181d862704095e65c38cfac077c984dc24ba54d3 + value-or-promise: "npm:^1.0.12" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/c2144949d98096c7f51ac0ecd9979d525e5b1b0f6b31c87961277fcefc82286ad9a9f55e93c2c3fd36745a1ef9ac341ec395d0db2c071a55a2a228ab563c953a languageName: node linkType: hard -"@formatjs/intl-displaynames@npm:6.6.8": - version: 6.6.8 - resolution: "@formatjs/intl-displaynames@npm:6.6.8" - dependencies: - "@formatjs/ecma402-abstract": "npm:2.0.0" - "@formatjs/intl-localematcher": "npm:0.5.4" +"@graphql-tools/url-loader@npm:^8.0.0, @graphql-tools/url-loader@npm:^8.0.2": + version: 8.0.2 + resolution: "@graphql-tools/url-loader@npm:8.0.2" + dependencies: + "@ardatan/sync-fetch": "npm:^0.0.1" + "@graphql-tools/delegate": "npm:^10.0.4" + "@graphql-tools/executor-graphql-ws": "npm:^1.1.2" + "@graphql-tools/executor-http": "npm:^1.0.9" + "@graphql-tools/executor-legacy-ws": "npm:^1.0.6" + "@graphql-tools/utils": "npm:^10.0.13" + "@graphql-tools/wrap": "npm:^10.0.2" + "@types/ws": "npm:^8.0.0" + "@whatwg-node/fetch": "npm:^0.9.0" + isomorphic-ws: "npm:^5.0.0" tslib: "npm:^2.4.0" - checksum: 10/d305787fe2a35f9725b1293f70a2ccc454310c5df48ad62613d674a648dd318bec2a3ccd8656047443d1e438bb1cf8140e354d9c4a4101ec38df00614e0244b7 + value-or-promise: "npm:^1.0.11" + ws: "npm:^8.12.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/0b5c7a5593ef33ae64f83fb3458c0e87525e16b06280408a4b1eca1f44b493d09b97052dc5059a5327fe8a98f91a22ccdf0990264e577d18243b489f1994d0ca languageName: node linkType: hard -"@formatjs/intl-listformat@npm:7.5.7": - version: 7.5.7 - resolution: "@formatjs/intl-listformat@npm:7.5.7" +"@graphql-tools/utils@npm:^10.0.0, @graphql-tools/utils@npm:^10.0.13, @graphql-tools/utils@npm:^10.1.1, @graphql-tools/utils@npm:^10.2.1, @graphql-tools/utils@npm:^10.2.3, @graphql-tools/utils@npm:^10.3.0, @graphql-tools/utils@npm:^10.3.2": + version: 10.3.2 + resolution: "@graphql-tools/utils@npm:10.3.2" dependencies: - "@formatjs/ecma402-abstract": "npm:2.0.0" - "@formatjs/intl-localematcher": "npm:0.5.4" + "@graphql-typed-document-node/core": "npm:^3.1.1" + cross-inspect: "npm:1.0.0" + dset: "npm:^3.1.2" tslib: "npm:^2.4.0" - checksum: 10/ba7e1ee9c29f2a8cfc66d51c5f273f279ced048b427533aa3d502cf7d12d510e79965d0471a158fd41d7cc7314f688c56d863c3ebec8e5e550d687f64d210794 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/37c4d11732fa470bbea3c8c9962837155bd0d22f5524a246e35d499872ca2db78acdaa515965564b4062be11d5f347e054724d6307d7bff78cd1c28885e2e371 languageName: node linkType: hard -"@formatjs/intl-localematcher@npm:0.5.4": - version: 0.5.4 - resolution: "@formatjs/intl-localematcher@npm:0.5.4" +"@graphql-tools/wrap@npm:^10.0.2": + version: 10.0.5 + resolution: "@graphql-tools/wrap@npm:10.0.5" dependencies: + "@graphql-tools/delegate": "npm:^10.0.4" + "@graphql-tools/schema": "npm:^10.0.3" + "@graphql-tools/utils": "npm:^10.1.1" tslib: "npm:^2.4.0" - checksum: 10/780cb29b42e1ea87f2eb5db268577fcdc53da52d9f096871f3a1bb78603b4ba81d208ea0b0b9bc21548797c941ce435321f62d2522795b83b740f90b0ceb5778 + value-or-promise: "npm:^1.0.12" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/02d5278bf1aea75897850f9bcf691104979490d5e74b577c9dc64df2c920d4be43fa8dbdedb7a58444d8baa96cf475939f2e5cc696ab60df16a0b04bf09c9219 languageName: node linkType: hard -"@formatjs/intl@npm:2.10.4": - version: 2.10.4 - resolution: "@formatjs/intl@npm:2.10.4" - dependencies: - "@formatjs/ecma402-abstract": "npm:2.0.0" - "@formatjs/fast-memoize": "npm:2.2.0" - "@formatjs/icu-messageformat-parser": "npm:2.7.8" - "@formatjs/intl-displaynames": "npm:6.6.8" - "@formatjs/intl-listformat": "npm:7.5.7" - intl-messageformat: "npm:10.5.14" - tslib: "npm:^2.4.0" +"@graphql-typed-document-node/core@npm:3.2.0, @graphql-typed-document-node/core@npm:^3.1.1, @graphql-typed-document-node/core@npm:^3.2.0": + version: 3.2.0 + resolution: "@graphql-typed-document-node/core@npm:3.2.0" peerDependencies: - typescript: ^4.7 || 5 - peerDependenciesMeta: - typescript: - optional: true - checksum: 10/1d4e60cfb3edecb51a5f8dd2c2b1c784c1a9bfcbc1b6240323d8009e5f375e5c526c9eb19149b42e0e50f6a473e9709378071658ed25a62dc3923dd7fc6a2843 + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/fa44443accd28c8cf4cb96aaaf39d144a22e8b091b13366843f4e97d19c7bfeaf609ce3c7603a4aeffe385081eaf8ea245d078633a7324c11c5ec4b2011bb76d languageName: node linkType: hard @@ -2627,6 +3546,13 @@ __metadata: languageName: node linkType: hard +"@kamilkisiela/fast-url-parser@npm:^1.1.4": + version: 1.1.4 + resolution: "@kamilkisiela/fast-url-parser@npm:1.1.4" + checksum: 10/5b79438235a81817b02b96ddc581c996961cec5b40c7d6ebabd01ac6af8d4a35a43b9b263144af25386cef92c054c3ef6b1723b09eb0d8cf7b4053781a474c5f + languageName: node + linkType: hard + "@lcdp/offline-plugin@npm:^5.0.7": version: 5.1.1 resolution: "@lcdp/offline-plugin@npm:5.1.1" @@ -2705,6 +3631,39 @@ __metadata: languageName: node linkType: hard +"@peculiar/asn1-schema@npm:^2.3.8": + version: 2.3.8 + resolution: "@peculiar/asn1-schema@npm:2.3.8" + dependencies: + asn1js: "npm:^3.0.5" + pvtsutils: "npm:^1.3.5" + tslib: "npm:^2.6.2" + checksum: 10/da349985cff73ae7ea52b6b66c6b4b339a768d5eb9164ad03e73c30985ec0a1c94849b323a826b00a049d7de3840368f77bebe84193205a77565cdfdac6ed524 + languageName: node + linkType: hard + +"@peculiar/json-schema@npm:^1.1.12": + version: 1.1.12 + resolution: "@peculiar/json-schema@npm:1.1.12" + dependencies: + tslib: "npm:^2.0.0" + checksum: 10/dfec178afe63a02b6d45da8a18e51ef417e9f5412a8c2809c9a07b29b9376fadee1b4f2ea2d92d4e5a7b8eba76d9e99afbef6d7e9a27bd85257f69c4da228cbc + languageName: node + linkType: hard + +"@peculiar/webcrypto@npm:^1.4.0": + version: 1.5.0 + resolution: "@peculiar/webcrypto@npm:1.5.0" + dependencies: + "@peculiar/asn1-schema": "npm:^2.3.8" + "@peculiar/json-schema": "npm:^1.1.12" + pvtsutils: "npm:^1.3.5" + tslib: "npm:^2.6.2" + webcrypto-core: "npm:^1.8.0" + checksum: 10/a6658390c37b1d386f46066e796985eb56f6f86a772e1373c364ec9a8257adf8623f156596613d2828b489e2b5f32f9d2b0820289b4981646001cba7d21ae2f6 + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -2790,6 +3749,13 @@ __metadata: languageName: node linkType: hard +"@repeaterjs/repeater@npm:^3.0.4": + version: 3.0.6 + resolution: "@repeaterjs/repeater@npm:3.0.6" + checksum: 10/25698e822847b776006428f31e2d31fbcb4faccf30c1c8d68d6e1308e58b49afb08764d1dd15536ddd67775cd01fd6c2fb22f039c05a71865448fbcfb2246af2 + languageName: node + linkType: hard + "@sinclair/typebox@npm:^0.27.8": version: 0.27.8 resolution: "@sinclair/typebox@npm:0.27.8" @@ -3070,6 +4036,13 @@ __metadata: languageName: node linkType: hard +"@types/js-yaml@npm:^4.0.0": + version: 4.0.9 + resolution: "@types/js-yaml@npm:4.0.9" + checksum: 10/a0ce595db8a987904badd21fc50f9f444cb73069f4b95a76cc222e0a17b3ff180669059c763ec314bc4c3ce284379177a9da80e83c5f650c6c1310cafbfaa8e6 + languageName: node + linkType: hard + "@types/json-schema@npm:*, @types/json-schema@npm:^7.0.15, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": version: 7.0.15 resolution: "@types/json-schema@npm:7.0.15" @@ -3250,6 +4223,15 @@ __metadata: languageName: node linkType: hard +"@types/ws@npm:^8.0.0": + version: 8.5.11 + resolution: "@types/ws@npm:8.5.11" + dependencies: + "@types/node": "npm:*" + checksum: 10/950d13b762fc7c092a0fc1450c41229a1d41abb93cb72251068885bd46fa4bbcf461c00df2e77de3f7a547371998b650a720ed90417562af0772b14a8a009dec + languageName: node + linkType: hard + "@types/ws@npm:^8.5.10": version: 8.5.10 resolution: "@types/ws@npm:8.5.10" @@ -3400,6 +4382,16 @@ __metadata: languageName: node linkType: hard +"@urql/core@npm:^5.0.4": + version: 5.0.4 + resolution: "@urql/core@npm:5.0.4" + dependencies: + "@0no-co/graphql.web": "npm:^1.0.5" + wonka: "npm:^6.3.2" + checksum: 10/fe3ee871bde8ee8931d1f791a1475f8bf97940eb93eaa690b11dbfcfa9409644ea87c204245f64e22ea1ddeb16a55e7532b765c18eedcdd9ae8032981889d06a + languageName: node + linkType: hard + "@webassemblyjs/ast@npm:1.12.1, @webassemblyjs/ast@npm:^1.12.1": version: 1.12.1 resolution: "@webassemblyjs/ast@npm:1.12.1" @@ -3584,6 +4576,69 @@ __metadata: languageName: node linkType: hard +"@whatwg-node/events@npm:^0.0.3": + version: 0.0.3 + resolution: "@whatwg-node/events@npm:0.0.3" + checksum: 10/af26f40d4d0a0f5f0ee45fc6124afb8d6b33988dae96ab0fb87aa5e66d1ff08a749491b9da533ea524bbaebd4a770736f254d574a91ab4455386aa098cee8c77 + languageName: node + linkType: hard + +"@whatwg-node/events@npm:^0.1.0": + version: 0.1.1 + resolution: "@whatwg-node/events@npm:0.1.1" + checksum: 10/3a356ca23522190201e27446cfd7ebf1cf96815ddb9d1ba5da0a00bbe6c1d28b4094862104411101fbedd47c758b25fe3683033f6a3e80933029efd664c33567 + languageName: node + linkType: hard + +"@whatwg-node/fetch@npm:^0.8.0": + version: 0.8.8 + resolution: "@whatwg-node/fetch@npm:0.8.8" + dependencies: + "@peculiar/webcrypto": "npm:^1.4.0" + "@whatwg-node/node-fetch": "npm:^0.3.6" + busboy: "npm:^1.6.0" + urlpattern-polyfill: "npm:^8.0.0" + web-streams-polyfill: "npm:^3.2.1" + checksum: 10/4d04f28a3db1886a5ab6070af0d8d6b90c891596495e62417aa296dcdf65506703fb5f76937f7a7b7f4125721ef80f4ac9204a948588c33517dc064c746d7a42 + languageName: node + linkType: hard + +"@whatwg-node/fetch@npm:^0.9.0": + version: 0.9.18 + resolution: "@whatwg-node/fetch@npm:0.9.18" + dependencies: + "@whatwg-node/node-fetch": "npm:^0.5.7" + urlpattern-polyfill: "npm:^10.0.0" + checksum: 10/3ef78969991a44ea99c4f88929c1472b65f066c9d2734992d0de77d11089eec5c3477503cd9513201516056496e28c9fb172635f69867417a0c68e88f4409c96 + languageName: node + linkType: hard + +"@whatwg-node/node-fetch@npm:^0.3.6": + version: 0.3.6 + resolution: "@whatwg-node/node-fetch@npm:0.3.6" + dependencies: + "@whatwg-node/events": "npm:^0.0.3" + busboy: "npm:^1.6.0" + fast-querystring: "npm:^1.1.1" + fast-url-parser: "npm:^1.1.3" + tslib: "npm:^2.3.1" + checksum: 10/8284e385cf50f4479f19a5be8feb0d55f448af3bb7a62ec654ec9e4232ce3f0858191494f508f5196a94b16017d5e08f8e0bce9f49af4dc133a39d5047b8e369 + languageName: node + linkType: hard + +"@whatwg-node/node-fetch@npm:^0.5.7": + version: 0.5.11 + resolution: "@whatwg-node/node-fetch@npm:0.5.11" + dependencies: + "@kamilkisiela/fast-url-parser": "npm:^1.1.4" + "@whatwg-node/events": "npm:^0.1.0" + busboy: "npm:^1.6.0" + fast-querystring: "npm:^1.1.1" + tslib: "npm:^2.3.1" + checksum: 10/0ddbe236ce38b7859ad4b3c2097560585473d6d70afd5e72d88a97daa89176af7b888c95b6fc93a0bd6a471a42b7fb32ef8dac4d1558fbd6d835b25887329a91 + languageName: node + linkType: hard + "@xtuc/ieee754@npm:^1.2.0": version: 1.2.0 resolution: "@xtuc/ieee754@npm:1.2.0" @@ -3728,7 +4783,7 @@ __metadata: languageName: node linkType: hard -"ansi-escapes@npm:^4.3.2": +"ansi-escapes@npm:^4.2.1, ansi-escapes@npm:^4.3.0, ansi-escapes@npm:^4.3.2": version: 4.3.2 resolution: "ansi-escapes@npm:4.3.2" dependencies: @@ -3864,6 +4919,31 @@ __metadata: languageName: node linkType: hard +"asap@npm:~2.0.3": + version: 2.0.6 + resolution: "asap@npm:2.0.6" + checksum: 10/b244c0458c571945e4b3be0b14eb001bea5596f9868cc50cc711dc03d58a7e953517d3f0dad81ccde3ff37d1f074701fa76a6f07d41aaa992d7204a37b915dda + languageName: node + linkType: hard + +"asn1js@npm:^3.0.1, asn1js@npm:^3.0.5": + version: 3.0.5 + resolution: "asn1js@npm:3.0.5" + dependencies: + pvtsutils: "npm:^1.3.2" + pvutils: "npm:^1.1.3" + tslib: "npm:^2.4.0" + checksum: 10/17fb0302432186631550de9606a4622ec366646d072cde9cdf4bcafa47bd2425e157eeb7b1377ee6520f8b46687b4ecaee31cf0ad2fa494361a1938b2ed53194 + languageName: node + linkType: hard + +"astral-regex@npm:^2.0.0": + version: 2.0.0 + resolution: "astral-regex@npm:2.0.0" + checksum: 10/876231688c66400473ba505731df37ea436e574dd524520294cc3bbc54ea40334865e01fa0d074d74d036ee874ee7e62f486ea38bc421ee8e6a871c06f011766 + languageName: node + linkType: hard + "async@npm:^3.2.3": version: 3.2.5 resolution: "async@npm:3.2.5" @@ -3885,6 +4965,13 @@ __metadata: languageName: node linkType: hard +"auto-bind@npm:~4.0.0": + version: 4.0.0 + resolution: "auto-bind@npm:4.0.0" + checksum: 10/00cad71cce5742faccb7dd65c1b55ebc4f45add4b0c9a1547b10b05bab22813230133b0c892c67ba3eb969a4524710c5e43cc45c72898ec84e56f3a596e7a04f + languageName: node + linkType: hard + "autoprefixer@npm:^10.4.19": version: 10.4.19 resolution: "autoprefixer@npm:10.4.19" @@ -3963,6 +5050,50 @@ __metadata: languageName: node linkType: hard +"babel-plugin-syntax-trailing-function-commas@npm:^7.0.0-beta.0": + version: 7.0.0-beta.0 + resolution: "babel-plugin-syntax-trailing-function-commas@npm:7.0.0-beta.0" + checksum: 10/e37509156ca945dd9e4b82c66dd74f2d842ad917bd280cb5aa67960942300cd065eeac476d2514bdcdedec071277a358f6d517c31d9f9244d9bbc3619a8ecf8a + languageName: node + linkType: hard + +"babel-preset-fbjs@npm:^3.4.0": + version: 3.4.0 + resolution: "babel-preset-fbjs@npm:3.4.0" + dependencies: + "@babel/plugin-proposal-class-properties": "npm:^7.0.0" + "@babel/plugin-proposal-object-rest-spread": "npm:^7.0.0" + "@babel/plugin-syntax-class-properties": "npm:^7.0.0" + "@babel/plugin-syntax-flow": "npm:^7.0.0" + "@babel/plugin-syntax-jsx": "npm:^7.0.0" + "@babel/plugin-syntax-object-rest-spread": "npm:^7.0.0" + "@babel/plugin-transform-arrow-functions": "npm:^7.0.0" + "@babel/plugin-transform-block-scoped-functions": "npm:^7.0.0" + "@babel/plugin-transform-block-scoping": "npm:^7.0.0" + "@babel/plugin-transform-classes": "npm:^7.0.0" + "@babel/plugin-transform-computed-properties": "npm:^7.0.0" + "@babel/plugin-transform-destructuring": "npm:^7.0.0" + "@babel/plugin-transform-flow-strip-types": "npm:^7.0.0" + "@babel/plugin-transform-for-of": "npm:^7.0.0" + "@babel/plugin-transform-function-name": "npm:^7.0.0" + "@babel/plugin-transform-literals": "npm:^7.0.0" + "@babel/plugin-transform-member-expression-literals": "npm:^7.0.0" + "@babel/plugin-transform-modules-commonjs": "npm:^7.0.0" + "@babel/plugin-transform-object-super": "npm:^7.0.0" + "@babel/plugin-transform-parameters": "npm:^7.0.0" + "@babel/plugin-transform-property-literals": "npm:^7.0.0" + "@babel/plugin-transform-react-display-name": "npm:^7.0.0" + "@babel/plugin-transform-react-jsx": "npm:^7.0.0" + "@babel/plugin-transform-shorthand-properties": "npm:^7.0.0" + "@babel/plugin-transform-spread": "npm:^7.0.0" + "@babel/plugin-transform-template-literals": "npm:^7.0.0" + babel-plugin-syntax-trailing-function-commas: "npm:^7.0.0-beta.0" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10/1e73ebaaeac805aad15793d06a40a63be096730f58708ec434f08578b5ccba890190cda8fdf1c626ab081a8e1cfd376c9db82eaf78a0fafdbcc2362eb2963804 + languageName: node + linkType: hard + "balanced-match@npm:^1.0.0": version: 1.0.2 resolution: "balanced-match@npm:1.0.2" @@ -4092,7 +5223,7 @@ __metadata: languageName: node linkType: hard -"braces@npm:^3.0.2, braces@npm:~3.0.2": +"braces@npm:^3.0.2, braces@npm:^3.0.3, braces@npm:~3.0.2": version: 3.0.3 resolution: "braces@npm:3.0.3" dependencies: @@ -4115,6 +5246,29 @@ __metadata: languageName: node linkType: hard +"browserslist@npm:^4.23.1": + version: 4.23.2 + resolution: "browserslist@npm:4.23.2" + dependencies: + caniuse-lite: "npm:^1.0.30001640" + electron-to-chromium: "npm:^1.4.820" + node-releases: "npm:^2.0.14" + update-browserslist-db: "npm:^1.1.0" + bin: + browserslist: cli.js + checksum: 10/326a98b1c39bcc9a99b197f15790dc28e122b1aead3257c837421899377ac96239123f26868698085b3d9be916d72540602738e1f857e86a387e810af3fda6e5 + languageName: node + linkType: hard + +"bser@npm:2.1.1": + version: 2.1.1 + resolution: "bser@npm:2.1.1" + dependencies: + node-int64: "npm:^0.4.0" + checksum: 10/edba1b65bae682450be4117b695997972bd9a3c4dfee029cab5bcb72ae5393a79a8f909b8bc77957eb0deec1c7168670f18f4d5c556f46cdd3bca5f3b3a8d020 + languageName: node + linkType: hard + "buffer-crc32@npm:~0.2.3": version: 0.2.13 resolution: "buffer-crc32@npm:0.2.13" @@ -4155,6 +5309,15 @@ __metadata: languageName: node linkType: hard +"busboy@npm:^1.6.0": + version: 1.6.0 + resolution: "busboy@npm:1.6.0" + dependencies: + streamsearch: "npm:^1.1.0" + checksum: 10/bee10fa10ea58e7e3e7489ffe4bda6eacd540a17de9f9cd21cc37e297b2dd9fe52b2715a5841afaec82900750d810d01d7edb4b2d456427f449b92b417579763 + languageName: node + linkType: hard + "bytes@npm:3.0.0": version: 3.0.0 resolution: "bytes@npm:3.0.0" @@ -4226,6 +5389,13 @@ __metadata: languageName: node linkType: hard +"camelcase@npm:^5.0.0": + version: 5.3.1 + resolution: "camelcase@npm:5.3.1" + checksum: 10/e6effce26b9404e3c0f301498184f243811c30dfe6d0b9051863bd8e4034d09c8c2923794f280d6827e5aa055f6c434115ff97864a16a963366fb35fd673024b + languageName: node + linkType: hard + "caniuse-api@npm:^3.0.0": version: 3.0.0 resolution: "caniuse-api@npm:3.0.0" @@ -4245,6 +5415,13 @@ __metadata: languageName: node linkType: hard +"caniuse-lite@npm:^1.0.30001640": + version: 1.0.30001642 + resolution: "caniuse-lite@npm:1.0.30001642" + checksum: 10/8d80ea82be453ae0fdfea8766d82740a4945c1b99189650f29bfc458d4e235d7e99027a8f8bc5a4228d8c4457ba896315284b0703f300353ad5f09d8e693de10 + languageName: node + linkType: hard + "capital-case@npm:^1.0.4": version: 1.0.4 resolution: "capital-case@npm:1.0.4" @@ -4277,7 +5454,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.2": +"chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.1, chalk@npm:^4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -4294,6 +5471,24 @@ __metadata: languageName: node linkType: hard +"change-case-all@npm:1.0.15": + version: 1.0.15 + resolution: "change-case-all@npm:1.0.15" + dependencies: + change-case: "npm:^4.1.2" + is-lower-case: "npm:^2.0.2" + is-upper-case: "npm:^2.0.2" + lower-case: "npm:^2.0.2" + lower-case-first: "npm:^2.0.2" + sponge-case: "npm:^1.0.1" + swap-case: "npm:^2.0.2" + title-case: "npm:^3.0.3" + upper-case: "npm:^2.0.2" + upper-case-first: "npm:^2.0.2" + checksum: 10/e1dabdcd8447a3690f3faf15f92979dfbc113109b50916976e1d5e518e6cfdebee4f05f54d0ca24fb79a4bf835185b59ae25e967bb3dc10bd236a775b19ecc52 + languageName: node + linkType: hard + "change-case@npm:^4.1.2": version: 4.1.2 resolution: "change-case@npm:4.1.2" @@ -4431,6 +5626,23 @@ __metadata: languageName: node linkType: hard +"cli-truncate@npm:^2.1.0": + version: 2.1.0 + resolution: "cli-truncate@npm:2.1.0" + dependencies: + slice-ansi: "npm:^3.0.0" + string-width: "npm:^4.2.0" + checksum: 10/976f1887de067a8cd6ec830a7a8508336aebe6cec79b521d98ed13f67ef073b637f7305675b6247dd22f9e9cf045ec55fe746c7bdb288fbe8db0dfdc9fd52e55 + languageName: node + linkType: hard + +"cli-width@npm:^3.0.0": + version: 3.0.0 + resolution: "cli-width@npm:3.0.0" + checksum: 10/8730848b04fb189666ab037a35888d191c8f05b630b1d770b0b0e4c920b47bb5cc14bddf6b8ffe5bfc66cee97c8211d4d18e756c1ffcc75d7dbe7e1186cd7826 + languageName: node + linkType: hard + "cli-width@npm:^4.1.0": version: 4.1.0 resolution: "cli-width@npm:4.1.0" @@ -4438,14 +5650,36 @@ __metadata: languageName: node linkType: hard -"clipboardy@npm:4.0.0": - version: 4.0.0 - resolution: "clipboardy@npm:4.0.0" +"clipboardy@npm:4.0.0": + version: 4.0.0 + resolution: "clipboardy@npm:4.0.0" + dependencies: + execa: "npm:^8.0.1" + is-wsl: "npm:^3.1.0" + is64bit: "npm:^2.0.0" + checksum: 10/ec4ebe7e5c81d9c9cb994637e7b0e068c1c8fc272167ecd5519f967427271ec66e0e64da7268a2630b860eff42933aeabe25ba5e42bb80dbf1fae6362df059ed + languageName: node + linkType: hard + +"cliui@npm:^6.0.0": + version: 6.0.0 + resolution: "cliui@npm:6.0.0" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.0" + wrap-ansi: "npm:^6.2.0" + checksum: 10/44afbcc29df0899e87595590792a871cd8c4bc7d6ce92832d9ae268d141a77022adafca1aeaeccff618b62a613b8354e57fe22a275c199ec04baf00d381ef6ab + languageName: node + linkType: hard + +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" dependencies: - execa: "npm:^8.0.1" - is-wsl: "npm:^3.1.0" - is64bit: "npm:^2.0.0" - checksum: 10/ec4ebe7e5c81d9c9cb994637e7b0e068c1c8fc272167ecd5519f967427271ec66e0e64da7268a2630b860eff42933aeabe25ba5e42bb80dbf1fae6362df059ed + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^7.0.0" + checksum: 10/eaa5561aeb3135c2cddf7a3b3f562fc4238ff3b3fc666869ef2adf264be0f372136702f16add9299087fb1907c2e4ec5dbfe83bd24bce815c70a80c6c1a2e950 languageName: node linkType: hard @@ -4526,7 +5760,7 @@ __metadata: languageName: node linkType: hard -"colorette@npm:^2.0.10, colorette@npm:^2.0.14": +"colorette@npm:^2.0.10, colorette@npm:^2.0.14, colorette@npm:^2.0.16": version: 2.0.20 resolution: "colorette@npm:2.0.20" checksum: 10/0b8de48bfa5d10afc160b8eaa2b9938f34a892530b2f7d7897e0458d9535a066e3998b49da9d21161c78225b272df19ae3a64d6df28b4c9734c0e55bbd02406f @@ -4577,6 +5811,13 @@ __metadata: languageName: node linkType: hard +"common-tags@npm:1.8.2": + version: 1.8.2 + resolution: "common-tags@npm:1.8.2" + checksum: 10/c665d0f463ee79dda801471ad8da6cb33ff7332ba45609916a508ad3d77ba07ca9deeb452e83f81f24c2b081e2c1315347f23d239210e63d1c5e1a0c7c019fe2 + languageName: node + linkType: hard + "compressible@npm:~2.0.16": version: 2.0.18 resolution: "compressible@npm:2.0.18" @@ -4709,7 +5950,7 @@ __metadata: languageName: node linkType: hard -"cosmiconfig@npm:^8.2.0": +"cosmiconfig@npm:^8.1.0, cosmiconfig@npm:^8.1.3, cosmiconfig@npm:^8.2.0": version: 8.3.6 resolution: "cosmiconfig@npm:8.3.6" dependencies: @@ -4743,6 +5984,24 @@ __metadata: languageName: node linkType: hard +"cross-fetch@npm:^3.1.5": + version: 3.1.8 + resolution: "cross-fetch@npm:3.1.8" + dependencies: + node-fetch: "npm:^2.6.12" + checksum: 10/ac8c4ca87d2ac0e17a19b6a293a67ee8934881aee5ec9a5a8323c30e9a9a60a0f5291d3c0d633ec2a2f970cbc60978d628804dfaf03add92d7e720b6d37f392c + languageName: node + linkType: hard + +"cross-inspect@npm:1.0.0": + version: 1.0.0 + resolution: "cross-inspect@npm:1.0.0" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/975c81799549627027254eb70f1c349cefb14435d580bea6f351f510c839dcb1a9288983407bac2ad317e6eff29cf1e99299606da21f404562bfa64cec502239 + languageName: node + linkType: hard + "cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": version: 7.0.3 resolution: "cross-spawn@npm:7.0.3" @@ -5098,6 +6357,13 @@ __metadata: languageName: node linkType: hard +"dataloader@npm:^2.2.2": + version: 2.2.2 + resolution: "dataloader@npm:2.2.2" + checksum: 10/9c7a1f02cfa6391ab8bc21ebd0ef60b03832bd3beafdfecf48b111fba14090f98d33965f8e268045ba3c289f801b6a9000a9e61a41188363bdee2344811f64f1 + languageName: node + linkType: hard + "date-fns-tz@npm:^2.0.0": version: 2.0.1 resolution: "date-fns-tz@npm:2.0.1" @@ -5132,6 +6398,8 @@ __metadata: "@blueprintjs/datetime2": "npm:^2.3.5" "@blueprintjs/icons": "npm:^5.1.0" "@blueprintjs/select": "npm:^5.1.5" + "@graphql-codegen/cli": "npm:^5.0.2" + "@graphql-codegen/client-preset": "npm:^4.3.2" "@lcdp/offline-plugin": "npm:^5.0.7" "@pmmmwh/react-refresh-webpack-plugin": "npm:^0.5.15" "@reduxjs/toolkit": "npm:^2.2.5" @@ -5144,6 +6412,7 @@ __metadata: "@types/react-dom": "npm:^18.3.0" "@typescript-eslint/eslint-plugin": "npm:^7.13.0" "@typescript-eslint/parser": "npm:^7.13.0" + "@urql/core": "npm:^5.0.4" autoprefixer: "npm:^10.4.19" axios: "npm:^1.7.2" babel-loader: "npm:^9.1.3" @@ -5156,6 +6425,7 @@ __metadata: css-loader: "npm:^7.1.2" css-minimizer-webpack-plugin: "npm:^7.0.0" date-fns: "npm:^2.28.0" + dotenv: "npm:^16.4.5" eslint: "npm:^8.57.0" eslint-config-prettier: "npm:^9.1.0" eslint-plugin-prettier: "npm:^5.1.3" @@ -5164,6 +6434,7 @@ __metadata: favicons-webpack-plugin: "npm:^6.0.0" fork-ts-checker-webpack-plugin: "npm:^9.0.2" fuzzy-search: "npm:^3.0.2" + graphql: "npm:^16.9.0" he: "npm:^1.2.0" html-entities: "npm:^2.5.2" html-loader: "npm:^5.0.0" @@ -5210,6 +6481,13 @@ __metadata: languageName: unknown linkType: soft +"debounce@npm:^1.2.0": + version: 1.2.1 + resolution: "debounce@npm:1.2.1" + checksum: 10/0b95b2a9d80ed69117d890f8dab8c0f2d6066f8d20edd1d810ae51f8f366a6d4c8b1d56e97dcb9304e93d57de4d5db440d34a03def7dad50403fc3f22bf16808 + languageName: node + linkType: hard + "debug@npm:2.6.9": version: 2.6.9 resolution: "debug@npm:2.6.9" @@ -5231,6 +6509,13 @@ __metadata: languageName: node linkType: hard +"decamelize@npm:^1.2.0": + version: 1.2.0 + resolution: "decamelize@npm:1.2.0" + checksum: 10/ad8c51a7e7e0720c70ec2eeb1163b66da03e7616d7b98c9ef43cce2416395e84c1e9548dd94f5f6ffecfee9f8b94251fc57121a8b021f2ff2469b2bae247b8aa + languageName: node + linkType: hard + "decimal.js@npm:^10.4.3": version: 10.4.3 resolution: "decimal.js@npm:10.4.3" @@ -5380,6 +6665,13 @@ __metadata: languageName: node linkType: hard +"dependency-graph@npm:^0.11.0": + version: 0.11.0 + resolution: "dependency-graph@npm:0.11.0" + checksum: 10/6b5eb540303753037a613e781da4b81534d139cbabc92f342630ed622e3ef4c332fc40cf87823e1ec71a7aeb4b195f8d88d7e625931ce6007bf2bf09a8bfb01e + languageName: node + linkType: hard + "destroy@npm:1.2.0": version: 1.2.0 resolution: "destroy@npm:1.2.0" @@ -5387,6 +6679,13 @@ __metadata: languageName: node linkType: hard +"detect-indent@npm:^6.0.0": + version: 6.1.0 + resolution: "detect-indent@npm:6.1.0" + checksum: 10/ab953a73c72dbd4e8fc68e4ed4bfd92c97eb6c43734af3900add963fd3a9316f3bc0578b018b24198d4c31a358571eff5f0656e81a1f3b9ad5c547d58b2d093d + languageName: node + linkType: hard + "detect-libc@npm:^2.0.0, detect-libc@npm:^2.0.2": version: 2.0.2 resolution: "detect-libc@npm:2.0.2" @@ -5533,6 +6832,20 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:^16.0.0, dotenv@npm:^16.4.5": + version: 16.4.5 + resolution: "dotenv@npm:16.4.5" + checksum: 10/55a3134601115194ae0f924e54473459ed0d9fc340ae610b676e248cca45aa7c680d86365318ea964e6da4e2ea80c4514c1adab5adb43d6867fb57ff068f95c8 + languageName: node + linkType: hard + +"dset@npm:^3.1.2": + version: 3.1.3 + resolution: "dset@npm:3.1.3" + checksum: 10/f3f7096718eeabe1608886364ea02254d5221a4d59d4fb4d2fd2fdf53cccf293d486793a44c894d3a07a916a283d1214e831e423839096d461a38571fc092126 + languageName: node + linkType: hard + "eastasianwidth@npm:^0.2.0": version: 0.2.0 resolution: "eastasianwidth@npm:0.2.0" @@ -5565,6 +6878,13 @@ __metadata: languageName: node linkType: hard +"electron-to-chromium@npm:^1.4.820": + version: 1.4.827 + resolution: "electron-to-chromium@npm:1.4.827" + checksum: 10/7fa44aeebc5548874d33e417579d998d8e9a3d7b07fae22429ee7de5866c73b3158d56969146df3dcf44a222dcd91972ee786d0427f461e0c98bff79e408e782 + languageName: node + linkType: hard + "emoji-regex@npm:^8.0.0": version: 8.0.0 resolution: "emoji-regex@npm:8.0.0" @@ -5831,6 +7151,13 @@ __metadata: languageName: node linkType: hard +"escalade@npm:^3.1.2": + version: 3.1.2 + resolution: "escalade@npm:3.1.2" + checksum: 10/a1e07fea2f15663c30e40b9193d658397846ffe28ce0a3e4da0d8e485fedfeca228ab846aee101a05015829adf39f9934ff45b2a3fca47bed37a29646bd05cd3 + languageName: node + linkType: hard + "escape-html@npm:^1.0.3, escape-html@npm:~1.0.3": version: 1.0.3 resolution: "escape-html@npm:1.0.3" @@ -6191,7 +7518,7 @@ __metadata: languageName: node linkType: hard -"external-editor@npm:^3.1.0": +"external-editor@npm:^3.0.3, external-editor@npm:^3.1.0": version: 3.1.0 resolution: "external-editor@npm:3.1.0" dependencies: @@ -6202,6 +7529,20 @@ __metadata: languageName: node linkType: hard +"extract-files@npm:^11.0.0": + version: 11.0.0 + resolution: "extract-files@npm:11.0.0" + checksum: 10/02bf0dde9617d67795e38a182d8bf58828a7c5d77762623ff05e72d461a0e980071a860e2503231db2cc8824d8da35cefb1750937dcbe018cb0e67e37f20a7be + languageName: node + linkType: hard + +"fast-decode-uri-component@npm:^1.0.1": + version: 1.0.1 + resolution: "fast-decode-uri-component@npm:1.0.1" + checksum: 10/4b6ed26974414f688be4a15eab6afa997bad4a7c8605cb1deb928b28514817b4523a1af0fa06621c6cbfedb7e5615144c2c3e7512860e3a333a31a28d537dca7 + languageName: node + linkType: hard + "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -6243,6 +7584,24 @@ __metadata: languageName: node linkType: hard +"fast-querystring@npm:^1.1.1": + version: 1.1.2 + resolution: "fast-querystring@npm:1.1.2" + dependencies: + fast-decode-uri-component: "npm:^1.0.1" + checksum: 10/981da9b914f2b639dc915bdfa4f34ab028b967d428f02fbd293d99258593fde69c48eea73dfa03ced088268e0a8045c642e8debcd9b4821ebd125e130a0430c7 + languageName: node + linkType: hard + +"fast-url-parser@npm:^1.1.3": + version: 1.1.3 + resolution: "fast-url-parser@npm:1.1.3" + dependencies: + punycode: "npm:^1.3.2" + checksum: 10/6d33f46ce9776f7f3017576926207a950ca39bc5eb78fc794404f2288fe494720f9a119084b75569bd9eb09d2b46678bfaf39c191fb2c808ef3c833dc8982752 + languageName: node + linkType: hard + "fastest-levenshtein@npm:^1.0.12": version: 1.0.16 resolution: "fastest-levenshtein@npm:1.0.16" @@ -6297,6 +7656,37 @@ __metadata: languageName: node linkType: hard +"fb-watchman@npm:^2.0.0": + version: 2.0.2 + resolution: "fb-watchman@npm:2.0.2" + dependencies: + bser: "npm:2.1.1" + checksum: 10/4f95d336fb805786759e383fd7fff342ceb7680f53efcc0ef82f502eb479ce35b98e8b207b6dfdfeea0eba845862107dc73813775fc6b56b3098c6e90a2dad77 + languageName: node + linkType: hard + +"fbjs-css-vars@npm:^1.0.0": + version: 1.0.2 + resolution: "fbjs-css-vars@npm:1.0.2" + checksum: 10/72baf6d22c45b75109118b4daecb6c8016d4c83c8c0f23f683f22e9d7c21f32fff6201d288df46eb561e3c7d4bb4489b8ad140b7f56444c453ba407e8bd28511 + languageName: node + linkType: hard + +"fbjs@npm:^3.0.0": + version: 3.0.5 + resolution: "fbjs@npm:3.0.5" + dependencies: + cross-fetch: "npm:^3.1.5" + fbjs-css-vars: "npm:^1.0.0" + loose-envify: "npm:^1.0.0" + object-assign: "npm:^4.1.0" + promise: "npm:^7.1.1" + setimmediate: "npm:^1.0.5" + ua-parser-js: "npm:^1.0.35" + checksum: 10/71252595b00b06fb0475a295c74d81ada1cc499b7e11f2cde51fef04618affa568f5b7f4927f61720c23254b9144be28f8acb2086a5001cf65df8eec87c6ca5c + languageName: node + linkType: hard + "fetch-blob@npm:^3.1.2, fetch-blob@npm:^3.1.4": version: 3.2.0 resolution: "fetch-blob@npm:3.2.0" @@ -6307,6 +7697,15 @@ __metadata: languageName: node linkType: hard +"figures@npm:^3.0.0": + version: 3.2.0 + resolution: "figures@npm:3.2.0" + dependencies: + escape-string-regexp: "npm:^1.0.5" + checksum: 10/a3bf94e001be51d3770500789157f067218d4bc681a65e1f69d482de15120bcac822dceb1a7b3803f32e4e3a61a46df44f7f2c8ba95d6375e7491502e0dd3d97 + languageName: node + linkType: hard + "file-entry-cache@npm:^6.0.1": version: 6.0.1 resolution: "file-entry-cache@npm:6.0.1" @@ -6384,7 +7783,7 @@ __metadata: languageName: node linkType: hard -"find-up@npm:^4.0.0": +"find-up@npm:^4.0.0, find-up@npm:^4.1.0": version: 4.1.0 resolution: "find-up@npm:4.1.0" dependencies: @@ -6615,6 +8014,13 @@ __metadata: languageName: node linkType: hard +"get-caller-file@npm:^2.0.1, get-caller-file@npm:^2.0.5": + version: 2.0.5 + resolution: "get-caller-file@npm:2.0.5" + checksum: 10/b9769a836d2a98c3ee734a88ba712e62703f1df31b94b784762c433c27a386dd6029ff55c2a920c392e33657d80191edbf18c61487e198844844516f843496b9 + languageName: node + linkType: hard + "get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.4": version: 1.2.4 resolution: "get-intrinsic@npm:1.2.4" @@ -6709,7 +8115,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^7.0.3, glob@npm:^7.1.3": +"glob@npm:^7.0.3, glob@npm:^7.1.1, glob@npm:^7.1.3": version: 7.2.3 resolution: "glob@npm:7.2.3" dependencies: @@ -6749,7 +8155,7 @@ __metadata: languageName: node linkType: hard -"globby@npm:^11.1.0": +"globby@npm:^11.0.3, globby@npm:^11.1.0": version: 11.1.0 resolution: "globby@npm:11.1.0" dependencies: @@ -6813,6 +8219,70 @@ __metadata: languageName: node linkType: hard +"graphql-config@npm:^5.0.2": + version: 5.0.3 + resolution: "graphql-config@npm:5.0.3" + dependencies: + "@graphql-tools/graphql-file-loader": "npm:^8.0.0" + "@graphql-tools/json-file-loader": "npm:^8.0.0" + "@graphql-tools/load": "npm:^8.0.0" + "@graphql-tools/merge": "npm:^9.0.0" + "@graphql-tools/url-loader": "npm:^8.0.0" + "@graphql-tools/utils": "npm:^10.0.0" + cosmiconfig: "npm:^8.1.0" + jiti: "npm:^1.18.2" + minimatch: "npm:^4.2.3" + string-env-interpolation: "npm:^1.0.1" + tslib: "npm:^2.4.0" + peerDependencies: + cosmiconfig-toml-loader: ^1.0.0 + graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + peerDependenciesMeta: + cosmiconfig-toml-loader: + optional: true + checksum: 10/be7667ea260c7db3e8b02c0d73d2a2bc214683d91886f883c73465e07aa204f9ae6bff494eaa253def31abd2bbe59e78c6b418ed456e06d2274050dbc45e33e7 + languageName: node + linkType: hard + +"graphql-request@npm:^6.0.0": + version: 6.1.0 + resolution: "graphql-request@npm:6.1.0" + dependencies: + "@graphql-typed-document-node/core": "npm:^3.2.0" + cross-fetch: "npm:^3.1.5" + peerDependencies: + graphql: 14 - 16 + checksum: 10/a9c6f2eeaad972cdecb91437c15c785a282263fd0ef36f6fc5648e0945da488cdc10ab4736891ee1fbb928c7bf6e0bc8e0284df514254adefe02cc406ba5fce5 + languageName: node + linkType: hard + +"graphql-tag@npm:^2.11.0": + version: 2.12.6 + resolution: "graphql-tag@npm:2.12.6" + dependencies: + tslib: "npm:^2.1.0" + peerDependencies: + graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 10/23a2bc1d3fbeae86444204e0ac08522e09dc369559ba75768e47421a7321b59f352fb5b2c9a5c37d3cf6de890dca4e5ac47e740c7cc622e728572ecaa649089e + languageName: node + linkType: hard + +"graphql-ws@npm:^5.14.0": + version: 5.16.0 + resolution: "graphql-ws@npm:5.16.0" + peerDependencies: + graphql: ">=0.11 <=16" + checksum: 10/e56d903920c78fa88966e31940d281f8b35ef8c9f4543255bfe349e47a0e972c6ca746bcb52040b1c6938d22e42560228994399972abc473cfa6bcd183aca709 + languageName: node + linkType: hard + +"graphql@npm:^16.9.0": + version: 16.9.0 + resolution: "graphql@npm:16.9.0" + checksum: 10/5833f82bb6c31bec120bbf9cd400eda873e1bb7ef5c17974fa262cd82dc68728fda5d4cb859dc8aaa4c4fe4f6fe1103a9c47efc01a12c02ae5cb581d8e4029e2 + languageName: node + linkType: hard + "handle-thing@npm:^2.0.0": version: 2.0.1 resolution: "handle-thing@npm:2.0.1" @@ -7086,6 +8556,16 @@ __metadata: languageName: node linkType: hard +"https-proxy-agent@npm:^7.0.0": + version: 7.0.5 + resolution: "https-proxy-agent@npm:7.0.5" + dependencies: + agent-base: "npm:^7.0.2" + debug: "npm:4" + checksum: 10/6679d46159ab3f9a5509ee80c3a3fc83fba3a920a5e18d32176c3327852c3c00ad640c0c4210a8fd70ea3c4a6d3a1b375bf01942516e7df80e2646bdc77658ab + languageName: node + linkType: hard + "https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.4": version: 7.0.4 resolution: "https-proxy-agent@npm:7.0.4" @@ -7167,6 +8647,13 @@ __metadata: languageName: node linkType: hard +"immutable@npm:~3.7.6": + version: 3.7.6 + resolution: "immutable@npm:3.7.6" + checksum: 10/4f2cc2e0b6839befa2ea9d3ca478971a88ca78cb66c2b077416e5d5203f8e168bffb78284dd45fe1b427a4a8ac37194dfa3cd3e50b39529a00cca387bd6ac955 + languageName: node + linkType: hard + "import-fresh@npm:^3.2.1, import-fresh@npm:^3.3.0": version: 3.3.0 resolution: "import-fresh@npm:3.3.0" @@ -7177,6 +8664,13 @@ __metadata: languageName: node linkType: hard +"import-from@npm:4.0.0": + version: 4.0.0 + resolution: "import-from@npm:4.0.0" + checksum: 10/1fa29c05b048da18914e91d9a529e5d9b91774bebbfab10e53f59bcc1667917672b971cf102fee857f142e5e433ce69fa1f0a596e1c7d82f9947a5ec352694b9 + languageName: node + linkType: hard + "import-local@npm:^3.0.2": version: 3.1.0 resolution: "import-local@npm:3.1.0" @@ -7234,6 +8728,29 @@ __metadata: languageName: node linkType: hard +"inquirer@npm:^8.0.0": + version: 8.2.6 + resolution: "inquirer@npm:8.2.6" + dependencies: + ansi-escapes: "npm:^4.2.1" + chalk: "npm:^4.1.1" + cli-cursor: "npm:^3.1.0" + cli-width: "npm:^3.0.0" + external-editor: "npm:^3.0.3" + figures: "npm:^3.0.0" + lodash: "npm:^4.17.21" + mute-stream: "npm:0.0.8" + ora: "npm:^5.4.1" + run-async: "npm:^2.4.0" + rxjs: "npm:^7.5.5" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + through: "npm:^2.3.6" + wrap-ansi: "npm:^6.0.1" + checksum: 10/f642b9e5a94faaba54f277bdda2af0e0a6b592bd7f88c60e1614b5795b19336c7025e0c2923915d5f494f600a02fe8517413779a794415bb79a9563b061d68ab + languageName: node + linkType: hard + "inquirer@npm:^9.2.23": version: 9.2.23 resolution: "inquirer@npm:9.2.23" @@ -7283,6 +8800,15 @@ __metadata: languageName: node linkType: hard +"invariant@npm:^2.2.4": + version: 2.2.4 + resolution: "invariant@npm:2.2.4" + dependencies: + loose-envify: "npm:^1.0.0" + checksum: 10/cc3182d793aad82a8d1f0af697b462939cb46066ec48bbf1707c150ad5fad6406137e91a262022c269702e01621f35ef60269f6c0d7fd178487959809acdfb14 + languageName: node + linkType: hard + "ip@npm:^2.0.0": version: 2.0.1 resolution: "ip@npm:2.0.1" @@ -7304,6 +8830,16 @@ __metadata: languageName: node linkType: hard +"is-absolute@npm:^1.0.0": + version: 1.0.0 + resolution: "is-absolute@npm:1.0.0" + dependencies: + is-relative: "npm:^1.0.0" + is-windows: "npm:^1.0.1" + checksum: 10/9d16b2605eda3f3ce755410f1d423e327ad3a898bcb86c9354cf63970ed3f91ba85e9828aa56f5d6a952b9fae43d0477770f78d37409ae8ecc31e59ebc279b27 + languageName: node + linkType: hard + "is-arrayish@npm:^0.2.1": version: 0.2.1 resolution: "is-arrayish@npm:0.2.1" @@ -7366,7 +8902,7 @@ __metadata: languageName: node linkType: hard -"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": +"is-glob@npm:4.0.3, is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": version: 4.0.3 resolution: "is-glob@npm:4.0.3" dependencies: @@ -7400,6 +8936,15 @@ __metadata: languageName: node linkType: hard +"is-lower-case@npm:^2.0.2": + version: 2.0.2 + resolution: "is-lower-case@npm:2.0.2" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10/ba57dd1201e15fd9b590654736afccf1b3b68e919f40c23ef13b00ebcc639b1d9c2f81fe86415bff3e8eccffec459786c9ac9dc8f3a19cfa4484206c411c1d7d + languageName: node + linkType: hard + "is-network-error@npm:^1.0.0": version: 1.0.1 resolution: "is-network-error@npm:1.0.1" @@ -7476,6 +9021,15 @@ __metadata: languageName: node linkType: hard +"is-relative@npm:^1.0.0": + version: 1.0.0 + resolution: "is-relative@npm:1.0.0" + dependencies: + is-unc-path: "npm:^1.0.0" + checksum: 10/3271a0df109302ef5e14a29dcd5d23d9788e15ade91a40b942b035827ffbb59f7ce9ff82d036ea798541a52913cbf9d2d0b66456340887b51f3542d57b5a4c05 + languageName: node + linkType: hard + "is-stream@npm:^2.0.0": version: 2.0.1 resolution: "is-stream@npm:2.0.1" @@ -7490,6 +9044,15 @@ __metadata: languageName: node linkType: hard +"is-unc-path@npm:^1.0.0": + version: 1.0.0 + resolution: "is-unc-path@npm:1.0.0" + dependencies: + unc-path-regex: "npm:^0.1.2" + checksum: 10/e8abfde203f7409f5b03a5f1f8636e3a41e78b983702ef49d9343eb608cdfe691429398e8815157519b987b739bcfbc73ae7cf4c8582b0ab66add5171088eab6 + languageName: node + linkType: hard + "is-unicode-supported@npm:^0.1.0": version: 0.1.0 resolution: "is-unicode-supported@npm:0.1.0" @@ -7497,6 +9060,22 @@ __metadata: languageName: node linkType: hard +"is-upper-case@npm:^2.0.2": + version: 2.0.2 + resolution: "is-upper-case@npm:2.0.2" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10/cf4fd43c00c2e72cd5cff911923070b89f0933b464941bd782e2315385f80b5a5acd772db3b796542e5e3cfed735f4dffd88c54d62db1ebfc5c3daa7b1af2bc6 + languageName: node + linkType: hard + +"is-windows@npm:^1.0.1": + version: 1.0.2 + resolution: "is-windows@npm:1.0.2" + checksum: 10/438b7e52656fe3b9b293b180defb4e448088e7023a523ec21a91a80b9ff8cdb3377ddb5b6e60f7c7de4fa8b63ab56e121b6705fe081b3cf1b828b0a380009ad7 + languageName: node + linkType: hard + "is-wsl@npm:^3.1.0": version: 3.1.0 resolution: "is-wsl@npm:3.1.0" @@ -7553,6 +9132,15 @@ __metadata: languageName: node linkType: hard +"isomorphic-ws@npm:^5.0.0": + version: 5.0.0 + resolution: "isomorphic-ws@npm:5.0.0" + peerDependencies: + ws: "*" + checksum: 10/e20eb2aee09ba96247465fda40c6d22c1153394c0144fa34fe6609f341af4c8c564f60ea3ba762335a7a9c306809349f9b863c8beedf2beea09b299834ad5398 + languageName: node + linkType: hard + "jackspeak@npm:^2.3.6": version: 2.3.6 resolution: "jackspeak@npm:2.3.6" @@ -7629,6 +9217,15 @@ __metadata: languageName: node linkType: hard +"jiti@npm:^1.17.1, jiti@npm:^1.18.2": + version: 1.21.6 + resolution: "jiti@npm:1.21.6" + bin: + jiti: bin/jiti.js + checksum: 10/289b124cea411c130a14ffe88e3d38376ab44b6695616dfa0a1f32176a8f20ec90cdd6d2b9d81450fc6467cfa4d865f04f49b98452bff0f812bc400fd0ae78d6 + languageName: node + linkType: hard + "jiti@npm:^1.20.0": version: 1.21.0 resolution: "jiti@npm:1.21.0" @@ -7638,6 +9235,13 @@ __metadata: languageName: node linkType: hard +"jose@npm:^5.0.0": + version: 5.6.3 + resolution: "jose@npm:5.6.3" + checksum: 10/829f07b8857221ada1cd112a5ebd084e34748f6e3247f5cb03aca0a1f25dd6a07283c9a4f5b854935bda505e3243405640550f8e9eb46c45b172dc4cb336ac89 + languageName: node + linkType: hard + "jotai@npm:^2.8.3": version: 2.8.3 resolution: "jotai@npm:2.8.3" @@ -7667,7 +9271,7 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:^4.1.0": +"js-yaml@npm:^4.0.0, js-yaml@npm:^4.1.0": version: 4.1.0 resolution: "js-yaml@npm:4.1.0" dependencies: @@ -7795,6 +9399,16 @@ __metadata: languageName: node linkType: hard +"json-to-pretty-yaml@npm:^1.2.2": + version: 1.2.2 + resolution: "json-to-pretty-yaml@npm:1.2.2" + dependencies: + remedial: "npm:^1.0.7" + remove-trailing-spaces: "npm:^1.0.6" + checksum: 10/3ccd527c9a9cf41e123d75445605801dd0eebcddf53e00af05febc212a3657fceb03063399693d79cb2b7a8530dd062420caf35fa02cc0a4ae182fb74843d920 + languageName: node + linkType: hard + "json5@npm:^2.1.2, json5@npm:^2.2.3": version: 2.2.3 resolution: "json5@npm:2.2.3" @@ -7874,6 +9488,27 @@ __metadata: languageName: node linkType: hard +"listr2@npm:^4.0.5": + version: 4.0.5 + resolution: "listr2@npm:4.0.5" + dependencies: + cli-truncate: "npm:^2.1.0" + colorette: "npm:^2.0.16" + log-update: "npm:^4.0.0" + p-map: "npm:^4.0.0" + rfdc: "npm:^1.3.0" + rxjs: "npm:^7.5.5" + through: "npm:^2.3.8" + wrap-ansi: "npm:^7.0.0" + peerDependencies: + enquirer: ">= 2.3.0 < 3" + peerDependenciesMeta: + enquirer: + optional: true + checksum: 10/9c591fdd4fd6b7e8b4feca60380be01d74c65a98857f6caff2418c609fb9f0016c2e1b65c0ef5b1f4ff015967be87e8642e7ac3ad7ce0aa3c1a0329b60128b3b + languageName: node + linkType: hard + "load-bmfont@npm:^1.4.1": version: 1.4.1 resolution: "load-bmfont@npm:1.4.1" @@ -7956,6 +9591,13 @@ __metadata: languageName: node linkType: hard +"lodash.sortby@npm:^4.7.0": + version: 4.7.0 + resolution: "lodash.sortby@npm:4.7.0" + checksum: 10/38df19ae28608af2c50ac342fc1f414508309d53e1d58ed9adfb2c3cd17c3af290058c0a0478028d932c5404df3d53349d19fa364ef6bed6145a6bc21320399e + languageName: node + linkType: hard + "lodash.uniq@npm:^4.5.0": version: 4.5.0 resolution: "lodash.uniq@npm:4.5.0" @@ -7963,14 +9605,14 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21": +"lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:~4.17.0": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: 10/c08619c038846ea6ac754abd6dd29d2568aa705feb69339e836dfa8d8b09abbb2f859371e86863eda41848221f9af43714491467b5b0299122431e202bb0c532 languageName: node linkType: hard -"log-symbols@npm:^4.1.0": +"log-symbols@npm:^4.0.0, log-symbols@npm:^4.1.0": version: 4.1.0 resolution: "log-symbols@npm:4.1.0" dependencies: @@ -7980,6 +9622,18 @@ __metadata: languageName: node linkType: hard +"log-update@npm:^4.0.0": + version: 4.0.0 + resolution: "log-update@npm:4.0.0" + dependencies: + ansi-escapes: "npm:^4.3.0" + cli-cursor: "npm:^3.1.0" + slice-ansi: "npm:^4.0.0" + wrap-ansi: "npm:^6.2.0" + checksum: 10/ae2f85bbabc1906034154fb7d4c4477c79b3e703d22d78adee8b3862fa913942772e7fa11713e3d96fb46de4e3cabefbf5d0a544344f03b58d3c4bff52aa9eb2 + languageName: node + linkType: hard + "loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" @@ -7991,6 +9645,15 @@ __metadata: languageName: node linkType: hard +"lower-case-first@npm:^2.0.2": + version: 2.0.2 + resolution: "lower-case-first@npm:2.0.2" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10/33e3da1098ddda219ce125d4ab7a78a944972c0ee8872e95b6ccc35df8ad405284ab233b0ba4d72315ad1a06fe2f0d418ee4cba9ec1ef1c386dea78899fc8958 + languageName: node + linkType: hard + "lower-case@npm:^2.0.2": version: 2.0.2 resolution: "lower-case@npm:2.0.2" @@ -8053,6 +9716,13 @@ __metadata: languageName: node linkType: hard +"map-cache@npm:^0.2.0": + version: 0.2.2 + resolution: "map-cache@npm:0.2.2" + checksum: 10/3067cea54285c43848bb4539f978a15dedc63c03022abeec6ef05c8cb6829f920f13b94bcaf04142fc6a088318e564c4785704072910d120d55dbc2e0c421969 + languageName: node + linkType: hard + "mdn-data@npm:2.0.28": version: 2.0.28 resolution: "mdn-data@npm:2.0.28" @@ -8129,6 +9799,18 @@ __metadata: languageName: node linkType: hard +"meros@npm:^1.2.1": + version: 1.3.0 + resolution: "meros@npm:1.3.0" + peerDependencies: + "@types/node": ">=13" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/1893d226866058a32161ab069294a1a16975c765416a2b05165dfafba07cd958ca12503e35c621ffe736c62d935ccb1ce60cb723e2a9e0b85e02bb3236722ef6 + languageName: node + linkType: hard + "methods@npm:~1.1.2": version: 1.1.2 resolution: "methods@npm:1.1.2" @@ -8146,6 +9828,16 @@ __metadata: languageName: node linkType: hard +"micromatch@npm:^4.0.5": + version: 4.0.7 + resolution: "micromatch@npm:4.0.7" + dependencies: + braces: "npm:^3.0.3" + picomatch: "npm:^2.3.1" + checksum: 10/a11ed1cb67dcbbe9a5fc02c4062cf8bb0157d73bf86956003af8dcfdf9b287f9e15ec0f6d6925ff6b8b5b496202335e497b01de4d95ef6cf06411bc5e5c474a0 + languageName: node + linkType: hard + "mime-db@npm:1.52.0, mime-db@npm:>= 1.43.0 < 2": version: 1.52.0 resolution: "mime-db@npm:1.52.0" @@ -8251,6 +9943,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^4.2.3": + version: 4.2.3 + resolution: "minimatch@npm:4.2.3" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10/02bacb187448c6aeeed5a794a64799fb1d1dd0274694da97272a9d3b919c7dfba6987d2089f6465191450d36d767c47fd7958227610b919121ccaed7de7f8924 + languageName: node + linkType: hard + "minimatch@npm:^5.0.1": version: 5.1.6 resolution: "minimatch@npm:5.1.6" @@ -8434,6 +10135,13 @@ __metadata: languageName: node linkType: hard +"mute-stream@npm:0.0.8": + version: 0.0.8 + resolution: "mute-stream@npm:0.0.8" + checksum: 10/a2d2e79dde87e3424ffc8c334472c7f3d17b072137734ca46e6f221131f1b014201cc593b69a38062e974fb2394d3d1cb4349f80f012bbf8b8ac1b28033e515f + languageName: node + linkType: hard + "mute-stream@npm:1.0.0": version: 1.0.0 resolution: "mute-stream@npm:1.0.0" @@ -8538,7 +10246,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.6.1": +"node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.12": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" dependencies: @@ -8590,6 +10298,13 @@ __metadata: languageName: node linkType: hard +"node-int64@npm:^0.4.0": + version: 0.4.0 + resolution: "node-int64@npm:0.4.0" + checksum: 10/b7afc2b65e56f7035b1a2eec57ae0fbdee7d742b1cdcd0f4387562b6527a011ab1cbe9f64cc8b3cca61e3297c9637c8bf61cec2e6b8d3a711d4b5267dfafbe02 + languageName: node + linkType: hard + "node-releases@npm:^2.0.14": version: 2.0.14 resolution: "node-releases@npm:2.0.14" @@ -8608,6 +10323,15 @@ __metadata: languageName: node linkType: hard +"normalize-path@npm:^2.1.1": + version: 2.1.1 + resolution: "normalize-path@npm:2.1.1" + dependencies: + remove-trailing-separator: "npm:^1.0.1" + checksum: 10/7e9cbdcf7f5b8da7aa191fbfe33daf290cdcd8c038f422faf1b8a83c972bf7a6d94c5be34c4326cb00fb63bc0fd97d9fbcfaf2e5d6142332c2cd36d2e1b86cea + languageName: node + linkType: hard + "normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": version: 3.0.0 resolution: "normalize-path@npm:3.0.0" @@ -8656,6 +10380,13 @@ __metadata: languageName: node linkType: hard +"nullthrows@npm:^1.1.1": + version: 1.1.1 + resolution: "nullthrows@npm:1.1.1" + checksum: 10/c7cf377a095535dc301d81cf7959d3784d090a609a2a4faa40b6121a0c1d7f70d3a3aa534a34ab852e8553b66848ec503c28f2c19efd617ed564dc07dfbb6d33 + languageName: node + linkType: hard + "nwsapi@npm:^2.2.10": version: 2.2.10 resolution: "nwsapi@npm:2.2.10" @@ -8663,7 +10394,7 @@ __metadata: languageName: node linkType: hard -"object-assign@npm:^4.0.1, object-assign@npm:^4.1.1": +"object-assign@npm:^4.0.1, object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" checksum: 10/fcc6e4ea8c7fe48abfbb552578b1c53e0d194086e2e6bbbf59e0a536381a292f39943c6e9628af05b5528aa5e3318bb30d6b2e53cadaf5b8fe9e12c4b69af23f @@ -8784,6 +10515,15 @@ __metadata: languageName: node linkType: hard +"p-limit@npm:3.1.0, p-limit@npm:^3.0.2": + version: 3.1.0 + resolution: "p-limit@npm:3.1.0" + dependencies: + yocto-queue: "npm:^0.1.0" + checksum: 10/7c3690c4dbf62ef625671e20b7bdf1cbc9534e83352a2780f165b0d3ceba21907e77ad63401708145ca4e25bfc51636588d89a8c0aeb715e6c37d1c066430360 + languageName: node + linkType: hard + "p-limit@npm:^2.2.0": version: 2.3.0 resolution: "p-limit@npm:2.3.0" @@ -8793,15 +10533,6 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^3.0.2": - version: 3.1.0 - resolution: "p-limit@npm:3.1.0" - dependencies: - yocto-queue: "npm:^0.1.0" - checksum: 10/7c3690c4dbf62ef625671e20b7bdf1cbc9534e83352a2780f165b0d3ceba21907e77ad63401708145ca4e25bfc51636588d89a8c0aeb715e6c37d1c066430360 - languageName: node - linkType: hard - "p-limit@npm:^4.0.0": version: 4.0.0 resolution: "p-limit@npm:4.0.0" @@ -8955,6 +10686,17 @@ __metadata: languageName: node linkType: hard +"parse-filepath@npm:^1.0.2": + version: 1.0.2 + resolution: "parse-filepath@npm:1.0.2" + dependencies: + is-absolute: "npm:^1.0.0" + map-cache: "npm:^0.2.0" + path-root: "npm:^0.1.1" + checksum: 10/6794c3f38d3921f0f7cc63fb1fb0c4d04cd463356ad389c8ce6726d3c50793b9005971f4138975a6d7025526058d5e65e9bfe634d0765e84c4e2571152665a69 + languageName: node + linkType: hard + "parse-headers@npm:^2.0.0": version: 2.0.5 resolution: "parse-headers@npm:2.0.5" @@ -9087,6 +10829,22 @@ __metadata: languageName: node linkType: hard +"path-root-regex@npm:^0.1.0": + version: 0.1.2 + resolution: "path-root-regex@npm:0.1.2" + checksum: 10/dcd75d1f8e93faabe35a58e875b0f636839b3658ff2ad8c289463c40bc1a844debe0dab73c3398ef9dc8f6ec6c319720aff390cf4633763ddcf3cf4b1bbf7e8b + languageName: node + linkType: hard + +"path-root@npm:^0.1.1": + version: 0.1.1 + resolution: "path-root@npm:0.1.1" + dependencies: + path-root-regex: "npm:^0.1.0" + checksum: 10/ff88aebfc1c59ace510cc06703d67692a11530989920427625e52b66a303ca9b3d4059b0b7d0b2a73248d1ad29bcb342b8b786ec00592f3101d38a45fd3b2e08 + languageName: node + linkType: hard + "path-scurry@npm:^1.10.2": version: 1.10.2 resolution: "path-scurry@npm:1.10.2" @@ -9156,6 +10914,13 @@ __metadata: languageName: node linkType: hard +"picocolors@npm:^1.0.1": + version: 1.0.1 + resolution: "picocolors@npm:1.0.1" + checksum: 10/fa68166d1f56009fc02a34cdfd112b0dd3cf1ef57667ac57281f714065558c01828cdf4f18600ad6851cbe0093952ed0660b1e0156bddf2184b6aaf5817553a5 + languageName: node + linkType: hard + "picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.3, picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" @@ -9727,6 +11492,15 @@ __metadata: languageName: node linkType: hard +"promise@npm:^7.1.1": + version: 7.3.1 + resolution: "promise@npm:7.3.1" + dependencies: + asap: "npm:~2.0.3" + checksum: 10/37dbe58ca7b0716cc881f0618128f1fd6ff9c46cdc529a269fd70004e567126a449a94e9428e2d19b53d06182d11b45d0c399828f103e06b2bb87643319bd2e7 + languageName: node + linkType: hard + "prop-types@npm:^15.6.2": version: 15.8.1 resolution: "prop-types@npm:15.8.1" @@ -9772,6 +11546,13 @@ __metadata: languageName: node linkType: hard +"punycode@npm:^1.3.2": + version: 1.4.1 + resolution: "punycode@npm:1.4.1" + checksum: 10/af2700dde1a116791ff8301348ff344c47d6c224e875057237d1b5112035655fb07a6175cfdb8bf0e3a8cdfd2dc82b3a622e0aefd605566c0e949a6d0d1256a4 + languageName: node + linkType: hard + "punycode@npm:^2.1.0, punycode@npm:^2.1.1, punycode@npm:^2.3.1": version: 2.3.1 resolution: "punycode@npm:2.3.1" @@ -9779,6 +11560,22 @@ __metadata: languageName: node linkType: hard +"pvtsutils@npm:^1.3.2, pvtsutils@npm:^1.3.5": + version: 1.3.5 + resolution: "pvtsutils@npm:1.3.5" + dependencies: + tslib: "npm:^2.6.1" + checksum: 10/0a908edafe0e9db7ced6c0b86c897aaadd7389de73e54cc4aff1c8a887a623251bf43aedbc19f983336bffdd119959fa4e57eb544ca3ad5f6a30896a25c37fcc + languageName: node + linkType: hard + +"pvutils@npm:^1.1.3": + version: 1.1.3 + resolution: "pvutils@npm:1.1.3" + checksum: 10/e5201b8f78ece68eae414a938c844bc45fb3f0de298178eed1775a217eedfd897c4346e5e54f410bb4d7466e09ceb262e85f20fd64239b8bb2595f14c52fa95e + languageName: node + linkType: hard + "qs@npm:6.11.0": version: 6.11.0 resolution: "qs@npm:6.11.0" @@ -10179,6 +11976,38 @@ __metadata: languageName: node linkType: hard +"relay-runtime@npm:12.0.0": + version: 12.0.0 + resolution: "relay-runtime@npm:12.0.0" + dependencies: + "@babel/runtime": "npm:^7.0.0" + fbjs: "npm:^3.0.0" + invariant: "npm:^2.2.4" + checksum: 10/d6211e8206ea7273f88dccd5ea72abe6836c6f0bfe95a48ddf80c54e47a08edaf312bedecba98a0a0ba6abcd360cbacd6a2ddb4cef65f00170fb0f36cc324f5e + languageName: node + linkType: hard + +"remedial@npm:^1.0.7": + version: 1.0.8 + resolution: "remedial@npm:1.0.8" + checksum: 10/41e23a7d656fd696678e4f648e57ece5c9e13c097094e8ac6e173990a0665a24d8e50cbb39d458af3b0d58cfbd7811fc0840c4646d10ce3285fe5819b1c82375 + languageName: node + linkType: hard + +"remove-trailing-separator@npm:^1.0.1": + version: 1.1.0 + resolution: "remove-trailing-separator@npm:1.1.0" + checksum: 10/d3c20b5a2d987db13e1cca9385d56ecfa1641bae143b620835ac02a6b70ab88f68f117a0021838db826c57b31373d609d52e4f31aca75fc490c862732d595419 + languageName: node + linkType: hard + +"remove-trailing-spaces@npm:^1.0.6": + version: 1.0.8 + resolution: "remove-trailing-spaces@npm:1.0.8" + checksum: 10/81f615c5cd8dd6a5e3017dcc9af598965575d176d42ef99cfd7b894529991f464e629fd68aba089f5c6bebf5bb8070a5eee56f3b621aba55e8ef524d6a4d4f69 + languageName: node + linkType: hard + "renderkid@npm:^3.0.0": version: 3.0.0 resolution: "renderkid@npm:3.0.0" @@ -10192,6 +12021,13 @@ __metadata: languageName: node linkType: hard +"require-directory@npm:^2.1.1": + version: 2.1.1 + resolution: "require-directory@npm:2.1.1" + checksum: 10/a72468e2589270d91f06c7d36ec97a88db53ae5d6fe3787fadc943f0b0276b10347f89b363b2a82285f650bdcc135ad4a257c61bdd4d00d6df1fa24875b0ddaf + languageName: node + linkType: hard + "require-from-string@npm:^2.0.2": version: 2.0.2 resolution: "require-from-string@npm:2.0.2" @@ -10199,6 +12035,13 @@ __metadata: languageName: node linkType: hard +"require-main-filename@npm:^2.0.0": + version: 2.0.0 + resolution: "require-main-filename@npm:2.0.0" + checksum: 10/8604a570c06a69c9d939275becc33a65676529e1c3e5a9f42d58471674df79357872b96d70bb93a0380a62d60dc9031c98b1a9dad98c946ffdd61b7ac0c8cedd + languageName: node + linkType: hard + "requires-port@npm:^1.0.0": version: 1.0.0 resolution: "requires-port@npm:1.0.0" @@ -10222,6 +12065,13 @@ __metadata: languageName: node linkType: hard +"resolve-from@npm:5.0.0, resolve-from@npm:^5.0.0": + version: 5.0.0 + resolution: "resolve-from@npm:5.0.0" + checksum: 10/be18a5e4d76dd711778664829841cde690971d02b6cbae277735a09c1c28f407b99ef6ef3cd585a1e6546d4097b28df40ed32c4a287b9699dcf6d7f208495e23 + languageName: node + linkType: hard + "resolve-from@npm:^4.0.0": version: 4.0.0 resolution: "resolve-from@npm:4.0.0" @@ -10229,13 +12079,6 @@ __metadata: languageName: node linkType: hard -"resolve-from@npm:^5.0.0": - version: 5.0.0 - resolution: "resolve-from@npm:5.0.0" - checksum: 10/be18a5e4d76dd711778664829841cde690971d02b6cbae277735a09c1c28f407b99ef6ef3cd585a1e6546d4097b28df40ed32c4a287b9699dcf6d7f208495e23 - languageName: node - linkType: hard - "resolve@npm:^1.14.2, resolve@npm:^1.20.0": version: 1.22.8 resolution: "resolve@npm:1.22.8" @@ -10293,6 +12136,13 @@ __metadata: languageName: node linkType: hard +"rfdc@npm:^1.3.0": + version: 1.4.1 + resolution: "rfdc@npm:1.4.1" + checksum: 10/2f3d11d3d8929b4bfeefc9acb03aae90f971401de0add5ae6c5e38fec14f0405e6a4aad8fdb76344bfdd20c5193110e3750cbbd28ba86d73729d222b6cf4a729 + languageName: node + linkType: hard + "rimraf@npm:^2.6.3": version: 2.7.1 resolution: "rimraf@npm:2.7.1" @@ -10347,6 +12197,13 @@ __metadata: languageName: node linkType: hard +"run-async@npm:^2.4.0": + version: 2.4.1 + resolution: "run-async@npm:2.4.1" + checksum: 10/c79551224dafa26ecc281cb1efad3510c82c79116aaf681f8a931ce70fdf4ca880d58f97d3b930a38992c7aad7955a08e065b32ec194e1dd49d7790c874ece50 + languageName: node + linkType: hard + "run-async@npm:^3.0.0": version: 3.0.0 resolution: "run-async@npm:3.0.0" @@ -10363,7 +12220,7 @@ __metadata: languageName: node linkType: hard -"rxjs@npm:^7.2.0, rxjs@npm:^7.8.1": +"rxjs@npm:^7.2.0, rxjs@npm:^7.5.5, rxjs@npm:^7.8.1": version: 7.8.1 resolution: "rxjs@npm:7.8.1" dependencies: @@ -10450,6 +12307,13 @@ __metadata: languageName: node linkType: hard +"scuid@npm:^1.1.0": + version: 1.1.0 + resolution: "scuid@npm:1.1.0" + checksum: 10/cd094ac3718b0070a222f9a499b280c698fdea10268cc163fa244421099544c1766dd893fdee0e2a8eba5d53ab9d0bcb11067bedff166665030fa6fda25a096b + languageName: node + linkType: hard + "select-hose@npm:^2.0.0": version: 2.0.0 resolution: "select-hose@npm:2.0.0" @@ -10546,6 +12410,13 @@ __metadata: languageName: node linkType: hard +"set-blocking@npm:^2.0.0": + version: 2.0.0 + resolution: "set-blocking@npm:2.0.0" + checksum: 10/8980ebf7ae9eb945bb036b6e283c547ee783a1ad557a82babf758a065e2fb6ea337fd82cac30dd565c1e606e423f30024a19fff7afbf4977d784720c4026a8ef + languageName: node + linkType: hard + "set-function-length@npm:^1.2.1": version: 1.2.2 resolution: "set-function-length@npm:1.2.2" @@ -10560,6 +12431,13 @@ __metadata: languageName: node linkType: hard +"setimmediate@npm:^1.0.5": + version: 1.0.5 + resolution: "setimmediate@npm:1.0.5" + checksum: 10/76e3f5d7f4b581b6100ff819761f04a984fa3f3990e72a6554b57188ded53efce2d3d6c0932c10f810b7c59414f85e2ab3c11521877d1dea1ce0b56dc906f485 + languageName: node + linkType: hard + "setprototypeof@npm:1.1.0": version: 1.1.0 resolution: "setprototypeof@npm:1.1.0" @@ -10668,7 +12546,7 @@ __metadata: languageName: node linkType: hard -"shell-quote@npm:^1.8.1": +"shell-quote@npm:^1.7.3, shell-quote@npm:^1.8.1": version: 1.8.1 resolution: "shell-quote@npm:1.8.1" checksum: 10/af19ab5a1ec30cb4b2f91fd6df49a7442d5c4825a2e269b3712eded10eedd7f9efeaab96d57829880733fc55bcdd8e9b1d8589b4befb06667c731d08145e274d @@ -10700,6 +12578,13 @@ __metadata: languageName: node linkType: hard +"signedsource@npm:^1.0.0": + version: 1.0.0 + resolution: "signedsource@npm:1.0.0" + checksum: 10/64b2c8d7a48de9009cfd3aff62bb7c88abf3b8e0421f17ebb1d7f5ca9cc9c3ad10f5a1e3ae6cd804e4e6121c87b668202ae9057065f058ddfbf34ea65f63945d + languageName: node + linkType: hard + "simfile-parser@npm:^0.7.2": version: 0.7.2 resolution: "simfile-parser@npm:0.7.2" @@ -10757,6 +12642,28 @@ __metadata: languageName: node linkType: hard +"slice-ansi@npm:^3.0.0": + version: 3.0.0 + resolution: "slice-ansi@npm:3.0.0" + dependencies: + ansi-styles: "npm:^4.0.0" + astral-regex: "npm:^2.0.0" + is-fullwidth-code-point: "npm:^3.0.0" + checksum: 10/5ec6d022d12e016347e9e3e98a7eb2a592213a43a65f1b61b74d2c78288da0aded781f665807a9f3876b9daa9ad94f64f77d7633a0458876c3a4fdc4eb223f24 + languageName: node + linkType: hard + +"slice-ansi@npm:^4.0.0": + version: 4.0.0 + resolution: "slice-ansi@npm:4.0.0" + dependencies: + ansi-styles: "npm:^4.0.0" + astral-regex: "npm:^2.0.0" + is-fullwidth-code-point: "npm:^3.0.0" + checksum: 10/4a82d7f085b0e1b070e004941ada3c40d3818563ac44766cca4ceadd2080427d337554f9f99a13aaeb3b4a94d9964d9466c807b3d7b7541d1ec37ee32d308756 + languageName: node + linkType: hard + "smart-buffer@npm:^4.2.0": version: 4.2.0 resolution: "smart-buffer@npm:4.2.0" @@ -10864,6 +12771,15 @@ __metadata: languageName: node linkType: hard +"sponge-case@npm:^1.0.1": + version: 1.0.1 + resolution: "sponge-case@npm:1.0.1" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10/64f53d930f63c5a9e59d4cae487c1ffa87d25eab682833b01d572cc885e7e3fdbad4f03409a41f03ecb27f1f8959432253eb48332c7007c3388efddb24ba2792 + languageName: node + linkType: hard + "ssri@npm:^10.0.0": version: 10.0.5 resolution: "ssri@npm:10.0.5" @@ -10911,7 +12827,21 @@ __metadata: languageName: node linkType: hard -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.3": +"streamsearch@npm:^1.1.0": + version: 1.1.0 + resolution: "streamsearch@npm:1.1.0" + checksum: 10/612c2b2a7dbcc859f74597112f80a42cbe4d448d03da790d5b7b39673c1197dd3789e91cd67210353e58857395d32c1e955a9041c4e6d5bae723436b3ed9ed14 + languageName: node + linkType: hard + +"string-env-interpolation@npm:^1.0.1": + version: 1.0.1 + resolution: "string-env-interpolation@npm:1.0.1" + checksum: 10/d126329587f635bee65300e4451e7352b9b67e03daeb62f006ca84244cac12a1f6e45176b018653ba0c3ec3b5d980f9ca59d2eeed99cf799501cdaa7f871dc6f + languageName: node + linkType: hard + +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -11079,6 +13009,15 @@ __metadata: languageName: node linkType: hard +"swap-case@npm:^2.0.2": + version: 2.0.2 + resolution: "swap-case@npm:2.0.2" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10/6e21c9e1b3cd5735eb2af679a99ec3efc78a14e3d4d5e3fd594e254b91cfd37185b3d1c6e41b22f53a2cdf5d1b963ce30c0fe8b78337e3fd43d0137084670a5f + languageName: node + linkType: hard + "symbol-tree@npm:^3.2.4": version: 3.2.4 resolution: "symbol-tree@npm:3.2.4" @@ -11210,6 +13149,13 @@ __metadata: languageName: node linkType: hard +"through@npm:^2.3.6, through@npm:^2.3.8": + version: 2.3.8 + resolution: "through@npm:2.3.8" + checksum: 10/5da78346f70139a7d213b65a0106f3c398d6bc5301f9248b5275f420abc2c4b1e77c2abc72d218dedc28c41efb2e7c312cb76a7730d04f9c2d37d247da3f4198 + languageName: node + linkType: hard + "thunky@npm:^1.0.2": version: 1.1.0 resolution: "thunky@npm:1.1.0" @@ -11241,6 +13187,15 @@ __metadata: languageName: node linkType: hard +"title-case@npm:^3.0.3": + version: 3.0.3 + resolution: "title-case@npm:3.0.3" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10/369fe90f650a66205c34ebef63a69c6d1fd411ae3aad23db0aae165ddb881af50e67c6ea6800d605bc2b9e0ab5f22dada58fe97a1a7e7f3131ee0ef176cc65ec + languageName: node + linkType: hard + "tmp@npm:^0.0.33": version: 0.0.33 resolution: "tmp@npm:0.0.33" @@ -11329,7 +13284,14 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.0, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.2.0, tslib@npm:^2.4.0, tslib@npm:^2.6.2, tslib@npm:~2.6.2": +"ts-log@npm:^2.2.3": + version: 2.2.5 + resolution: "ts-log@npm:2.2.5" + checksum: 10/b8fb444ae3b05ac8f709a1acee26dba014ed601e1fc36fa2bfcac5555032eb6c6ca9cd16b8da21832f1631785c3ad7de7177d8e7631c197a1aeca64f03a872a4 + languageName: node + linkType: hard + +"tslib@npm:^2.0.0, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.2.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.6.1, tslib@npm:^2.6.2, tslib@npm:~2.6.0, tslib@npm:~2.6.2": version: 2.6.3 resolution: "tslib@npm:2.6.3" checksum: 10/52109bb681f8133a2e58142f11a50e05476de4f075ca906d13b596ae5f7f12d30c482feb0bff167ae01cfc84c5803e575a307d47938999246f5a49d174fc558c @@ -11412,6 +13374,20 @@ __metadata: languageName: node linkType: hard +"ua-parser-js@npm:^1.0.35": + version: 1.0.38 + resolution: "ua-parser-js@npm:1.0.38" + checksum: 10/f2345e9bd0f9c5f85bcaa434535fae88f4bb891538e568106f0225b2c2937fbfbeb5782bd22320d07b6b3d68b350b8861574c1d7af072ff9b2362fb72d326fd9 + languageName: node + linkType: hard + +"unc-path-regex@npm:^0.1.2": + version: 0.1.2 + resolution: "unc-path-regex@npm:0.1.2" + checksum: 10/a05fa2006bf4606051c10fc7968f08ce7b28fa646befafa282813aeb1ac1a56f65cb1b577ca7851af2726198d59475bb49b11776036257b843eaacee2860a4ec + languageName: node + linkType: hard + "undici-types@npm:~5.26.4": version: 5.26.5 resolution: "undici-types@npm:5.26.5" @@ -11505,6 +13481,15 @@ __metadata: languageName: node linkType: hard +"unixify@npm:^1.0.0": + version: 1.0.0 + resolution: "unixify@npm:1.0.0" + dependencies: + normalize-path: "npm:^2.1.1" + checksum: 10/3be30e48579fc6c7390bd59b4ab9e745fede0c164dfb7351cf710bd1dbef8484b1441186205af6bcb13b731c0c88caf9b33459f7bf8c89e79c046e656ae433f0 + languageName: node + linkType: hard + "unpipe@npm:1.0.0, unpipe@npm:~1.0.0": version: 1.0.0 resolution: "unpipe@npm:1.0.0" @@ -11526,6 +13511,20 @@ __metadata: languageName: node linkType: hard +"update-browserslist-db@npm:^1.1.0": + version: 1.1.0 + resolution: "update-browserslist-db@npm:1.1.0" + dependencies: + escalade: "npm:^3.1.2" + picocolors: "npm:^1.0.1" + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 10/d70b9efeaf4601aadb1a4f6456a7a5d9118e0063d995866b8e0c5e0cf559482671dab6ce7b079f9536b06758a344fbd83f974b965211e1c6e8d1958540b0c24c + languageName: node + linkType: hard + "upper-case-first@npm:^2.0.2": version: 2.0.2 resolution: "upper-case-first@npm:2.0.2" @@ -11563,6 +13562,20 @@ __metadata: languageName: node linkType: hard +"urlpattern-polyfill@npm:^10.0.0": + version: 10.0.0 + resolution: "urlpattern-polyfill@npm:10.0.0" + checksum: 10/346819dbe718e929988298d02a988b8ddfa601d08daaa7e69b1148eab699c86c0f0f933d68d8c8cf913166fe64156ed28904e673200d18ef7e9ed6b58cea3fc7 + languageName: node + linkType: hard + +"urlpattern-polyfill@npm:^8.0.0": + version: 8.0.2 + resolution: "urlpattern-polyfill@npm:8.0.2" + checksum: 10/fd86b5c55473f3abbf9ed317b953c9cbb4fa6b3f75f681a1d982fe9c17bbc8d9bcf988f4cf3bda35e2e5875984086c97e177f97f076bb80dfa2beb85d1dd7b23 + languageName: node + linkType: hard + "use-sync-external-store@npm:1.2.0": version: 1.2.0 resolution: "use-sync-external-store@npm:1.2.0" @@ -11627,6 +13640,13 @@ __metadata: languageName: node linkType: hard +"value-or-promise@npm:^1.0.11, value-or-promise@npm:^1.0.12": + version: 1.0.12 + resolution: "value-or-promise@npm:1.0.12" + checksum: 10/a4cc31fc9c3826b8a216ef2037b676904324c00c4acd903aaec2fe0c08516a189345261dd3cc822ec108532b2ea36b7c99bbdee1c3ddcb7f4b3d57d7e61b2064 + languageName: node + linkType: hard + "vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" @@ -12094,13 +14114,26 @@ __metadata: languageName: node linkType: hard -"web-streams-polyfill@npm:^3.0.3": +"web-streams-polyfill@npm:^3.0.3, web-streams-polyfill@npm:^3.2.1": version: 3.3.3 resolution: "web-streams-polyfill@npm:3.3.3" checksum: 10/8e7e13501b3834094a50abe7c0b6456155a55d7571312b89570012ef47ec2a46d766934768c50aabad10a9c30dd764a407623e8bfcc74fcb58495c29130edea9 languageName: node linkType: hard +"webcrypto-core@npm:^1.8.0": + version: 1.8.0 + resolution: "webcrypto-core@npm:1.8.0" + dependencies: + "@peculiar/asn1-schema": "npm:^2.3.8" + "@peculiar/json-schema": "npm:^1.1.12" + asn1js: "npm:^3.0.1" + pvtsutils: "npm:^1.3.5" + tslib: "npm:^2.6.2" + checksum: 10/c8403d32d99a9497d02a1ab4eee8a7691867d3f38817b1059e619a5603c22313d4a851ff3aa7ea59f1b3708d787c787b76b2484926cad2d5e01f584ae7c126b8 + languageName: node + linkType: hard + "webidl-conversions@npm:^3.0.0": version: 3.0.1 resolution: "webidl-conversions@npm:3.0.1" @@ -12329,6 +14362,13 @@ __metadata: languageName: node linkType: hard +"which-module@npm:^2.0.0": + version: 2.0.1 + resolution: "which-module@npm:2.0.1" + checksum: 10/1967b7ce17a2485544a4fdd9063599f0f773959cca24176dbe8f405e55472d748b7c549cd7920ff6abb8f1ab7db0b0f1b36de1a21c57a8ff741f4f1e792c52be + languageName: node + linkType: hard + "which@npm:^2.0.1": version: 2.0.2 resolution: "which@npm:2.0.2" @@ -12358,6 +14398,13 @@ __metadata: languageName: node linkType: hard +"wonka@npm:^6.3.2": + version: 6.3.4 + resolution: "wonka@npm:6.3.4" + checksum: 10/0f102630182828268b57b54102003449b97abbc2483392239baf856a2fca7b72ae9be67c208415124a3d26a320674ed64387e9bf07a8d0badedb5f607d2ccfdc + languageName: node + linkType: hard + "workerd@npm:1.20240610.1": version: 1.20240610.1 resolution: "workerd@npm:1.20240610.1" @@ -12384,7 +14431,7 @@ __metadata: languageName: node linkType: hard -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0" dependencies: @@ -12395,7 +14442,7 @@ __metadata: languageName: node linkType: hard -"wrap-ansi@npm:^6.2.0": +"wrap-ansi@npm:^6.0.1, wrap-ansi@npm:^6.2.0": version: 6.2.0 resolution: "wrap-ansi@npm:6.2.0" dependencies: @@ -12439,6 +14486,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:^8.12.0, ws@npm:^8.17.1": + version: 8.18.0 + resolution: "ws@npm:8.18.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10/70dfe53f23ff4368d46e4c0b1d4ca734db2c4149c6f68bc62cb16fc21f753c47b35fcc6e582f3bdfba0eaeb1c488cddab3c2255755a5c3eecb251431e42b3ff6 + languageName: node + linkType: hard + "xhr@npm:^2.0.1": version: 2.6.0 resolution: "xhr@npm:2.6.0" @@ -12496,6 +14558,20 @@ __metadata: languageName: node linkType: hard +"y18n@npm:^4.0.0": + version: 4.0.3 + resolution: "y18n@npm:4.0.3" + checksum: 10/392870b2a100bbc643bc035fe3a89cef5591b719c7bdc8721bcdb3d27ab39fa4870acdca67b0ee096e146d769f311d68eda6b8195a6d970f227795061923013f + languageName: node + linkType: hard + +"y18n@npm:^5.0.5": + version: 5.0.8 + resolution: "y18n@npm:5.0.8" + checksum: 10/5f1b5f95e3775de4514edbb142398a2c37849ccfaf04a015be5d75521e9629d3be29bd4432d23c57f37e5b61ade592fb0197022e9993f81a06a5afbdcda9346d + languageName: node + linkType: hard + "yallist@npm:^3.0.2": version: 3.1.1 resolution: "yallist@npm:3.1.1" @@ -12510,6 +14586,73 @@ __metadata: languageName: node linkType: hard +"yaml-ast-parser@npm:^0.0.43": + version: 0.0.43 + resolution: "yaml-ast-parser@npm:0.0.43" + checksum: 10/a54d00c8e0716a392c6e76eee965b3b4bba434494196490946e416fc47f20a1d89820461afacd9431edbb8209e28fce33bcff1fb42dd83f90e51fc31e80251c9 + languageName: node + linkType: hard + +"yaml@npm:^2.3.1": + version: 2.4.5 + resolution: "yaml@npm:2.4.5" + bin: + yaml: bin.mjs + checksum: 10/b09bf5a615a65276d433d76b8e34ad6b4c0320b85eb3f1a39da132c61ae6e2ff34eff4624e6458d96d49566c93cf43408ba5e568218293a8c6541a2006883f64 + languageName: node + linkType: hard + +"yargs-parser@npm:^18.1.2": + version: 18.1.3 + resolution: "yargs-parser@npm:18.1.3" + dependencies: + camelcase: "npm:^5.0.0" + decamelize: "npm:^1.2.0" + checksum: 10/235bcbad5b7ca13e5abc54df61d42f230857c6f83223a38e4ed7b824681875b7f8b6ed52139d88a3ad007050f28dc0324b3c805deac7db22ae3b4815dae0e1bf + languageName: node + linkType: hard + +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: 10/9dc2c217ea3bf8d858041252d43e074f7166b53f3d010a8c711275e09cd3d62a002969a39858b92bbda2a6a63a585c7127014534a560b9c69ed2d923d113406e + languageName: node + linkType: hard + +"yargs@npm:^15.3.1": + version: 15.4.1 + resolution: "yargs@npm:15.4.1" + dependencies: + cliui: "npm:^6.0.0" + decamelize: "npm:^1.2.0" + find-up: "npm:^4.1.0" + get-caller-file: "npm:^2.0.1" + require-directory: "npm:^2.1.1" + require-main-filename: "npm:^2.0.0" + set-blocking: "npm:^2.0.0" + string-width: "npm:^4.2.0" + which-module: "npm:^2.0.0" + y18n: "npm:^4.0.0" + yargs-parser: "npm:^18.1.2" + checksum: 10/bbcc82222996c0982905b668644ca363eebe6ffd6a572fbb52f0c0e8146661d8ce5af2a7df546968779bb03d1e4186f3ad3d55dfaadd1c4f0d5187c0e3a5ba16 + languageName: node + linkType: hard + +"yargs@npm:^17.0.0": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" + dependencies: + cliui: "npm:^8.0.1" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.3" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^21.1.1" + checksum: 10/abb3e37678d6e38ea85485ed86ebe0d1e3464c640d7d9069805ea0da12f69d5a32df8e5625e370f9c96dd1c2dc088ab2d0a4dd32af18222ef3c4224a19471576 + languageName: node + linkType: hard + "yazl@npm:^2.5.1": version: 2.5.1 resolution: "yazl@npm:2.5.1" From f05677b88a9f959cd3ed09ccf16d6d8874b9020b Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Thu, 18 Jul 2024 12:07:16 -0700 Subject: [PATCH 25/91] remove unused zustand utils --- src/zustand/contextual-zustand.ts | 74 ------------------- src/zustand/shared-zustand.ts | 118 ------------------------------ 2 files changed, 192 deletions(-) delete mode 100644 src/zustand/contextual-zustand.ts delete mode 100644 src/zustand/shared-zustand.ts diff --git a/src/zustand/contextual-zustand.ts b/src/zustand/contextual-zustand.ts deleted file mode 100644 index e879006e3..000000000 --- a/src/zustand/contextual-zustand.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { - createContext, - ReactNode, - useContext, - useState, - createElement, - useEffect, -} from "react"; -import { StoreApi, createStore } from "zustand"; -import { useStoreWithEqualityFn } from "zustand/traditional"; - -export function createContextualStore< - StoreValue, - ProviderProps extends { children?: ReactNode }, ->( - /** returns initial store state for given props */ - creator: ( - p: ProviderProps, - set: StoreApi["setState"], - get: StoreApi["getState"], - ) => StoreValue, - /** returns a unique id for a given set of props */ - getUniqueId: (p: ProviderProps) => string, - globalProps: ProviderProps, -) { - const globalStore = createStore(creator.bind(undefined, globalProps)); - const context = createContext(globalStore); - const StoreIndex = new Map>(); - - const Provider = (props: ProviderProps) => { - const [localStore] = useState(() => - createStore(creator.bind(undefined, props)), - ); - useEffect(() => { - const thisId = getUniqueId(props); - StoreIndex.set(thisId, localStore); - return () => { - StoreIndex.delete(thisId); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - return createElement( - context.Provider, - { value: localStore }, - props.children, - ); - }; - - const useThisStore = () => useContext(context); - - function useContextValue(): StoreValue; - function useContextValue( - selector: (state: StoreValue) => Slice, - equalityFn?: (a: Slice, b: Slice) => boolean, - ): Slice; - function useContextValue( - selector?: (state: StoreValue) => Slice, - equalityFn?: (a: Slice, b: Slice) => boolean, - ) { - const store = useThisStore(); - return selector - ? // eslint-disable-next-line react-hooks/rules-of-hooks - useStoreWithEqualityFn(store, selector, equalityFn) - : // eslint-disable-next-line react-hooks/rules-of-hooks - useStoreWithEqualityFn(store); - } - - return { - Provider, - useContextValue, - StoreIndex, - useStore: useThisStore, - }; -} diff --git a/src/zustand/shared-zustand.ts b/src/zustand/shared-zustand.ts deleted file mode 100644 index 95ba95ac6..000000000 --- a/src/zustand/shared-zustand.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { DataConnection } from "peerjs"; -import type { StoreApi } from "zustand"; - -export interface SerializibleStore { - id: string; - serializeSyncFields(): ReducedState; -} - -type SyncMessages = InitSync | StateUpdate | { type: "other" }; - -interface InitSync { - type: "syncedStore.init"; - storeType: string; - storeId: string; - timestamp: number; - state: State; -} - -interface StateUpdate { - type: "syncedStore.stateUpdate"; - storeId: string; - timestamp: number; - state: State; -} - -export function acceptIncomingSyncedStores( - storeType: string, - peer: DataConnection, - handleNewStore: (initialState: SharedState) => void, -) { - const handlePeerMessage = (evt: SyncMessages) => { - if (evt.type !== "syncedStore.init") { - return; - } - if (evt.storeType !== storeType) { - return; - } - handleNewStore(evt.state); - }; - - peer.on("data", handlePeerMessage); - - return () => { - peer.off("data", handlePeerMessage); - }; -} - -export function initShareWithPeer( - storeType: string, - store: StoreApi>, - peer: DataConnection, -) { - const state = store.getState(); - sendMessage(peer, { - type: "syncedStore.init", - storeType, - storeId: state.id, - state: state.serializeSyncFields(), - timestamp: 0, - }); -} - -function sendMessage(peer: DataConnection, msg: SyncMessages) { - peer.send(msg); -} - -/** - * Based on https://github.com/Tom-Julux/shared-zustand - * @returns unsync function - */ -export function syncStoreWithPeer>( - store: StoreApi, - peer: DataConnection, -) { - const storeId = store.getState().id; - let externalUpdate = false; - let timestamp = 0; - - const handleStoreUpdate = (state: State) => { - if (!externalUpdate) { - timestamp = Date.now(); - sendMessage(peer, { - type: "syncedStore.stateUpdate", - storeId: state.id, - timestamp, - state: state.serializeSyncFields(), - }); - } - externalUpdate = false; - }; - - const handlePeerMessage = (evt: SyncMessages) => { - switch (evt.type) { - case "syncedStore.stateUpdate": - if (evt.storeId !== storeId) { - return; - } - break; - default: - return; - } - - if (evt.timestamp <= timestamp) { - return; - } - externalUpdate = true; - timestamp = evt.timestamp; - store.setState(evt.state); - }; - - const unsubscribeFromStore = store.subscribe(handleStoreUpdate); - peer.on("data", handlePeerMessage); - - return () => { - peer.off("data", handlePeerMessage); - unsubscribeFromStore(); - }; -} From 648e575cb2d493e6b3fe5c5787a340cc80398a4a Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Thu, 18 Jul 2024 12:08:38 -0700 Subject: [PATCH 26/91] groundwork for entrants state from startgg --- graphql.config.ts | 2 +- src/startgg-gql/entrants.graphql | 23 ++++++++++ src/startgg-gql/generated/gql.ts | 9 +++- src/startgg-gql/generated/graphql.ts | 16 +++++-- src/startgg-gql/index.ts | 64 ++++++++++++++++++++++++++-- src/startgg-gql/queries.ts | 10 ----- src/startgg-gql/sets.graphql | 25 +++++++++++ src/state/entrants.slice.ts | 25 +++++++++++ src/state/root-reducer.ts | 2 + webpack.config.js | 2 +- 10 files changed, 158 insertions(+), 20 deletions(-) create mode 100644 src/startgg-gql/entrants.graphql delete mode 100644 src/startgg-gql/queries.ts create mode 100644 src/startgg-gql/sets.graphql create mode 100644 src/state/entrants.slice.ts diff --git a/graphql.config.ts b/graphql.config.ts index 56375f723..c1648823e 100644 --- a/graphql.config.ts +++ b/graphql.config.ts @@ -10,5 +10,5 @@ export default { }, }, ], - documents: ["./src/startgg-gql/queries.ts"], + documents: ["./src/startgg-gql/*.graphql"], }; diff --git a/src/startgg-gql/entrants.graphql b/src/startgg-gql/entrants.graphql new file mode 100644 index 000000000..f2865c392 --- /dev/null +++ b/src/startgg-gql/entrants.graphql @@ -0,0 +1,23 @@ +query EventEntrants($eventSlug: String!, $pageNo: Int!) { + event(slug: $eventSlug) { + id + name + entrants(query: { page: $pageNo, perPage: 100 }) { + pageInfo { + totalPages + } + nodes { + id + name + # paginatedSets { + # nodes { + # id + # } + # pageInfo { + # totalPages + # } + # } + } + } + } +} diff --git a/src/startgg-gql/generated/gql.ts b/src/startgg-gql/generated/gql.ts index bf3542bfc..80ff6080b 100644 --- a/src/startgg-gql/generated/gql.ts +++ b/src/startgg-gql/generated/gql.ts @@ -13,7 +13,8 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/ * Therefore it is highly recommended to use the babel or swc plugin for production. */ const documents = { - "\n query EventEntrants($eventSlug: String) {\n event(slug: $eventSlug) {\n id\n name\n }\n }\n": types.EventEntrantsDocument, + "query EventEntrants($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n id\n name\n entrants(query: {page: $pageNo, perPage: 100}) {\n pageInfo {\n totalPages\n }\n nodes {\n id\n name\n }\n }\n }\n}": types.EventEntrantsDocument, + "query EventSets($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n id\n sets(filters: {hideEmpty: true}, perPage: 100, page: $pageNo) {\n pageInfo {\n totalPages\n total\n }\n nodes {\n id\n fullRoundText\n identifier\n slots {\n prereqType\n prereqId\n prereqPlacement\n entrant {\n id\n name\n }\n }\n }\n }\n }\n}": types.EventSetsDocument, }; /** @@ -33,7 +34,11 @@ export function graphql(source: string): unknown; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query EventEntrants($eventSlug: String) {\n event(slug: $eventSlug) {\n id\n name\n }\n }\n"): (typeof documents)["\n query EventEntrants($eventSlug: String) {\n event(slug: $eventSlug) {\n id\n name\n }\n }\n"]; +export function graphql(source: "query EventEntrants($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n id\n name\n entrants(query: {page: $pageNo, perPage: 100}) {\n pageInfo {\n totalPages\n }\n nodes {\n id\n name\n }\n }\n }\n}"): (typeof documents)["query EventEntrants($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n id\n name\n entrants(query: {page: $pageNo, perPage: 100}) {\n pageInfo {\n totalPages\n }\n nodes {\n id\n name\n }\n }\n }\n}"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "query EventSets($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n id\n sets(filters: {hideEmpty: true}, perPage: 100, page: $pageNo) {\n pageInfo {\n totalPages\n total\n }\n nodes {\n id\n fullRoundText\n identifier\n slots {\n prereqType\n prereqId\n prereqPlacement\n entrant {\n id\n name\n }\n }\n }\n }\n }\n}"): (typeof documents)["query EventSets($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n id\n sets(filters: {hideEmpty: true}, perPage: 100, page: $pageNo) {\n pageInfo {\n totalPages\n total\n }\n nodes {\n id\n fullRoundText\n identifier\n slots {\n prereqType\n prereqId\n prereqPlacement\n entrant {\n id\n name\n }\n }\n }\n }\n }\n}"]; export function graphql(source: string) { return (documents as any)[source] ?? {}; diff --git a/src/startgg-gql/generated/graphql.ts b/src/startgg-gql/generated/graphql.ts index e77ac7332..59cca4ff3 100644 --- a/src/startgg-gql/generated/graphql.ts +++ b/src/startgg-gql/generated/graphql.ts @@ -2403,11 +2403,21 @@ export type WaveUpsertInput = { }; export type EventEntrantsQueryVariables = Exact<{ - eventSlug?: InputMaybe; + eventSlug: Scalars['String']['input']; + pageNo: Scalars['Int']['input']; }>; -export type EventEntrantsQuery = { __typename?: 'Query', event?: { __typename?: 'Event', id?: string | null, name?: string | null } | null }; +export type EventEntrantsQuery = { __typename?: 'Query', event?: { __typename?: 'Event', id?: string | null, name?: string | null, entrants?: { __typename?: 'EntrantConnection', pageInfo?: { __typename?: 'PageInfo', totalPages?: number | null } | null, nodes?: Array<{ __typename?: 'Entrant', id?: string | null, name?: string | null } | null> | null } | null } | null }; + +export type EventSetsQueryVariables = Exact<{ + eventSlug: Scalars['String']['input']; + pageNo: Scalars['Int']['input']; +}>; + + +export type EventSetsQuery = { __typename?: 'Query', event?: { __typename?: 'Event', id?: string | null, sets?: { __typename?: 'SetConnection', pageInfo?: { __typename?: 'PageInfo', totalPages?: number | null, total?: number | null } | null, nodes?: Array<{ __typename?: 'Set', id?: string | null, fullRoundText?: string | null, identifier?: string | null, slots?: Array<{ __typename?: 'SetSlot', prereqType?: string | null, prereqId?: string | null, prereqPlacement?: number | null, entrant?: { __typename?: 'Entrant', id?: string | null, name?: string | null } | null } | null> | null } | null> | null } | null } | null }; -export const EventEntrantsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EventEntrants"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"eventSlug"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"event"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"eventSlug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file +export const EventEntrantsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EventEntrants"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"eventSlug"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageNo"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"event"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"eventSlug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"entrants"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"query"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageNo"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"perPage"},"value":{"kind":"IntValue","value":"100"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalPages"}}]}},{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]} as unknown as DocumentNode; +export const EventSetsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EventSets"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"eventSlug"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageNo"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"event"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"eventSlug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"sets"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"hideEmpty"},"value":{"kind":"BooleanValue","value":true}}]}},{"kind":"Argument","name":{"kind":"Name","value":"perPage"},"value":{"kind":"IntValue","value":"100"}},{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageNo"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalPages"}},{"kind":"Field","name":{"kind":"Name","value":"total"}}]}},{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"fullRoundText"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"slots"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"prereqType"}},{"kind":"Field","name":{"kind":"Name","value":"prereqId"}},{"kind":"Field","name":{"kind":"Name","value":"prereqPlacement"}},{"kind":"Field","name":{"kind":"Name","value":"entrant"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file diff --git a/src/startgg-gql/index.ts b/src/startgg-gql/index.ts index deb490bf5..1be3c15ed 100644 --- a/src/startgg-gql/index.ts +++ b/src/startgg-gql/index.ts @@ -1,4 +1,5 @@ -import { EventEntrantsDocument } from "./generated/graphql"; +import { Entrant } from "../state/entrants.slice"; +import { EventEntrantsDocument, EventSetsDocument } from "./generated/graphql"; import { Client, cacheExchange, fetchExchange } from "@urql/core"; const client = new Client({ @@ -11,6 +12,63 @@ const client = new Client({ exchanges: [cacheExchange, fetchExchange], }); -export function getEventEntrants(slug: string) { - return client.query(EventEntrantsDocument, { eventSlug: slug }); +export async function getEventEntrants(slug: string) { + let pageNo = 0; + + const ret: Entrant[] = []; + + let totalPages = 0; + do { + const results = await client.query(EventEntrantsDocument, { + eventSlug: slug, + pageNo, + }); + totalPages = results.data?.event?.entrants?.pageInfo?.totalPages || 0; + if (results.data?.event?.entrants?.nodes) { + for (const entrant of results.data.event.entrants.nodes) { + if (!entrant) continue; + ret.push({ + id: entrant.id!, + startggTag: entrant.name!, + }); + } + } + pageNo++; + } while (totalPages > pageNo + 1); + + return ret; +} + +export interface TournamentSet { + id: string; + roundText: string; + playerIds: string[]; +} + +export async function getEventSets(slug: string) { + let pageNo = 0; + + const ret: TournamentSet[] = []; + + let totalPages = 0; + do { + const results = await client.query(EventSetsDocument, { + eventSlug: slug, + pageNo, + }); + totalPages = results.data?.event?.sets?.pageInfo?.totalPages || 0; + if (results.data?.event?.sets?.nodes) { + for (const set of results.data.event.sets.nodes) { + if (!set) continue; + ret.push({ + id: set.id!, + roundText: set.fullRoundText!, + playerIds: set.slots!.map((slot) => slot!.entrant!.id!), + }); + } + } + pageNo++; + } while (totalPages > pageNo + 1); + + return ret; } diff --git a/src/startgg-gql/queries.ts b/src/startgg-gql/queries.ts deleted file mode 100644 index 9f6e66085..000000000 --- a/src/startgg-gql/queries.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { gql } from "@urql/core"; - -gql` - query EventEntrants($eventSlug: String) { - event(slug: $eventSlug) { - id - name - } - } -`; diff --git a/src/startgg-gql/sets.graphql b/src/startgg-gql/sets.graphql new file mode 100644 index 000000000..386e25f6c --- /dev/null +++ b/src/startgg-gql/sets.graphql @@ -0,0 +1,25 @@ +query EventSets($eventSlug: String!, $pageNo: Int!) { + event(slug: $eventSlug) { + id + sets(filters: { hideEmpty: true }, perPage: 100, page: $pageNo) { + pageInfo { + totalPages + total + } + nodes { + id + fullRoundText + identifier + slots { + prereqType + prereqId + prereqPlacement + entrant { + id + name + } + } + } + } + } +} diff --git a/src/state/entrants.slice.ts b/src/state/entrants.slice.ts new file mode 100644 index 000000000..399275ba3 --- /dev/null +++ b/src/state/entrants.slice.ts @@ -0,0 +1,25 @@ +import { createEntityAdapter, createSlice } from "@reduxjs/toolkit"; + +export interface Entrant { + id: string; + ingameName?: string; + startggTag: string; +} + +const entrantsAdapter = createEntityAdapter(); +const selectors = entrantsAdapter.getSelectors(); + +export const entrantsSlice = createSlice({ + name: "entrants", + initialState: entrantsAdapter.getInitialState(), + reducers: { + removeMany: entrantsAdapter.removeMany, + upsertMany: entrantsAdapter.upsertMany, + upsertOne: entrantsAdapter.updateOne, + }, + selectors: { + selectAll: selectors.selectAll, + selectById: selectors.selectById, + selectIds: selectors.selectIds, + }, +}); diff --git a/src/state/root-reducer.ts b/src/state/root-reducer.ts index d0ffe07a6..faa547958 100644 --- a/src/state/root-reducer.ts +++ b/src/state/root-reducer.ts @@ -4,12 +4,14 @@ import { drawingsSlice } from "./drawings.slice"; import { gameDataSlice } from "./game-data.slice"; import { receivePartyState } from "./central"; import { eventSlice } from "./event.slice"; +import { entrantsSlice } from "./entrants.slice"; const combinedReducer = combineSlices( drawingsSlice, configSlice, gameDataSlice, eventSlice, + entrantsSlice, ); export const reducer: typeof combinedReducer = (state, action) => { diff --git a/webpack.config.js b/webpack.config.js index 0c43b5464..4c68a9f96 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,4 +1,4 @@ -require("dotenv").setup(); +require("dotenv/config"); const fs = require("fs"); const { resolve, basename } = require("path"); From a5cd4754f6940e8314c58fe1979355eb54012aa0 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Thu, 18 Jul 2024 12:28:36 -0700 Subject: [PATCH 27/91] move participatns tab from drawer to main view --- src/app.tsx | 4 ++-- src/controls/controls-drawer.tsx | 31 +------------------------------ src/main-view.css | 16 ++++++++++++++++ src/main-view.tsx | 28 ++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 32 deletions(-) create mode 100644 src/main-view.css create mode 100644 src/main-view.tsx diff --git a/src/app.tsx b/src/app.tsx index aed79b5bb..28ed1ec3b 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -8,7 +8,6 @@ import { FocusStyleManager } from "@blueprintjs/core"; FocusStyleManager.onlyShowFocusOnTabs(); -import { DrawingList } from "./drawing-list"; import { UpdateManager } from "./update-manager"; import { IntlProvider } from "./intl-provider"; import { Header } from "./header"; @@ -25,6 +24,7 @@ import { useParams, } from "react-router-dom"; import { CabManagement } from "./cab-management"; +import { MainView } from "./main-view"; const router = createBrowserRouter([ { @@ -115,7 +115,7 @@ function AppForRoom() { }} > - + diff --git a/src/controls/controls-drawer.tsx b/src/controls/controls-drawer.tsx index 6ec0bf63b..03f2ab727 100644 --- a/src/controls/controls-drawer.tsx +++ b/src/controls/controls-drawer.tsx @@ -3,18 +3,13 @@ import { ButtonGroup, Card, Checkbox, - Classes, Collapse, Divider, FormGroup, HTMLSelect, NumericInput, - Tab, - Tabs, } from "@blueprintjs/core"; import { - Settings, - People, CaretDown, CaretRight, Plus, @@ -31,7 +26,6 @@ import { useIsNarrow } from "../hooks/useMediaQuery"; import { GameData } from "../models/SongData"; import { WeightsControls } from "./controls-weights"; import styles from "./controls.css"; -import { PlayerNamesControls } from "./player-names"; import { getAvailableLevels } from "../game-data-utils"; import { ShowChartsToggle } from "./show-charts-toggle"; import { Fraction } from "../utils/fraction"; @@ -80,32 +74,9 @@ function getDiffsAndRangeForNewStyle( } export default function ControlsDrawer() { - const { t } = useIntl(); return (
- - } - panel={} - > - {t("controls.tabs.general")} - - {/* } />} - panel={} - > - {t("controls.tabs.networking")} - */} - } - panel={} - > - {t("controls.tabs.players")} - - +
); } diff --git a/src/main-view.css b/src/main-view.css new file mode 100644 index 000000000..dc46a6fcc --- /dev/null +++ b/src/main-view.css @@ -0,0 +1,16 @@ +.mainView { + flex: 1 1 0px; + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; +} + +.mainView > div:first-child { + flex: 0 0 auto; +} + +.mainView > div:not(:first-child) { + flex: 1 1 auto; + overflow: hidden; +} diff --git a/src/main-view.tsx b/src/main-view.tsx new file mode 100644 index 000000000..51b7a5c72 --- /dev/null +++ b/src/main-view.tsx @@ -0,0 +1,28 @@ +import { Tabs, Tab } from "@blueprintjs/core"; +import { PlayerNamesControls } from "./controls/player-names"; +import { DrawingList } from "./drawing-list"; +import { atom, useAtom } from "jotai"; +import styles from "./main-view.css"; + +export type MainTabId = "drawings" | "players"; +export const mainTabAtom = atom("drawings"); + +export function MainView() { + const [currentTab, setCurrentTab] = useAtom(mainTabAtom); + return ( + setCurrentTab(newTabId)} + > + }> + Drawings + + }> + Participants + + + ); +} From 9dea64df835608a74af3a9e30230faff8f840d90 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Thu, 18 Jul 2024 14:16:12 -0700 Subject: [PATCH 28/91] first working name imports --- src/controls/player-names.tsx | 156 +++++++++++++++++----------------- src/main-view.css | 4 +- src/startgg-gql/index.ts | 24 +++--- 3 files changed, 94 insertions(+), 90 deletions(-) diff --git a/src/controls/player-names.tsx b/src/controls/player-names.tsx index 359a16735..2ff49a422 100644 --- a/src/controls/player-names.tsx +++ b/src/controls/player-names.tsx @@ -2,62 +2,100 @@ import { Checkbox, Classes, FormGroup, + Label, + Text, NumericInput, - TagInput, } from "@blueprintjs/core"; -import { ReactNode } from "react"; +import React, { useCallback } from "react"; import { useConfigState, useUpdateConfig } from "../state/hooks"; import { useIntl } from "../hooks/useIntl"; -import { DiagramTree, Person } from "@blueprintjs/icons"; -import { useAtom } from "jotai"; +import { atom, useAtom } from "jotai"; import { showPlayerAndRoundLabels } from "../config-state"; +import { useAppDispatch, useAppState } from "../state/store"; +import { entrantsSlice, Entrant } from "../state/entrants.slice"; +import { getEventEntrants } from "../startgg-gql"; export function PlayerNamesControls() { const { t } = useIntl(); - const playerNames = useConfigState((s) => s.playerNames); - const updateConfig = useUpdateConfig(); - - function addPlayers(names: string[]) { - updateConfig((prev) => { - const next = prev.playerNames.slice(); - for (const name of names) { - if (!next.includes(name)) { - next.push(name); - } - } - if (next.length !== prev.playerNames.length) { - return { playerNames: next }; - } - return {}; - }); - } - function removePlayer(name: ReactNode, index: number) { - updateConfig((prev) => { - const next = prev.playerNames.slice(); - next.splice(index, 1); - return { playerNames: next }; - }); - } + const entrants = useAppState(entrantsSlice.selectors.selectAll); return ( <> - - } - onAdd={addPlayers} - onRemove={removePlayer} - /> - - + {entrants.length ? ( + + {entrants.map((e) => ( + + ))} + + ) : ( + + )} ); } +const startggKeyAtom = atom(process.env.STARTGG_TOKEN as string); +const startggEventSlug = atom(null); + +function StartggEntrantImport() { + const [apiKey, setApiKey] = useAtom(startggKeyAtom); + const [eventSlug, setEventSlug] = useAtom(startggEventSlug); + const dispatch = useAppDispatch(); + const importEntrants = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + if (!apiKey || !eventSlug) { + return; + } + const entrants = await getEventEntrants(apiKey, eventSlug); + dispatch(entrantsSlice.actions.upsertMany(entrants)); + }, + [dispatch, apiKey, eventSlug], + ); + return ( +
+ No players added yet, import from start.gg + + + +
+ ); +} + +function inferShortname(name: string) { + const namePieces = name.split(" | "); + return namePieces.length > 1 ? namePieces[namePieces.length - 1] : undefined; +} + +function EntrantNameForm(props: { entrant: Entrant }) { + return ( + + ); +} + function ShowLabelsToggle() { const [enabled, updateShowLabels] = useAtom(showPlayerAndRoundLabels); const { t } = useIntl(); @@ -90,43 +128,3 @@ function PlayersPerDraw() { ); } - -function TournamentLabelEditor() { - const { t } = useIntl(); - const tournamentRounds = useConfigState((s) => s.tournamentRounds); - const updateConfig = useUpdateConfig(); - - function addLabels(names: string[]) { - updateConfig((prev) => { - const next = prev.tournamentRounds.slice(); - for (const name of names) { - if (!next.includes(name)) { - next.push(name); - } - } - if (next.length !== prev.tournamentRounds.length) { - return { tournamentRounds: next }; - } - return {}; - }); - } - function removeLabel(name: ReactNode, index: number) { - updateConfig((prev) => { - const next = prev.tournamentRounds.slice(); - next.splice(index, 1); - return { tournamentRounds: next }; - }); - } - return ( - - } - onAdd={addLabels} - onRemove={removeLabel} - /> - - ); -} diff --git a/src/main-view.css b/src/main-view.css index dc46a6fcc..e5399647f 100644 --- a/src/main-view.css +++ b/src/main-view.css @@ -6,11 +6,13 @@ overflow: hidden; } +/* the tabs themselves */ .mainView > div:first-child { flex: 0 0 auto; } +/* all the full page tab contents */ .mainView > div:not(:first-child) { flex: 1 1 auto; - overflow: hidden; + overflow-y: auto; } diff --git a/src/startgg-gql/index.ts b/src/startgg-gql/index.ts index 1be3c15ed..23dc4ea67 100644 --- a/src/startgg-gql/index.ts +++ b/src/startgg-gql/index.ts @@ -2,17 +2,20 @@ import { Entrant } from "../state/entrants.slice"; import { EventEntrantsDocument, EventSetsDocument } from "./generated/graphql"; import { Client, cacheExchange, fetchExchange } from "@urql/core"; -const client = new Client({ - url: "https://api.start.gg/gql/alpha", - fetchOptions: { - headers: { - Authorization: `Bearer ${process.env.STARTGG_TOKEN}`, +function getClient(token: string) { + return new Client({ + url: "https://api.start.gg/gql/alpha", + fetchOptions: { + headers: { + Authorization: `Bearer ${token}`, + }, }, - }, - exchanges: [cacheExchange, fetchExchange], -}); + exchanges: [cacheExchange, fetchExchange], + }); +} -export async function getEventEntrants(slug: string) { +export async function getEventEntrants(token: string, slug: string) { + const client = getClient(token); let pageNo = 0; const ret: Entrant[] = []; @@ -45,7 +48,8 @@ export interface TournamentSet { playerIds: string[]; } -export async function getEventSets(slug: string) { +export async function getEventSets(token: string, slug: string) { + const client = getClient(token); let pageNo = 0; const ret: TournamentSet[] = []; From 453681c0c3b118740c603b79e032f86f2d629647 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Thu, 18 Jul 2024 17:04:47 -0700 Subject: [PATCH 29/91] prototype of set imports --- src/cab-management.tsx | 4 ++ src/controls/player-names.tsx | 6 ++- src/main-view.tsx | 6 ++- src/matches.tsx | 72 +++++++++++++++++++++++++++++++++++ src/startgg-gql/index.ts | 10 ++--- src/state/entrants.slice.ts | 4 +- src/state/matches.slice.ts | 25 ++++++++++++ src/state/root-reducer.ts | 2 + 8 files changed, 118 insertions(+), 11 deletions(-) create mode 100644 src/matches.tsx create mode 100644 src/state/matches.slice.ts diff --git a/src/cab-management.tsx b/src/cab-management.tsx index c50156332..48ff27023 100644 --- a/src/cab-management.tsx +++ b/src/cab-management.tsx @@ -22,6 +22,8 @@ import { } from "@blueprintjs/icons"; import { detectedLanguage } from "./utils"; import { copyPlainTextToClipboard } from "./utils/share"; +import { useSetAtom } from "jotai"; +import { mainTabAtom } from "./main-view"; export function CabManagement() { const cabs = Object.values(useAppState((s) => s.event.cabs)); @@ -155,6 +157,7 @@ function CurrentMatch(props: { cab: CabInfo }) { if (!props.cab.activeMatch) return null; return s.drawings.entities[props.cab.activeMatch] || null; }); + const setMainTab = useSetAtom(mainTabAtom); const scrollToDrawing = useCallback(() => { if (!drawing) { @@ -170,6 +173,7 @@ function CurrentMatch(props: { cab: CabInfo }) { if (priorFocus) { delete priorFocus.dataset.focused; } + setMainTab("drawings"); el.scrollIntoView({ behavior: "smooth" }); el.dataset.focused = ""; }, [drawing]); diff --git a/src/controls/player-names.tsx b/src/controls/player-names.tsx index 2ff49a422..6ad8d709b 100644 --- a/src/controls/player-names.tsx +++ b/src/controls/player-names.tsx @@ -36,8 +36,10 @@ export function PlayerNamesControls() { ); } -const startggKeyAtom = atom(process.env.STARTGG_TOKEN as string); -const startggEventSlug = atom(null); +export const startggKeyAtom = atom( + process.env.STARTGG_TOKEN as string, +); +export const startggEventSlug = atom(null); function StartggEntrantImport() { const [apiKey, setApiKey] = useAtom(startggKeyAtom); diff --git a/src/main-view.tsx b/src/main-view.tsx index 51b7a5c72..0183dc83f 100644 --- a/src/main-view.tsx +++ b/src/main-view.tsx @@ -3,8 +3,9 @@ import { PlayerNamesControls } from "./controls/player-names"; import { DrawingList } from "./drawing-list"; import { atom, useAtom } from "jotai"; import styles from "./main-view.css"; +import { MatchListAndSettings } from "./matches"; -export type MainTabId = "drawings" | "players"; +export type MainTabId = "drawings" | "players" | "sets"; export const mainTabAtom = atom("drawings"); export function MainView() { @@ -23,6 +24,9 @@ export function MainView() { }> Participants + }> + Sets + ); } diff --git a/src/matches.tsx b/src/matches.tsx new file mode 100644 index 000000000..2cc273010 --- /dev/null +++ b/src/matches.tsx @@ -0,0 +1,72 @@ +import { Classes, Label, Text } from "@blueprintjs/core"; +import { useAtom } from "jotai"; +import { useCallback } from "react"; +import { startggKeyAtom, startggEventSlug } from "./controls/player-names"; +import { getEventSets } from "./startgg-gql"; +import { useAppDispatch, useAppState } from "./state/store"; +import { setsSlice, TournamentSet } from "./state/matches.slice"; +import { entrantsSlice } from "./state/entrants.slice"; + +export function MatchListAndSettings() { + const sets = useAppState(setsSlice.selectors.selectAll); + return sets.length ? ( + <> + {sets.map((set) => ( + + ))} + + ) : ( + + ); +} + +function TournamentSetPreview(props: { set: TournamentSet }) { + const players = useAppState((s) => + entrantsSlice.selectors.selectFromIds(s, props.set.playerIds), + ); + return ( + + {props.set.roundText} - {players[0]?.startggTag} vs{" "} + {players[1]?.startggTag || ???} + + ); +} + +function StartggSetImport() { + const [apiKey, setApiKey] = useAtom(startggKeyAtom); + const [eventSlug, setEventSlug] = useAtom(startggEventSlug); + const dispatch = useAppDispatch(); + const importEntrants = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + if (!apiKey || !eventSlug) { + return; + } + const sets = await getEventSets(apiKey, eventSlug); + dispatch(setsSlice.actions.upsertMany(sets)); + }, + [dispatch, apiKey, eventSlug], + ); + return ( +
+ No sets added yet, import from start.gg + + + +
+ ); +} diff --git a/src/startgg-gql/index.ts b/src/startgg-gql/index.ts index 23dc4ea67..2c3632081 100644 --- a/src/startgg-gql/index.ts +++ b/src/startgg-gql/index.ts @@ -1,4 +1,5 @@ import { Entrant } from "../state/entrants.slice"; +import { TournamentSet } from "../state/matches.slice"; import { EventEntrantsDocument, EventSetsDocument } from "./generated/graphql"; import { Client, cacheExchange, fetchExchange } from "@urql/core"; @@ -42,12 +43,6 @@ export async function getEventEntrants(token: string, slug: string) { return ret; } -export interface TournamentSet { - id: string; - roundText: string; - playerIds: string[]; -} - export async function getEventSets(token: string, slug: string) { const client = getClient(token); let pageNo = 0; @@ -64,10 +59,11 @@ export async function getEventSets(token: string, slug: string) { if (results.data?.event?.sets?.nodes) { for (const set of results.data.event.sets.nodes) { if (!set) continue; + const players = set.slots!.map((slot) => slot!.entrant?.id); ret.push({ id: set.id!, roundText: set.fullRoundText!, - playerIds: set.slots!.map((slot) => slot!.entrant!.id!), + playerIds: players.filter((pid): pid is string => !!pid), }); } } diff --git a/src/state/entrants.slice.ts b/src/state/entrants.slice.ts index 399275ba3..e4fc69700 100644 --- a/src/state/entrants.slice.ts +++ b/src/state/entrants.slice.ts @@ -1,5 +1,4 @@ import { createEntityAdapter, createSlice } from "@reduxjs/toolkit"; - export interface Entrant { id: string; ingameName?: string; @@ -20,6 +19,9 @@ export const entrantsSlice = createSlice({ selectors: { selectAll: selectors.selectAll, selectById: selectors.selectById, + selectFromIds(state, entrantIds: string[]) { + return entrantIds.map((id) => selectors.selectById(state, id)); + }, selectIds: selectors.selectIds, }, }); diff --git a/src/state/matches.slice.ts b/src/state/matches.slice.ts new file mode 100644 index 000000000..619220a08 --- /dev/null +++ b/src/state/matches.slice.ts @@ -0,0 +1,25 @@ +import { createEntityAdapter, createSlice } from "@reduxjs/toolkit"; + +export interface TournamentSet { + id: string; + roundText: string; + playerIds: string[]; +} + +const setsAdapter = createEntityAdapter(); +const selectors = setsAdapter.getSelectors(); + +export const setsSlice = createSlice({ + name: "sets", + initialState: setsAdapter.getInitialState(), + reducers: { + removeMany: setsAdapter.removeMany, + upsertMany: setsAdapter.upsertMany, + upsertOne: setsAdapter.updateOne, + }, + selectors: { + selectAll: selectors.selectAll, + selectById: selectors.selectById, + selectIds: selectors.selectIds, + }, +}); diff --git a/src/state/root-reducer.ts b/src/state/root-reducer.ts index faa547958..65e9f37fd 100644 --- a/src/state/root-reducer.ts +++ b/src/state/root-reducer.ts @@ -5,6 +5,7 @@ import { gameDataSlice } from "./game-data.slice"; import { receivePartyState } from "./central"; import { eventSlice } from "./event.slice"; import { entrantsSlice } from "./entrants.slice"; +import { setsSlice } from "./matches.slice"; const combinedReducer = combineSlices( drawingsSlice, @@ -12,6 +13,7 @@ const combinedReducer = combineSlices( gameDataSlice, eventSlice, entrantsSlice, + setsSlice, ); export const reducer: typeof combinedReducer = (state, action) => { From 14c65c19bc18be4af0527c480bc655283d9bba06 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Thu, 18 Jul 2024 22:07:59 -0700 Subject: [PATCH 30/91] preliminary handling of prereqs --- src/controls/player-names.tsx | 4 +++- src/matches.tsx | 24 +++++++++++++++++------- src/startgg-gql/index.ts | 7 +++++-- src/state/matches.slice.ts | 15 ++++++++++++++- 4 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/controls/player-names.tsx b/src/controls/player-names.tsx index 6ad8d709b..751ddfba6 100644 --- a/src/controls/player-names.tsx +++ b/src/controls/player-names.tsx @@ -39,7 +39,9 @@ export function PlayerNamesControls() { export const startggKeyAtom = atom( process.env.STARTGG_TOKEN as string, ); -export const startggEventSlug = atom(null); +export const startggEventSlug = atom( + "tournament/red-october-2024/event/stepmaniax-singles-hard-and-wild", +); function StartggEntrantImport() { const [apiKey, setApiKey] = useAtom(startggKeyAtom); diff --git a/src/matches.tsx b/src/matches.tsx index 2cc273010..50d492181 100644 --- a/src/matches.tsx +++ b/src/matches.tsx @@ -3,8 +3,8 @@ import { useAtom } from "jotai"; import { useCallback } from "react"; import { startggKeyAtom, startggEventSlug } from "./controls/player-names"; import { getEventSets } from "./startgg-gql"; -import { useAppDispatch, useAppState } from "./state/store"; -import { setsSlice, TournamentSet } from "./state/matches.slice"; +import { AppState, useAppDispatch, useAppState } from "./state/store"; +import { setsSlice, Slot, TournamentSet } from "./state/matches.slice"; import { entrantsSlice } from "./state/entrants.slice"; export function MatchListAndSettings() { @@ -21,13 +21,23 @@ export function MatchListAndSettings() { } function TournamentSetPreview(props: { set: TournamentSet }) { - const players = useAppState((s) => - entrantsSlice.selectors.selectFromIds(s, props.set.playerIds), - ); + const [slot1, slot2] = props.set.slots; + const getPlayerForSlot = (slot: Slot) => (s: AppState) => { + if (slot.type === "player") + return entrantsSlice.selectors.selectById(s, slot.playerId); + const prereqSet = setsSlice.selectors.selectById(s, slot.setId); + if (prereqSet.winningPlayerId) { + return entrantsSlice.selectors.selectById(s, prereqSet.winningPlayerId); + } + return null; + }; + const player1 = useAppState(getPlayerForSlot(slot1)); + const player2 = useAppState(getPlayerForSlot(slot2)); return ( - {props.set.roundText} - {players[0]?.startggTag} vs{" "} - {players[1]?.startggTag || ???} + {props.set.roundText} -{" "} + {player1?.startggTag || TBD} vs{" "} + {player2?.startggTag || TBD} ); } diff --git a/src/startgg-gql/index.ts b/src/startgg-gql/index.ts index 2c3632081..444a8de10 100644 --- a/src/startgg-gql/index.ts +++ b/src/startgg-gql/index.ts @@ -59,11 +59,14 @@ export async function getEventSets(token: string, slug: string) { if (results.data?.event?.sets?.nodes) { for (const set of results.data.event.sets.nodes) { if (!set) continue; - const players = set.slots!.map((slot) => slot!.entrant?.id); ret.push({ id: set.id!, roundText: set.fullRoundText!, - playerIds: players.filter((pid): pid is string => !!pid), + slots: set.slots!.map((slot) => + slot!.entrant?.id + ? { type: "player", playerId: slot!.entrant.id } + : { type: "setprereq", setId: slot!.prereqId! }, + ), }); } } diff --git a/src/state/matches.slice.ts b/src/state/matches.slice.ts index 619220a08..d372b5098 100644 --- a/src/state/matches.slice.ts +++ b/src/state/matches.slice.ts @@ -1,9 +1,22 @@ import { createEntityAdapter, createSlice } from "@reduxjs/toolkit"; +interface SetPlayer { + type: "player"; + playerId: string; +} + +interface SetPrereq { + type: "setprereq"; + setId: string; +} + +export type Slot = SetPlayer | SetPrereq; + export interface TournamentSet { id: string; roundText: string; - playerIds: string[]; + slots: Slot[]; + winningPlayerId?: string; } const setsAdapter = createEntityAdapter(); From 905134dc9e1b3b0ceaf2fd585296c1ff2a075c3a Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Fri, 26 Jul 2024 23:34:03 -0700 Subject: [PATCH 31/91] cleaner root handler --- src/app.tsx | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/app.tsx b/src/app.tsx index 28ed1ec3b..367ef0819 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -22,21 +22,27 @@ import { Outlet, RouterProvider, useParams, + Link, } from "react-router-dom"; import { CabManagement } from "./cab-management"; import { MainView } from "./main-view"; +import { nanoid } from "nanoid"; const router = createBrowserRouter([ { path: "/", - element: ( - <> -

- You need to pick an event first. How about this one:{" "} - Default Event -

- - ), + Component: () => { + return ( +
+

DDR Tools Event Mode

+

Alpha Preview

+

+ You need to pick an event first. Would you like to:{" "} + Create New Event? +

+
+ ); + }, }, { path: "e/:roomName", From e122c3b0217f0b269a3703d82f0b2f62369ccf42 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Fri, 26 Jul 2024 23:52:55 -0700 Subject: [PATCH 32/91] temporary rollback for player name management --- src/controls/player-names.tsx | 61 +++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/src/controls/player-names.tsx b/src/controls/player-names.tsx index 751ddfba6..1e1fe4be3 100644 --- a/src/controls/player-names.tsx +++ b/src/controls/player-names.tsx @@ -5,6 +5,7 @@ import { Label, Text, NumericInput, + TagInput, } from "@blueprintjs/core"; import React, { useCallback } from "react"; import { useConfigState, useUpdateConfig } from "../state/hooks"; @@ -14,24 +15,49 @@ import { showPlayerAndRoundLabels } from "../config-state"; import { useAppDispatch, useAppState } from "../state/store"; import { entrantsSlice, Entrant } from "../state/entrants.slice"; import { getEventEntrants } from "../startgg-gql"; +import { Person } from "@blueprintjs/icons"; export function PlayerNamesControls() { + const updateConfig = useUpdateConfig(); + const playerNames = useConfigState((s) => s.playerNames); const { t } = useIntl(); - const entrants = useAppState(entrantsSlice.selectors.selectAll); + + function addPlayers(names: string[]) { + updateConfig((prev) => { + const next = prev.playerNames.slice(); + for (const name of names) { + if (!next.includes(name)) { + next.push(name); + } + } + if (next.length !== prev.playerNames.length) { + return { playerNames: next }; + } + return {}; + }); + } + function removePlayer(name: React.ReactNode, index: number) { + updateConfig((prev) => { + const next = prev.playerNames.slice(); + next.splice(index, 1); + return { playerNames: next }; + }); + } return ( <> - {entrants.length ? ( - - {entrants.map((e) => ( - - ))} - - ) : ( - - )} + + } + onAdd={addPlayers} + onRemove={removePlayer} + /> + ); } @@ -43,6 +69,21 @@ export const startggEventSlug = atom( "tournament/red-october-2024/event/stepmaniax-singles-hard-and-wild", ); +export function StartggEntrantManager() { + const { t } = useIntl(); + const entrants = useAppState(entrantsSlice.selectors.selectAll); + if (!entrants.length) { + return ; + } + return ( + + {entrants.map((e) => ( + + ))} + + ); +} + function StartggEntrantImport() { const [apiKey, setApiKey] = useAtom(startggKeyAtom); const [eventSlug, setEventSlug] = useAtom(startggEventSlug); From 1a0e53fd784c6b07214870c622ca65714bb6f411 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Sat, 27 Jul 2024 11:43:06 -0700 Subject: [PATCH 33/91] fix reorder by pick/ban --- src/song-card/song-card.tsx | 5 +++- src/state/drawings.slice.ts | 46 +++++++++++++++++++++++++++++++++---- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/song-card/song-card.tsx b/src/song-card/song-card.tsx index 3170bc892..af38c1099 100644 --- a/src/song-card/song-card.tsx +++ b/src/song-card/song-card.tsx @@ -56,6 +56,7 @@ function useIconCallbacksForChart(chartId: string): IconCallbacks { player: number, pick?: EligibleChart, ) => { + const reorder = store.getState().config.orderByAction; let action; if (type === "pocket") { if (pick) { @@ -65,6 +66,7 @@ function useIconCallbacksForChart(chartId: string): IconCallbacks { type, player, pick, + reorder, }); } } else { @@ -73,13 +75,14 @@ function useIconCallbacksForChart(chartId: string): IconCallbacks { chartId, type, player, + reorder, }); } if (action) { dispatch(action); } }, - [drawingId, chartId, dispatch], + [drawingId, chartId, dispatch, store], ); return useMemo( diff --git a/src/state/drawings.slice.ts b/src/state/drawings.slice.ts index 48d6e4924..f7d86ffbc 100644 --- a/src/state/drawings.slice.ts +++ b/src/state/drawings.slice.ts @@ -62,6 +62,15 @@ type ActionOnSingleDrawing = PayloadAction; type ActionOnSingleChart = PayloadAction< { drawingId: string; chartId: string } & extra >; +// eslint-disable-next-line @typescript-eslint/ban-types +type PlayerActionOnChartPayload = PayloadAction< + { + drawingId: string; + chartId: string; + player: number; + reorder: boolean; + } & extra +>; export const drawingsSlice = createSlice({ name: "drawings", @@ -125,22 +134,30 @@ export const drawingsSlice = createSlice({ }, banProtectReplace( state, - action: ActionOnSingleChart< - | { type: "ban" | "protect"; player: number } - | { type: "pocket"; player: number; pick: EligibleChart } + action: PlayerActionOnChartPayload< + { type: "ban" | "protect" } | { type: "pocket"; pick: EligibleChart } >, ) { - const { chartId, drawingId, player } = action.payload; + const { chartId, drawingId, player, reorder } = action.payload; const drawing = state.entities[drawingId]; if (!drawing) { return; } const playerAction: PlayerActionOnChart = { chartId, player }; if (action.payload.type === "ban") { + if (reorder) { + moveChartInArray(drawing, chartId, "end"); + } drawing.bans[chartId] = playerAction; } else if (action.payload.type === "protect") { + if (reorder) { + moveChartInArray(drawing, chartId, "start"); + } drawing.protects[chartId] = playerAction; } else if (action.payload.type === "pocket") { + if (reorder) { + moveChartInArray(drawing, chartId, "start"); + } drawing.pocketPicks[chartId] = { chartId, player, @@ -221,3 +238,24 @@ export function createRedrawChart( changes: chart, }); } + +function moveChartInArray( + drawing: Drawing, + chartId: string, + pos: "start" | "end", +) { + const targetChart = drawing.charts.find((c) => c.id === chartId); + if (!targetChart) { + return; + } + const chartsWithoutTarget = drawing.charts.filter((c) => c.id !== chartId); + if (pos === "start") { + const insertIdx = + Object.keys(drawing.protects).length + + Object.keys(drawing.pocketPicks).length; + chartsWithoutTarget.splice(insertIdx, 0, targetChart); + } else { + chartsWithoutTarget.push(targetChart); + } + drawing.charts = chartsWithoutTarget; +} From 16e0e39fe9da994b93cb34385747a6c9b26ae5bb Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Sat, 27 Jul 2024 11:49:56 -0700 Subject: [PATCH 34/91] fix jacket images in search results --- src/song-card/song-card.tsx | 4 ++-- src/song-jacket.tsx | 3 ++- src/utils/jackets.ts | 4 ++++ 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 src/utils/jackets.ts diff --git a/src/song-card/song-card.tsx b/src/song-card/song-card.tsx index af38c1099..c14f8f07f 100644 --- a/src/song-card/song-card.tsx +++ b/src/song-card/song-card.tsx @@ -18,6 +18,7 @@ import styles from "./song-card.css"; import { ChartLevel } from "./chart-level"; import { useAppDispatch, useAppStore } from "../state/store"; import { createRedrawChart, drawingsSlice } from "../state/drawings.slice"; +import { getJacketUrl } from "../utils/jackets"; const isJapanese = detectedLanguage === "ja"; @@ -155,9 +156,8 @@ export function SongCard(props: Props) { let jacketBg = {}; if (jacket) { - const prefix = jacket.startsWith("blob:") ? "" : "/jackets/"; jacketBg = { - backgroundImage: `url("${prefix}${jacket}")`, + backgroundImage: `url("${getJacketUrl(jacket)}")`, }; } diff --git a/src/song-jacket.tsx b/src/song-jacket.tsx index 0df854f5d..8cd171d59 100644 --- a/src/song-jacket.tsx +++ b/src/song-jacket.tsx @@ -1,6 +1,7 @@ import { Song } from "./models/SongData"; import { Icon } from "@blueprintjs/core"; import { Music } from "@blueprintjs/icons"; +import { getJacketUrl } from "./utils/jackets"; interface Props { song: Song; @@ -12,7 +13,7 @@ export function SongJacket(props: Props) { if (props.song.jacket) { return ( diff --git a/src/utils/jackets.ts b/src/utils/jackets.ts new file mode 100644 index 000000000..c5237c06a --- /dev/null +++ b/src/utils/jackets.ts @@ -0,0 +1,4 @@ +export function getJacketUrl(jacketFromData: string) { + const prefix = jacketFromData.startsWith("blob:") ? "" : "/jackets/"; + return `${prefix}${jacketFromData}`; +} From a441fd69185aa9a06909db7aefc90f6d532d705a Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Sun, 28 Jul 2024 15:12:59 -0700 Subject: [PATCH 35/91] use a single urql client instacne --- src/controls/player-names.tsx | 17 +++++++---------- src/matches.tsx | 5 ++--- src/startgg-gql/index.ts | 32 ++++++++++++++++++-------------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/controls/player-names.tsx b/src/controls/player-names.tsx index 1e1fe4be3..7cbeda30c 100644 --- a/src/controls/player-names.tsx +++ b/src/controls/player-names.tsx @@ -10,11 +10,15 @@ import { import React, { useCallback } from "react"; import { useConfigState, useUpdateConfig } from "../state/hooks"; import { useIntl } from "../hooks/useIntl"; -import { atom, useAtom } from "jotai"; +import { useAtom } from "jotai"; import { showPlayerAndRoundLabels } from "../config-state"; import { useAppDispatch, useAppState } from "../state/store"; import { entrantsSlice, Entrant } from "../state/entrants.slice"; -import { getEventEntrants } from "../startgg-gql"; +import { + getEventEntrants, + startggEventSlug, + startggKeyAtom, +} from "../startgg-gql"; import { Person } from "@blueprintjs/icons"; export function PlayerNamesControls() { @@ -62,13 +66,6 @@ export function PlayerNamesControls() { ); } -export const startggKeyAtom = atom( - process.env.STARTGG_TOKEN as string, -); -export const startggEventSlug = atom( - "tournament/red-october-2024/event/stepmaniax-singles-hard-and-wild", -); - export function StartggEntrantManager() { const { t } = useIntl(); const entrants = useAppState(entrantsSlice.selectors.selectAll); @@ -94,7 +91,7 @@ function StartggEntrantImport() { if (!apiKey || !eventSlug) { return; } - const entrants = await getEventEntrants(apiKey, eventSlug); + const entrants = await getEventEntrants(eventSlug); dispatch(entrantsSlice.actions.upsertMany(entrants)); }, [dispatch, apiKey, eventSlug], diff --git a/src/matches.tsx b/src/matches.tsx index 50d492181..dc49fc8b4 100644 --- a/src/matches.tsx +++ b/src/matches.tsx @@ -1,8 +1,7 @@ import { Classes, Label, Text } from "@blueprintjs/core"; import { useAtom } from "jotai"; import { useCallback } from "react"; -import { startggKeyAtom, startggEventSlug } from "./controls/player-names"; -import { getEventSets } from "./startgg-gql"; +import { startggKeyAtom, startggEventSlug, getEventSets } from "./startgg-gql"; import { AppState, useAppDispatch, useAppState } from "./state/store"; import { setsSlice, Slot, TournamentSet } from "./state/matches.slice"; import { entrantsSlice } from "./state/entrants.slice"; @@ -52,7 +51,7 @@ function StartggSetImport() { if (!apiKey || !eventSlug) { return; } - const sets = await getEventSets(apiKey, eventSlug); + const sets = await getEventSets(eventSlug); dispatch(setsSlice.actions.upsertMany(sets)); }, [dispatch, apiKey, eventSlug], diff --git a/src/startgg-gql/index.ts b/src/startgg-gql/index.ts index 444a8de10..bc7774843 100644 --- a/src/startgg-gql/index.ts +++ b/src/startgg-gql/index.ts @@ -2,21 +2,26 @@ import { Entrant } from "../state/entrants.slice"; import { TournamentSet } from "../state/matches.slice"; import { EventEntrantsDocument, EventSetsDocument } from "./generated/graphql"; import { Client, cacheExchange, fetchExchange } from "@urql/core"; +import { getDefaultStore, atom } from "jotai"; -function getClient(token: string) { - return new Client({ - url: "https://api.start.gg/gql/alpha", - fetchOptions: { - headers: { - Authorization: `Bearer ${token}`, - }, +export const startggKeyAtom = atom( + process.env.STARTGG_TOKEN as string, +); +export const startggEventSlug = atom( + "tournament/red-october-2024/event/stepmaniax-singles-hard-and-wild", +); + +export const client = new Client({ + url: "https://api.start.gg/gql/alpha", + fetchOptions: () => ({ + headers: { + Authorization: `Bearer ${getDefaultStore().get(startggKeyAtom)}`, }, - exchanges: [cacheExchange, fetchExchange], - }); -} + }), + exchanges: [cacheExchange, fetchExchange], +}); -export async function getEventEntrants(token: string, slug: string) { - const client = getClient(token); +export async function getEventEntrants(slug: string) { let pageNo = 0; const ret: Entrant[] = []; @@ -43,8 +48,7 @@ export async function getEventEntrants(token: string, slug: string) { return ret; } -export async function getEventSets(token: string, slug: string) { - const client = getClient(token); +export async function getEventSets(slug: string) { let pageNo = 0; const ret: TournamentSet[] = []; From a71097265c5852a03bbc3f40de768df89782904f Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Wed, 31 Jul 2024 21:30:33 -0700 Subject: [PATCH 36/91] big steps towards startgg integration --- codegen.ts | 3 + graphql.config.ts | 2 +- package.json | 2 + src/card-draw.ts | 12 +- src/config-state.ts | 12 -- src/controls/degrs-tester.tsx | 5 +- src/controls/index.tsx | 14 +- src/controls/player-names.tsx | 41 +---- src/drawing-context.tsx | 2 + src/models/Drawing.ts | 3 +- src/song-card/song-card.tsx | 41 +---- src/startgg-gql/entrants.graphql | 23 --- src/startgg-gql/generated/fragment-masking.ts | 87 ---------- src/startgg-gql/generated/gql.ts | 18 +- src/startgg-gql/generated/graphql.ts | 20 ++- src/startgg-gql/generated/index.ts | 1 - src/startgg-gql/index.ts | 129 ++++++++++++-- src/startgg-gql/sets.graphql | 25 --- src/state/central.ts | 5 - src/state/config.slice.ts | 12 -- src/state/drawings.slice.ts | 111 +----------- src/state/store.ts | 12 +- src/state/thunks.ts | 163 ++++++++++++++++++ src/tournament-mode/drawing-actions.tsx | 34 +--- src/tournament-mode/drawing-labels.tsx | 65 +------ src/tournament-mode/round-select.tsx | 96 ----------- yarn.lock | 38 ++++ 27 files changed, 421 insertions(+), 555 deletions(-) delete mode 100644 src/startgg-gql/entrants.graphql delete mode 100644 src/startgg-gql/generated/fragment-masking.ts delete mode 100644 src/startgg-gql/sets.graphql create mode 100644 src/state/thunks.ts delete mode 100644 src/tournament-mode/round-select.tsx diff --git a/codegen.ts b/codegen.ts index 490e23ec6..3f5005a14 100644 --- a/codegen.ts +++ b/codegen.ts @@ -7,6 +7,9 @@ const config: CodegenConfig = { generates: { "./src/startgg-gql/generated/": { preset: "client", + presetConfig: { + fragmentMasking: false, + }, }, }, }; diff --git a/graphql.config.ts b/graphql.config.ts index c1648823e..ff650c77b 100644 --- a/graphql.config.ts +++ b/graphql.config.ts @@ -10,5 +10,5 @@ export default { }, }, ], - documents: ["./src/startgg-gql/*.graphql"], + documents: ["./src/startgg-gql/*.ts"], }; diff --git a/package.json b/package.json index 9cd690cf8..6ae1e366b 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "@typescript-eslint/eslint-plugin": "^7.17.0", "@typescript-eslint/parser": "^7.17.0", "@urql/core": "^5.0.4", + "@urql/exchange-graphcache": "^7.1.2", "autoprefixer": "^10.4.19", "axios": "^1.7.2", "babel-loader": "^9.1.3", @@ -118,6 +119,7 @@ "style-loader": "^4.0.0", "typescript": "^5.5.3", "undici": "^6.19.4", + "urql": "^4.1.0", "victory": "^37.0.2", "webpack": "^5.93.0", "webpack-cli": "^5.1.4", diff --git a/src/card-draw.ts b/src/card-draw.ts index 7523995ca..8f4ef9383 100644 --- a/src/card-draw.ts +++ b/src/card-draw.ts @@ -225,19 +225,25 @@ function bucketIndexForLvl(lvl: number, buckets: LvlRanges): number | null { return null; } +export type StartggInfo = Pick; +export type StartingPoint = Drawing | StartggInfo; + /** * Produces a drawn set of charts given the song data and the user * input of the html form elements. * @param songs The song data (see `src/songs/`) * @param configData the data gathered by all form elements on the page, indexed by `name` attribute */ -export function draw(gameData: GameData, configData: ConfigState): Drawing { +export function draw( + gameData: GameData, + configData: ConfigState, + startPoint: StartingPoint, +): Drawing { const { chartCount: numChartsToRandom, useWeights, forceDistribution, weights, - defaultPlayersPerDraw, useGranularLevels, } = configData; @@ -389,10 +395,10 @@ export function draw(gameData: GameData, configData: ConfigState): Drawing { return { id: `draw-${nanoid(10)}`, charts, - players: times(defaultPlayersPerDraw, () => ""), bans: {}, protects: {}, pocketPicks: {}, winners: {}, + ...startPoint, }; } diff --git a/src/config-state.ts b/src/config-state.ts index 22e8fe6ac..3581f1c8a 100644 --- a/src/config-state.ts +++ b/src/config-state.ts @@ -20,8 +20,6 @@ export interface ConfigState { difficulties: Array; flags: Array; cutoffDate: string; - playerNames: string[]; - tournamentRounds: string[]; defaultPlayersPerDraw: number; sortByLevel: boolean; useGranularLevels: boolean; @@ -44,16 +42,6 @@ export const initialState: ConfigState = { folders: [], difficulties: [], flags: [], - playerNames: [], - tournamentRounds: [ - "Pools", - "Winner's Bracket", - "Winner's Finals", - "Loser's Bracket", - "Loser's Finals", - "Grand Finals", - "Tiebreaker", - ], sortByLevel: false, defaultPlayersPerDraw: 2, useGranularLevels: false, diff --git a/src/controls/degrs-tester.tsx b/src/controls/degrs-tester.tsx index dc5bd2de7..f88f73f1a 100644 --- a/src/controls/degrs-tester.tsx +++ b/src/controls/degrs-tester.tsx @@ -32,7 +32,10 @@ function* oneMillionDraws(gameData: GameData) { const configState = configSlice.selectSlice(store.getState()); for (let idx = 0; idx < TEST_SIZE; idx++) { - yield [draw(gameData, configState), idx] as const; + yield [ + draw(gameData, configState, { players: [], title: "", startggSetId: "" }), + idx, + ] as const; } } diff --git a/src/controls/index.tsx b/src/controls/index.tsx index dce5cc59e..c64a2d727 100644 --- a/src/controls/index.tsx +++ b/src/controls/index.tsx @@ -16,8 +16,8 @@ import { useIsNarrow } from "../hooks/useMediaQuery"; import { ErrorBoundary } from "react-error-boundary"; import { ErrorFallback } from "../utils/error-fallback"; import { ShowChartsToggle } from "./show-charts-toggle"; -import { createDraw } from "../state/drawings.slice"; -import { useAppDispatch, useAppStore } from "../state/store"; +import { createDraw } from "../state/thunks"; +import { useAppDispatch } from "../state/store"; import { useAtomValue, useSetAtom } from "jotai"; import { showEligibleCharts } from "../config-state"; import { gameDataLoadingStatus } from "../state/game-data.atoms"; @@ -31,15 +31,19 @@ export function HeaderControls() { const hasGameData = useAtomValue(gameDataLoadingStatus) === "available"; const isNarrow = useIsNarrow(); const dispatch = useAppDispatch(); - const store = useAppStore(); function handleDraw() { setShowEligibleCharts(false); - const result = createDraw(store.getState()); + const result = dispatch( + createDraw({ + players: ["TEMP1", "TEMP2"], + title: "TEMP TITLE", + startggSetId: "PLACEHOLDER", + }), + ); if (typeof result === "boolean") { setLastDrawFailed(result); } else { - dispatch(result); setLastDrawFailed(false); } } diff --git a/src/controls/player-names.tsx b/src/controls/player-names.tsx index 7cbeda30c..0d3209a71 100644 --- a/src/controls/player-names.tsx +++ b/src/controls/player-names.tsx @@ -5,7 +5,6 @@ import { Label, Text, NumericInput, - TagInput, } from "@blueprintjs/core"; import React, { useCallback } from "react"; import { useConfigState, useUpdateConfig } from "../state/hooks"; @@ -19,54 +18,18 @@ import { startggEventSlug, startggKeyAtom, } from "../startgg-gql"; -import { Person } from "@blueprintjs/icons"; export function PlayerNamesControls() { - const updateConfig = useUpdateConfig(); - const playerNames = useConfigState((s) => s.playerNames); - const { t } = useIntl(); - - function addPlayers(names: string[]) { - updateConfig((prev) => { - const next = prev.playerNames.slice(); - for (const name of names) { - if (!next.includes(name)) { - next.push(name); - } - } - if (next.length !== prev.playerNames.length) { - return { playerNames: next }; - } - return {}; - }); - } - function removePlayer(name: React.ReactNode, index: number) { - updateConfig((prev) => { - const next = prev.playerNames.slice(); - next.splice(index, 1); - return { playerNames: next }; - }); - } - return ( <> - - } - onAdd={addPlayers} - onRemove={removePlayer} - /> - + ); } -export function StartggEntrantManager() { +function StartggEntrantManager() { const { t } = useIntl(); const entrants = useAppState(entrantsSlice.selectors.selectAll); if (!entrants.length) { diff --git a/src/drawing-context.tsx b/src/drawing-context.tsx index cb875c6b7..8382a7da6 100644 --- a/src/drawing-context.tsx +++ b/src/drawing-context.tsx @@ -6,6 +6,8 @@ import { EqualityFn } from "react-redux"; const stubDrawing: Drawing = { id: "", + title: "", + startggSetId: "", bans: {}, charts: [], players: [], diff --git a/src/models/Drawing.ts b/src/models/Drawing.ts index c9b033993..3f1192480 100644 --- a/src/models/Drawing.ts +++ b/src/models/Drawing.ts @@ -41,7 +41,8 @@ export interface PocketPick extends PlayerActionOnChart { export interface Drawing { id: string; - title?: string; + startggSetId: string; + title: string; players: string[]; charts: Array; bans: Record; diff --git a/src/song-card/song-card.tsx b/src/song-card/song-card.tsx index c14f8f07f..4d86bdbb2 100644 --- a/src/song-card/song-card.tsx +++ b/src/song-card/song-card.tsx @@ -16,9 +16,10 @@ import { IconMenu } from "./icon-menu"; import { ShockBadge } from "./shock-badge"; import styles from "./song-card.css"; import { ChartLevel } from "./chart-level"; -import { useAppDispatch, useAppStore } from "../state/store"; -import { createRedrawChart, drawingsSlice } from "../state/drawings.slice"; +import { useAppDispatch } from "../state/store"; +import { createPickBanPocket, createRedrawChart } from "../state/thunks"; import { getJacketUrl } from "../utils/jackets"; +import { drawingsSlice } from "../state/drawings.slice"; const isJapanese = detectedLanguage === "ja"; @@ -48,7 +49,6 @@ export { Props as SongCardProps }; function useIconCallbacksForChart(chartId: string): IconCallbacks { const dispatch = useAppDispatch(); - const store = useAppStore(); const drawingId = useDrawing((s) => s.id); const handleBanPickPocket = useCallback( @@ -56,34 +56,8 @@ function useIconCallbacksForChart(chartId: string): IconCallbacks { type: "ban" | "protect" | "pocket", player: number, pick?: EligibleChart, - ) => { - const reorder = store.getState().config.orderByAction; - let action; - if (type === "pocket") { - if (pick) { - action = drawingsSlice.actions.banProtectReplace({ - drawingId, - chartId, - type, - player, - pick, - reorder, - }); - } - } else { - action = drawingsSlice.actions.banProtectReplace({ - drawingId, - chartId, - type, - player, - reorder, - }); - } - if (action) { - dispatch(action); - } - }, - [drawingId, chartId, dispatch, store], + ) => dispatch(createPickBanPocket(drawingId, chartId, type, player, pick)), + [drawingId, chartId, dispatch], ); return useMemo( @@ -92,8 +66,7 @@ function useIconCallbacksForChart(chartId: string): IconCallbacks { onProtect: handleBanPickPocket.bind(undefined, "protect"), onReplace: handleBanPickPocket.bind(undefined, "pocket"), onRedraw: () => { - const action = createRedrawChart(store.getState(), drawingId, chartId); - if (action) dispatch(action); + dispatch(createRedrawChart(drawingId, chartId)); }, onReset: () => dispatch(drawingsSlice.actions.resetChart({ drawingId, chartId })), @@ -102,7 +75,7 @@ function useIconCallbacksForChart(chartId: string): IconCallbacks { drawingsSlice.actions.setWinner({ drawingId, chartId, player }), ), }), - [handleBanPickPocket, store, drawingId, chartId, dispatch], + [handleBanPickPocket, drawingId, chartId, dispatch], ); } diff --git a/src/startgg-gql/entrants.graphql b/src/startgg-gql/entrants.graphql deleted file mode 100644 index f2865c392..000000000 --- a/src/startgg-gql/entrants.graphql +++ /dev/null @@ -1,23 +0,0 @@ -query EventEntrants($eventSlug: String!, $pageNo: Int!) { - event(slug: $eventSlug) { - id - name - entrants(query: { page: $pageNo, perPage: 100 }) { - pageInfo { - totalPages - } - nodes { - id - name - # paginatedSets { - # nodes { - # id - # } - # pageInfo { - # totalPages - # } - # } - } - } - } -} diff --git a/src/startgg-gql/generated/fragment-masking.ts b/src/startgg-gql/generated/fragment-masking.ts deleted file mode 100644 index aca71b135..000000000 --- a/src/startgg-gql/generated/fragment-masking.ts +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable */ -import { ResultOf, DocumentTypeDecoration, TypedDocumentNode } from '@graphql-typed-document-node/core'; -import { FragmentDefinitionNode } from 'graphql'; -import { Incremental } from './graphql'; - - -export type FragmentType> = TDocumentType extends DocumentTypeDecoration< - infer TType, - any -> - ? [TType] extends [{ ' $fragmentName'?: infer TKey }] - ? TKey extends string - ? { ' $fragmentRefs'?: { [key in TKey]: TType } } - : never - : never - : never; - -// return non-nullable if `fragmentType` is non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> -): TType; -// return nullable if `fragmentType` is undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | undefined -): TType | undefined; -// return nullable if `fragmentType` is nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null -): TType | null; -// return nullable if `fragmentType` is nullable or undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined -): TType | null | undefined; -// return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> -): Array; -// return array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> | null | undefined -): Array | null | undefined; -// return readonly array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> -): ReadonlyArray; -// return readonly array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined -): ReadonlyArray | null | undefined; -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | Array>> | ReadonlyArray>> | null | undefined -): TType | Array | ReadonlyArray | null | undefined { - return fragmentType as any; -} - - -export function makeFragmentData< - F extends DocumentTypeDecoration, - FT extends ResultOf ->(data: FT, _fragment: F): FragmentType { - return data as FragmentType; -} -export function isFragmentReady( - queryNode: DocumentTypeDecoration, - fragmentNode: TypedDocumentNode, - data: FragmentType, any>> | null | undefined -): data is FragmentType { - const deferredFields = (queryNode as { __meta__?: { deferredFields: Record } }).__meta__ - ?.deferredFields; - - if (!deferredFields) return true; - - const fragDef = fragmentNode.definitions[0] as FragmentDefinitionNode | undefined; - const fragName = fragDef?.name?.value; - - const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every(field => data && field in data); -} diff --git a/src/startgg-gql/generated/gql.ts b/src/startgg-gql/generated/gql.ts index 80ff6080b..5d92f15a1 100644 --- a/src/startgg-gql/generated/gql.ts +++ b/src/startgg-gql/generated/gql.ts @@ -13,8 +13,10 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/ * Therefore it is highly recommended to use the babel or swc plugin for production. */ const documents = { - "query EventEntrants($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n id\n name\n entrants(query: {page: $pageNo, perPage: 100}) {\n pageInfo {\n totalPages\n }\n nodes {\n id\n name\n }\n }\n }\n}": types.EventEntrantsDocument, - "query EventSets($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n id\n sets(filters: {hideEmpty: true}, perPage: 100, page: $pageNo) {\n pageInfo {\n totalPages\n total\n }\n nodes {\n id\n fullRoundText\n identifier\n slots {\n prereqType\n prereqId\n prereqPlacement\n entrant {\n id\n name\n }\n }\n }\n }\n }\n}": types.EventSetsDocument, + "\n query PlayerName($pid: ID!) {\n entrant(id: $pid) {\n __typename\n id\n name\n }\n }\n": types.PlayerNameDocument, + "\n query SetName($sid: ID!) {\n set(id: $sid) {\n __typename\n id\n fullRoundText\n }\n }\n": types.SetNameDocument, + "\n query EventEntrants($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n __typename\n id\n name\n entrants(query: { page: $pageNo, perPage: 100 }) {\n pageInfo {\n totalPages\n }\n nodes {\n __typename\n id\n name\n # paginatedSets {\n # nodes {\n # id\n # }\n # pageInfo {\n # totalPages\n # }\n # }\n }\n }\n }\n }\n": types.EventEntrantsDocument, + "\n query EventSets($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n id\n sets(filters: { hideEmpty: true }, perPage: 100, page: $pageNo) {\n pageInfo {\n totalPages\n total\n }\n nodes {\n id\n fullRoundText\n identifier\n slots {\n prereqType\n prereqId\n prereqPlacement\n entrant {\n id\n name\n }\n }\n }\n }\n }\n }\n": types.EventSetsDocument, }; /** @@ -34,11 +36,19 @@ export function graphql(source: string): unknown; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "query EventEntrants($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n id\n name\n entrants(query: {page: $pageNo, perPage: 100}) {\n pageInfo {\n totalPages\n }\n nodes {\n id\n name\n }\n }\n }\n}"): (typeof documents)["query EventEntrants($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n id\n name\n entrants(query: {page: $pageNo, perPage: 100}) {\n pageInfo {\n totalPages\n }\n nodes {\n id\n name\n }\n }\n }\n}"]; +export function graphql(source: "\n query PlayerName($pid: ID!) {\n entrant(id: $pid) {\n __typename\n id\n name\n }\n }\n"): (typeof documents)["\n query PlayerName($pid: ID!) {\n entrant(id: $pid) {\n __typename\n id\n name\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "query EventSets($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n id\n sets(filters: {hideEmpty: true}, perPage: 100, page: $pageNo) {\n pageInfo {\n totalPages\n total\n }\n nodes {\n id\n fullRoundText\n identifier\n slots {\n prereqType\n prereqId\n prereqPlacement\n entrant {\n id\n name\n }\n }\n }\n }\n }\n}"): (typeof documents)["query EventSets($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n id\n sets(filters: {hideEmpty: true}, perPage: 100, page: $pageNo) {\n pageInfo {\n totalPages\n total\n }\n nodes {\n id\n fullRoundText\n identifier\n slots {\n prereqType\n prereqId\n prereqPlacement\n entrant {\n id\n name\n }\n }\n }\n }\n }\n}"]; +export function graphql(source: "\n query SetName($sid: ID!) {\n set(id: $sid) {\n __typename\n id\n fullRoundText\n }\n }\n"): (typeof documents)["\n query SetName($sid: ID!) {\n set(id: $sid) {\n __typename\n id\n fullRoundText\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query EventEntrants($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n __typename\n id\n name\n entrants(query: { page: $pageNo, perPage: 100 }) {\n pageInfo {\n totalPages\n }\n nodes {\n __typename\n id\n name\n # paginatedSets {\n # nodes {\n # id\n # }\n # pageInfo {\n # totalPages\n # }\n # }\n }\n }\n }\n }\n"): (typeof documents)["\n query EventEntrants($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n __typename\n id\n name\n entrants(query: { page: $pageNo, perPage: 100 }) {\n pageInfo {\n totalPages\n }\n nodes {\n __typename\n id\n name\n # paginatedSets {\n # nodes {\n # id\n # }\n # pageInfo {\n # totalPages\n # }\n # }\n }\n }\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query EventSets($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n id\n sets(filters: { hideEmpty: true }, perPage: 100, page: $pageNo) {\n pageInfo {\n totalPages\n total\n }\n nodes {\n id\n fullRoundText\n identifier\n slots {\n prereqType\n prereqId\n prereqPlacement\n entrant {\n id\n name\n }\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query EventSets($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n id\n sets(filters: { hideEmpty: true }, perPage: 100, page: $pageNo) {\n pageInfo {\n totalPages\n total\n }\n nodes {\n id\n fullRoundText\n identifier\n slots {\n prereqType\n prereqId\n prereqPlacement\n entrant {\n id\n name\n }\n }\n }\n }\n }\n }\n"]; export function graphql(source: string) { return (documents as any)[source] ?? {}; diff --git a/src/startgg-gql/generated/graphql.ts b/src/startgg-gql/generated/graphql.ts index 59cca4ff3..278aecba5 100644 --- a/src/startgg-gql/generated/graphql.ts +++ b/src/startgg-gql/generated/graphql.ts @@ -2402,13 +2402,27 @@ export type WaveUpsertInput = { startAt: Scalars['Timestamp']['input']; }; +export type PlayerNameQueryVariables = Exact<{ + pid: Scalars['ID']['input']; +}>; + + +export type PlayerNameQuery = { __typename?: 'Query', entrant?: { __typename: 'Entrant', id?: string | null, name?: string | null } | null }; + +export type SetNameQueryVariables = Exact<{ + sid: Scalars['ID']['input']; +}>; + + +export type SetNameQuery = { __typename?: 'Query', set?: { __typename: 'Set', id?: string | null, fullRoundText?: string | null } | null }; + export type EventEntrantsQueryVariables = Exact<{ eventSlug: Scalars['String']['input']; pageNo: Scalars['Int']['input']; }>; -export type EventEntrantsQuery = { __typename?: 'Query', event?: { __typename?: 'Event', id?: string | null, name?: string | null, entrants?: { __typename?: 'EntrantConnection', pageInfo?: { __typename?: 'PageInfo', totalPages?: number | null } | null, nodes?: Array<{ __typename?: 'Entrant', id?: string | null, name?: string | null } | null> | null } | null } | null }; +export type EventEntrantsQuery = { __typename?: 'Query', event?: { __typename: 'Event', id?: string | null, name?: string | null, entrants?: { __typename?: 'EntrantConnection', pageInfo?: { __typename?: 'PageInfo', totalPages?: number | null } | null, nodes?: Array<{ __typename: 'Entrant', id?: string | null, name?: string | null } | null> | null } | null } | null }; export type EventSetsQueryVariables = Exact<{ eventSlug: Scalars['String']['input']; @@ -2419,5 +2433,7 @@ export type EventSetsQueryVariables = Exact<{ export type EventSetsQuery = { __typename?: 'Query', event?: { __typename?: 'Event', id?: string | null, sets?: { __typename?: 'SetConnection', pageInfo?: { __typename?: 'PageInfo', totalPages?: number | null, total?: number | null } | null, nodes?: Array<{ __typename?: 'Set', id?: string | null, fullRoundText?: string | null, identifier?: string | null, slots?: Array<{ __typename?: 'SetSlot', prereqType?: string | null, prereqId?: string | null, prereqPlacement?: number | null, entrant?: { __typename?: 'Entrant', id?: string | null, name?: string | null } | null } | null> | null } | null> | null } | null } | null }; -export const EventEntrantsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EventEntrants"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"eventSlug"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageNo"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"event"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"eventSlug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"entrants"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"query"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageNo"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"perPage"},"value":{"kind":"IntValue","value":"100"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalPages"}}]}},{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]} as unknown as DocumentNode; +export const PlayerNameDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PlayerName"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entrant"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode; +export const SetNameDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"SetName"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"set"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"fullRoundText"}}]}}]}}]} as unknown as DocumentNode; +export const EventEntrantsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EventEntrants"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"eventSlug"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageNo"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"event"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"eventSlug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"entrants"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"query"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageNo"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"perPage"},"value":{"kind":"IntValue","value":"100"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalPages"}}]}},{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const EventSetsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EventSets"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"eventSlug"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageNo"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"event"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"eventSlug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"sets"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"hideEmpty"},"value":{"kind":"BooleanValue","value":true}}]}},{"kind":"Argument","name":{"kind":"Name","value":"perPage"},"value":{"kind":"IntValue","value":"100"}},{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageNo"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalPages"}},{"kind":"Field","name":{"kind":"Name","value":"total"}}]}},{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"fullRoundText"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"slots"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"prereqType"}},{"kind":"Field","name":{"kind":"Name","value":"prereqId"}},{"kind":"Field","name":{"kind":"Name","value":"prereqPlacement"}},{"kind":"Field","name":{"kind":"Name","value":"entrant"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file diff --git a/src/startgg-gql/generated/index.ts b/src/startgg-gql/generated/index.ts index f51599168..af7839936 100644 --- a/src/startgg-gql/generated/index.ts +++ b/src/startgg-gql/generated/index.ts @@ -1,2 +1 @@ -export * from "./fragment-masking"; export * from "./gql"; \ No newline at end of file diff --git a/src/startgg-gql/index.ts b/src/startgg-gql/index.ts index bc7774843..70f8522bb 100644 --- a/src/startgg-gql/index.ts +++ b/src/startgg-gql/index.ts @@ -1,7 +1,14 @@ +import { useQuery } from "urql"; import { Entrant } from "../state/entrants.slice"; import { TournamentSet } from "../state/matches.slice"; -import { EventEntrantsDocument, EventSetsDocument } from "./generated/graphql"; -import { Client, cacheExchange, fetchExchange } from "@urql/core"; +import { + EventEntrantsDocument, + EventSetsDocument, + PlayerNameDocument, + SetNameDocument, +} from "./generated/graphql"; +import { Client, fetchExchange, gql } from "@urql/core"; +import { cacheExchange } from "@urql/exchange-graphcache"; import { getDefaultStore, atom } from "jotai"; export const startggKeyAtom = atom( @@ -18,9 +25,77 @@ export const client = new Client({ Authorization: `Bearer ${getDefaultStore().get(startggKeyAtom)}`, }, }), - exchanges: [cacheExchange, fetchExchange], + exchanges: [cacheExchange(), fetchExchange], }); +const PlayerNameDoc: typeof PlayerNameDocument = gql` + query PlayerName($pid: ID!) { + entrant(id: $pid) { + __typename + id + name + } + } +`; + +export function useStartggPlayerName(playerId: string) { + const [result] = useQuery({ + query: PlayerNameDoc, + variables: { + pid: playerId, + }, + }); + return result.data?.entrant?.name; +} + +const SetNameDoc: typeof SetNameDocument = gql` + query SetName($sid: ID!) { + set(id: $sid) { + __typename + id + fullRoundText + } + } +`; + +export function useStartggSetName(setId: string) { + const [result] = useQuery({ + query: SetNameDoc, + variables: { + sid: setId, + }, + }); + return result.data?.set?.fullRoundText; +} + +const EventEntrantsDoc: typeof EventEntrantsDocument = gql` + query EventEntrants($eventSlug: String!, $pageNo: Int!) { + event(slug: $eventSlug) { + __typename + id + name + entrants(query: { page: $pageNo, perPage: 100 }) { + pageInfo { + totalPages + } + nodes { + __typename + id + name + # paginatedSets { + # nodes { + # id + # } + # pageInfo { + # totalPages + # } + # } + } + } + } + } +`; + export async function getEventEntrants(slug: string) { let pageNo = 0; @@ -28,10 +103,12 @@ export async function getEventEntrants(slug: string) { let totalPages = 0; do { - const results = await client.query(EventEntrantsDocument, { - eventSlug: slug, - pageNo, - }); + const results = await client + .query(EventEntrantsDoc, { + eventSlug: slug, + pageNo, + }) + .toPromise(); totalPages = results.data?.event?.entrants?.pageInfo?.totalPages || 0; if (results.data?.event?.entrants?.nodes) { for (const entrant of results.data.event.entrants.nodes) { @@ -48,6 +125,34 @@ export async function getEventEntrants(slug: string) { return ret; } +const EventSetsDoc: typeof EventSetsDocument = gql` + query EventSets($eventSlug: String!, $pageNo: Int!) { + event(slug: $eventSlug) { + id + sets(filters: { hideEmpty: true }, perPage: 100, page: $pageNo) { + pageInfo { + totalPages + total + } + nodes { + id + fullRoundText + identifier + slots { + prereqType + prereqId + prereqPlacement + entrant { + id + name + } + } + } + } + } + } +`; + export async function getEventSets(slug: string) { let pageNo = 0; @@ -55,10 +160,12 @@ export async function getEventSets(slug: string) { let totalPages = 0; do { - const results = await client.query(EventSetsDocument, { - eventSlug: slug, - pageNo, - }); + const results = await client + .query(EventSetsDoc, { + eventSlug: slug, + pageNo, + }) + .toPromise(); totalPages = results.data?.event?.sets?.pageInfo?.totalPages || 0; if (results.data?.event?.sets?.nodes) { for (const set of results.data.event.sets.nodes) { diff --git a/src/startgg-gql/sets.graphql b/src/startgg-gql/sets.graphql deleted file mode 100644 index 386e25f6c..000000000 --- a/src/startgg-gql/sets.graphql +++ /dev/null @@ -1,25 +0,0 @@ -query EventSets($eventSlug: String!, $pageNo: Int!) { - event(slug: $eventSlug) { - id - sets(filters: { hideEmpty: true }, perPage: 100, page: $pageNo) { - pageInfo { - totalPages - total - } - nodes { - id - fullRoundText - identifier - slots { - prereqType - prereqId - prereqPlacement - entrant { - id - name - } - } - } - } - } -} diff --git a/src/state/central.ts b/src/state/central.ts index 6096443a1..60cf57a88 100644 --- a/src/state/central.ts +++ b/src/state/central.ts @@ -6,8 +6,3 @@ export const receivePartyState = createAction( "party/supplyState", withPayload(), ); - -export const addPlayerNameToDrawing = createAction( - "players/addToDrawing", - withPayload<{ name: string; asPlayerNo: number; drawingId: string }>(), -); diff --git a/src/state/config.slice.ts b/src/state/config.slice.ts index 65e075517..5a36ca856 100644 --- a/src/state/config.slice.ts +++ b/src/state/config.slice.ts @@ -1,7 +1,6 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { ConfigState, initialState } from "../config-state"; import { GameData } from "../models/SongData"; -import { addPlayerNameToDrawing } from "./central"; export const configSlice = createSlice({ name: "config", @@ -32,15 +31,4 @@ export const configSlice = createSlice({ Object.assign(state, patch); }, }, - extraReducers(builder) { - builder.addCase(addPlayerNameToDrawing, (state, action) => { - if (!action.payload.name) { - return; - } - if (state.playerNames.includes(action.payload.name)) { - return; - } - state.playerNames.push(action.payload.name); - }); - }, }); diff --git a/src/state/drawings.slice.ts b/src/state/drawings.slice.ts index f7d86ffbc..9931dd2b3 100644 --- a/src/state/drawings.slice.ts +++ b/src/state/drawings.slice.ts @@ -9,53 +9,9 @@ import { EligibleChart, PlayerActionOnChart, } from "../models/Drawing"; -import { addPlayerNameToDrawing } from "./central"; -import { AppState } from "./store"; -import { draw } from "../card-draw"; -import { getDefaultStore } from "jotai"; -import { gameDataAtom } from "./game-data.atoms"; export const drawingsAdapter = createEntityAdapter({}); -declare const umami: { - track( - eventName?: string, - eventProperties?: Record, - ): void; -}; - -function trackDraw(count: number | null, game?: string) { - if (typeof umami === "undefined") { - return; - } - const results = - count === null ? { result: "failed" } : { result: "success", count, game }; - umami.track("cards-drawn", results); -} - -/** - * Performs a draw and returns an action to be dispatched - * @returns false if no draw was possible, and - * true if fewer charts were drawn than requested, - * addDrawing action in the default successful case - */ -export function createDraw(state: AppState) { - const jotaiStore = getDefaultStore(); - const gameData = jotaiStore.get(gameDataAtom); - if (!gameData) { - trackDraw(null); - return false; - } - - const drawing = draw(gameData, state.config); - trackDraw(drawing.charts.length, state.gameData.dataSetName); - if (!drawing.charts.length) { - return true; - } - - return drawingsSlice.actions.addDrawing(drawing); -} - /** payload is the drawing id */ type ActionOnSingleDrawing = PayloadAction; // eslint-disable-next-line @typescript-eslint/ban-types @@ -95,15 +51,12 @@ export const drawingsSlice = createSlice({ } Object.assign(chart, action.payload.changes); }, - addEmptyPlayer(state, action: ActionOnSingleDrawing) { + swapPlayerPositions(state, action: ActionOnSingleDrawing) { const drawing = state.entities[action.payload]; - if (!drawing) return; - drawing.players.push(""); - }, - dropPlayer(state, action: ActionOnSingleDrawing) { - const drawing = state.entities[action.payload]; - if (!drawing) return; - drawing.players.pop(); + if (!drawing) { + return; + } + drawing.players = drawing.players.toReversed(); }, incrementPriorityPlayer(state, action: ActionOnSingleDrawing) { const drawing = state.entities[action.payload]; @@ -174,15 +127,6 @@ export const drawingsSlice = createSlice({ } }, }, - extraReducers(builder) { - builder.addCase(addPlayerNameToDrawing, (state, action) => { - const drawing = state.entities[action.payload.drawingId]; - if (!drawing) { - return; - } - drawing.players[action.payload.asPlayerNo - 1] = action.payload.name; - }); - }, selectors: { haveDrawings(state) { return !!state.ids.length; @@ -194,51 +138,6 @@ export const drawingSelectors = drawingsAdapter.getSelectors( drawingsSlice.selectSlice, ); -export function createRedrawAll(state: AppState, drawingId: string) { - // TODO preserve protects and pocket picks in this logic - const drawing = state.drawings.entities[drawingId]; - const drawConfig = { - ...state.config, - chartCount: drawing.charts.length, - }; - const jotaiStore = getDefaultStore(); - const gameData = jotaiStore.get(gameDataAtom); - const drawResult = draw(gameData!, drawConfig); - return drawingsSlice.actions.updateOne({ - id: drawingId, - changes: { - charts: drawResult.charts, - pocketPicks: {}, - bans: {}, - protects: {}, - winners: {}, - }, - }); -} - -export function createRedrawChart( - state: AppState, - drawingId: string, - chartId: string, -) { - const drawConfig = { - ...state.config, - chartCount: 1, - }; - const jotaiStore = getDefaultStore(); - const gameData = jotaiStore.get(gameDataAtom); - const drawResult = draw(gameData!, drawConfig); - const chart = drawResult.charts.find((c) => c.type === "DRAWN"); - if (!chart) { - return; - } - return drawingsSlice.actions.updateOneChart({ - drawingId, - chartId, - changes: chart, - }); -} - function moveChartInArray( drawing: Drawing, chartId: string, diff --git a/src/state/store.ts b/src/state/store.ts index 058c3f6b4..f74555f20 100644 --- a/src/state/store.ts +++ b/src/state/store.ts @@ -1,4 +1,8 @@ -import { configureStore } from "@reduxjs/toolkit"; +import { + configureStore, + ThunkAction, + ActionFromReducer, +} from "@reduxjs/toolkit"; import { useDispatch, useSelector, useStore } from "react-redux"; import { reducer } from "./root-reducer"; import { listenerMiddleware } from "./listener-middleware"; @@ -14,3 +18,9 @@ export const useAppState = useSelector.withTypes(); export type AppDispatch = typeof store.dispatch; export const useAppDispatch = useDispatch.withTypes(); export const useAppStore = useStore.withTypes(); +export type AppThunk = ThunkAction< + ReturnType, + AppState, + unknown, + ActionFromReducer +>; diff --git a/src/state/thunks.ts b/src/state/thunks.ts new file mode 100644 index 000000000..8232ca849 --- /dev/null +++ b/src/state/thunks.ts @@ -0,0 +1,163 @@ +import { AppThunk } from "./store"; +import { draw, StartggInfo } from "../card-draw"; +import { getDefaultStore } from "jotai"; +import { gameDataAtom } from "./game-data.atoms"; +import { drawingsSlice } from "./drawings.slice"; +import { EligibleChart } from "../models/Drawing"; + +declare const umami: { + track( + eventName?: string, + eventProperties?: Record, + ): void; +}; + +function trackDraw(count: number | null, game?: string) { + if (typeof umami === "undefined") { + return; + } + const results = + count === null ? { result: "failed" } : { result: "success", count, game }; + umami.track("cards-drawn", results); +} + +/** + * Thunk creator for performing a new draw + * @returns false if draw was unsuccessful + */ +export function createDraw(startggTargetSet: StartggInfo): AppThunk { + return (dispatch, getState) => { + const state = getState(); + const jotaiStore = getDefaultStore(); + const gameData = jotaiStore.get(gameDataAtom); + if (!gameData) { + trackDraw(null); + return false; // no draw was possible + } + + const drawing = draw(gameData, state.config, startggTargetSet); + trackDraw(drawing.charts.length, state.gameData.dataSetName); + if (!drawing.charts.length) { + return false; // could not draw the requested number of charts + } + + dispatch(drawingsSlice.actions.addDrawing(drawing)); + }; +} + +/** + * thunk creator for redrawing all charts in a target drawing + */ +export function createRedrawAll(drawingId: string): AppThunk { + return (dispatch, getState) => { + const state = getState(); + const drawing = state.drawings.entities[drawingId]; + const drawConfig = { + ...state.config, + chartCount: drawing.charts.length, + }; + const jotaiStore = getDefaultStore(); + const gameData = jotaiStore.get(gameDataAtom); + + // preserve pocket picks and protects in the redraw by keeping them in the starting point info + // and filtering out all other charts + const protectedChartIds = new Set( + Object.keys(drawing.pocketPicks).concat(Object.keys(drawing.protects)), + ); + const startingPoint = { + ...drawing, + charts: drawing.charts.filter( + (chart) => + protectedChartIds.has(chart.id) || chart.type === "PLACEHOLDER", + ), + }; + + const drawResult = draw(gameData!, drawConfig, startingPoint); + dispatch( + drawingsSlice.actions.updateOne({ + id: drawingId, + changes: { + charts: drawResult.charts, + pocketPicks: drawing.pocketPicks, + bans: {}, + protects: drawing.protects, + winners: {}, + }, + }), + ); + }; +} + +/** + * thunk creator for redrawing a single chart within a drawing + */ +export function createRedrawChart( + drawingId: string, + chartId: string, +): AppThunk { + return (dispatch, getState) => { + const jotaiStore = getDefaultStore(); + const gameData = jotaiStore.get(gameDataAtom); + if (!gameData) return; + const state = getState(); + const drawing = state.drawings.entities[drawingId]; + const startingPoint = { + ...drawing, + charts: drawing.charts.filter((chart) => chart.id !== chartId), + }; + + const drawResult = draw(gameData, state.config, startingPoint); + const chart = drawResult.charts.pop(); + if ( + !chart || + chart.type !== "DRAWN" || + drawing.charts.some((c) => c.id === chart.id) + ) { + return; // result didn't include a new chart + } + dispatch( + drawingsSlice.actions.updateOneChart({ + drawingId, + chartId, + changes: chart, + }), + ); + }; +} + +/** thunk creator for pick/ban/pocket pick that can include orderByAction setting */ +export function createPickBanPocket( + drawingId: string, + chartId: string, + type: "ban" | "protect" | "pocket", + player: number, + pick?: EligibleChart, +): AppThunk { + return (dispatch, getState) => { + const reorder = getState().config.orderByAction; + let action; + if (type === "pocket") { + if (pick) { + action = drawingsSlice.actions.banProtectReplace({ + drawingId, + chartId, + type, + player, + pick, + reorder, + }); + } + } else { + action = drawingsSlice.actions.banProtectReplace({ + drawingId, + chartId, + type, + player, + reorder, + }); + } + if (action) { + dispatch(action); + } + }; +} diff --git a/src/tournament-mode/drawing-actions.tsx b/src/tournament-mode/drawing-actions.tsx index 5594a8d3d..5971be41d 100644 --- a/src/tournament-mode/drawing-actions.tsx +++ b/src/tournament-mode/drawing-actions.tsx @@ -1,22 +1,16 @@ import { Button, Menu, MenuItem, Popover, Tooltip } from "@blueprintjs/core"; -import { - Camera, - Refresh, - NewPerson, - BlockedPerson, - Error, - CubeAdd, -} from "@blueprintjs/icons"; +import { Camera, Refresh, Error, CubeAdd, Exchange } from "@blueprintjs/icons"; import { useDrawing } from "../drawing-context"; import styles from "./drawing-actions.css"; import { domToPng } from "modern-screenshot"; import { shareImage } from "../utils/share"; import { useErrorBoundary } from "react-error-boundary"; -import { useAppDispatch, useAppState, useAppStore } from "../state/store"; +import { useAppDispatch, useAppState } from "../state/store"; import { useAtomValue } from "jotai"; import { showPlayerAndRoundLabels } from "../config-state"; -import { createRedrawAll, drawingsSlice } from "../state/drawings.slice"; +import { drawingsSlice } from "../state/drawings.slice"; import { eventSlice } from "../state/event.slice"; +import { createRedrawAll } from "../state/thunks"; const DEFAULT_FILENAME = "card-draw.png"; @@ -24,10 +18,8 @@ export function DrawingActions() { const dispatch = useAppDispatch(); const cabs = useAppState(eventSlice.selectors.allCabs); const drawingId = useDrawing((s) => s.id); - const hasPlayers = useDrawing((s) => !!s.players.length); const showLabels = useAtomValue(showPlayerAndRoundLabels); const { showBoundary } = useErrorBoundary(); - const store = useAppStore(); const addToCabMenu = ( @@ -76,7 +68,7 @@ export function DrawingActions() { onClick={() => confirm( "This will replace everything besides protects and picks!", - ) && dispatch(createRedrawAll(store.getState(), drawingId)) + ) && dispatch(createRedrawAll(drawingId)) } /> @@ -94,22 +86,12 @@ export function DrawingActions() { )} {showLabels && ( <> - + + + ); +} + function StartggSetImport() { const [apiKey, setApiKey] = useAtom(startggKeyAtom); const [eventSlug, setEventSlug] = useAtom(startggEventSlug); diff --git a/src/startgg-gql/index.ts b/src/startgg-gql/index.ts index 70f8522bb..b4720db78 100644 --- a/src/startgg-gql/index.ts +++ b/src/startgg-gql/index.ts @@ -9,7 +9,7 @@ import { } from "./generated/graphql"; import { Client, fetchExchange, gql } from "@urql/core"; import { cacheExchange } from "@urql/exchange-graphcache"; -import { getDefaultStore, atom } from "jotai"; +import { getDefaultStore, atom, useAtomValue } from "jotai"; export const startggKeyAtom = atom( process.env.STARTGG_TOKEN as string, @@ -18,7 +18,7 @@ export const startggEventSlug = atom( "tournament/red-october-2024/event/stepmaniax-singles-hard-and-wild", ); -export const client = new Client({ +export const urqlClient = new Client({ url: "https://api.start.gg/gql/alpha", fetchOptions: () => ({ headers: { @@ -68,6 +68,17 @@ export function useStartggSetName(setId: string) { return result.data?.set?.fullRoundText; } +export function useStartggMatches() { + const eventSlug = useAtomValue(startggEventSlug)!; + return useQuery({ + query: EventSetsDoc, + variables: { + eventSlug, + pageNo: 0, + }, + }); +} + const EventEntrantsDoc: typeof EventEntrantsDocument = gql` query EventEntrants($eventSlug: String!, $pageNo: Int!) { event(slug: $eventSlug) { @@ -103,7 +114,7 @@ export async function getEventEntrants(slug: string) { let totalPages = 0; do { - const results = await client + const results = await urqlClient .query(EventEntrantsDoc, { eventSlug: slug, pageNo, @@ -160,7 +171,7 @@ export async function getEventSets(slug: string) { let totalPages = 0; do { - const results = await client + const results = await urqlClient .query(EventSetsDoc, { eventSlug: slug, pageNo, diff --git a/src/state/drawings.slice.ts b/src/state/drawings.slice.ts index 9931dd2b3..2679c593b 100644 --- a/src/state/drawings.slice.ts +++ b/src/state/drawings.slice.ts @@ -131,6 +131,11 @@ export const drawingsSlice = createSlice({ haveDrawings(state) { return !!state.ids.length; }, + associatedMatchIds(state) { + return Object.values(state.entities).map( + (drawing) => drawing.startggSetId, + ); + }, }, }); From 935ec73c41386b045d20062abe401bac3e5fabd3 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Mon, 12 Aug 2024 17:16:57 -0700 Subject: [PATCH 44/91] display url example inline --- src/matches.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/matches.tsx b/src/matches.tsx index bc7db0682..8e4579fa4 100644 --- a/src/matches.tsx +++ b/src/matches.tsx @@ -158,7 +158,10 @@ export function StartggApiKeyGated(props: { children: ReactNode }) { From 2359225081a4cced216afb379cc7afdb401ece7b Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Mon, 12 Aug 2024 17:18:29 -0700 Subject: [PATCH 45/91] pre-populate initial values for startgg inputs --- src/matches.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/matches.tsx b/src/matches.tsx index 8e4579fa4..61f999ff1 100644 --- a/src/matches.tsx +++ b/src/matches.tsx @@ -154,7 +154,12 @@ export function StartggApiKeyGated(props: { children: ReactNode }) { create a personal token here - ) + ){" "} + From 95384a9d4375c5dd292646f5195f3f94318a4766 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Mon, 12 Aug 2024 17:40:26 -0700 Subject: [PATCH 46/91] persist startgg event info to localstorage --- src/startgg-gql/index.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/startgg-gql/index.ts b/src/startgg-gql/index.ts index b4720db78..52c05be45 100644 --- a/src/startgg-gql/index.ts +++ b/src/startgg-gql/index.ts @@ -9,13 +9,20 @@ import { } from "./generated/graphql"; import { Client, fetchExchange, gql } from "@urql/core"; import { cacheExchange } from "@urql/exchange-graphcache"; -import { getDefaultStore, atom, useAtomValue } from "jotai"; +import { getDefaultStore, useAtomValue } from "jotai"; +import { atomWithStorage } from "jotai/utils"; -export const startggKeyAtom = atom( +export const startggKeyAtom = atomWithStorage( + "ddrtools.event.startggtoken", process.env.STARTGG_TOKEN as string, + undefined, + { getOnInit: true }, ); -export const startggEventSlug = atom( +export const startggEventSlug = atomWithStorage( + "ddrtools.event.startggslug", "tournament/red-october-2024/event/stepmaniax-singles-hard-and-wild", + undefined, + { getOnInit: true }, ); export const urqlClient = new Client({ From 39af9fa2873043ce510c9f617ce8dd9f65906f78 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Wed, 14 Aug 2024 16:28:51 -0700 Subject: [PATCH 47/91] fix swapping player positions by totally blowing up the drawing model --- src/cab-management.tsx | 7 ++-- src/card-draw.ts | 8 +++- src/controls/degrs-tester.tsx | 4 +- src/controls/index.tsx | 9 +++-- src/drawing-context.tsx | 9 +++-- src/matches.tsx | 7 +++- src/models/Drawing.ts | 43 ++++++++++++++++++-- src/obs-sources/text.tsx | 7 +++- src/song-card/card-label.tsx | 8 ++-- src/song-card/icon-menu.tsx | 14 ++++--- src/song-card/song-card.tsx | 55 ++++++++++++++------------ src/song-card/use-player-label.ts | 5 ++- src/state/drawings.slice.ts | 12 +++--- src/tournament-mode/drawing-labels.tsx | 13 +++--- 14 files changed, 137 insertions(+), 64 deletions(-) diff --git a/src/cab-management.tsx b/src/cab-management.tsx index 9590e3e7e..6421f7818 100644 --- a/src/cab-management.tsx +++ b/src/cab-management.tsx @@ -24,6 +24,7 @@ import { detectedLanguage } from "./utils"; import { copyPlainTextToClipboard } from "./utils/share"; import { useSetAtom } from "jotai"; import { mainTabAtom } from "./main-view"; +import { playerNameByIndex } from "./models/Drawing"; export function CabManagement() { const cabs = useAppState(eventSlice.selectors.allCabs); @@ -181,8 +182,8 @@ function CurrentMatch(props: { cab: CabInfo }) { if (!drawing) { return

No match

; } - const filledPlayers = drawing.players.map( - (p, idx) => p || `Player ${idx + 1}`, + const filledPlayers = drawing.playerDisplayOrder.map((pIdx, idx) => + playerNameByIndex(drawing.meta, pIdx, `Player ${idx + 1}`), ); return ( -

{drawing.title}

+

{drawing.meta.title}

{listFormatter.format(filledPlayers)}

); diff --git a/src/card-draw.ts b/src/card-draw.ts index 20d54ec6d..5a4efaefd 100644 --- a/src/card-draw.ts +++ b/src/card-draw.ts @@ -225,7 +225,7 @@ function bucketIndexForLvl(lvl: number, buckets: LvlRanges): number | null { return null; } -export type StartggInfo = Pick; +export type StartggInfo = Pick; export type StartingPoint = Drawing | StartggInfo; const artistDrawBlocklist = new Set(["Carlito", "Dr. Bombay"]); @@ -432,12 +432,18 @@ export function draw( ); } + const players = + startPoint.meta.type === "simple" + ? startPoint.meta.players + : startPoint.meta.entrants; + return { id: `draw-${nanoid(10)}`, bans: {}, protects: {}, pocketPicks: {}, winners: {}, + playerDisplayOrder: players.map((_, idx) => idx), ...startPoint, charts, }; diff --git a/src/controls/degrs-tester.tsx b/src/controls/degrs-tester.tsx index f88f73f1a..82ce55f23 100644 --- a/src/controls/degrs-tester.tsx +++ b/src/controls/degrs-tester.tsx @@ -33,7 +33,9 @@ function* oneMillionDraws(gameData: GameData) { for (let idx = 0; idx < TEST_SIZE; idx++) { yield [ - draw(gameData, configState, { players: [], title: "", startggSetId: "" }), + draw(gameData, configState, { + meta: { players: [], title: "", type: "simple" }, + }), idx, ] as const; } diff --git a/src/controls/index.tsx b/src/controls/index.tsx index 47b9af62f..d09c44b8d 100644 --- a/src/controls/index.tsx +++ b/src/controls/index.tsx @@ -40,9 +40,12 @@ export function HeaderControls() { setShowEligibleCharts(false); const result = dispatch( createDraw({ - players: match.players, - title: match.title, - startggSetId: match.id, + meta: { + type: "startgg", + entrants: match.players, + title: match.title, + id: match.id, + }, }), ); if (typeof result === "boolean") { diff --git a/src/drawing-context.tsx b/src/drawing-context.tsx index 8382a7da6..dcfb10ea7 100644 --- a/src/drawing-context.tsx +++ b/src/drawing-context.tsx @@ -6,11 +6,14 @@ import { EqualityFn } from "react-redux"; const stubDrawing: Drawing = { id: "", - title: "", - startggSetId: "", + meta: { + type: "simple", + players: [], + title: "", + }, + playerDisplayOrder: [], bans: {}, charts: [], - players: [], pocketPicks: {}, protects: {}, winners: {}, diff --git a/src/matches.tsx b/src/matches.tsx index 61f999ff1..f0957d4ad 100644 --- a/src/matches.tsx +++ b/src/matches.tsx @@ -29,7 +29,7 @@ export function MatchListAndSettings() { export interface PickedMatch { title: string; - players: string[]; + players: Array<{ id: string; name: string }>; id: string; } @@ -91,7 +91,10 @@ export function MatchPicker(props: { onPickMatch?(match: PickedMatch): void }) { : () => props.onPickMatch?.({ title, - players: [p1!, p2!], + players: match.slots!.map((slot) => ({ + id: slot!.entrant!.id!, + name: inferShortname(slot!.entrant!.name)!, + })), id: match.id!, }) } diff --git a/src/models/Drawing.ts b/src/models/Drawing.ts index 3f1192480..7ba477f0c 100644 --- a/src/models/Drawing.ts +++ b/src/models/Drawing.ts @@ -39,15 +39,52 @@ export interface PocketPick extends PlayerActionOnChart { pick: EligibleChart; } -export interface Drawing { +export interface StartggMeta { + type: "startgg"; + /** id of the set */ id: string; - startggSetId: string; title: string; + entrants: Array<{ id: string; name: string }>; +} + +export interface SimpleMeta { + type: "simple"; + title: string; + /** plain player names */ players: string[]; +} + +export function playerNameByDisplayPos( + d: Pick, + pos: number, +) { + const playerIndex = d.playerDisplayOrder[pos - 1]; + return playerNameByIndex(d.meta, playerIndex); +} + +export function playerNameByIndex( + meta: Drawing["meta"], + idx: number, + fallback = `P${idx + 1}`, +) { + switch (meta.type) { + case "simple": + return meta.players[idx] || fallback; + case "startgg": + return meta.entrants[idx].name || fallback; + } +} + +export interface Drawing { + id: string; + meta: SimpleMeta | StartggMeta; + /** index of items of the players array, in the order they should be displayed */ + playerDisplayOrder: number[]; + /** map of song ID to player index or id */ + winners: Record; charts: Array; bans: Record; protects: Record; - winners: Record; pocketPicks: Record; priorityPlayer?: number; } diff --git a/src/obs-sources/text.tsx b/src/obs-sources/text.tsx index 3cbc31020..6cdba4dea 100644 --- a/src/obs-sources/text.tsx +++ b/src/obs-sources/text.tsx @@ -1,13 +1,14 @@ import { useParams } from "react-router-dom"; import { drawingSelectors } from "../state/drawings.slice"; import { useAppState } from "../state/store"; +import { playerNameByIndex } from "../models/Drawing"; export function CabTitle() { const params = useParams<"roomName" | "cabId">(); const text = useAppState((s) => { const drawingId = s.event.cabs[params.cabId!].activeMatch; if (!drawingId) return null; - return drawingSelectors.selectById(s, drawingId).title; + return drawingSelectors.selectById(s, drawingId).meta.title; }); return

{text}

; } @@ -17,7 +18,9 @@ export function CabPlayer(props: { p: number }) { const text = useAppState((s) => { const drawingId = s.event.cabs[params.cabId!].activeMatch; if (!drawingId) return null; - return drawingSelectors.selectById(s, drawingId).players[props.p - 1]; + const drawing = drawingSelectors.selectById(s, drawingId); + const playerIndex = drawing.playerDisplayOrder[props.p - 1]; + return playerNameByIndex(drawing.meta, playerIndex, ""); }); return

{text}

; } diff --git a/src/song-card/card-label.tsx b/src/song-card/card-label.tsx index a1c3c05bb..6806646f1 100644 --- a/src/song-card/card-label.tsx +++ b/src/song-card/card-label.tsx @@ -3,7 +3,7 @@ import React from "react"; import { Intent, Tag } from "@blueprintjs/core"; import styles from "./card-label.css"; import { Inheritance, BanCircle, Lock, Crown, Draw } from "@blueprintjs/icons"; -import { usePlayerLabel } from "./use-player-label"; +import { usePlayerLabelForIndex } from "./use-player-label"; export enum LabelType { Protect = 1, @@ -14,7 +14,7 @@ export enum LabelType { } interface Props { - player: number; + playerIdx: number; type: LabelType; onRemove?: () => void; } @@ -49,8 +49,8 @@ function getIcon(type: LabelType) { } } -export function CardLabel({ player, type, onRemove }: Props) { - const label = usePlayerLabel(player); +export function CardLabel({ playerIdx, type, onRemove }: Props) { + const label = usePlayerLabelForIndex(playerIdx); const rootClassname = classNames(styles.cardLabel, { [styles.winner]: type === LabelType.Winner, diff --git a/src/song-card/icon-menu.tsx b/src/song-card/icon-menu.tsx index f673ea922..705d0f386 100644 --- a/src/song-card/icon-menu.tsx +++ b/src/song-card/icon-menu.tsx @@ -9,6 +9,7 @@ import { } from "@blueprintjs/icons"; import { Menu, MenuItem, MenuDivider } from "@blueprintjs/core"; import { useDrawing } from "../drawing-context"; +import { playerNameByIndex } from "../models/Drawing"; interface Props { onStartPocketPick?: (p: number) => void; @@ -74,14 +75,17 @@ interface IconRowProps { } function PlayerList({ icon, text, onClick }: IconRowProps) { - const players = useDrawing((d) => d.players); + const drawingMeta = useDrawing((d) => d.meta); + const players = useDrawing((d) => d.playerDisplayOrder).map( + (pIdx) => [playerNameByIndex(drawingMeta, pIdx), pIdx] as const, + ); return ( - {players.map((p, idx) => ( + {players.map(([playerName, pIdx]) => ( onClick(idx + 1)} + key={pIdx} + text={playerName} + onClick={() => onClick(pIdx)} icon={} /> ))} diff --git a/src/song-card/song-card.tsx b/src/song-card/song-card.tsx index 4d86bdbb2..a0c427969 100644 --- a/src/song-card/song-card.tsx +++ b/src/song-card/song-card.tsx @@ -23,24 +23,24 @@ import { drawingsSlice } from "../state/drawings.slice"; const isJapanese = detectedLanguage === "ja"; -type Player = number; +type PlayerIdx = number; interface IconCallbacks { - onVeto: (p: Player) => void; - onProtect: (p: Player) => void; - onReplace: (p: Player, chart: EligibleChart) => void; + onVeto: (p: PlayerIdx) => void; + onProtect: (p: PlayerIdx) => void; + onReplace: (p: PlayerIdx, chart: EligibleChart) => void; onRedraw: () => void; onReset: () => void; - onSetWinner: (p: Player | null) => void; + onSetWinner: (p: PlayerIdx | null) => void; } interface Props { onClick?: () => void; chart: DrawnChart | EligibleChart | PlayerPickPlaceholder; - vetoedBy?: Player; - protectedBy?: Player; - replacedBy?: Player; - winner?: Player | null; + vetoedBy?: PlayerIdx; + protectedBy?: PlayerIdx; + replacedBy?: PlayerIdx; + winner?: PlayerIdx | null; replacedWith?: EligibleChart; actionsEnabled?: boolean; } @@ -125,7 +125,12 @@ export function SongCard(props: Props) { dateAdded, } = replacedWith || baseChartValues(chart); - const hasLabel = !!(vetoedBy || protectedBy || replacedBy); + const hasLabel = !!( + vetoedBy !== undefined || + protectedBy !== undefined || + replacedBy !== undefined + ); + const hasWinner = typeof winner === "number"; let jacketBg = {}; if (jacket) { @@ -137,8 +142,8 @@ export function SongCard(props: Props) { const iconCallbacks = useIconCallbacksForChart((chart as DrawnChart).id); let menuContent: undefined | JSX.Element; - if (actionsEnabled && !winner) { - if (!replacedWith && baseChartIsPlaceholder) { + if (actionsEnabled && !hasWinner) { + if (replacedWith !== undefined && baseChartIsPlaceholder) { menuContent = ( ); @@ -152,16 +157,16 @@ export function SongCard(props: Props) { onSetWinner={iconCallbacks.onSetWinner} /> ); - } else if (!vetoedBy) { + } else if (vetoedBy === undefined) { menuContent = ; } } const rootClassname = classNames(styles.chart, { - [styles.vetoed]: vetoedBy, - [styles.protected]: protectedBy, - [styles.replaced]: replacedBy && !baseChartIsPlaceholder, - [styles.picked]: replacedBy && baseChartIsPlaceholder, + [styles.vetoed]: vetoedBy !== undefined, + [styles.protected]: protectedBy !== undefined, + [styles.replaced]: replacedBy !== undefined && !baseChartIsPlaceholder, + [styles.picked]: replacedBy !== undefined && baseChartIsPlaceholder, [styles.clickable]: !!menuContent || !!props.onClick, [styles.hideVeto]: hideVetos, }); @@ -187,32 +192,32 @@ export function SongCard(props: Props) { onCancel={() => setPocketPickPendingForPlayer(0)} />
- {vetoedBy && ( + {vetoedBy !== undefined && ( )} - {protectedBy && ( + {protectedBy !== undefined && ( )} - {replacedBy && ( + {replacedBy !== undefined && ( )} - {winner && ( + {hasWinner && ( iconCallbacks?.onSetWinner(null)} /> diff --git a/src/song-card/use-player-label.ts b/src/song-card/use-player-label.ts index 6dc193d03..b3ecd1f9d 100644 --- a/src/song-card/use-player-label.ts +++ b/src/song-card/use-player-label.ts @@ -1,5 +1,6 @@ import { useDrawing } from "../drawing-context"; +import { playerNameByIndex } from "../models/Drawing"; -export function usePlayerLabel(n: number) { - return useDrawing((d) => d.players[n - 1] || `P${n}`); +export function usePlayerLabelForIndex(pIdx: number) { + return useDrawing((d) => playerNameByIndex(d.meta, pIdx)); } diff --git a/src/state/drawings.slice.ts b/src/state/drawings.slice.ts index 2679c593b..41f27fdca 100644 --- a/src/state/drawings.slice.ts +++ b/src/state/drawings.slice.ts @@ -56,7 +56,7 @@ export const drawingsSlice = createSlice({ if (!drawing) { return; } - drawing.players = drawing.players.toReversed(); + drawing.playerDisplayOrder = drawing.playerDisplayOrder.toReversed(); }, incrementPriorityPlayer(state, action: ActionOnSingleDrawing) { const drawing = state.entities[action.payload]; @@ -68,7 +68,7 @@ export const drawingsSlice = createSlice({ priorityPlayer = 1; } else { priorityPlayer += 1; - if (priorityPlayer >= drawing.players.length + 1) { + if (priorityPlayer >= drawing.playerDisplayOrder.length + 1) { priorityPlayer = undefined; } } @@ -132,9 +132,11 @@ export const drawingsSlice = createSlice({ return !!state.ids.length; }, associatedMatchIds(state) { - return Object.values(state.entities).map( - (drawing) => drawing.startggSetId, - ); + return Object.values(state.entities) + .map((drawing) => drawing.meta.type === "startgg" && drawing.meta.id) + .filter( + (value): value is string => typeof value === "string" && !!value, + ); }, }, }); diff --git a/src/tournament-mode/drawing-labels.tsx b/src/tournament-mode/drawing-labels.tsx index e4706961a..6b403bbc3 100644 --- a/src/tournament-mode/drawing-labels.tsx +++ b/src/tournament-mode/drawing-labels.tsx @@ -7,22 +7,25 @@ import { useAtomValue } from "jotai"; import { showPlayerAndRoundLabels } from "../config-state"; import { useAppDispatch } from "../state/store"; import { drawingsSlice } from "../state/drawings.slice"; +import { playerNameByDisplayPos } from "../models/Drawing"; export function SetLabels() { const showLabels = useAtomValue(showPlayerAndRoundLabels); - const players = useDrawing((s) => s.players); - const title = useDrawing((s) => s.title); + const playerDisplayOrder = useDrawing((d) => d.playerDisplayOrder); + const meta = useDrawing((d) => d.meta); if (!showLabels) { return null; } + const psuedoDrawing = { meta, playerDisplayOrder }; + return (
-
{title}
+
{meta.title}
- {players[0]} + {playerNameByDisplayPos(psuedoDrawing, 1)} - {players[1]} + {playerNameByDisplayPos(psuedoDrawing, 2)}
); From d365278d738894504a28018498fa89af61aa7fb0 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Fri, 16 Aug 2024 19:57:53 -0700 Subject: [PATCH 48/91] added working reporting of results to startgg --- src/controls/index.tsx | 3 +- src/controls/player-names.tsx | 178 ++++++++---------------- src/main-view.tsx | 6 +- src/matches.tsx | 151 ++------------------ src/models/Drawing.ts | 2 +- src/startgg-gql/components.tsx | 65 +++++++++ src/startgg-gql/generated/gql.ts | 5 + src/startgg-gql/generated/graphql.ts | 12 +- src/startgg-gql/index.ts | 141 +++++++------------ src/state/entrants.slice.ts | 27 ---- src/state/matches.slice.ts | 38 ----- src/state/root-reducer.ts | 4 - src/tournament-mode/drawing-actions.tsx | 122 ++++++++++++++-- 13 files changed, 319 insertions(+), 435 deletions(-) create mode 100644 src/startgg-gql/components.tsx delete mode 100644 src/state/entrants.slice.ts delete mode 100644 src/state/matches.slice.ts diff --git a/src/controls/index.tsx b/src/controls/index.tsx index d09c44b8d..f6e23f16e 100644 --- a/src/controls/index.tsx +++ b/src/controls/index.tsx @@ -23,7 +23,8 @@ import { useAppDispatch } from "../state/store"; import { useAtomValue, useSetAtom } from "jotai"; import { showEligibleCharts } from "../config-state"; import { gameDataLoadingStatus } from "../state/game-data.atoms"; -import { MatchPicker, PickedMatch, StartggApiKeyGated } from "../matches"; +import { MatchPicker, PickedMatch } from "../matches"; +import { StartggApiKeyGated } from "../startgg-gql/components"; const ControlsDrawer = lazy(() => import("./controls-drawer")); diff --git a/src/controls/player-names.tsx b/src/controls/player-names.tsx index 9f0bc22c4..96e68d8af 100644 --- a/src/controls/player-names.tsx +++ b/src/controls/player-names.tsx @@ -1,136 +1,78 @@ -import { - Checkbox, - Classes, - FormGroup, - Label, - Text, - NumericInput, -} from "@blueprintjs/core"; -import React, { useCallback } from "react"; -import { useConfigState, useUpdateConfig } from "../state/hooks"; -import { useIntl } from "../hooks/useIntl"; -import { useAtom } from "jotai"; -import { showPlayerAndRoundLabels } from "../config-state"; -import { useAppDispatch, useAppState } from "../state/store"; -import { entrantsSlice, Entrant } from "../state/entrants.slice"; -import { - getEventEntrants, - startggEventSlug, - startggKeyAtom, -} from "../startgg-gql"; +import { Section, SectionCard } from "@blueprintjs/core"; +// import { useConfigState, useUpdateConfig } from "../state/hooks"; +// import { useIntl } from "../hooks/useIntl"; +import { useAtomValue } from "jotai"; +// import { showPlayerAndRoundLabels } from "../config-state"; +// import { useAppState } from "../state/store"; +import { startggEventSlug, startggKeyAtom } from "../startgg-gql"; +import { StartggCredsManager } from "../startgg-gql/components"; export function PlayerNamesControls() { + const apiKey = useAtomValue(startggKeyAtom); + const eventSlug = useAtomValue(startggEventSlug); return ( <> - - - +
+ + + +
); } -function StartggEntrantManager() { - const { t } = useIntl(); - const entrants = useAppState(entrantsSlice.selectors.selectAll); - if (!entrants.length) { - return ; - } - return ( - - {entrants.map((e) => ( - - ))} - - ); -} - -function StartggEntrantImport() { - const [apiKey, setApiKey] = useAtom(startggKeyAtom); - const [eventSlug, setEventSlug] = useAtom(startggEventSlug); - const dispatch = useAppDispatch(); - const importEntrants = useCallback( - async (e: React.FormEvent) => { - e.preventDefault(); - if (!apiKey || !eventSlug) { - return; - } - const entrants = await getEventEntrants(eventSlug); - dispatch(entrantsSlice.actions.upsertMany(entrants)); - }, - [dispatch, apiKey, eventSlug], - ); - return ( -
- No players added yet, import from start.gg - - - -
- ); -} - export function inferShortname(name: string | null | undefined) { if (!name) return; const namePieces = name.split(" | "); return namePieces.length >= 1 ? namePieces[namePieces.length - 1] : undefined; } -function EntrantNameForm(props: { entrant: Entrant }) { - return ( - - ); -} +// function EntrantNameForm(props: { entrant: Entrant }) { +// return ( +// +// ); +// } -function ShowLabelsToggle() { - const [enabled, updateShowLabels] = useAtom(showPlayerAndRoundLabels); - const { t } = useIntl(); +// function ShowLabelsToggle() { +// const [enabled, updateShowLabels] = useAtom(showPlayerAndRoundLabels); +// const { t } = useIntl(); - return ( - updateShowLabels(e.currentTarget.checked)} - label={t("controls.playerLabels")} - /> - ); -} +// return ( +// updateShowLabels(e.currentTarget.checked)} +// label={t("controls.playerLabels")} +// /> +// ); +// } -function PlayersPerDraw() { - const update = useUpdateConfig(); - const ppd = useConfigState((s) => s.defaultPlayersPerDraw); - const { t } = useIntl(); +// function PlayersPerDraw() { +// const update = useUpdateConfig(); +// const ppd = useConfigState((s) => s.defaultPlayersPerDraw); +// const { t } = useIntl(); - return ( - - update({ defaultPlayersPerDraw: next })} - /> - - ); -} +// return ( +// +// update({ defaultPlayersPerDraw: next })} +// /> +// +// ); +// } diff --git a/src/main-view.tsx b/src/main-view.tsx index 0183dc83f..e78c1fa36 100644 --- a/src/main-view.tsx +++ b/src/main-view.tsx @@ -3,7 +3,6 @@ import { PlayerNamesControls } from "./controls/player-names"; import { DrawingList } from "./drawing-list"; import { atom, useAtom } from "jotai"; import styles from "./main-view.css"; -import { MatchListAndSettings } from "./matches"; export type MainTabId = "drawings" | "players" | "sets"; export const mainTabAtom = atom("drawings"); @@ -22,10 +21,7 @@ export function MainView() { Drawings }> - Participants - - }> - Sets + Start.gg Sync ); diff --git a/src/matches.tsx b/src/matches.tsx index f0957d4ad..e5c9eeff2 100644 --- a/src/matches.tsx +++ b/src/matches.tsx @@ -1,32 +1,10 @@ -import { Button, Card, Classes, Label, Spinner, Text } from "@blueprintjs/core"; -import { useAtom } from "jotai"; -import { ReactNode, useCallback, useRef } from "react"; -import { - startggKeyAtom, - startggEventSlug, - getEventSets, - useStartggMatches, -} from "./startgg-gql"; -import { AppState, useAppDispatch, useAppState } from "./state/store"; -import { setsSlice, Slot, TournamentSet } from "./state/matches.slice"; -import { entrantsSlice } from "./state/entrants.slice"; +import { Button, Card, Classes, Spinner, Text } from "@blueprintjs/core"; +import { useStartggMatches } from "./startgg-gql"; +import { useAppState } from "./state/store"; import { inferShortname } from "./controls/player-names"; import { Refresh } from "@blueprintjs/icons"; import { drawingsSlice } from "./state/drawings.slice"; -export function MatchListAndSettings() { - const sets = useAppState(setsSlice.selectors.selectAll); - return sets.length ? ( - <> - {sets.map((set) => ( - - ))} - - ) : ( - - ); -} - export interface PickedMatch { title: string; players: Array<{ id: string; name: string }>; @@ -38,17 +16,26 @@ export function MatchPicker(props: { onPickMatch?(match: PickedMatch): void }) { const existingMatches = useAppState( drawingsSlice.selectors.associatedMatchIds, ); - const matches = resp.data?.event?.sets?.nodes; + const event = resp.data?.event; + const matches = event?.sets?.nodes; const reloadButton = (
); } - -function TournamentSetPreview(props: { set: TournamentSet }) { - const [slot1, slot2] = props.set.slots; - const getPlayerForSlot = (slot: Slot) => (s: AppState) => { - if (slot.type === "player") - return entrantsSlice.selectors.selectById(s, slot.playerId); - const prereqSet = setsSlice.selectors.selectById(s, slot.setId); - if (prereqSet.winningPlayerId) { - return entrantsSlice.selectors.selectById(s, prereqSet.winningPlayerId); - } - return null; - }; - const player1 = useAppState(getPlayerForSlot(slot1)); - const player2 = useAppState(getPlayerForSlot(slot2)); - return ( - - {props.set.roundText} -{" "} - {player1?.startggTag || TBD} vs{" "} - {player2?.startggTag || TBD} - - ); -} - -export function StartggApiKeyGated(props: { children: ReactNode }) { - const [apiKey, setApiKey] = useAtom(startggKeyAtom); - const [eventSlug, setEventSlug] = useAtom(startggEventSlug); - const apikeyRef = useRef(null); - const slugRef = useRef(null); - const saveKey = useCallback( - async (e: React.FormEvent) => { - e.preventDefault(); - if (!apikeyRef.current || !slugRef.current) return; - setApiKey(apikeyRef.current.value); - setEventSlug(slugRef.current.value); - }, - [setApiKey, setEventSlug], - ); - if (apiKey && eventSlug) { - return props.children; - } - return ( -
- Start.gg info needed - - - -
- ); -} - -function StartggSetImport() { - const [apiKey, setApiKey] = useAtom(startggKeyAtom); - const [eventSlug, setEventSlug] = useAtom(startggEventSlug); - const dispatch = useAppDispatch(); - const importEntrants = useCallback( - async (e: React.FormEvent) => { - e.preventDefault(); - if (!apiKey || !eventSlug) { - return; - } - const sets = await getEventSets(eventSlug); - dispatch(setsSlice.actions.upsertMany(sets)); - }, - [dispatch, apiKey, eventSlug], - ); - return ( -
- No sets added yet, import from start.gg - - - -
- ); -} diff --git a/src/models/Drawing.ts b/src/models/Drawing.ts index 7ba477f0c..bf138799d 100644 --- a/src/models/Drawing.ts +++ b/src/models/Drawing.ts @@ -80,7 +80,7 @@ export interface Drawing { meta: SimpleMeta | StartggMeta; /** index of items of the players array, in the order they should be displayed */ playerDisplayOrder: number[]; - /** map of song ID to player index or id */ + /** map of song ID to player index */ winners: Record; charts: Array; bans: Record; diff --git a/src/startgg-gql/components.tsx b/src/startgg-gql/components.tsx new file mode 100644 index 000000000..f6136bbc1 --- /dev/null +++ b/src/startgg-gql/components.tsx @@ -0,0 +1,65 @@ +import { Classes, Label, Text } from "@blueprintjs/core"; +import { useAtomValue, useAtom } from "jotai"; +import { ReactNode, useRef, useCallback } from "react"; +import { startggKeyAtom, startggEventSlug } from "."; + +export function StartggApiKeyGated(props: { children: ReactNode }) { + const apiKey = useAtomValue(startggKeyAtom); + const eventSlug = useAtomValue(startggEventSlug); + if (apiKey && eventSlug) { + return props.children; + } else { + return ; + } +} + +export function StartggCredsManager() { + const [apiKey, setApiKey] = useAtom(startggKeyAtom); + const [eventSlug, setEventSlug] = useAtom(startggEventSlug); + const apikeyRef = useRef(null); + const slugRef = useRef(null); + const saveKey = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + if (!apikeyRef.current || !slugRef.current) return; + setApiKey(apikeyRef.current.value); + setEventSlug(slugRef.current.value); + }, + [setApiKey, setEventSlug], + ); + return ( +
+ + Start.gg credentials are saved locally on this device and never synced + with other devices + + + + +
+ ); +} diff --git a/src/startgg-gql/generated/gql.ts b/src/startgg-gql/generated/gql.ts index 5d92f15a1..0991c0eb0 100644 --- a/src/startgg-gql/generated/gql.ts +++ b/src/startgg-gql/generated/gql.ts @@ -17,6 +17,7 @@ const documents = { "\n query SetName($sid: ID!) {\n set(id: $sid) {\n __typename\n id\n fullRoundText\n }\n }\n": types.SetNameDocument, "\n query EventEntrants($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n __typename\n id\n name\n entrants(query: { page: $pageNo, perPage: 100 }) {\n pageInfo {\n totalPages\n }\n nodes {\n __typename\n id\n name\n # paginatedSets {\n # nodes {\n # id\n # }\n # pageInfo {\n # totalPages\n # }\n # }\n }\n }\n }\n }\n": types.EventEntrantsDocument, "\n query EventSets($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n id\n sets(filters: { hideEmpty: true }, perPage: 100, page: $pageNo) {\n pageInfo {\n totalPages\n total\n }\n nodes {\n id\n fullRoundText\n identifier\n slots {\n prereqType\n prereqId\n prereqPlacement\n entrant {\n id\n name\n }\n }\n }\n }\n }\n }\n": types.EventSetsDocument, + "\n mutation ReportSet(\n $setId: ID!\n $winnerId: ID\n $gameData: [BracketSetGameDataInput]\n ) {\n reportBracketSet(setId: $setId, winnerId: $winnerId, gameData: $gameData) {\n id\n completedAt\n }\n }\n": types.ReportSetDocument, }; /** @@ -49,6 +50,10 @@ export function graphql(source: "\n query EventEntrants($eventSlug: String!, $p * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "\n query EventSets($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n id\n sets(filters: { hideEmpty: true }, perPage: 100, page: $pageNo) {\n pageInfo {\n totalPages\n total\n }\n nodes {\n id\n fullRoundText\n identifier\n slots {\n prereqType\n prereqId\n prereqPlacement\n entrant {\n id\n name\n }\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query EventSets($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n id\n sets(filters: { hideEmpty: true }, perPage: 100, page: $pageNo) {\n pageInfo {\n totalPages\n total\n }\n nodes {\n id\n fullRoundText\n identifier\n slots {\n prereqType\n prereqId\n prereqPlacement\n entrant {\n id\n name\n }\n }\n }\n }\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n mutation ReportSet(\n $setId: ID!\n $winnerId: ID\n $gameData: [BracketSetGameDataInput]\n ) {\n reportBracketSet(setId: $setId, winnerId: $winnerId, gameData: $gameData) {\n id\n completedAt\n }\n }\n"): (typeof documents)["\n mutation ReportSet(\n $setId: ID!\n $winnerId: ID\n $gameData: [BracketSetGameDataInput]\n ) {\n reportBracketSet(setId: $setId, winnerId: $winnerId, gameData: $gameData) {\n id\n completedAt\n }\n }\n"]; export function graphql(source: string) { return (documents as any)[source] ?? {}; diff --git a/src/startgg-gql/generated/graphql.ts b/src/startgg-gql/generated/graphql.ts index 278aecba5..5dc55663b 100644 --- a/src/startgg-gql/generated/graphql.ts +++ b/src/startgg-gql/generated/graphql.ts @@ -2432,8 +2432,18 @@ export type EventSetsQueryVariables = Exact<{ export type EventSetsQuery = { __typename?: 'Query', event?: { __typename?: 'Event', id?: string | null, sets?: { __typename?: 'SetConnection', pageInfo?: { __typename?: 'PageInfo', totalPages?: number | null, total?: number | null } | null, nodes?: Array<{ __typename?: 'Set', id?: string | null, fullRoundText?: string | null, identifier?: string | null, slots?: Array<{ __typename?: 'SetSlot', prereqType?: string | null, prereqId?: string | null, prereqPlacement?: number | null, entrant?: { __typename?: 'Entrant', id?: string | null, name?: string | null } | null } | null> | null } | null> | null } | null } | null }; +export type ReportSetMutationVariables = Exact<{ + setId: Scalars['ID']['input']; + winnerId?: InputMaybe; + gameData?: InputMaybe> | InputMaybe>; +}>; + + +export type ReportSetMutation = { __typename?: 'Mutation', reportBracketSet?: Array<{ __typename?: 'Set', id?: string | null, completedAt?: any | null } | null> | null }; + export const PlayerNameDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PlayerName"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entrant"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode; export const SetNameDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"SetName"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"set"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"fullRoundText"}}]}}]}}]} as unknown as DocumentNode; export const EventEntrantsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EventEntrants"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"eventSlug"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageNo"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"event"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"eventSlug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"entrants"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"query"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageNo"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"perPage"},"value":{"kind":"IntValue","value":"100"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalPages"}}]}},{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]} as unknown as DocumentNode; -export const EventSetsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EventSets"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"eventSlug"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageNo"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"event"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"eventSlug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"sets"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"hideEmpty"},"value":{"kind":"BooleanValue","value":true}}]}},{"kind":"Argument","name":{"kind":"Name","value":"perPage"},"value":{"kind":"IntValue","value":"100"}},{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageNo"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalPages"}},{"kind":"Field","name":{"kind":"Name","value":"total"}}]}},{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"fullRoundText"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"slots"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"prereqType"}},{"kind":"Field","name":{"kind":"Name","value":"prereqId"}},{"kind":"Field","name":{"kind":"Name","value":"prereqPlacement"}},{"kind":"Field","name":{"kind":"Name","value":"entrant"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file +export const EventSetsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EventSets"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"eventSlug"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageNo"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"event"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"eventSlug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"sets"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"hideEmpty"},"value":{"kind":"BooleanValue","value":true}}]}},{"kind":"Argument","name":{"kind":"Name","value":"perPage"},"value":{"kind":"IntValue","value":"100"}},{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageNo"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalPages"}},{"kind":"Field","name":{"kind":"Name","value":"total"}}]}},{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"fullRoundText"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"slots"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"prereqType"}},{"kind":"Field","name":{"kind":"Name","value":"prereqId"}},{"kind":"Field","name":{"kind":"Name","value":"prereqPlacement"}},{"kind":"Field","name":{"kind":"Name","value":"entrant"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; +export const ReportSetDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ReportSet"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"setId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"winnerId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"gameData"}},"type":{"kind":"ListType","type":{"kind":"NamedType","name":{"kind":"Name","value":"BracketSetGameDataInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"reportBracketSet"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"setId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"setId"}}},{"kind":"Argument","name":{"kind":"Name","value":"winnerId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"winnerId"}}},{"kind":"Argument","name":{"kind":"Name","value":"gameData"},"value":{"kind":"Variable","name":{"kind":"Name","value":"gameData"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"completedAt"}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file diff --git a/src/startgg-gql/index.ts b/src/startgg-gql/index.ts index 52c05be45..3cf924e16 100644 --- a/src/startgg-gql/index.ts +++ b/src/startgg-gql/index.ts @@ -1,10 +1,8 @@ -import { useQuery } from "urql"; -import { Entrant } from "../state/entrants.slice"; -import { TournamentSet } from "../state/matches.slice"; +import { useMutation, useQuery } from "urql"; import { - EventEntrantsDocument, EventSetsDocument, PlayerNameDocument, + ReportSetDocument, SetNameDocument, } from "./generated/graphql"; import { Client, fetchExchange, gql } from "@urql/core"; @@ -86,62 +84,33 @@ export function useStartggMatches() { }); } -const EventEntrantsDoc: typeof EventEntrantsDocument = gql` - query EventEntrants($eventSlug: String!, $pageNo: Int!) { - event(slug: $eventSlug) { - __typename - id - name - entrants(query: { page: $pageNo, perPage: 100 }) { - pageInfo { - totalPages - } - nodes { - __typename - id - name - # paginatedSets { - # nodes { - # id - # } - # pageInfo { - # totalPages - # } - # } - } - } - } - } -`; - -export async function getEventEntrants(slug: string) { - let pageNo = 0; - - const ret: Entrant[] = []; - - let totalPages = 0; - do { - const results = await urqlClient - .query(EventEntrantsDoc, { - eventSlug: slug, - pageNo, - }) - .toPromise(); - totalPages = results.data?.event?.entrants?.pageInfo?.totalPages || 0; - if (results.data?.event?.entrants?.nodes) { - for (const entrant of results.data.event.entrants.nodes) { - if (!entrant) continue; - ret.push({ - id: entrant.id!, - startggTag: entrant.name!, - }); - } - } - pageNo++; - } while (totalPages > pageNo + 1); - - return ret; -} +// const EventEntrantsDoc: typeof EventEntrantsDocument = gql` +// query EventEntrants($eventSlug: String!, $pageNo: Int!) { +// event(slug: $eventSlug) { +// __typename +// id +// name +// entrants(query: { page: $pageNo, perPage: 100 }) { +// pageInfo { +// totalPages +// } +// nodes { +// __typename +// id +// name +// # paginatedSets { +// # nodes { +// # id +// # } +// # pageInfo { +// # totalPages +// # } +// # } +// } +// } +// } +// } +// `; const EventSetsDoc: typeof EventSetsDocument = gql` query EventSets($eventSlug: String!, $pageNo: Int!) { @@ -171,36 +140,28 @@ const EventSetsDoc: typeof EventSetsDocument = gql` } `; -export async function getEventSets(slug: string) { - let pageNo = 0; - - const ret: TournamentSet[] = []; - - let totalPages = 0; - do { - const results = await urqlClient - .query(EventSetsDoc, { - eventSlug: slug, - pageNo, - }) - .toPromise(); - totalPages = results.data?.event?.sets?.pageInfo?.totalPages || 0; - if (results.data?.event?.sets?.nodes) { - for (const set of results.data.event.sets.nodes) { - if (!set) continue; - ret.push({ - id: set.id!, - roundText: set.fullRoundText!, - slots: set.slots!.map((slot) => - slot!.entrant?.id - ? { type: "player", playerId: slot!.entrant.id } - : { type: "setprereq", setId: slot!.prereqId! }, - ), - }); - } +const ReportSetMutation: typeof ReportSetDocument = gql` + mutation ReportSet( + $setId: ID! + $winnerId: ID + $gameData: [BracketSetGameDataInput] + ) { + reportBracketSet(setId: $setId, winnerId: $winnerId, gameData: $gameData) { + id + completedAt } - pageNo++; - } while (totalPages > pageNo + 1); + } +`; + +export type { + BracketSetGameDataInput, + ReportSetMutationVariables, +} from "./generated/graphql"; - return ret; +/** + * Passing a winnerId will mark the set as completed. + * Passing game data will overwrite any existing game data. + */ +export function useReportSetMutation() { + return useMutation(ReportSetMutation); } diff --git a/src/state/entrants.slice.ts b/src/state/entrants.slice.ts deleted file mode 100644 index e4fc69700..000000000 --- a/src/state/entrants.slice.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { createEntityAdapter, createSlice } from "@reduxjs/toolkit"; -export interface Entrant { - id: string; - ingameName?: string; - startggTag: string; -} - -const entrantsAdapter = createEntityAdapter(); -const selectors = entrantsAdapter.getSelectors(); - -export const entrantsSlice = createSlice({ - name: "entrants", - initialState: entrantsAdapter.getInitialState(), - reducers: { - removeMany: entrantsAdapter.removeMany, - upsertMany: entrantsAdapter.upsertMany, - upsertOne: entrantsAdapter.updateOne, - }, - selectors: { - selectAll: selectors.selectAll, - selectById: selectors.selectById, - selectFromIds(state, entrantIds: string[]) { - return entrantIds.map((id) => selectors.selectById(state, id)); - }, - selectIds: selectors.selectIds, - }, -}); diff --git a/src/state/matches.slice.ts b/src/state/matches.slice.ts deleted file mode 100644 index d372b5098..000000000 --- a/src/state/matches.slice.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { createEntityAdapter, createSlice } from "@reduxjs/toolkit"; - -interface SetPlayer { - type: "player"; - playerId: string; -} - -interface SetPrereq { - type: "setprereq"; - setId: string; -} - -export type Slot = SetPlayer | SetPrereq; - -export interface TournamentSet { - id: string; - roundText: string; - slots: Slot[]; - winningPlayerId?: string; -} - -const setsAdapter = createEntityAdapter(); -const selectors = setsAdapter.getSelectors(); - -export const setsSlice = createSlice({ - name: "sets", - initialState: setsAdapter.getInitialState(), - reducers: { - removeMany: setsAdapter.removeMany, - upsertMany: setsAdapter.upsertMany, - upsertOne: setsAdapter.updateOne, - }, - selectors: { - selectAll: selectors.selectAll, - selectById: selectors.selectById, - selectIds: selectors.selectIds, - }, -}); diff --git a/src/state/root-reducer.ts b/src/state/root-reducer.ts index 65e9f37fd..d0ffe07a6 100644 --- a/src/state/root-reducer.ts +++ b/src/state/root-reducer.ts @@ -4,16 +4,12 @@ import { drawingsSlice } from "./drawings.slice"; import { gameDataSlice } from "./game-data.slice"; import { receivePartyState } from "./central"; import { eventSlice } from "./event.slice"; -import { entrantsSlice } from "./entrants.slice"; -import { setsSlice } from "./matches.slice"; const combinedReducer = combineSlices( drawingsSlice, configSlice, gameDataSlice, eventSlice, - entrantsSlice, - setsSlice, ); export const reducer: typeof combinedReducer = (state, action) => { diff --git a/src/tournament-mode/drawing-actions.tsx b/src/tournament-mode/drawing-actions.tsx index 5971be41d..46d7bf67e 100644 --- a/src/tournament-mode/drawing-actions.tsx +++ b/src/tournament-mode/drawing-actions.tsx @@ -1,19 +1,116 @@ import { Button, Menu, MenuItem, Popover, Tooltip } from "@blueprintjs/core"; -import { Camera, Refresh, Error, CubeAdd, Exchange } from "@blueprintjs/icons"; +import { + Camera, + Refresh, + Error, + CubeAdd, + Exchange, + FloppyDisk, +} from "@blueprintjs/icons"; import { useDrawing } from "../drawing-context"; import styles from "./drawing-actions.css"; import { domToPng } from "modern-screenshot"; import { shareImage } from "../utils/share"; import { useErrorBoundary } from "react-error-boundary"; -import { useAppDispatch, useAppState } from "../state/store"; +import { AppThunk, useAppDispatch, useAppState } from "../state/store"; import { useAtomValue } from "jotai"; import { showPlayerAndRoundLabels } from "../config-state"; import { drawingsSlice } from "../state/drawings.slice"; import { eventSlice } from "../state/event.slice"; import { createRedrawAll } from "../state/thunks"; +import { + useReportSetMutation, + ReportSetMutationVariables as MutationVariables, + BracketSetGameDataInput as GDI, +} from "../startgg-gql"; +import { CountingSet } from "../utils/counting-set"; + +/** thunk that dispatches nothing, but calculates the result to be sent to startgg */ +function getMatchResult( + drawingId: string, +): AppThunk { + return (_, getState): MutationVariables | undefined => { + const s = getState(); + const gameData: Array = []; + const drawing = s.drawings.entities[drawingId]; + if (drawing.meta.type !== "startgg") { + return; + } + const winsPerPlayer = new CountingSet(); + for (const [songId, pIdx] of Object.entries(drawing.winners)) { + if (pIdx === null) { + continue; + } + try { + const entrant = drawing.meta.entrants[pIdx]; + gameData.push({ + gameNum: gameData.length + 1, + winnerId: entrant.id, + }); + winsPerPlayer.add(entrant.id); + } catch (e) { + console.warn(`failed to add game data for song ${songId}`, e); + } + } + let winnerId: string | undefined; + const orderedByWins = Array.from(winsPerPlayer.valuesWithCount()).sort( + (a, b) => b[1] - a[1], + ); + if (orderedByWins[0][1] > orderedByWins[1][1]) { + // confirmed no tie for first place + winnerId = orderedByWins[0][0]; + } + const ret: MutationVariables = { + setId: drawing.meta.id, + winnerId, + }; + if (gameData.length) { + ret.gameData = gameData; + } + return ret; + }; +} const DEFAULT_FILENAME = "card-draw.png"; +function SaveToStartggButton() { + const dispatch = useAppDispatch(); + const drawingId = useDrawing((s) => s.id); + const drawingMeta = useDrawing((s) => s.meta); + const [mutationData, reportSet] = useReportSetMutation(); + if (drawingMeta.type !== "startgg") { + return null; + } + + let tooltipContent = "Save Winner to Start.gg"; + if (mutationData.error) { + tooltipContent = `Error saving: ${mutationData.error.message}`; + } + if (mutationData.fetching) { + tooltipContent = "Saving..."; + } + + return ( + + +

- { - handleDraw(match); - setMatchPickerOpen(false); - }} - /> + From 6699a3fbaa140dec72e5cb6627bcbac41720c06c Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Sun, 18 Aug 2024 22:38:46 -0700 Subject: [PATCH 53/91] add quick picker for startgg events --- src/controls/player-names.tsx | 1 + src/startgg-gql/components.tsx | 87 +++++++++++++++++++++++----- src/startgg-gql/generated/gql.ts | 8 +-- src/startgg-gql/generated/graphql.ts | 20 +++---- src/startgg-gql/index.ts | 42 ++++++++++++++ 5 files changed, 130 insertions(+), 28 deletions(-) diff --git a/src/controls/player-names.tsx b/src/controls/player-names.tsx index 96e68d8af..a3edacdf0 100644 --- a/src/controls/player-names.tsx +++ b/src/controls/player-names.tsx @@ -16,6 +16,7 @@ export function PlayerNamesControls() { title="Start.gg Credentials" collapsible collapseProps={{ defaultIsOpen: !apiKey || !eventSlug }} + style={{ maxWidth: "50em" }} > diff --git a/src/startgg-gql/components.tsx b/src/startgg-gql/components.tsx index f6136bbc1..7e95530ff 100644 --- a/src/startgg-gql/components.tsx +++ b/src/startgg-gql/components.tsx @@ -1,7 +1,7 @@ -import { Classes, Label, Text } from "@blueprintjs/core"; -import { useAtomValue, useAtom } from "jotai"; -import { ReactNode, useRef, useCallback } from "react"; -import { startggKeyAtom, startggEventSlug } from "."; +import { Button, Classes, InputGroup, Label, Text } from "@blueprintjs/core"; +import { useAtomValue, useAtom, useSetAtom } from "jotai"; +import React, { ReactNode, useRef, useCallback } from "react"; +import { startggKeyAtom, startggEventSlug, useCurrentUserEvents } from "."; export function StartggApiKeyGated(props: { children: ReactNode }) { const apiKey = useAtomValue(startggKeyAtom); @@ -21,8 +21,9 @@ export function StartggCredsManager() { const saveKey = useCallback( async (e: React.FormEvent) => { e.preventDefault(); - if (!apikeyRef.current || !slugRef.current) return; + if (!apikeyRef.current) return; setApiKey(apikeyRef.current.value); + if (!slugRef.current) return; setEventSlug(slugRef.current.value); }, [setApiKey, setEventSlug], @@ -39,11 +40,10 @@ export function StartggCredsManager() { create a personal token here ){" "} - Save} /> - + {!!apiKey && ( + { + if (slugRef.current) { + slugRef.current.value = slug; + } + }} + /> + )} ); } + +function EventPicker(props: { onSelected(slug: string): void }) { + const [result] = useCurrentUserEvents(); + const setEventSlug = useSetAtom(startggEventSlug); + const tournaments = result.data?.currentUser?.tournaments?.nodes; + + function handleSelect(e: React.MouseEvent) { + e.preventDefault(); + const slug = e.currentTarget.dataset.slug; + if (slug) { + setEventSlug(slug); + props.onSelected(slug); + } + } + + if (!tournaments) { + return null; + } + return ( + <> +

Try the easy way and pick from your tournaments:

+
    + {tournaments.map((t) => { + if (!t) return null; + const events = t.events; + return ( +
  • + {t.name} + {events?.length ? ( +
      + {events.map((evt) => { + if (!evt) return null; + return ( +
    • + + {evt.name} + +
    • + ); + })} +
    + ) : ( + " (no events)" + )} +
  • + ); + })} +
+ + ); +} diff --git a/src/startgg-gql/generated/gql.ts b/src/startgg-gql/generated/gql.ts index 0991c0eb0..260bffed2 100644 --- a/src/startgg-gql/generated/gql.ts +++ b/src/startgg-gql/generated/gql.ts @@ -15,9 +15,9 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/ const documents = { "\n query PlayerName($pid: ID!) {\n entrant(id: $pid) {\n __typename\n id\n name\n }\n }\n": types.PlayerNameDocument, "\n query SetName($sid: ID!) {\n set(id: $sid) {\n __typename\n id\n fullRoundText\n }\n }\n": types.SetNameDocument, - "\n query EventEntrants($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n __typename\n id\n name\n entrants(query: { page: $pageNo, perPage: 100 }) {\n pageInfo {\n totalPages\n }\n nodes {\n __typename\n id\n name\n # paginatedSets {\n # nodes {\n # id\n # }\n # pageInfo {\n # totalPages\n # }\n # }\n }\n }\n }\n }\n": types.EventEntrantsDocument, "\n query EventSets($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n id\n sets(filters: { hideEmpty: true }, perPage: 100, page: $pageNo) {\n pageInfo {\n totalPages\n total\n }\n nodes {\n id\n fullRoundText\n identifier\n slots {\n prereqType\n prereqId\n prereqPlacement\n entrant {\n id\n name\n }\n }\n }\n }\n }\n }\n": types.EventSetsDocument, "\n mutation ReportSet(\n $setId: ID!\n $winnerId: ID\n $gameData: [BracketSetGameDataInput]\n ) {\n reportBracketSet(setId: $setId, winnerId: $winnerId, gameData: $gameData) {\n id\n completedAt\n }\n }\n": types.ReportSetDocument, + "\n query EventList($page: Int!, $perPage: Int!) {\n currentUser {\n tournaments(\n query: {\n page: $page\n perPage: $perPage\n filter: { tournamentView: \"admin\", upcoming: true }\n }\n ) {\n nodes {\n id\n name\n slug\n events {\n id\n name\n slug\n }\n }\n pageInfo {\n total\n totalPages\n page\n perPage\n }\n }\n }\n }\n": types.EventListDocument, }; /** @@ -45,15 +45,15 @@ export function graphql(source: "\n query SetName($sid: ID!) {\n set(id: $si /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query EventEntrants($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n __typename\n id\n name\n entrants(query: { page: $pageNo, perPage: 100 }) {\n pageInfo {\n totalPages\n }\n nodes {\n __typename\n id\n name\n # paginatedSets {\n # nodes {\n # id\n # }\n # pageInfo {\n # totalPages\n # }\n # }\n }\n }\n }\n }\n"): (typeof documents)["\n query EventEntrants($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n __typename\n id\n name\n entrants(query: { page: $pageNo, perPage: 100 }) {\n pageInfo {\n totalPages\n }\n nodes {\n __typename\n id\n name\n # paginatedSets {\n # nodes {\n # id\n # }\n # pageInfo {\n # totalPages\n # }\n # }\n }\n }\n }\n }\n"]; +export function graphql(source: "\n query EventSets($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n id\n sets(filters: { hideEmpty: true }, perPage: 100, page: $pageNo) {\n pageInfo {\n totalPages\n total\n }\n nodes {\n id\n fullRoundText\n identifier\n slots {\n prereqType\n prereqId\n prereqPlacement\n entrant {\n id\n name\n }\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query EventSets($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n id\n sets(filters: { hideEmpty: true }, perPage: 100, page: $pageNo) {\n pageInfo {\n totalPages\n total\n }\n nodes {\n id\n fullRoundText\n identifier\n slots {\n prereqType\n prereqId\n prereqPlacement\n entrant {\n id\n name\n }\n }\n }\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query EventSets($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n id\n sets(filters: { hideEmpty: true }, perPage: 100, page: $pageNo) {\n pageInfo {\n totalPages\n total\n }\n nodes {\n id\n fullRoundText\n identifier\n slots {\n prereqType\n prereqId\n prereqPlacement\n entrant {\n id\n name\n }\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query EventSets($eventSlug: String!, $pageNo: Int!) {\n event(slug: $eventSlug) {\n id\n sets(filters: { hideEmpty: true }, perPage: 100, page: $pageNo) {\n pageInfo {\n totalPages\n total\n }\n nodes {\n id\n fullRoundText\n identifier\n slots {\n prereqType\n prereqId\n prereqPlacement\n entrant {\n id\n name\n }\n }\n }\n }\n }\n }\n"]; +export function graphql(source: "\n mutation ReportSet(\n $setId: ID!\n $winnerId: ID\n $gameData: [BracketSetGameDataInput]\n ) {\n reportBracketSet(setId: $setId, winnerId: $winnerId, gameData: $gameData) {\n id\n completedAt\n }\n }\n"): (typeof documents)["\n mutation ReportSet(\n $setId: ID!\n $winnerId: ID\n $gameData: [BracketSetGameDataInput]\n ) {\n reportBracketSet(setId: $setId, winnerId: $winnerId, gameData: $gameData) {\n id\n completedAt\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation ReportSet(\n $setId: ID!\n $winnerId: ID\n $gameData: [BracketSetGameDataInput]\n ) {\n reportBracketSet(setId: $setId, winnerId: $winnerId, gameData: $gameData) {\n id\n completedAt\n }\n }\n"): (typeof documents)["\n mutation ReportSet(\n $setId: ID!\n $winnerId: ID\n $gameData: [BracketSetGameDataInput]\n ) {\n reportBracketSet(setId: $setId, winnerId: $winnerId, gameData: $gameData) {\n id\n completedAt\n }\n }\n"]; +export function graphql(source: "\n query EventList($page: Int!, $perPage: Int!) {\n currentUser {\n tournaments(\n query: {\n page: $page\n perPage: $perPage\n filter: { tournamentView: \"admin\", upcoming: true }\n }\n ) {\n nodes {\n id\n name\n slug\n events {\n id\n name\n slug\n }\n }\n pageInfo {\n total\n totalPages\n page\n perPage\n }\n }\n }\n }\n"): (typeof documents)["\n query EventList($page: Int!, $perPage: Int!) {\n currentUser {\n tournaments(\n query: {\n page: $page\n perPage: $perPage\n filter: { tournamentView: \"admin\", upcoming: true }\n }\n ) {\n nodes {\n id\n name\n slug\n events {\n id\n name\n slug\n }\n }\n pageInfo {\n total\n totalPages\n page\n perPage\n }\n }\n }\n }\n"]; export function graphql(source: string) { return (documents as any)[source] ?? {}; diff --git a/src/startgg-gql/generated/graphql.ts b/src/startgg-gql/generated/graphql.ts index 5dc55663b..2c5b76faa 100644 --- a/src/startgg-gql/generated/graphql.ts +++ b/src/startgg-gql/generated/graphql.ts @@ -2416,14 +2416,6 @@ export type SetNameQueryVariables = Exact<{ export type SetNameQuery = { __typename?: 'Query', set?: { __typename: 'Set', id?: string | null, fullRoundText?: string | null } | null }; -export type EventEntrantsQueryVariables = Exact<{ - eventSlug: Scalars['String']['input']; - pageNo: Scalars['Int']['input']; -}>; - - -export type EventEntrantsQuery = { __typename?: 'Query', event?: { __typename: 'Event', id?: string | null, name?: string | null, entrants?: { __typename?: 'EntrantConnection', pageInfo?: { __typename?: 'PageInfo', totalPages?: number | null } | null, nodes?: Array<{ __typename: 'Entrant', id?: string | null, name?: string | null } | null> | null } | null } | null }; - export type EventSetsQueryVariables = Exact<{ eventSlug: Scalars['String']['input']; pageNo: Scalars['Int']['input']; @@ -2441,9 +2433,17 @@ export type ReportSetMutationVariables = Exact<{ export type ReportSetMutation = { __typename?: 'Mutation', reportBracketSet?: Array<{ __typename?: 'Set', id?: string | null, completedAt?: any | null } | null> | null }; +export type EventListQueryVariables = Exact<{ + page: Scalars['Int']['input']; + perPage: Scalars['Int']['input']; +}>; + + +export type EventListQuery = { __typename?: 'Query', currentUser?: { __typename?: 'User', tournaments?: { __typename?: 'TournamentConnection', nodes?: Array<{ __typename?: 'Tournament', id?: string | null, name?: string | null, slug?: string | null, events?: Array<{ __typename?: 'Event', id?: string | null, name?: string | null, slug?: string | null } | null> | null } | null> | null, pageInfo?: { __typename?: 'PageInfo', total?: number | null, totalPages?: number | null, page?: number | null, perPage?: number | null } | null } | null } | null }; + export const PlayerNameDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PlayerName"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entrant"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode; export const SetNameDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"SetName"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"set"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"fullRoundText"}}]}}]}}]} as unknown as DocumentNode; -export const EventEntrantsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EventEntrants"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"eventSlug"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageNo"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"event"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"eventSlug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"entrants"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"query"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageNo"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"perPage"},"value":{"kind":"IntValue","value":"100"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalPages"}}]}},{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const EventSetsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EventSets"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"eventSlug"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageNo"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"event"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"eventSlug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"sets"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"hideEmpty"},"value":{"kind":"BooleanValue","value":true}}]}},{"kind":"Argument","name":{"kind":"Name","value":"perPage"},"value":{"kind":"IntValue","value":"100"}},{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageNo"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalPages"}},{"kind":"Field","name":{"kind":"Name","value":"total"}}]}},{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"fullRoundText"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"slots"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"prereqType"}},{"kind":"Field","name":{"kind":"Name","value":"prereqId"}},{"kind":"Field","name":{"kind":"Name","value":"prereqPlacement"}},{"kind":"Field","name":{"kind":"Name","value":"entrant"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; -export const ReportSetDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ReportSet"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"setId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"winnerId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"gameData"}},"type":{"kind":"ListType","type":{"kind":"NamedType","name":{"kind":"Name","value":"BracketSetGameDataInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"reportBracketSet"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"setId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"setId"}}},{"kind":"Argument","name":{"kind":"Name","value":"winnerId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"winnerId"}}},{"kind":"Argument","name":{"kind":"Name","value":"gameData"},"value":{"kind":"Variable","name":{"kind":"Name","value":"gameData"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"completedAt"}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file +export const ReportSetDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ReportSet"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"setId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"winnerId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"gameData"}},"type":{"kind":"ListType","type":{"kind":"NamedType","name":{"kind":"Name","value":"BracketSetGameDataInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"reportBracketSet"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"setId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"setId"}}},{"kind":"Argument","name":{"kind":"Name","value":"winnerId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"winnerId"}}},{"kind":"Argument","name":{"kind":"Name","value":"gameData"},"value":{"kind":"Variable","name":{"kind":"Name","value":"gameData"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"completedAt"}}]}}]}}]} as unknown as DocumentNode; +export const EventListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EventList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"perPage"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"currentUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"tournaments"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"query"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"perPage"},"value":{"kind":"Variable","name":{"kind":"Name","value":"perPage"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"filter"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"tournamentView"},"value":{"kind":"StringValue","value":"admin","block":false}},{"kind":"ObjectField","name":{"kind":"Name","value":"upcoming"},"value":{"kind":"BooleanValue","value":true}}]}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"events"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"totalPages"}},{"kind":"Field","name":{"kind":"Name","value":"page"}},{"kind":"Field","name":{"kind":"Name","value":"perPage"}}]}}]}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file diff --git a/src/startgg-gql/index.ts b/src/startgg-gql/index.ts index 3cf924e16..d3c0c4963 100644 --- a/src/startgg-gql/index.ts +++ b/src/startgg-gql/index.ts @@ -4,6 +4,7 @@ import { PlayerNameDocument, ReportSetDocument, SetNameDocument, + EventListDocument, } from "./generated/graphql"; import { Client, fetchExchange, gql } from "@urql/core"; import { cacheExchange } from "@urql/exchange-graphcache"; @@ -165,3 +166,44 @@ export type { export function useReportSetMutation() { return useMutation(ReportSetMutation); } + +const EventListQuery: typeof EventListDocument = gql` + query EventList($page: Int!, $perPage: Int!) { + currentUser { + tournaments( + query: { + page: $page + perPage: $perPage + filter: { tournamentView: "admin" } + } + ) { + nodes { + id + name + slug + events { + id + name + slug + } + } + pageInfo { + total + totalPages + page + perPage + } + } + } + } +`; + +export function useCurrentUserEvents() { + return useQuery({ + query: EventListQuery, + variables: { + page: 1, + perPage: 25, + }, + }); +} From c41193f1881121efd12f4b3139e8dab34edcfcd8 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Wed, 21 Aug 2024 20:51:25 -0700 Subject: [PATCH 54/91] another dedupe --- yarn.lock | 95 +++++-------------------------------------------------- 1 file changed, 8 insertions(+), 87 deletions(-) diff --git a/yarn.lock b/yarn.lock index 03c261368..0eeba3043 100644 --- a/yarn.lock +++ b/yarn.lock @@ -167,7 +167,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-create-class-features-plugin@npm:^7.18.6": +"@babel/helper-create-class-features-plugin@npm:^7.18.6, @babel/helper-create-class-features-plugin@npm:^7.24.7": version: 7.24.8 resolution: "@babel/helper-create-class-features-plugin@npm:7.24.8" dependencies: @@ -186,25 +186,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-create-class-features-plugin@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-create-class-features-plugin@npm:7.24.7" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-environment-visitor": "npm:^7.24.7" - "@babel/helper-function-name": "npm:^7.24.7" - "@babel/helper-member-expression-to-functions": "npm:^7.24.7" - "@babel/helper-optimise-call-expression": "npm:^7.24.7" - "@babel/helper-replace-supers": "npm:^7.24.7" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" - "@babel/helper-split-export-declaration": "npm:^7.24.7" - semver: "npm:^6.3.1" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10/8ecb1c2acc808e1e0c21dccc7ea6899de9a140cb1856946800176b4784de6fccd575661fbff7744bb895d01aa6956ce963446b8577c4c2334293ba5579d5cdb9 - languageName: node - linkType: hard - "@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-create-regexp-features-plugin@npm:7.24.7" @@ -1515,7 +1496,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0": +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7": version: 7.24.8 resolution: "@babel/runtime@npm:7.24.8" dependencies: @@ -1524,15 +1505,6 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7": - version: 7.24.4 - resolution: "@babel/runtime@npm:7.24.4" - dependencies: - regenerator-runtime: "npm:^0.14.0" - checksum: 10/8ec8ce2c145bc7e31dd39ab66df124f357f65c11489aefacb30f431bae913b9aaa66aa5efe5321ea2bf8878af3fcee338c87e7599519a952e3a6f83aa1b03308 - languageName: node - linkType: hard - "@babel/template@npm:^7.18.10, @babel/template@npm:^7.20.7, @babel/template@npm:^7.24.7": version: 7.24.7 resolution: "@babel/template@npm:7.24.7" @@ -3296,20 +3268,13 @@ __metadata: languageName: node linkType: hard -"@jridgewell/resolve-uri@npm:^3.0.3": +"@jridgewell/resolve-uri@npm:^3.0.3, @jridgewell/resolve-uri@npm:^3.1.0": version: 3.1.2 resolution: "@jridgewell/resolve-uri@npm:3.1.2" checksum: 10/97106439d750a409c22c8bff822d648f6a71f3aa9bc8e5129efdc36343cd3096ddc4eeb1c62d2fe48e9bdd4db37b05d4646a17114ecebd3bbcacfa2de51c3c1d languageName: node linkType: hard -"@jridgewell/resolve-uri@npm:^3.1.0": - version: 3.1.1 - resolution: "@jridgewell/resolve-uri@npm:3.1.1" - checksum: 10/64d59df8ae1a4e74315eb1b61e012f1c7bc8aac47a3a1e683f6fe7008eab07bc512a742b7aa7c0405685d1421206de58c9c2e6adbfe23832f8bd69408ffc183e - languageName: node - linkType: hard - "@jridgewell/set-array@npm:^1.2.1": version: 1.2.1 resolution: "@jridgewell/set-array@npm:1.2.1" @@ -4047,7 +4012,7 @@ __metadata: languageName: node linkType: hard -"@types/ws@npm:^8.0.0": +"@types/ws@npm:^8.0.0, @types/ws@npm:^8.5.10": version: 8.5.11 resolution: "@types/ws@npm:8.5.11" dependencies: @@ -4056,15 +4021,6 @@ __metadata: languageName: node linkType: hard -"@types/ws@npm:^8.5.10": - version: 8.5.10 - resolution: "@types/ws@npm:8.5.10" - dependencies: - "@types/node": "npm:*" - checksum: 10/9b414dc5e0b6c6f1ea4b1635b3568c58707357f68076df9e7cd33194747b7d1716d5189c0dbdd68c8d2521b148e88184cf881bac7429eb0e5c989b001539ed31 - languageName: node - linkType: hard - "@types/yargs-parser@npm:*": version: 21.0.3 resolution: "@types/yargs-parser@npm:21.0.3" @@ -4206,7 +4162,7 @@ __metadata: languageName: node linkType: hard -"@urql/core@npm:^5.0.0": +"@urql/core@npm:^5.0.0, @urql/core@npm:^5.0.4": version: 5.0.5 resolution: "@urql/core@npm:5.0.5" dependencies: @@ -4216,16 +4172,6 @@ __metadata: languageName: node linkType: hard -"@urql/core@npm:^5.0.4": - version: 5.0.4 - resolution: "@urql/core@npm:5.0.4" - dependencies: - "@0no-co/graphql.web": "npm:^1.0.5" - wonka: "npm:^6.3.2" - checksum: 10/fe3ee871bde8ee8931d1f791a1475f8bf97940eb93eaa690b11dbfcfa9409644ea87c204245f64e22ea1ddeb16a55e7532b765c18eedcdd9ae8032981889d06a - languageName: node - linkType: hard - "@urql/exchange-graphcache@npm:^7.1.2": version: 7.1.2 resolution: "@urql/exchange-graphcache@npm:7.1.2" @@ -4544,7 +4490,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.11.0, acorn@npm:^8.8.0": +"acorn@npm:^8.11.0, acorn@npm:^8.7.1, acorn@npm:^8.8.0, acorn@npm:^8.8.2, acorn@npm:^8.9.0": version: 8.12.0 resolution: "acorn@npm:8.12.0" bin: @@ -4553,15 +4499,6 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.7.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0": - version: 8.11.3 - resolution: "acorn@npm:8.11.3" - bin: - acorn: bin/acorn - checksum: 10/b688e7e3c64d9bfb17b596e1b35e4da9d50553713b3b3630cf5690f2b023a84eac90c56851e6912b483fe60e8b4ea28b254c07e92f17ef83d72d78745a8352dd - languageName: node - linkType: hard - "agent-base@npm:^7.0.2, agent-base@npm:^7.1.0": version: 7.1.0 resolution: "agent-base@npm:7.1.0" @@ -9014,7 +8951,7 @@ __metadata: languageName: node linkType: hard -"jiti@npm:^1.17.1, jiti@npm:^1.18.2": +"jiti@npm:^1.17.1, jiti@npm:^1.18.2, jiti@npm:^1.20.0": version: 1.21.6 resolution: "jiti@npm:1.21.6" bin: @@ -9023,15 +8960,6 @@ __metadata: languageName: node linkType: hard -"jiti@npm:^1.20.0": - version: 1.21.0 - resolution: "jiti@npm:1.21.0" - bin: - jiti: bin/jiti.js - checksum: 10/005a0239e50381b5c9919f59dbab86128367bd64872f3376dbbde54b6523f41bd134bf22909e2a509e38fd87e1c22125ca255b9b6b53e7df0fedd23f737334cc - languageName: node - linkType: hard - "jose@npm:^5.0.0": version: 5.6.3 resolution: "jose@npm:5.6.3" @@ -13041,14 +12969,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.0, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.6.2, tslib@npm:~2.6.2": - version: 2.6.2 - resolution: "tslib@npm:2.6.2" - checksum: 10/bd26c22d36736513980091a1e356378e8b662ded04204453d353a7f34a4c21ed0afc59b5f90719d4ba756e581a162ecbf93118dc9c6be5acf70aa309188166ca - languageName: node - linkType: hard - -"tslib@npm:^2.2.0, tslib@npm:^2.3.1, tslib@npm:^2.5.0, tslib@npm:^2.6.1, tslib@npm:~2.6.0": +"tslib@npm:^2.0.0, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.2.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.6.1, tslib@npm:^2.6.2, tslib@npm:~2.6.0, tslib@npm:~2.6.2": version: 2.6.3 resolution: "tslib@npm:2.6.3" checksum: 10/52109bb681f8133a2e58142f11a50e05476de4f075ca906d13b596ae5f7f12d30c482feb0bff167ae01cfc84c5803e575a307d47938999246f5a49d174fc558c From a79f4aace7bc16c03712c45db3fe8cb7994c9faa Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Mon, 2 Sep 2024 13:47:50 -0700 Subject: [PATCH 55/91] experimental static cards display endpoint --- src/app.tsx | 13 +++++++ src/drawn-set.tsx | 11 ++++++ src/obs-sources/static-cards.tsx | 60 ++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 src/obs-sources/static-cards.tsx diff --git a/src/app.tsx b/src/app.tsx index 0a37ca32c..665cda878 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -46,6 +46,19 @@ const router = createBrowserRouter([ ); }, }, + { + path: "static-cards", + lazy: async () => { + const { StaticCards } = await import("./obs-sources/static-cards"); + return { + element: ( + + + + ), + }; + }, + }, { path: "e/:roomName", element: , diff --git a/src/drawn-set.tsx b/src/drawn-set.tsx index 1bed3891e..b9c1cef6d 100644 --- a/src/drawn-set.tsx +++ b/src/drawn-set.tsx @@ -8,6 +8,7 @@ import { DrawingActions } from "./tournament-mode/drawing-actions"; import { ErrorFallback } from "./utils/error-fallback"; import { useAtomValue } from "jotai"; import { showPlayerAndRoundLabels } from "./config-state"; +import { EligibleChart } from "./models/Drawing"; const HUE_STEP = (255 / 8) * 3; let hue = Math.floor(Math.random() * 255); @@ -40,6 +41,16 @@ export function ChartsOnly({ drawingId }: Props) { ); } +export function RawChartList(props: { charts: Array }) { + return ( +
+ {props.charts.map((c, idx) => ( + + ))} +
+ ); +} + function ChartFromContext({ chartId }: { chartId: string }) { const chart = useDrawing((d) => d.charts.find((c) => c.id === chartId)); const veto = useDrawing((d) => d.bans[chartId]); diff --git a/src/obs-sources/static-cards.tsx b/src/obs-sources/static-cards.tsx new file mode 100644 index 000000000..e686b5ee0 --- /dev/null +++ b/src/obs-sources/static-cards.tsx @@ -0,0 +1,60 @@ +import { useSearchParams } from "react-router-dom"; +import { availableGameData } from "../utils"; +import { useEffect } from "react"; +import { useAtomValue } from "jotai"; +import { gameDataAtom } from "../state/game-data.atoms"; +import { gameDataSlice } from "../state/game-data.slice"; +import { useAppDispatch } from "../state/store"; +import { GameData } from "../models/SongData"; +import { getDrawnChart } from "../card-draw"; +import { RawChartList } from "../drawn-set"; + +export function StaticCards() { + const [searchParams] = useSearchParams(); + const game = searchParams.get("game"); + if (!game) { + return "must specify game param"; + } + if (!availableGameData.some((data) => data.name === game)) { + return `no available game known by stub ${game}. use one of ${availableGameData.map((data) => data.name).join(", ")}`; + } + const charts = searchParams.getAll("chart"); + if (!charts.length) { + return "must specify charts with the chart param"; + } + return ; +} + +function StaticCardsLoader(props: { game: string; charts: string[] }) { + const gameData = useAtomValue(gameDataAtom); + const dispatch = useAppDispatch(); + useEffect(() => { + dispatch( + gameDataSlice.actions.selectGameData({ + dataSetName: props.game, + dataType: "stock", + }), + ); + }, [dispatch, props.game]); + if (!gameData) { + return null; + } + return ; +} + +function StaticCardsPresentation(props: { + gameData: GameData; + charts: string[]; +}) { + const cardDatas = props.charts + .map((chartString) => { + const [songId, chartId] = chartString.split(","); + const song = props.gameData.songs.find((s) => s.saIndex === songId); + if (!song) return null; + const chart = song.charts[+chartId]; + if (!chart) return null; + return getDrawnChart(props.gameData, song, chart); + }) + .filter((chart) => chart !== null); + return ; +} From 43eb438b505f52554064dbf8d3584b1740af4b4c Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Tue, 3 Sep 2024 23:33:34 -0700 Subject: [PATCH 56/91] early bits of multiconfig (all broken) --- src/card-draw.ts | 1 + src/config-state.ts | 44 +--------- src/controls/controls-drawer.tsx | 10 ++- src/controls/degrs-tester.tsx | 4 +- src/drawing-context.tsx | 1 + src/models/Drawing.ts | 1 + src/state/config.slice.ts | 138 +++++++++++++++++++++++++------ src/state/game-data.atoms.ts | 7 -- src/state/hooks.ts | 34 -------- src/state/hooks.tsx | 61 ++++++++++++++ src/state/listener-middleware.ts | 6 +- src/state/store.ts | 5 +- src/state/thunks.ts | 15 ++-- 13 files changed, 203 insertions(+), 124 deletions(-) delete mode 100644 src/state/hooks.ts create mode 100644 src/state/hooks.tsx diff --git a/src/card-draw.ts b/src/card-draw.ts index 5a4efaefd..f1035fa6e 100644 --- a/src/card-draw.ts +++ b/src/card-draw.ts @@ -439,6 +439,7 @@ export function draw( return { id: `draw-${nanoid(10)}`, + configId: configData.id, bans: {}, protects: {}, pocketPicks: {}, diff --git a/src/config-state.ts b/src/config-state.ts index 3581f1c8a..10e1561f5 100644 --- a/src/config-state.ts +++ b/src/config-state.ts @@ -3,46 +3,4 @@ import { atom } from "jotai"; export const showEligibleCharts = atom(false); export const showPlayerAndRoundLabels = atom(true); -export interface ConfigState { - chartCount: number; - playerPicks: number; - upperBound: number; - lowerBound: number; - useWeights: boolean; - orderByAction: boolean; - hideVetos: boolean; - weights: Array; - probabilityBucketCount: number | null; - forceDistribution: boolean; - constrainPocketPicks: boolean; - style: string; - folders: Array; - difficulties: Array; - flags: Array; - cutoffDate: string; - defaultPlayersPerDraw: number; - sortByLevel: boolean; - useGranularLevels: boolean; -} - -export const initialState: ConfigState = { - chartCount: 5, - playerPicks: 0, - upperBound: 0, - lowerBound: 0, - useWeights: false, - hideVetos: false, - orderByAction: true, - weights: [], - probabilityBucketCount: null, - forceDistribution: true, - constrainPocketPicks: true, - style: "", - cutoffDate: "", - folders: [], - difficulties: [], - flags: [], - sortByLevel: false, - defaultPlayersPerDraw: 2, - useGranularLevels: false, -}; +export type { ConfigState } from "./state/config.slice"; diff --git a/src/controls/controls-drawer.tsx b/src/controls/controls-drawer.tsx index 03f2ab727..b9242d282 100644 --- a/src/controls/controls-drawer.tsx +++ b/src/controls/controls-drawer.tsx @@ -30,7 +30,11 @@ import { getAvailableLevels } from "../game-data-utils"; import { ShowChartsToggle } from "./show-charts-toggle"; import { Fraction } from "../utils/fraction"; import { detectedLanguage } from "../utils"; -import { useConfigState, useUpdateConfig } from "../state/hooks"; +import { + SelectedConfigContextProvider, + useConfigState, + useUpdateConfig, +} from "../state/hooks"; import { useAtomValue } from "jotai"; import { showEligibleCharts } from "../config-state"; import { useAppState } from "../state/store"; @@ -76,7 +80,9 @@ function getDiffsAndRangeForNewStyle( export default function ControlsDrawer() { return (
- + + +
); } diff --git a/src/controls/degrs-tester.tsx b/src/controls/degrs-tester.tsx index 82ce55f23..5d86e8536 100644 --- a/src/controls/degrs-tester.tsx +++ b/src/controls/degrs-tester.tsx @@ -29,11 +29,11 @@ export function isDegrs(thing: EligibleChart | PlayerPickPlaceholder) { } function* oneMillionDraws(gameData: GameData) { - const configState = configSlice.selectSlice(store.getState()); + const configState = configSlice.selectors.getCurrent(store.getState()); for (let idx = 0; idx < TEST_SIZE; idx++) { yield [ - draw(gameData, configState, { + draw(gameData, configState!, { meta: { players: [], title: "", type: "simple" }, }), idx, diff --git a/src/drawing-context.tsx b/src/drawing-context.tsx index dcfb10ea7..ee6a6f5bf 100644 --- a/src/drawing-context.tsx +++ b/src/drawing-context.tsx @@ -6,6 +6,7 @@ import { EqualityFn } from "react-redux"; const stubDrawing: Drawing = { id: "", + configId: "", meta: { type: "simple", players: [], diff --git a/src/models/Drawing.ts b/src/models/Drawing.ts index bf138799d..31032b52b 100644 --- a/src/models/Drawing.ts +++ b/src/models/Drawing.ts @@ -77,6 +77,7 @@ export function playerNameByIndex( export interface Drawing { id: string; + configId: string; meta: SimpleMeta | StartggMeta; /** index of items of the players array, in the order they should be displayed */ playerDisplayOrder: number[]; diff --git a/src/state/config.slice.ts b/src/state/config.slice.ts index a79c80c4d..d69a9ad32 100644 --- a/src/state/config.slice.ts +++ b/src/state/config.slice.ts @@ -1,36 +1,122 @@ -import { createSlice, PayloadAction } from "@reduxjs/toolkit"; -import { ConfigState, initialState } from "../config-state"; +import { + createSlice, + createEntityAdapter, + PayloadAction, +} from "@reduxjs/toolkit"; import { GameData } from "../models/SongData"; +import { nanoid } from "nanoid"; + +export interface ConfigState { + id: string; + name: string; + gameKey: string; + chartCount: number; + playerPicks: number; + upperBound: number; + lowerBound: number; + useWeights: boolean; + orderByAction: boolean; + hideVetos: boolean; + weights: Array; + probabilityBucketCount: number | null; + forceDistribution: boolean; + constrainPocketPicks: boolean; + style: string; + folders: Array; + difficulties: Array; + flags: Array; + cutoffDate: string; + defaultPlayersPerDraw: number; + sortByLevel: boolean; + useGranularLevels: boolean; +} + +export const defaultConfig: Omit = { + chartCount: 5, + playerPicks: 0, + upperBound: 0, + lowerBound: 0, + useWeights: false, + hideVetos: false, + orderByAction: true, + weights: [], + probabilityBucketCount: null, + forceDistribution: true, + constrainPocketPicks: true, + style: "", + cutoffDate: "", + folders: [], + difficulties: [], + flags: [], + sortByLevel: false, + defaultPlayersPerDraw: 2, + useGranularLevels: false, +}; + +const adapter = createEntityAdapter({}); export const configSlice = createSlice({ name: "config", - initialState, + initialState: { + ...adapter.getInitialState(), + current: null as string | null, + }, reducers: { - update(state, action: PayloadAction>) { - Object.assign(state, action.payload); + pickCurrent(state, action: PayloadAction) { + state.current = action.payload; }, - applyDefaults( - state, - action: PayloadAction< - GameData["defaults"] & { supportsGranular: boolean } - >, - ) { - const { flags, difficulties, folders, style } = action.payload; - const patch: Partial = { - lowerBound: action.payload.lowerLvlBound, - upperBound: action.payload.upperLvlBound, - flags, - difficulties, - style, - cutoffDate: "", - }; - if (folders) { - patch.folders = folders; - } - if (!action.payload.supportsGranular) { - patch.useGranularLevels = false; + addOne: adapter.addOne, + updateOne: adapter.updateOne, + removeOne: adapter.removeOne, + }, + selectors: { + ...adapter.getSelectors(), + getCurrent(state) { + if (state.current) { + return state.entities[state.current]; } - Object.assign(state, patch); + return null; }, }, }); + +function getOverridesFromGameData(gameData: GameData) { + const { + flags, + difficulties, + folders, + style, + lowerLvlBound: lowerBound, + upperLvlBound: upperBound, + } = gameData.defaults; + const gameSpecificOverrides: Partial = { + lowerBound, + upperBound, + flags, + difficulties, + style, + cutoffDate: "", + }; + if (folders) { + gameSpecificOverrides.folders = folders; + } + if (!gameData.meta.granularTierResolution) { + gameSpecificOverrides.useGranularLevels = false; + } + return gameSpecificOverrides; +} + +export function createFromGameData( + gameData: GameData, + name: string, + gameKey: string, +) { + const newConfig: ConfigState = { + id: nanoid(10), + name, + gameKey, + ...defaultConfig, + ...getOverridesFromGameData(gameData), + }; + return configSlice.actions.addOne(newConfig); +} diff --git a/src/state/game-data.atoms.ts b/src/state/game-data.atoms.ts index 8a543c1f7..e2f208694 100644 --- a/src/state/game-data.atoms.ts +++ b/src/state/game-data.atoms.ts @@ -1,7 +1,6 @@ import { atom, getDefaultStore } from "jotai"; import { GameData } from "../models/SongData"; import { startAppListening } from "./listener-middleware"; -import { configSlice } from "./config.slice"; export const gameDataAtom = atom(null); @@ -53,12 +52,6 @@ startAppListening({ if (newData) { console.log("data arrived", { newData }); jotaiStore.set(gameDataAtom, newData); - api.dispatch( - configSlice.actions.applyDefaults({ - supportsGranular: !!newData.meta.granularTierResolution, - ...newData.defaults, - }), - ); jotaiStore.set(gameDataLoadingStatus, "available"); } else { console.log("data did not arrive"); diff --git a/src/state/hooks.ts b/src/state/hooks.ts deleted file mode 100644 index e15d142bf..000000000 --- a/src/state/hooks.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { store, useAppDispatch, useAppState } from "./store"; -import { EqualityFn } from "react-redux"; -import { useCallback } from "react"; -import { ConfigState } from "../config-state"; -import { configSlice } from "./config.slice"; - -export function useConfigState( - selector?: (state: ConfigState) => T, - equalityFn?: EqualityFn, -) { - return useAppState((state) => { - const sliceState = configSlice.selectSlice(state); - if (!selector) return sliceState as T; - return selector(sliceState); - }, equalityFn); -} - -export function useUpdateConfig() { - const dispatch = useAppDispatch(); - return useCallback( - ( - patch: - | Partial - | ((state: ConfigState) => Partial), - ) => { - if (typeof patch === "function") { - const state = configSlice.selectSlice(store.getState()); - patch = patch(state); - } - dispatch(configSlice.actions.update(patch)); - }, - [dispatch], - ); -} diff --git a/src/state/hooks.tsx b/src/state/hooks.tsx new file mode 100644 index 000000000..b53eecf76 --- /dev/null +++ b/src/state/hooks.tsx @@ -0,0 +1,61 @@ +import { useAppDispatch, useAppState } from "./store"; +import { EqualityFn } from "react-redux"; +import { createContext, ReactNode, useCallback, useContext } from "react"; +import { ConfigState } from "../config-state"; +import { configSlice } from "./config.slice"; + +const configContext = createContext(null); + +export const ConfigContextProvider = configContext.Provider; +export function SelectedConfigContextProvider(props: { children: ReactNode }) { + const selected = useAppState(configSlice.selectors.getCurrent); + if (!selected) return null; + return ( + + {props.children} + + ); +} + +function useConfigId() { + const id = useContext(configContext); + if (!id) { + throw new Error("config id used without provider parent"); + } + return id; +} + +export function useConfigState( + selector?: (state: ConfigState) => T, + equalityFn?: EqualityFn, +) { + const configId = useConfigId(); + return useAppState((state) => { + const configObj = configSlice.selectors.selectById(state, configId); + if (!selector) return configObj as T; + return selector(configObj); + }, equalityFn); +} + +export function useUpdateConfig() { + const configId = useConfigId(); + const dispatch = useAppDispatch(); + return useCallback( + ( + patch: + | Partial + | ((state: ConfigState) => Partial), + ) => { + dispatch((dispatch, getState) => { + if (typeof patch === "function") { + const state = configSlice.selectors.selectById(getState(), configId); + patch = patch(state); + } + dispatch( + configSlice.actions.updateOne({ id: configId, changes: patch }), + ); + }); + }, + [dispatch, configId], + ); +} diff --git a/src/state/listener-middleware.ts b/src/state/listener-middleware.ts index 9184fa02c..c1612ae41 100644 --- a/src/state/listener-middleware.ts +++ b/src/state/listener-middleware.ts @@ -1,9 +1,11 @@ import { createListenerMiddleware } from "@reduxjs/toolkit"; import type { AppState, AppDispatch } from "./store"; -export const listenerMiddleware = createListenerMiddleware(); +const listener = createListenerMiddleware(); -export const startAppListening = listenerMiddleware.startListening.withTypes< +export const middleware = listener.middleware; + +export const startAppListening = listener.startListening.withTypes< AppState, AppDispatch >(); diff --git a/src/state/store.ts b/src/state/store.ts index f74555f20..0d594f05b 100644 --- a/src/state/store.ts +++ b/src/state/store.ts @@ -5,12 +5,11 @@ import { } from "@reduxjs/toolkit"; import { useDispatch, useSelector, useStore } from "react-redux"; import { reducer } from "./root-reducer"; -import { listenerMiddleware } from "./listener-middleware"; +import { middleware as listener } from "./listener-middleware"; export const store = configureStore({ reducer, - middleware: (getDefaults) => - getDefaults().concat(listenerMiddleware.middleware), + middleware: (getDefaults) => getDefaults().concat(listener), }); export type AppState = ReturnType; diff --git a/src/state/thunks.ts b/src/state/thunks.ts index d1fcc001c..fff03aee4 100644 --- a/src/state/thunks.ts +++ b/src/state/thunks.ts @@ -4,6 +4,7 @@ import { getDefaultStore } from "jotai"; import { gameDataAtom } from "./game-data.atoms"; import { drawingsSlice } from "./drawings.slice"; import { EligibleChart } from "../models/Drawing"; +import { configSlice } from "./config.slice"; declare const umami: { track( @@ -30,12 +31,13 @@ export function createDraw(startggTargetSet: StartggInfo): AppThunk { const state = getState(); const jotaiStore = getDefaultStore(); const gameData = jotaiStore.get(gameDataAtom); - if (!gameData) { + const config = configSlice.selectors.getCurrent(state); + if (!gameData || !config) { trackDraw(null); return false; // no draw was possible } - const drawing = draw(gameData, state.config, startggTargetSet); + const drawing = draw(gameData, config, startggTargetSet); trackDraw(drawing.charts.length, state.gameData.dataSetName); if (!drawing.charts.length) { return false; // could not draw the requested number of charts @@ -52,8 +54,9 @@ export function createRedrawAll(drawingId: string): AppThunk { return (dispatch, getState) => { const state = getState(); const drawing = state.drawings.entities[drawingId]; + const originalConfig = state.config.entities[drawing.configId]; const drawConfig = { - ...state.config, + ...originalConfig, chartCount: drawing.charts.length, }; const jotaiStore = getDefaultStore(); @@ -102,12 +105,13 @@ export function createRedrawChart( if (!gameData) return; const state = getState(); const drawing = state.drawings.entities[drawingId]; + const originalConfig = state.config.entities[drawing.configId]; const startingPoint = { ...drawing, charts: drawing.charts.filter((chart) => chart.id !== chartId), }; - const drawResult = draw(gameData, state.config, startingPoint); + const drawResult = draw(gameData, originalConfig, startingPoint); const chart = drawResult.charts.pop(); if ( !chart || @@ -135,7 +139,8 @@ export function createPickBanPocket( pick?: EligibleChart, ): AppThunk { return (dispatch, getState) => { - const reorder = getState().config.orderByAction; + const reorder = + !!configSlice.selectors.getCurrent(getState())?.orderByAction; let action; if (type === "pocket") { if (pick) { From 7dff004f1c22628d58859d9be27d3c3d8cb40662 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Fri, 6 Sep 2024 01:31:22 -0700 Subject: [PATCH 57/91] kind of working multiconfig --- src/controls/controls-drawer.tsx | 124 ++++++++++++++++++++++++++++++- src/controls/index.tsx | 74 +++++++++++++----- src/drawn-set.tsx | 57 +++++++------- src/header.tsx | 4 +- src/matches.tsx | 16 ++-- src/startgg-gql/index.ts | 1 + src/state/config.slice.ts | 8 +- src/state/drawings.slice.ts | 7 -- src/state/game-data.atoms.ts | 36 ++++++++- src/state/hooks.tsx | 3 +- src/state/store.ts | 2 + src/state/thunks.ts | 27 +++---- 12 files changed, 274 insertions(+), 85 deletions(-) diff --git a/src/controls/controls-drawer.tsx b/src/controls/controls-drawer.tsx index b9242d282..a67967f15 100644 --- a/src/controls/controls-drawer.tsx +++ b/src/controls/controls-drawer.tsx @@ -3,11 +3,17 @@ import { ButtonGroup, Card, Checkbox, + Classes, Collapse, + ControlGroup, + Dialog, + DialogBody, + DialogFooter, Divider, FormGroup, HTMLSelect, NumericInput, + Tooltip, } from "@blueprintjs/core"; import { CaretDown, @@ -15,6 +21,9 @@ import { Plus, SmallTick, SmallCross, + Add, + Duplicate, + Delete, } from "@blueprintjs/icons"; import { DateInput3 } from "@blueprintjs/datetime2"; import parse from "date-fns/parse"; @@ -29,7 +38,7 @@ import styles from "./controls.css"; import { getAvailableLevels } from "../game-data-utils"; import { ShowChartsToggle } from "./show-charts-toggle"; import { Fraction } from "../utils/fraction"; -import { detectedLanguage } from "../utils"; +import { availableGameData, detectedLanguage } from "../utils"; import { SelectedConfigContextProvider, useConfigState, @@ -37,8 +46,9 @@ import { } from "../state/hooks"; import { useAtomValue } from "jotai"; import { showEligibleCharts } from "../config-state"; -import { useAppState } from "../state/store"; -import { gameDataAtom } from "../state/game-data.atoms"; +import { createAppSelector, useAppDispatch, useAppState } from "../state/store"; +import { gameDataAtom, useStockGameData } from "../state/game-data.atoms"; +import { configSlice, createConfigFromInputs } from "../state/config.slice"; function getAvailableDifficulties(gameData: GameData, selectedStyle: string) { const s = new Set(); @@ -80,6 +90,7 @@ function getDiffsAndRangeForNewStyle( export default function ControlsDrawer() { return (
+ @@ -87,6 +98,111 @@ export default function ControlsDrawer() { ); } +const getConfigEntries = createAppSelector( + [(s) => s.config.entities], + (entities) => + Object.entries(entities).map( + ([key, config]) => + [ + key, + config.name, + config.gameKey, + config.lowerBound, + config.upperBound, + ] as const, + ), +); + +function ControlsList() { + const summaryValues = useAppState(getConfigEntries); + const selected = useAppState((s) => s.config.current); + const [addOpen, setAddOpen] = useState(false); + const [busyCreating, setBusyCreating] = useState(false); + const dispatch = useAppDispatch(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (busyCreating) return; + + const data = new FormData(e.currentTarget); + const name = data.get("name") as string; + const gameStub = data.get("game") as string; + + if (!name) { + return; + } + if (!gameStub) { + return; + } + + setBusyCreating(true); + dispatch(await createConfigFromInputs(name, gameStub)); + setAddOpen(false); + setBusyCreating(false); + }; + + return ( +
+ setAddOpen(false)} + title="Create Config" + > +
+ + + + + + + {availableGameData.map((data) => ( + + ))} + + + + + Create + + } + > +
+
+ + + + dispatch(configSlice.actions.pickCurrent(e.currentTarget.value)) + } + > + {summaryValues.map(([key, name, gameKey, lb, ub]) => ( + + ))} + + +
+ ); +} + const dateFormat = "yyyy-MM-dd"; function ReleaseDateFilter() { const { t } = useIntl(); @@ -231,10 +347,10 @@ function FolderSettings() { function GeneralSettings() { const { t } = useIntl(); - const gameData = useAtomValue(gameDataAtom); const updateState = useUpdateConfig(); const showingEligibleCharts = useAtomValue(showEligibleCharts); const configState = useConfigState(); + const gameData = useStockGameData(configState.gameKey); const { useWeights, constrainPocketPicks, diff --git a/src/controls/index.tsx b/src/controls/index.tsx index 3a3887647..a263f2205 100644 --- a/src/controls/index.tsx +++ b/src/controls/index.tsx @@ -1,10 +1,11 @@ import { Button, - ButtonGroup, + ControlGroup, Dialog, DialogBody, Drawer, DrawerSize, + HTMLSelect, Intent, NavbarDivider, Position, @@ -19,21 +20,58 @@ import { ErrorBoundary } from "react-error-boundary"; import { ErrorFallback } from "../utils/error-fallback"; import { ShowChartsToggle } from "./show-charts-toggle"; import { createDraw } from "../state/thunks"; -import { useAppDispatch } from "../state/store"; -import { useAtomValue, useSetAtom } from "jotai"; +import { createAppSelector, useAppDispatch, useAppState } from "../state/store"; +import { useSetAtom } from "jotai"; import { showEligibleCharts } from "../config-state"; -import { gameDataLoadingStatus } from "../state/game-data.atoms"; import { MatchPicker, PickedMatch } from "../matches"; import { StartggApiKeyGated } from "../startgg-gql/components"; +import { configSlice } from "../state/config.slice"; const ControlsDrawer = lazy(() => import("./controls-drawer")); +const getConfigEntries = createAppSelector( + [(s) => s.config.entities], + (entities) => + Object.entries(entities).map(([key, config]) => [key, config.name]), +); + +function ConfigSelect() { + const configEntries = useAppState(getConfigEntries); + const current = useAppState((s) => s.config.current); + const dispatch = useAppDispatch(); + + if (!configEntries.length) { + return ( + + + + ); + } + + return ( + + dispatch(configSlice.actions.pickCurrent(e.currentTarget.value)) + } + > + {configEntries.map(([key, name]) => ( + + ))} + + ); +} + export function HeaderControls() { const setShowEligibleCharts = useSetAtom(showEligibleCharts); const [settingsOpen, setSettingsOpen] = useState(false); const [lastDrawFailed, setLastDrawFailed] = useState(false); const [matchPickerOpen, setMatchPickerOpen] = useState(false); - const hasGameData = useAtomValue(gameDataLoadingStatus) === "available"; + const hasConfig = useAppState((s) => !!s.config.current); const isNarrow = useIsNarrow(); const dispatch = useAppDispatch(); @@ -118,17 +156,8 @@ export function HeaderControls() { )} - - - - + + } @@ -142,7 +171,18 @@ export function HeaderControls() { data-umami-event="settings-open" /> - + + + + + ); } diff --git a/src/drawn-set.tsx b/src/drawn-set.tsx index b9c1cef6d..b94e4c80d 100644 --- a/src/drawn-set.tsx +++ b/src/drawn-set.tsx @@ -9,6 +9,8 @@ import { ErrorFallback } from "./utils/error-fallback"; import { useAtomValue } from "jotai"; import { showPlayerAndRoundLabels } from "./config-state"; import { EligibleChart } from "./models/Drawing"; +import { ConfigContextProvider } from "./state/hooks"; +import { useAppState } from "./state/store"; const HUE_STEP = (255 / 8) * 3; let hue = Math.floor(Math.random() * 255); @@ -83,39 +85,42 @@ function TournamentModeSpacer() { const DrawnSet = memo(function DrawnSet({ drawingId }) { const [backgroundImage] = useState(getRandomGradiant()); + const configId = useAppState((s) => s.drawings.entities[drawingId].configId); return ( - + + +
+ } + >
- -
- } - > -
- -
- - + +
+ + +
+
- -
- + + ); }); diff --git a/src/header.tsx b/src/header.tsx index b1d420e55..ed7d2921a 100644 --- a/src/header.tsx +++ b/src/header.tsx @@ -14,7 +14,6 @@ import { HeaderControls } from "./controls"; import { useIntl } from "./hooks/useIntl"; import { LastUpdate } from "./last-update"; import { ThemeToggle } from "./theme-toggle"; -import { DataLoadingSpinner, VersionSelect } from "./version-select"; import { useAppDispatch, useAppState } from "./state/store"; import { drawingsSlice } from "./state/drawings.slice"; @@ -68,8 +67,7 @@ export function Header() { - } - > - - - - - - dispatch(configSlice.actions.pickCurrent(e.currentTarget.value)) - } - > - {summaryValues.map(([key, name, gameKey, lb, ub]) => ( - - ))} - - - - - */} + } > @@ -186,3 +197,161 @@ export function HeaderControls() { ); } + +const getConfigSummaryValues = createAppSelector( + [(s) => s.config.entities], + (entities) => + Object.entries(entities).map( + ([key, config]) => + [ + key, + config.name, + config.gameKey, + config.lowerBound, + config.upperBound, + ] as const, + ), +); + +function ControlsList() { + const summaryValues = useAppState(getConfigSummaryValues); + const selected = useAppState((s) => s.config.current); + const selectedName = useAppState((s) => + selected ? s.config.entities[selected].name : undefined, + ); + const selectedGameId = useAppState((s) => + selected ? s.config.entities[selected].gameKey : undefined, + ); + const [addOpen, setAddOpen] = useState(false); + const [busyCreating, setBusyCreating] = useState(false); + const dispatch = useAppDispatch(); + const createBasisRef = useRef(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (busyCreating) return; + + const data = new FormData(e.currentTarget); + const name = data.get("name") as string; + const gameStub = data.get("game") as string; + + if (!name) { + return; + } + if (!gameStub) { + return; + } + + setBusyCreating(true); + await dispatch( + createConfigFromInputs(name, gameStub, createBasisRef.current), + ); + setAddOpen(false); + setBusyCreating(false); + createBasisRef.current = undefined; + }; + + return ( + <> + { + setAddOpen(false); + createBasisRef.current = undefined; + }} + title={"Create Config"} + > +
+ + + + + + + + + + Create + + } + > +
+
+ + } + onClick={() => setAddOpen(true)} + /> + } + onClick={() => { + createBasisRef.current = selected || undefined; + setAddOpen(true); + }} + /> + } + onClick={() => dispatch(configSlice.actions.removeOne(selected!))} + /> + + } + onClick={loadConfig} + /> + } + onClick={() => + saveConfig( + dispatch( + (d, gs) => gs().config.entities[gs().config.current!], + ), + ) + } + /> +
+ } + > + + + ); +} + export function HeaderControls() { const setShowEligibleCharts = useSetAtom(showEligibleCharts); const [settingsOpen, setSettingsOpen] = useState(false); @@ -97,23 +139,17 @@ export function HeaderControls() { const isNarrow = useIsNarrow(); const dispatch = useAppDispatch(); - function handleDraw(match?: PickedMatch) { + function handleDraw(match: PickedMatch) { setMatchPickerOpen(false); setShowEligibleCharts(false); const result = dispatch( createDraw({ - meta: match - ? { - type: "startgg", - entrants: match.players, - title: match.title, - id: match.id, - } - : { - type: "simple", - title: "", - players: ["", ""], - }, + meta: { + type: "startgg", + entrants: match.players, + title: match.title, + id: match.id, + }, }), ); if (typeof result === "boolean") { @@ -154,15 +190,28 @@ export function HeaderControls() { title="New Draw" > -

- Pick a startgg match or{" "} - -

- - - + + + + + } + > + start.gg match + + dispatch(createDraw({ meta }))} + /> + } + > + custom draw + +
{!isNarrow && ( diff --git a/src/models/Drawing.ts b/src/models/Drawing.ts index 31032b52b..d7896b2b4 100644 --- a/src/models/Drawing.ts +++ b/src/models/Drawing.ts @@ -54,6 +54,23 @@ export interface SimpleMeta { players: string[]; } +export function playerCount(meta: Drawing["meta"]) { + switch (meta.type) { + case "simple": + return meta.players.length; + case "startgg": + return meta.entrants.length; + } +} + +export function getAllPlayers(d: Pick) { + const ret = [] as string[]; + for (let i = 1; i <= playerCount(d.meta); i++) { + ret.push(playerNameByDisplayPos(d, i)); + } + return ret; +} + export function playerNameByDisplayPos( d: Pick, pos: number, diff --git a/src/tournament-mode/drawing-actions.tsx b/src/tournament-mode/drawing-actions.tsx index c9046ca2d..bcecf9f2b 100644 --- a/src/tournament-mode/drawing-actions.tsx +++ b/src/tournament-mode/drawing-actions.tsx @@ -25,6 +25,7 @@ import { BracketSetGameDataInput as GDI, } from "../startgg-gql"; import { CountingSet } from "../utils/counting-set"; +import { playerCount } from "../models/Drawing"; /** thunk that dispatches nothing, but calculates the result to be sent to startgg */ function getMatchResult( @@ -135,6 +136,7 @@ export function DrawingActions() { const dispatch = useAppDispatch(); const cabs = useAppState(eventSlice.selectors.allCabs); const drawingId = useDrawing((s) => s.id); + const isTwoPlayers = useDrawing((s) => playerCount(s.meta) === 2); const showLabels = useAtomValue(showPlayerAndRoundLabels); const { showBoundary } = useErrorBoundary(); @@ -201,7 +203,7 @@ export function DrawingActions() { )} - {showLabels && ( + {showLabels && isTwoPlayers && ( @@ -131,27 +153,32 @@ function CustomDrawForm(props: { } export function HeaderControls() { - const setShowEligibleCharts = useSetAtom(showEligibleCharts); + const [configId, setConfigId] = useState(null); const [settingsOpen, setSettingsOpen] = useState(false); const [lastDrawFailed, setLastDrawFailed] = useState(false); const [matchPickerOpen, setMatchPickerOpen] = useState(false); - const hasConfig = useAppState((s) => !!s.config.current); + const hasAnyConfig = useAppState((s) => !!s.config.ids.length); const isNarrow = useIsNarrow(); const dispatch = useAppDispatch(); function handleDraw(match: PickedMatch) { + if (!configId) { + return; + } setMatchPickerOpen(false); - setShowEligibleCharts(false); const result = dispatch( - createDraw({ - meta: { - type: "startgg", - subtype: match.subtype, - entrants: match.players, - title: match.title, - id: match.id, + createDraw( + { + meta: { + type: "startgg", + subtype: match.subtype, + entrants: match.players, + title: match.title, + id: match.id, + }, }, - }), + configId, + ), ); if (typeof result === "boolean") { setLastDrawFailed(result); @@ -167,30 +194,24 @@ export function HeaderControls() { return ( <> - setSettingsOpen(false)} - title={ - <> - - - - } - > - }> - }> - - - - + /> setMatchPickerOpen(false)} title="New Draw" > + + + dispatch(createDraw({ meta }))} + disableCreate={!configId} + onSubmit={(meta) => dispatch(createDraw({ meta }, configId!))} /> } > @@ -225,14 +247,7 @@ export function HeaderControls() { - {!isNarrow && ( - <> - - - - )} - - + } @@ -241,45 +256,64 @@ export function HeaderControls() { position={Position.BOTTOM_RIGHT} > - - - - + icon={} + onClick={openSettings} + data-umami-event="settings-open" + /> + ); } -const getConfigSummaryValues = createAppSelector( - [(s) => s.config.entities], - (entities) => - Object.entries(entities).map( - ([key, config]) => - [ - key, - config.name, - config.gameKey, - config.lowerBound, - config.upperBound, - ] as const, - ), -); +function MultiControlsDrawer(props: { + isOpen: boolean; + isNarrow: boolean; + onClose(): void; +}) { + const [configId, setConfigId] = useState(null); -function ControlsList() { - const summaryValues = useAppState(getConfigSummaryValues); - const selected = useAppState((s) => s.config.current); + return ( + + + + + } + > + }> + }> + + + + + ); +} + +/** + * provides UI for selecting/creating/cloning/import/exporting configs at the top of the config drawer + */ +function MultiControlsManager(props: { + configId: string | null; + onConfigSelected(configId: string): void; +}) { + const selected = props.configId; const selectedName = useAppState((s) => selected ? s.config.entities[selected].name : undefined, ); @@ -311,10 +345,11 @@ function ControlsList() { typeof createBasisRef.current === "object" ? createConfigFromImport(name, gameStub, createBasisRef.current) : createConfigFromInputs(name, gameStub, createBasisRef.current); - await dispatch(action); + const newConfig = await dispatch(action); setAddOpen(false); setBusyCreating(false); createBasisRef.current = undefined; + props.onConfigSelected(newConfig.id); }; function titleFromBasis() { @@ -411,11 +446,7 @@ function ControlsList() { disabled={!selected} icon={} onClick={() => - saveConfig( - dispatch( - (d, gs) => gs().config.entities[gs().config.current!], - ), - ) + saveConfig(dispatch((d, gs) => gs().config.entities[selected!])) } /> @@ -423,25 +454,11 @@ function ControlsList() { > + + + + + ); + } + return (
- + + + + +
{cabs.map((cab) => ( @@ -43,7 +66,7 @@ export function CabManagement() { ); } -function AddCabControl() { +function AddCabControl(props: { children?: ReactNode }) { const [name, setName] = useState(""); const dispatch = useAppDispatch(); const addCab = useCallback(() => { @@ -64,6 +87,7 @@ function AddCabControl() { placeholder="Cab name" />
@@ -123,22 +123,19 @@ function AppForRoom() { - - -
-
- - -
- +
+
+ + +
@@ -147,9 +144,10 @@ function AppForRoom() { export function App() { return ( - <> + + - + ); } From 5f835d79fcb11440595fa01ef45d84a4e7247bda Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Fri, 25 Oct 2024 11:03:35 -0700 Subject: [PATCH 87/91] safely return nothing when drawing is missing --- src/obs-sources/text.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/obs-sources/text.tsx b/src/obs-sources/text.tsx index 259148b8c..96824c9bd 100644 --- a/src/obs-sources/text.tsx +++ b/src/obs-sources/text.tsx @@ -8,7 +8,9 @@ export function CabTitle() { const text = useAppState((s) => { const drawingId = s.event.cabs[params.cabId!].activeMatch; if (!drawingId) return null; - return drawingSelectors.selectById(s, drawingId).meta.title; + const drawing = drawingSelectors.selectById(s, drawingId); + if (!drawing) return null; + return drawing.meta.title; }); return

{text}

; } @@ -19,6 +21,7 @@ export function CabPlayers() { const drawingId = s.event.cabs[params.cabId!].activeMatch; if (!drawingId) return null; const drawing = drawingSelectors.selectById(s, drawingId); + if (!drawing) return null; return getAllPlayers(drawing).join(", "); }); return

{text}

; @@ -30,6 +33,7 @@ export function CabPlayer(props: { p: number }) { const drawingId = s.event.cabs[params.cabId!].activeMatch; if (!drawingId) return null; const drawing = drawingSelectors.selectById(s, drawingId); + if (!drawing) return null; const playerIndex = drawing.playerDisplayOrder[props.p - 1]; const name = playerNameByIndex(drawing.meta, playerIndex, ""); const hideWins = From a66db55242c65c7c8c0a506f3822f4a5edbafffb Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Mon, 6 Jan 2025 11:02:08 -0800 Subject: [PATCH 88/91] remove artist blocklist --- src/card-draw.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/card-draw.ts b/src/card-draw.ts index 8c63070ac..3de5b2bf4 100644 --- a/src/card-draw.ts +++ b/src/card-draw.ts @@ -228,7 +228,7 @@ function bucketIndexForLvl(lvl: number, buckets: LvlRanges): number | null { export type StartggInfo = Pick; export type StartingPoint = Drawing | StartggInfo; -const artistDrawBlocklist = new Set(["Carlito", "Dr. Bombay"]); +const artistDrawBlocklist = new Set(); /** * Produces a drawn set of charts given the song data and the user From 2bbcb3e9844e208f62ccf4dd8eea9bdfba618ae0 Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Sat, 11 Jan 2025 13:23:05 -0800 Subject: [PATCH 89/91] catch errors in restoring state --- src/party/server.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/party/server.ts b/src/party/server.ts index aa1b493e0..7d4036d39 100644 --- a/src/party/server.ts +++ b/src/party/server.ts @@ -29,8 +29,11 @@ export default class Server implements Party.Server { } async onStart() { - const preloadedState = - (await this.getFromStorage()) || (await this.getFromSupabase()); + let preloadedState: AppState | undefined; + try { + preloadedState = + (await this.getFromStorage()) || (await this.getFromSupabase()); + } catch {} if (preloadedState) { this.store = configureStore({ reducer, preloadedState }); } else { From 153c9440e6ca3b493f735b9469847b2a3a53c50f Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Sat, 11 Jan 2025 13:34:00 -0800 Subject: [PATCH 90/91] catch errors from upsert, respond to get reqs --- src/party/server.ts | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/party/server.ts b/src/party/server.ts index 7d4036d39..3936f256f 100644 --- a/src/party/server.ts +++ b/src/party/server.ts @@ -5,7 +5,7 @@ import { reducer } from "../state/root-reducer"; import { AppState, type store as appReduxStore } from "../state/store"; import { createClient } from "@supabase/supabase-js"; -import { Database, Json } from "./database.types"; +import type { Database, Json } from "./database.types"; const supabase = createClient( process.env.SUPABASE_URL as string, @@ -57,6 +57,14 @@ export default class Server implements Party.Server { return this.room.storage.get("currentState"); } + onRequest(req: Party.Request): Response | Promise { + if (req.method === "GET") { + return new Response(this.getRoomState()); + } + + return new Response("Method not allowed", { status: 405 }); + } + onConnect(conn: Party.Connection, ctx: Party.ConnectionContext) { // A websocket just connected! console.log( @@ -70,7 +78,7 @@ export default class Server implements Party.Server { conn.send(this.getRoomState()); } - onMessage(message: string, sender: Party.Connection) { + async onMessage(message: string, sender: Party.Connection) { // broadcast it to all the other connections in the room... this.room.broadcast( message, @@ -85,11 +93,15 @@ export default class Server implements Party.Server { // persist to partykit storage this.room.storage.put("currentState", nextState); // persist the state to supabase - supabase.from("event_state").upsert({ - id: this.room.id, - state: nextState as unknown as Json, - updated_at: new Date().toISOString(), - }); + try { + await supabase.from("event_state").upsert({ + id: this.room.id, + state: nextState as unknown as Json, + updated_at: new Date().toISOString(), + }); + } catch (e) { + console.warn("error with upsert", e); + } } private getRoomState() { From 822e5b9154a5a515c1746b3223ff0c65d033421c Mon Sep 17 00:00:00 2001 From: Noah Manneschmidt Date: Mon, 20 Jan 2025 15:41:50 -0800 Subject: [PATCH 91/91] Add classic mode route with localstorage persistence --- src/app.tsx | 62 ++++---- src/classic-mode/index.tsx | 18 +++ src/classic-mode/localstorage-manager.tsx | 20 +++ src/common-components/app-mode.tsx | 26 ++++ src/controls/draw-dialog.tsx | 143 +++++++++++++++++++ src/controls/index.tsx | 128 +---------------- src/eligible-charts/index.tsx | 2 +- src/header.tsx | 7 +- src/state/localstorage.ts | 14 ++ src/state/root-reducer.ts | 2 + src/state/store.ts | 6 +- src/state/thunks.ts | 9 +- src/{ => tournament-mode}/cab-management.tsx | 10 +- src/tournament-mode/drawing-actions.tsx | 21 +-- src/tournament-mode/index.tsx | 37 +++++ src/{ => tournament-mode}/main-view.css | 0 src/{ => tournament-mode}/main-view.tsx | 10 +- 17 files changed, 331 insertions(+), 184 deletions(-) create mode 100644 src/classic-mode/index.tsx create mode 100644 src/classic-mode/localstorage-manager.tsx create mode 100644 src/common-components/app-mode.tsx create mode 100644 src/controls/draw-dialog.tsx create mode 100644 src/state/localstorage.ts rename src/{ => tournament-mode}/cab-management.tsx (95%) create mode 100644 src/tournament-mode/index.tsx rename src/{ => tournament-mode}/main-view.css (100%) rename src/{ => tournament-mode}/main-view.tsx (78%) diff --git a/src/app.tsx b/src/app.tsx index b3a1e3297..ae8aef0c9 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -11,12 +11,10 @@ FocusStyleManager.onlyShowFocusOnTabs(); import { UpdateManager } from "./update-manager"; import { IntlProvider } from "./intl-provider"; -import { Header } from "./header"; import { ThemeSyncWidget } from "./theme-toggle"; import { Provider } from "react-redux"; import { store } from "./state/store"; import { PartySocketManager } from "./party/client"; -import { Provider as UrqlProvider } from "urql"; import { createBrowserRouter, @@ -25,10 +23,8 @@ import { useParams, Link, } from "react-router-dom"; -import { CabManagement } from "./cab-management"; -import { MainView } from "./main-view"; import { nanoid } from "nanoid"; -import { urqlClient } from "./startgg-gql"; +import { ClassicModeShell } from "./classic-mode"; const router = createBrowserRouter([ { @@ -42,6 +38,10 @@ const router = createBrowserRouter([ You need to pick an event first. Would you like to:{" "} Create New Event?

+

+ Or... perhaps just use the app in{" "} + Classic Mode +

No idea what this is?{" "} Here's a video trying to @@ -51,9 +51,31 @@ const router = createBrowserRouter([ ); }, }, + { + path: "classic", + Component: ClassicModeShell, + children: [ + { + index: true, + lazy: async () => { + const mod = await import("./drawing-list"); + return { Component: mod.DrawingList }; + }, + }, + { + path: "charts", + lazy: async () => { + const mod = await import("./eligible-charts"); + return { Component: mod.default }; + }, + }, + ], + }, { path: "e/:roomName", - element: , + lazy: async () => ({ + Component: (await import("./tournament-mode")).TournamentModeApp, + }), }, { path: "e/:roomName/cab/:cabId/source", @@ -114,34 +136,6 @@ function ObsSource() { ); } -function AppForRoom() { - const params = useParams<"roomName">(); - if (!params.roomName) { - return null; - } - return ( - - - -

-
- - -
- - - - ); -} - export function App() { return ( diff --git a/src/classic-mode/index.tsx b/src/classic-mode/index.tsx new file mode 100644 index 000000000..af9aee8e8 --- /dev/null +++ b/src/classic-mode/index.tsx @@ -0,0 +1,18 @@ +import { Provider as ReduxProvider } from "react-redux"; +import { Header } from "../header"; +import { store } from "../state/store"; +import { Outlet } from "react-router-dom"; +import { ClassicModeContext } from "../common-components/app-mode"; +import { LocalStorageManager } from "./localstorage-manager"; + +export function ClassicModeShell() { + return ( + + + +
+ + + + ); +} diff --git a/src/classic-mode/localstorage-manager.tsx b/src/classic-mode/localstorage-manager.tsx new file mode 100644 index 000000000..a1e5ccd9a --- /dev/null +++ b/src/classic-mode/localstorage-manager.tsx @@ -0,0 +1,20 @@ +import { LOCAL_STATE_STORAGE_KEY } from "../state/localstorage"; +import { useAppStore } from "../state/store"; +import { useEffect } from "react"; + +/** + * component that persists the redux state to localstorage with every change + */ +export function LocalStorageManager() { + const store = useAppStore(); + useEffect(() => { + return store.subscribe(() => { + const state = store.getState(); + setTimeout(() => { + const encoded = JSON.stringify(state); + localStorage.setItem(LOCAL_STATE_STORAGE_KEY, encoded); + }); + }); + }, [store]); + return null; +} diff --git a/src/common-components/app-mode.tsx b/src/common-components/app-mode.tsx new file mode 100644 index 000000000..ffba0ea65 --- /dev/null +++ b/src/common-components/app-mode.tsx @@ -0,0 +1,26 @@ +import { createContext, ReactNode, useContext } from "react"; + +const appModeContext = createContext<"classic" | "event">("event"); + +export function ClassicModeContext(props: { children: ReactNode }) { + return ( + + {props.children} + + ); +} + +export function useAppMode() { + return useContext(appModeContext); +} + +export function EventModeGated(props: { + children: ReactNode; + fallback?: ReactNode; +}) { + const mode = useAppMode(); + if (mode === "classic") { + return props.fallback || null; + } + return props.children; +} diff --git a/src/controls/draw-dialog.tsx b/src/controls/draw-dialog.tsx new file mode 100644 index 000000000..8e1aa1827 --- /dev/null +++ b/src/controls/draw-dialog.tsx @@ -0,0 +1,143 @@ +import { + Dialog, + DialogBody, + FormGroup, + Tabs, + Tab, + InputGroup, + TagInput, + Button, +} from "@blueprintjs/core"; +import { ConfigSelect } from "."; +import { MatchPicker, GauntletPicker, PickedMatch } from "../matches"; +import { StartggApiKeyGated } from "../startgg-gql/components"; +import { createDraw } from "../state/thunks"; +import { useAppDispatch } from "../state/store"; +import { SimpleMeta } from "../models/Drawing"; +import { useState } from "react"; +import { EventModeGated } from "../common-components/app-mode"; + +interface Props { + isOpen: boolean; + onClose(): void; + onDrawAttempt(wasSuccess: boolean): void; +} + +export function DrawDialog(props: Props) { + const [configId, setConfigId] = useState(null); + const dispatch = useAppDispatch(); + + function handleDraw(match: PickedMatch) { + if (!configId) { + return; + } + props.onClose(); + dispatch( + createDraw( + { + meta: { + type: "startgg", + subtype: match.subtype, + entrants: match.players, + title: match.title, + id: match.id, + }, + }, + configId, + ), + ).then((result) => { + props.onDrawAttempt(result === "ok"); + }); + } + + return ( + + + + + + + dispatch(createDraw({ meta }, configId!))} + /> + } + > + custom draw + + + + + + } + > + start.gg (h2h) + + + + + } + > + start.gg (gauntlet) + + + + + + ); +} + +function CustomDrawForm(props: { + initialMeta?: SimpleMeta; + disableCreate?: boolean; + onSubmit(meta: SimpleMeta): void; +}) { + const [players, setPlayers] = useState( + props.initialMeta?.players || [], + ); + const [title, setTitle] = useState(props.initialMeta?.title || ""); + + function handleSubmit() { + props.onSubmit({ + type: "simple", + players, + title, + }); + } + return ( + <> + + setTitle(e.currentTarget.value)} + /> + + + setPlayers(v as string[])} + values={players} + /> + + + + ); +} diff --git a/src/controls/index.tsx b/src/controls/index.tsx index 5b6f17bdb..24454dbc5 100644 --- a/src/controls/index.tsx +++ b/src/controls/index.tsx @@ -9,7 +9,6 @@ import { DrawerSize, FormGroup, HTMLSelect, - InputGroup, Intent, Menu, MenuDivider, @@ -17,9 +16,6 @@ import { Popover, Position, Spinner, - Tab, - Tabs, - TagInput, Tooltip, } from "@blueprintjs/core"; import { @@ -40,15 +36,12 @@ import { ErrorFallback } from "../utils/error-fallback"; import { createConfigFromImport, createConfigFromInputs, - createDraw, } from "../state/thunks"; import { createAppSelector, useAppDispatch, useAppState } from "../state/store"; -import { GauntletPicker, MatchPicker, PickedMatch } from "../matches"; -import { StartggApiKeyGated } from "../startgg-gql/components"; import { configSlice, ConfigState } from "../state/config.slice"; import { GameDataSelect } from "../version-select"; import { loadConfig, saveConfig } from "../config-persistence"; -import { SimpleMeta } from "../models/Drawing"; +import { DrawDialog } from "./draw-dialog"; const ControlsDrawer = lazy(() => import("./controls-drawer")); @@ -110,82 +103,12 @@ export function ConfigSelect(props: { ); } -function CustomDrawForm(props: { - initialMeta?: SimpleMeta; - disableCreate?: boolean; - onSubmit(meta: SimpleMeta): void; -}) { - const [players, setPlayers] = useState( - props.initialMeta?.players || [], - ); - const [title, setTitle] = useState(props.initialMeta?.title || ""); - - function handleSubmit() { - props.onSubmit({ - type: "simple", - players, - title, - }); - } - return ( - <> - - setTitle(e.currentTarget.value)} - /> - - - setPlayers(v as string[])} - values={players} - /> - - - - ); -} - export function HeaderControls() { - const [configId, setConfigId] = useState(null); const [settingsOpen, setSettingsOpen] = useState(false); const [lastDrawFailed, setLastDrawFailed] = useState(false); const [matchPickerOpen, setMatchPickerOpen] = useState(false); const hasAnyConfig = useAppState((s) => !!s.config.ids.length); const isNarrow = useIsNarrow(); - const dispatch = useAppDispatch(); - - function handleDraw(match: PickedMatch) { - if (!configId) { - return; - } - setMatchPickerOpen(false); - const result = dispatch( - createDraw( - { - meta: { - type: "startgg", - subtype: match.subtype, - entrants: match.players, - title: match.title, - id: match.id, - }, - }, - configId, - ), - ); - if (typeof result === "boolean") { - setLastDrawFailed(result); - } else { - setLastDrawFailed(false); - } - } function openSettings() { setSettingsOpen((open) => !open); @@ -199,54 +122,11 @@ export function HeaderControls() { isOpen={settingsOpen} onClose={() => setSettingsOpen(false)} /> - setMatchPickerOpen(false)} - title="New Draw" - > - - - - - - dispatch(createDraw({ meta }, configId!))} - /> - } - > - custom draw - - - - - } - > - start.gg (h2h) - - - - - } - > - start.gg (gauntlet) - - - - + onDrawAttempt={(success) => setLastDrawFailed(!success)} + /> Event Mode{" "} - Alpha Preview + + + Alpha Preview + + diff --git a/src/state/localstorage.ts b/src/state/localstorage.ts new file mode 100644 index 000000000..585b4233a --- /dev/null +++ b/src/state/localstorage.ts @@ -0,0 +1,14 @@ +import type { AppState } from "./root-reducer"; + +export const LOCAL_STATE_STORAGE_KEY = "ddrtools.classic.state"; + +export let preloadedState: AppState | undefined; + +try { + const persisted = localStorage.getItem(LOCAL_STATE_STORAGE_KEY); + if (persisted) { + preloadedState = JSON.parse(persisted); + } +} catch { + // YOLO +} diff --git a/src/state/root-reducer.ts b/src/state/root-reducer.ts index ac7505292..d98e58647 100644 --- a/src/state/root-reducer.ts +++ b/src/state/root-reducer.ts @@ -6,6 +6,8 @@ import { eventSlice } from "./event.slice"; const combinedReducer = combineSlices(drawingsSlice, configSlice, eventSlice); +export type AppState = ReturnType; + export const reducer: typeof combinedReducer = (state, action) => { if (receivePartyState.match(action)) { return Object.assign({}, state, action.payload); diff --git a/src/state/store.ts b/src/state/store.ts index 37ab869d8..dc03a5dfa 100644 --- a/src/state/store.ts +++ b/src/state/store.ts @@ -5,15 +5,17 @@ import { createSelector, } from "@reduxjs/toolkit"; import { useDispatch, useSelector, useStore } from "react-redux"; -import { reducer } from "./root-reducer"; +import { reducer, type AppState } from "./root-reducer"; import { middleware as listener } from "./listener-middleware"; +import { preloadedState } from "./localstorage"; export const store = configureStore({ reducer, middleware: (getDefaults) => getDefaults().concat(listener), + preloadedState, }); -export type AppState = ReturnType; +export type { AppState }; export const useAppState = useSelector.withTypes(); export const createAppSelector = createSelector.withTypes(); export type AppDispatch = typeof store.dispatch; diff --git a/src/state/thunks.ts b/src/state/thunks.ts index f3044dc2e..bfebb66db 100644 --- a/src/state/thunks.ts +++ b/src/state/thunks.ts @@ -28,28 +28,29 @@ function trackDraw(count: number | null, game?: string) { export function createDraw( startggTargetSet: StartggInfo, configId: string, -): AppThunk { +): AppThunk> { return async (dispatch, getState) => { const state = getState(); const config = configSlice.selectors.selectById(state, configId); if (!config) { console.error("couldnt draw, no config"); - return false; + return "nok"; } const gameData = await loadStockGamedataByName(config.gameKey); if (!gameData) { console.error("couldnt draw, no game data"); trackDraw(null); - return false; // no draw was possible + return "nok"; // no draw was possible } const drawing = draw(gameData, config, startggTargetSet); trackDraw(drawing.charts.length, gameData.i18n.en.name as string); if (!drawing.charts.length) { - return false; // could not draw the requested number of charts + return "nok"; // could not draw the requested number of charts } dispatch(drawingsSlice.actions.addDrawing(drawing)); + return "ok"; }; } diff --git a/src/cab-management.tsx b/src/tournament-mode/cab-management.tsx similarity index 95% rename from src/cab-management.tsx rename to src/tournament-mode/cab-management.tsx index cb9dd22a2..2d406d043 100644 --- a/src/cab-management.tsx +++ b/src/tournament-mode/cab-management.tsx @@ -8,9 +8,9 @@ import { Popover, Tooltip, } from "@blueprintjs/core"; -import { useAppDispatch, useAppState } from "./state/store"; +import { useAppDispatch, useAppState } from "../state/store"; import React, { ReactNode, useCallback, useState } from "react"; -import { CabInfo, eventSlice } from "./state/event.slice"; +import { CabInfo, eventSlice } from "../state/event.slice"; import { Add, CaretLeft, @@ -24,11 +24,11 @@ import { Person, Remove, } from "@blueprintjs/icons"; -import { detectedLanguage } from "./utils"; -import { copyPlainTextToClipboard } from "./utils/share"; +import { detectedLanguage } from "../utils"; +import { copyPlainTextToClipboard } from "../utils/share"; import { useSetAtom } from "jotai"; import { mainTabAtom } from "./main-view"; -import { playerNameByIndex } from "./models/Drawing"; +import { playerNameByIndex } from "../models/Drawing"; export function CabManagement() { const [isCollapsed, setCollapsed] = useState(true); diff --git a/src/tournament-mode/drawing-actions.tsx b/src/tournament-mode/drawing-actions.tsx index 634dc5bf8..aec7195e5 100644 --- a/src/tournament-mode/drawing-actions.tsx +++ b/src/tournament-mode/drawing-actions.tsx @@ -37,6 +37,7 @@ import { createPlusOneChart, createRedrawAll } from "../state/thunks"; import { CountingSet } from "../utils/counting-set"; import { shareImage } from "../utils/share"; import styles from "./drawing-actions.css"; +import { EventModeGated } from "../common-components/app-mode"; const GauntletEditor = lazy(() => import("./gauntlet-scores")); @@ -215,13 +216,15 @@ export function DrawingActions() {