From 7913a684120c1fcae31918071e3004c309eab819 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Mar 2025 19:05:07 +0000 Subject: [PATCH 001/168] [npm]: Bump @types/node from 22.13.0 to 22.13.13 Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.13.0 to 22.13.13. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index e0c88b87..1bbf2efb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,7 +59,7 @@ "@jest/globals": "^29.7.0", "@types/formidable": "^3.4.5", "@types/jest": "^29.5.14", - "@types/node": "^22.13.0", + "@types/node": "^22.13.13", "@types/react": "^18.3.8", "autoprefixer": "^10.4.21", "cross-env": "^7.0.3", @@ -2939,9 +2939,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.13.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.0.tgz", - "integrity": "sha512-ClIbNe36lawluuvq3+YYhnIN2CELi+6q8NpnM7PYp4hBn/TatfboPgVSm2rwKRfnV2M+Ty9GWDFI64KEe+kysA==", + "version": "22.13.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.13.tgz", + "integrity": "sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ==", "license": "MIT", "dependencies": { "undici-types": "~6.20.0" diff --git a/package.json b/package.json index cdcfe611..448efb4f 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "@jest/globals": "^29.7.0", "@types/formidable": "^3.4.5", "@types/jest": "^29.5.14", - "@types/node": "^22.13.0", + "@types/node": "^22.13.13", "@types/react": "^18.3.8", "autoprefixer": "^10.4.21", "cross-env": "^7.0.3", From 230544c5831e94b8cd2bee118b3b828d53fcbc58 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Mar 2025 19:05:26 +0000 Subject: [PATCH 002/168] [npm]: Bump minimongo from 6.19.0 to 7.0.0 Bumps [minimongo](https://github.com/mWater/minimongo) from 6.19.0 to 7.0.0. - [Changelog](https://github.com/mWater/minimongo/blob/master/CHANGELOG.md) - [Commits](https://github.com/mWater/minimongo/commits) --- updated-dependencies: - dependency-name: minimongo dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package-lock.json | 36 ++++++++++++++++++++++++++++++++---- package.json | 2 +- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index e0c88b87..1d55c3f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "formidable": "^3.5.2", "jose": "^6.0.8", "levenary": "^1.1.1", - "minimongo": "^6.19.0", + "minimongo": "^7.0.0", "mongo-anywhere": "^1.1.11", "mongodb": "^5.0.0", "next": "^15.2.3", @@ -8272,9 +8272,10 @@ } }, "node_modules/minimongo": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/minimongo/-/minimongo-6.19.0.tgz", - "integrity": "sha512-DF2FLEJ6vTJSz7QsWrm2FMq2UR2yowwXPKhgQ69mxT5KOm/bYcrVv1RDw3zyeeYw/I7a/2MkiRWyV1EglniONA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/minimongo/-/minimongo-7.0.0.tgz", + "integrity": "sha512-kFF0oXLr8GKv4mZ2HCMNSwNjpDsGtXr5WfdnBcYwzSAvgS3HNf2UMbbhrBDG5ElYJ0im7QAQxFkSj8S66g0cvg==", + "license": "LGPLv3", "dependencies": { "@turf/boolean-crosses": "^6.0.1", "@turf/boolean-point-in-polygon": "^6.0.1", @@ -8315,6 +8316,33 @@ "node-cache": "^5.1.2" } }, + "node_modules/mongo-anywhere/node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", + "license": "MIT" + }, + "node_modules/mongo-anywhere/node_modules/minimongo": { + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/minimongo/-/minimongo-6.19.0.tgz", + "integrity": "sha512-DF2FLEJ6vTJSz7QsWrm2FMq2UR2yowwXPKhgQ69mxT5KOm/bYcrVv1RDw3zyeeYw/I7a/2MkiRWyV1EglniONA==", + "license": "LGPLv3", + "dependencies": { + "@turf/boolean-crosses": "^6.0.1", + "@turf/boolean-point-in-polygon": "^6.0.1", + "@turf/boolean-within": "^6.0.1", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/intersect": "^6.1.3", + "@turf/nearest-point-on-line": "^6.5.0", + "async": "^1.4.2", + "bowser": "^0.7.1", + "idb-wrapper": "^1.4.1", + "jquery": "^3.6.0", + "js-sha1": "^0.6.0", + "lodash": "^4.0.0" + } + }, "node_modules/mongodb": { "version": "5.9.2", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.2.tgz", diff --git a/package.json b/package.json index cdcfe611..fc839831 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "formidable": "^3.5.2", "jose": "^6.0.8", "levenary": "^1.1.1", - "minimongo": "^6.19.0", + "minimongo": "^7.0.0", "mongo-anywhere": "^1.1.11", "mongodb": "^5.0.0", "next": "^15.2.3", From 5707745da32490c93b2160b3e1a5f1128645293c Mon Sep 17 00:00:00 2001 From: Gearbox Bot Date: Mon, 24 Mar 2025 19:05:27 +0000 Subject: [PATCH 003/168] 1.2.23 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1bbf2efb..59c15f62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sj3", - "version": "1.2.22", + "version": "1.2.23", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sj3", - "version": "1.2.22", + "version": "1.2.23", "license": "CC BY-NC-SA 4.0", "dependencies": { "dependencies": "^0.0.1", diff --git a/package.json b/package.json index 448efb4f..864f192e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sj3", - "version": "1.2.22", + "version": "1.2.23", "private": true, "repository": "https://github.com/Decatur-Robotics/Gearbox", "license": "CC BY-NC-SA 4.0", From ede1e6cabfd7dedcff75678abf8a7a304dc96531 Mon Sep 17 00:00:00 2001 From: Gearbox Bot Date: Mon, 24 Mar 2025 19:05:37 +0000 Subject: [PATCH 004/168] 1.2.23 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1d55c3f8..2337ec52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sj3", - "version": "1.2.22", + "version": "1.2.23", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sj3", - "version": "1.2.22", + "version": "1.2.23", "license": "CC BY-NC-SA 4.0", "dependencies": { "dependencies": "^0.0.1", diff --git a/package.json b/package.json index fc839831..d6b6270a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sj3", - "version": "1.2.22", + "version": "1.2.23", "private": true, "repository": "https://github.com/Decatur-Robotics/Gearbox", "license": "CC BY-NC-SA 4.0", From d8a099ed7bd8bad79496a28e66d1c3fb048ea91e Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Mon, 24 Mar 2025 16:22:25 -0400 Subject: [PATCH 005/168] Add Jest to recommended extensions --- .vscode/extensions.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 1249c1a7..0f35dd6f 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -7,6 +7,7 @@ "mongodb.mongodb-vscode", "pmneo.tsimporter", "austenc.tailwind-docs", - "bradlc.vscode-tailwindcss" + "bradlc.vscode-tailwindcss", + "Orta.vscode-jest" ] } From fae8cec0459afb16412aadadc8c42f0e544c559e Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Mon, 24 Mar 2025 17:02:07 -0400 Subject: [PATCH 006/168] Add public pit reports --- components/stats/TeamPage.tsx | 20 ++--- lib/api/ClientApi.ts | 82 +++++++++++++++---- lib/client/ClientUtils.ts | 37 +++++++++ lib/client/StatsMath.ts | 8 +- .../[seasonSlug]/[competitonSlug]/index.tsx | 2 +- .../[competitonSlug]/pitstats.tsx | 4 +- .../[seasonSlug]/[competitonSlug]/stats.tsx | 53 +++++++++--- tests/lib/client/ClientUtils.test.ts | 69 ++++++++++++++++ 8 files changed, 231 insertions(+), 44 deletions(-) diff --git a/components/stats/TeamPage.tsx b/components/stats/TeamPage.tsx index df6114de..a05da6ea 100644 --- a/components/stats/TeamPage.tsx +++ b/components/stats/TeamPage.tsx @@ -122,6 +122,7 @@ function TeamCard(props: { } export default function TeamPage(props: { + teams: Set; reports: Report[]; pitReports: Pitreport[]; subjectiveReports: SubjectiveReport[]; @@ -138,13 +139,12 @@ export default function TeamPage(props: { [key: number]: SubjectiveReport[]; }>({}); - const teamNumbers = Array.from( - new Set([ - ...Object.keys(teamReports), - ...Object.keys(pitReports), - ...Object.keys(teamSubjectiveReports), - ]), - ); + const teamNumbers = props.teams; + [ + ...Object.keys(teamReports), + ...Object.keys(pitReports), + ...Object.keys(teamSubjectiveReports), + ].forEach((team) => teamNumbers.add(Number(team))); const [selectedTeam, setSelectedTeam] = useState(); const selectedReports = teamReports[selectedTeam ? selectedTeam : 0]; @@ -212,9 +212,9 @@ export default function TeamPage(props: { }); // Find teams not in team ranking - const missingTeams = teamNumbers.filter( - (team) => !teamRanking.includes(team), - ); + const missingTeams = Array.from(teamNumbers) + .filter((team) => !teamRanking.includes(team.toString())) + .map((team) => team.toString()); return (
diff --git a/lib/api/ClientApi.ts b/lib/api/ClientApi.ts index 96bc0685..fe0d800b 100644 --- a/lib/api/ClientApi.ts +++ b/lib/api/ClientApi.ts @@ -630,7 +630,7 @@ export default class ClientApi extends NextApiTemplate { competitionReports = createNextRoute< [string, boolean, boolean], - Report[], + { quantReports: Report[]; pitReports: { [team: number]: Pitreport[] } }, ApiDependencies, { team: Team; comp: Competition } >({ @@ -656,19 +656,51 @@ export default class ClientApi extends NextApiTemplate { if (usePublicData && !comp.publicData) usedComps.push(comp); - const reports = ( - await db.findObjects(CollectionId.Reports, { - match: { $in: usedComps.flatMap((m) => m.matches) }, - submitted: submitted ? true : { $exists: true }, - }) - ) - // Filter out comments from other competitions - .map((report) => - comp.matches.includes(report.match) - ? report - : { ...report, data: { ...report.data, comments: "" } }, - ); - return res.status(200).send(reports); + const [reports, pitReports] = await Promise.all([ + ( + await db.findObjects(CollectionId.Reports, { + match: { $in: usedComps.flatMap((m) => m.matches) }, + submitted: submitted ? true : { $exists: true }, + }) + ) + // Filter out comments from other competitions + .map((report) => + comp.matches.includes(report.match) + ? report + : { ...report, data: { ...report.data, comments: "" } }, + ), + ( + await db.findObjects(CollectionId.PitReports, { + _id: { + $in: usedComps + .flatMap((m) => m.pitReports) + .map((id) => new ObjectId(id)), + }, + submitted: submitted ? true : { $exists: true }, + }) + ) + .map((pitReport) => + comp.pitReports.includes(pitReport._id!.toString()) + ? pitReport + : ({ + ...pitReport, + data: { ...pitReport.data, comments: "" }, + } as Pitreport), + ) + .reduce( + (dict, pitReport) => { + dict[pitReport.teamNumber] ??= []; + dict[pitReport.teamNumber].push(pitReport); + return dict; + }, + {} as { [team: number]: Pitreport[] }, + ), + ]); + + return res.status(200).send({ + quantReports: reports, + pitReports: pitReports, + }); }, }); @@ -696,6 +728,28 @@ export default class ClientApi extends NextApiTemplate { }, }); + getTeamsAtComp = createNextRoute< + [string], + number[], + ApiDependencies, + { team: Team; comp: Competition } + >({ + isAuthorized: (req, res, deps, [compId]) => + AccessLevels.IfOnTeamThatOwnsComp(req, res, deps, compId), + handler: async (req, res, { db: dbPromise }, { team, comp }, [compId]) => { + const db = await dbPromise; + + const pitReports = await db.findObjects(CollectionId.PitReports, { + _id: { + $in: comp.pitReports.map((pitReportId) => new ObjectId(pitReportId)), + }, + }); + return res + .status(200) + .send(pitReports.map((pitReport) => pitReport.teamNumber)); + }, + }); + matchReports = createNextRoute< [string], Report[], diff --git a/lib/client/ClientUtils.ts b/lib/client/ClientUtils.ts index e0f85d77..10f471ca 100644 --- a/lib/client/ClientUtils.ts +++ b/lib/client/ClientUtils.ts @@ -1,3 +1,6 @@ +import { Pitreport } from "../Types"; +import { MostCommonValue } from "./StatsMath"; + export function getIdsInProgressFromTimestamps(timestamps: { [id: string]: string; }) { @@ -151,3 +154,37 @@ export function promisify( export async function wait(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } + +export function mergePitReports(reports: Pitreport[]) { + if (reports.length === 0) { + throw new Error("Cannot merge 0 pit reports"); + } + + if (reports.length === 1) { + return reports[0]; + } + + const dataKeys = removeDuplicates( + reports.reduce( + (acc, report) => [...acc, ...Object.keys(report.data!)], + [] as string[], + ), + ); + + const newReport = { + teamNumber: reports[0].teamNumber, + data: dataKeys.reduce( + (acc, key) => { + acc[key] = MostCommonValue(key, reports); + return acc; + }, + {} as Record, + ), + } as Pitreport; + + for (const report of reports) { + if (report.data?.comments) newReport.data!.comments = report.data.comments; + } + + return newReport; +} diff --git a/lib/client/StatsMath.ts b/lib/client/StatsMath.ts index 8464971e..855d0b68 100644 --- a/lib/client/StatsMath.ts +++ b/lib/client/StatsMath.ts @@ -10,7 +10,7 @@ type Selector = ((r: T) => number) | (keyof T & string); function getSelection( selector: Selector, - report: Report, + report: Record, ) { return typeof selector === "string" ? report.data[selector] @@ -53,13 +53,13 @@ export function NumericalTotal( */ export function MostCommonValue( selector: Selector, - reports: Report[], + objects: Record[], ) { // Get a list of all values of the specified field let values: string[] = []; - reports?.forEach((report) => { + objects?.forEach((report) => { const val = getSelection(selector, report); - values.push((val as any).toString?.() ?? JSON.stringify(val)); + values.push((val as any)?.toString?.() ?? JSON.stringify(val)); }); // Count the occurrences of each value diff --git a/pages/[teamSlug]/[seasonSlug]/[competitonSlug]/index.tsx b/pages/[teamSlug]/[seasonSlug]/[competitonSlug]/index.tsx index 91442621..3a01e4ee 100644 --- a/pages/[teamSlug]/[seasonSlug]/[competitonSlug]/index.tsx +++ b/pages/[teamSlug]/[seasonSlug]/[competitonSlug]/index.tsx @@ -186,7 +186,7 @@ export default function CompetitionIndex({ if (!silent) setLoadingReports(true); - let newReports: Report[] = await api.competitionReports( + let { quantReports: newReports } = await api.competitionReports( comp?._id!, false, false, diff --git a/pages/[teamSlug]/[seasonSlug]/[competitonSlug]/pitstats.tsx b/pages/[teamSlug]/[seasonSlug]/[competitonSlug]/pitstats.tsx index dfec166a..33b02abe 100644 --- a/pages/[teamSlug]/[seasonSlug]/[competitonSlug]/pitstats.tsx +++ b/pages/[teamSlug]/[seasonSlug]/[competitonSlug]/pitstats.tsx @@ -231,11 +231,11 @@ export default function Pitstats(props: { competition: Competition }) { const layout = games[comp.gameId].pitStatsLayout; const loadReports = useCallback(async () => { - const newReports = (await api.competitionReports( + const { quantReports: newReports } = await api.competitionReports( comp._id!, true, usePublicData, - )) as Report[]; + ); const rankings = await api.compRankings(comp.tbaId!); diff --git a/pages/[teamSlug]/[seasonSlug]/[competitonSlug]/stats.tsx b/pages/[teamSlug]/[seasonSlug]/[competitonSlug]/stats.tsx index 6f342c26..d4b87f88 100644 --- a/pages/[teamSlug]/[seasonSlug]/[competitonSlug]/stats.tsx +++ b/pages/[teamSlug]/[seasonSlug]/[competitonSlug]/stats.tsx @@ -1,5 +1,5 @@ import ClientApi from "@/lib/api/ClientApi"; -import { NotLinkedToTba } from "@/lib/client/ClientUtils"; +import { mergePitReports, NotLinkedToTba } from "@/lib/client/ClientUtils"; import { defaultGameId } from "@/lib/client/GameId"; import { Competition, @@ -41,6 +41,9 @@ export default function Stats(props: { >(props.subjectiveReports); const [page, setPage] = useState(0); const [usePublicData, setUsePublicData] = useState(true); + const [fetchedTeamNumbers, setFetchedTeamNumbers] = useState([]); + const [externalPitReportCount, setExternalPitReportCount] = useState(0); + const [internalPitReportCount, setInternalPitReportCount] = useState(0); useEffect(() => { const i = setInterval(() => { @@ -58,15 +61,32 @@ export default function Stats(props: { const promises = [ api .competitionReports(props.competition._id!, true, usePublicData) - .then((data) => setReports(data)), - pitReports.length === 0 && - props.competition._id && - api.getPitReports(props.competition._id).then((data) => { - setPitReports(data); + .then((data) => { + setReports(data.quantReports); + + const newPitReports: Pitreport[] = []; + let internalPitReportCount = 0, + externalPitReportCount = 0; + for (const teamPitReports of Object.values(data.pitReports)) { + newPitReports.push(mergePitReports(teamPitReports)); + + teamPitReports.forEach((r) => { + if (props.competition.pitReports.includes(r._id!.toString())) { + internalPitReportCount++; + } else { + externalPitReportCount++; + } + }); + } + + setPitReports(newPitReports); + setInternalPitReportCount(internalPitReportCount); + setExternalPitReportCount(externalPitReportCount); }), api .getSubjectiveReportsForComp(props.competition._id!) .then(setSubjectiveReports), + api.getTeamsAtComp(props.competition._id!).then(setFetchedTeamNumbers), ].flat(); await Promise.all(promises); @@ -85,14 +105,15 @@ export default function Stats(props: { subjectiveReports.forEach((r) => Object.keys(r.robotComments).forEach((c) => teams.add(+c)), ); //+str converts to number + fetchedTeamNumbers.forEach((t) => teams.add(t)); - let internalReportCount = 0, - externalReportCount = 0; + let internalQuantReportCount = 0, + externalQuantReportCount = 0; reports.forEach((r) => { if (props.competition.matches.includes(r.match)) { - internalReportCount++; + internalQuantReportCount++; } else { - externalReportCount++; + externalQuantReportCount++; } }); @@ -110,12 +131,17 @@ export default function Stats(props: { > {usePublicData ? (
- Using public data ({internalReportCount} internal reports +{" "} - {externalReportCount} external reports) + Using public data ({internalQuantReportCount} internal quant + reports + {externalQuantReportCount} external quant reports,{" "} + {internalPitReportCount} internal pit reports +{" "} + {externalPitReportCount} external pit reports, and{" "} + {subjectiveReports.length} subjective reports)
) : (
- Not using public data ({internalReportCount} internal reports) + Not using public data ({internalQuantReportCount} internal + reports, {internalPitReportCount} internal pit reports,{" "} + {subjectiveReports.length} subjective reports)
)}
(Click to toggle)
@@ -170,6 +196,7 @@ export default function Stats(props: { {page === 0 && ( { expect(camelCaseToTitleCase("notLinkedToTba")).toBe("Not Linked To Tba"); @@ -113,3 +116,69 @@ describe(wait.name, () => { } }); }); + +describe(mergePitReports.name, () => { + test("Takes the most common value for each key in data", () => { + const reports: Pitreport[] = [ + { + teamNumber: 1, + data: { + motorType: Motors.CIMs, + }, + } as any, + { + teamNumber: 1, + data: { + motorType: Motors.CIMs, + }, + } as any, + { + teamNumber: 1, + data: { + motorType: Motors.Falcons, + }, + } as any, + ]; + + const merged = mergePitReports(reports); + + expect(merged).toEqual({ + teamNumber: 1, + data: { + motorType: Motors.CIMs, + }, + }); + }); + + test("Merges comments when only one report has a comment", () => { + const reports: Pitreport[] = [ + { + teamNumber: 1, + data: { + comments: "", + }, + } as any, + { + teamNumber: 1, + data: { + comments: "", + }, + } as any, + { + teamNumber: 1, + data: { + comments: "comment 3", + }, + } as any, + ]; + + const merged = mergePitReports(reports); + + expect(merged).toEqual({ + teamNumber: 1, + data: { + comments: "comment 3", + }, + }); + }); +}); From 6c3a1950afe443339aefee29757a38d8fd92b9ce Mon Sep 17 00:00:00 2001 From: Gearbox Bot Date: Mon, 24 Mar 2025 21:03:07 +0000 Subject: [PATCH 007/168] 1.2.24 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3ee4c646..5421fe05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sj3", - "version": "1.2.23", + "version": "1.2.24", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sj3", - "version": "1.2.23", + "version": "1.2.24", "license": "CC BY-NC-SA 4.0", "dependencies": { "dependencies": "^0.0.1", diff --git a/package.json b/package.json index 9500f4bd..64ffaf98 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sj3", - "version": "1.2.23", + "version": "1.2.24", "private": true, "repository": "https://github.com/Decatur-Robotics/Gearbox", "license": "CC BY-NC-SA 4.0", From cad2790a6a35747b921a2b26919ce167ae1f2db1 Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Mon, 24 Mar 2025 17:05:02 -0400 Subject: [PATCH 008/168] Add pitReports to stats dependency array --- pages/[teamSlug]/[seasonSlug]/[competitonSlug]/stats.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pages/[teamSlug]/[seasonSlug]/[competitonSlug]/stats.tsx b/pages/[teamSlug]/[seasonSlug]/[competitonSlug]/stats.tsx index d4b87f88..24f60d2e 100644 --- a/pages/[teamSlug]/[seasonSlug]/[competitonSlug]/stats.tsx +++ b/pages/[teamSlug]/[seasonSlug]/[competitonSlug]/stats.tsx @@ -93,7 +93,12 @@ export default function Stats(props: { setUpdate(Date.now()); setUpdating(false); - }, [pitReports.length, props.competition._id, usePublicData]); + }, [ + pitReports.length, + props.competition._id, + props.competition.pitReports, + usePublicData, + ]); useEffect(() => { resync(); From 044c48a74fe9907f6558ef9a265c31224f9581bb Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Mon, 24 Mar 2025 17:07:10 -0400 Subject: [PATCH 009/168] Remove unnecessary useCallback dependency in resync in stats --- pages/[teamSlug]/[seasonSlug]/[competitonSlug]/stats.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pages/[teamSlug]/[seasonSlug]/[competitonSlug]/stats.tsx b/pages/[teamSlug]/[seasonSlug]/[competitonSlug]/stats.tsx index 24f60d2e..6bee5996 100644 --- a/pages/[teamSlug]/[seasonSlug]/[competitonSlug]/stats.tsx +++ b/pages/[teamSlug]/[seasonSlug]/[competitonSlug]/stats.tsx @@ -93,12 +93,7 @@ export default function Stats(props: { setUpdate(Date.now()); setUpdating(false); - }, [ - pitReports.length, - props.competition._id, - props.competition.pitReports, - usePublicData, - ]); + }, [props.competition._id, props.competition.pitReports, usePublicData]); useEffect(() => { resync(); From 674e1c76093fc94d5ab74bdaaa46b05c51c46196 Mon Sep 17 00:00:00 2001 From: Davis Becker <143132652+BanEvading@users.noreply.github.com> Date: Thu, 27 Mar 2025 16:44:04 -0400 Subject: [PATCH 010/168] Added initial tag. --- components/SignInMenu.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/components/SignInMenu.tsx b/components/SignInMenu.tsx index 1c9ae53b..f34cb64b 100644 --- a/components/SignInMenu.tsx +++ b/components/SignInMenu.tsx @@ -60,6 +60,7 @@ function SignInCard() {

Sign In

{error &&

{error}

}

Choose a login provider

+

We currently only support signing in with one login provider

)}
From 76402d2a90dd97d216f5a5cff7ebd9ea919dc2f4 Mon Sep 17 00:00:00 2001 From: Davis Becker <143132652+BanEvading@users.noreply.github.com> Date: Mon, 31 Mar 2025 16:35:38 -0400 Subject: [PATCH 042/168] Making paragraph hidden --- pages/[teamSlug]/[seasonSlug]/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/[teamSlug]/[seasonSlug]/index.tsx b/pages/[teamSlug]/[seasonSlug]/index.tsx index eb041aea..5c1d4da6 100644 --- a/pages/[teamSlug]/[seasonSlug]/index.tsx +++ b/pages/[teamSlug]/[seasonSlug]/index.tsx @@ -72,10 +72,10 @@ export default function Home(props: SeasonPageProps) { {owner && ( )}
From 008acf36bfeb875a684f3a22f0f800fe33c2635c Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Mon, 31 Mar 2025 17:01:14 -0400 Subject: [PATCH 043/168] Fix sign in --- lib/MongoDB.ts | 24 +++++------- lib/testutils/TestUtils.ts | 2 +- package-lock.json | 8 ++-- package.json | 2 +- playwright.config.ts | 1 + tests/e2e/misc.spec.ts | 12 ++++++ tests/unit/lib/api/ClientApi.test.ts | 55 ++++++++++++++++++++++++++++ 7 files changed, 84 insertions(+), 20 deletions(-) diff --git a/lib/MongoDB.ts b/lib/MongoDB.ts index 78c0fa5c..f1ae7e1a 100644 --- a/lib/MongoDB.ts +++ b/lib/MongoDB.ts @@ -35,9 +35,7 @@ let clientPromise: Promise; if (uri && !global.clientPromise) { client = new MongoClient(uri, options); - global.clientPromise = client - .connect() - .then(() => console.error("MongoDB connected.")); + global.clientPromise = client.connect(); } clientPromise = global.clientPromise; @@ -46,21 +44,19 @@ export { clientPromise }; export async function getDatabase( useCache: boolean = true, ): Promise { - if (!global.interface) { - await clientPromise; + if (global.interface) return global.interface; // Return the existing instance if already created - const mongo = new MongoDBInterface(clientPromise); + await clientPromise; - const dbInterface = useCache - ? new CachedDbInterface(mongo, cacheOptions) - : mongo; - await dbInterface.init(); - global.interface = dbInterface; + const mongo = new MongoDBInterface(clientPromise); - return dbInterface; - } + const dbInterface = useCache + ? new CachedDbInterface(mongo, cacheOptions) + : mongo; + await dbInterface.init(); + global.interface = dbInterface; - return global.interface; + return dbInterface; } export class MongoDBInterface diff --git a/lib/testutils/TestUtils.ts b/lib/testutils/TestUtils.ts index 4df6f588..6d4c01df 100644 --- a/lib/testutils/TestUtils.ts +++ b/lib/testutils/TestUtils.ts @@ -179,7 +179,7 @@ export namespace PlaywrightUtils { await context.addCookies([ { - name: "__Secure-next-auth.session-token", + name: "next-auth.session-token", value: sessionToken, path: "/", domain: "localhost", diff --git a/package-lock.json b/package-lock.json index 277a3200..4759a53c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "jose": "^6.0.10", "levenary": "^1.1.1", "minimongo": "^7.0.0", - "mongo-anywhere": "^1.1.11", + "mongo-anywhere": "^1.1.15", "mongodb": "^5.0.0", "next": "^15.2.3", "next-auth": "^4.24.11", @@ -8322,9 +8322,9 @@ } }, "node_modules/mongo-anywhere": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/mongo-anywhere/-/mongo-anywhere-1.1.11.tgz", - "integrity": "sha512-wM5FMS7sj6vZAEw9XaRaWFYqNeA8slKcQxxWdhyN4a8xGBrOxN2U4wF33kuAkYTgqsdy5fxS3NSF4KkQAUq5Zg==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/mongo-anywhere/-/mongo-anywhere-1.1.15.tgz", + "integrity": "sha512-wN6E/jN0lae5EqAeaAaE5fdUdb+ZchZKib3FWGOOOQUYZvTv2ino9Aii3GK+uIMxNdnm6N//B0bEGEeCNbBy1g==", "dependencies": { "bson": "^5.0.0", "minimongo": "^6.19.0", diff --git a/package.json b/package.json index 38a06059..066245ab 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "jose": "^6.0.10", "levenary": "^1.1.1", "minimongo": "^7.0.0", - "mongo-anywhere": "^1.1.11", + "mongo-anywhere": "^1.1.15", "mongodb": "^5.0.0", "next": "^15.2.3", "next-auth": "^4.24.11", diff --git a/playwright.config.ts b/playwright.config.ts index c321927d..21b6e8f9 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -31,6 +31,7 @@ export default defineConfig({ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: "on-first-retry", + video: "retain-on-failure", // Record video only for failed tests }, /* Configure projects for major browsers */ diff --git a/tests/e2e/misc.spec.ts b/tests/e2e/misc.spec.ts index 161f67e6..b60bb200 100644 --- a/tests/e2e/misc.spec.ts +++ b/tests/e2e/misc.spec.ts @@ -4,7 +4,19 @@ import { PlaywrightUtils } from "@/lib/testutils/TestUtils"; test("Sign in function signs in", async ({ page, context }) => { const { user } = await PlaywrightUtils.signIn(context); + const sessionToken = await context + .cookies() + .then( + (cookies) => + cookies.find((cookie) => cookie.name === "next-auth.session-token") + ?.value, + ); + + expect(sessionToken).toBeDefined(); + expect(sessionToken).not.toBe(""); + const res = await context.request.get("/api/auth/session"); + const foundUser = (await res.json()).user; if (foundUser) foundUser.id = user.id; // ID mismatches are normal diff --git a/tests/unit/lib/api/ClientApi.test.ts b/tests/unit/lib/api/ClientApi.test.ts index 24b6c6ad..a10478de 100644 --- a/tests/unit/lib/api/ClientApi.test.ts +++ b/tests/unit/lib/api/ClientApi.test.ts @@ -1107,3 +1107,58 @@ describe(`${ClientApi.name}.${api.changeTeamNumberForReport.name}`, () => { ); }); }); + +describe(`${ClientApi.name}.${api.testSignIn.name}`, () => { + test("Returns user", async () => { + const { db, res } = await getTestApiUtils(); + + await api.testSignIn.handler(...(await getTestApiParams(res, { db }, []))); + + const { user } = res.send.mock.calls[0][0] as { + user: User; + }; + + expect(user).toBeDefined(); + expect(user._id).toBeDefined(); + expect( + await db.findObjectById(CollectionId.Users, new ObjectId(user._id!)), + ).toEqual(user); + }); + + test("Returns valid sessionToken", async () => { + const { db, res } = await getTestApiUtils(); + + await api.testSignIn.handler(...(await getTestApiParams(res, { db }, []))); + + const { sessionToken } = res.send.mock.calls[0][0] as { + sessionToken: string; + }; + + expect(sessionToken).toBeDefined(); + expect(sessionToken).not.toBe(""); + + const session = await db.findObject(CollectionId.Sessions, { + sessionToken, + }); + expect(session).toBeDefined(); + }); + + test("Session has correct userId", async () => { + const { db, res } = await getTestApiUtils(); + + await api.testSignIn.handler(...(await getTestApiParams(res, { db }, []))); + + const { user, sessionToken } = res.send.mock.calls[0][0] as { + user: User; + sessionToken: string; + }; + + expect(sessionToken).toBeDefined(); + + const session = await db.findObject(CollectionId.Sessions, { + sessionToken, + }); + expect(session).toBeDefined(); + expect(session?.userId).toEqual(user._id); + }); +}); From 6a383805e6f51111cdd5f22483c38f3e9cb34e71 Mon Sep 17 00:00:00 2001 From: Davis Becker <143132652+BanEvading@users.noreply.github.com> Date: Mon, 31 Mar 2025 17:01:25 -0400 Subject: [PATCH 044/168] Tried to add an xs style, realized one shouldn't exist, not sure what these changes are. --- pages/[teamSlug]/[seasonSlug]/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pages/[teamSlug]/[seasonSlug]/index.tsx b/pages/[teamSlug]/[seasonSlug]/index.tsx index 5c1d4da6..817f824a 100644 --- a/pages/[teamSlug]/[seasonSlug]/index.tsx +++ b/pages/[teamSlug]/[seasonSlug]/index.tsx @@ -72,10 +72,12 @@ export default function Home(props: SeasonPageProps) { {owner && ( )}
From 1e023b3a1e454a719211addf862afd901be7e9a5 Mon Sep 17 00:00:00 2001 From: Davis Becker <143132652+BanEvading@users.noreply.github.com> Date: Mon, 31 Mar 2025 17:09:37 -0400 Subject: [PATCH 045/168] Removes the text on xs devices. --- pages/[teamSlug]/[seasonSlug]/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/[teamSlug]/[seasonSlug]/index.tsx b/pages/[teamSlug]/[seasonSlug]/index.tsx index 817f824a..60e893d3 100644 --- a/pages/[teamSlug]/[seasonSlug]/index.tsx +++ b/pages/[teamSlug]/[seasonSlug]/index.tsx @@ -75,7 +75,7 @@ export default function Home(props: SeasonPageProps) { className="w-1/6 sm:1/8 h-1/4 btn btn-sm btn-error flex" > -

+

Delete Season

From cfa49d169a28b22f6436ec63a235d31e26f9688b Mon Sep 17 00:00:00 2001 From: Davis Becker <143132652+BanEvading@users.noreply.github.com> Date: Mon, 31 Mar 2025 17:22:14 -0400 Subject: [PATCH 046/168] Adds break and makes the thing work --- pages/[teamSlug]/[seasonSlug]/index.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pages/[teamSlug]/[seasonSlug]/index.tsx b/pages/[teamSlug]/[seasonSlug]/index.tsx index 60e893d3..f76edbaa 100644 --- a/pages/[teamSlug]/[seasonSlug]/index.tsx +++ b/pages/[teamSlug]/[seasonSlug]/index.tsx @@ -75,8 +75,9 @@ export default function Home(props: SeasonPageProps) { className="w-1/6 sm:1/8 h-1/4 btn btn-sm btn-error flex" > -

- Delete Season +

+ Delete +
Season

)} From 6e7f84107b4f4e3a27ddeeddcaf2fcd1a8def019 Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Mon, 31 Mar 2025 17:32:09 -0400 Subject: [PATCH 047/168] Fixed some profile tests --- lib/testutils/TestUtils.ts | 16 ++++++++++------ pages/profile.tsx | 2 +- tests/e2e/misc.spec.ts | 4 ++-- tests/e2e/profile.spec.ts | 22 +++++++++++++++++----- 4 files changed, 30 insertions(+), 14 deletions(-) diff --git a/lib/testutils/TestUtils.ts b/lib/testutils/TestUtils.ts index 6d4c01df..3ff8963a 100644 --- a/lib/testutils/TestUtils.ts +++ b/lib/testutils/TestUtils.ts @@ -174,9 +174,18 @@ export namespace PlaywrightUtils { return api; } - export async function signIn(context: BrowserContext) { + export async function signUp(context: BrowserContext) { const { sessionToken, user } = await getTestClientApi().testSignIn(); + await signIn(context, sessionToken); + + return { + sessionToken, + user, + }; + } + + export async function signIn(context: BrowserContext, sessionToken: string) { await context.addCookies([ { name: "next-auth.session-token", @@ -189,10 +198,5 @@ export namespace PlaywrightUtils { expires: Math.floor(Date.now() / 1000) + 60 * 60 * 24, // 1 day expiration }, ]); - - return { - sessionToken, - user, - }; } } diff --git a/pages/profile.tsx b/pages/profile.tsx index b3db52db..8bc972f5 100644 --- a/pages/profile.tsx +++ b/pages/profile.tsx @@ -91,7 +91,7 @@ export default function Profile(props: { teamList: Team[] }) { hideMenu={false} title="Profile" > - + {/* */} { - const { user } = await PlaywrightUtils.signIn(context); +test("Sign up function signs up", async ({ page, context }) => { + const { user } = await PlaywrightUtils.signUp(context); const sessionToken = await context .cookies() diff --git a/tests/e2e/profile.spec.ts b/tests/e2e/profile.spec.ts index 15919ebd..b82ac4d7 100644 --- a/tests/e2e/profile.spec.ts +++ b/tests/e2e/profile.spec.ts @@ -1,12 +1,24 @@ import { PlaywrightUtils } from "@/lib/testutils/TestUtils"; import { test, expect } from "@playwright/test"; -test("Displays username", async ({ page, context }) => { - const { user } = await PlaywrightUtils.signIn(context); +test("Displays sign in page when not signed in", async ({ page }) => { + await page.goto("/profile"); + + await expect( + page.getByRole("heading", { name: /sign in/i }).first(), + ).toBeVisible(); +}); + +test("Displays user information when signed in", async ({ page, context }) => { + const { user } = await PlaywrightUtils.signUp(context); await page.goto("/profile"); - expect(page.getByText(new RegExp(user.name!))).toBeVisible({ - timeout: 30000, - }); + await expect( + page.getByRole("heading", { name: /sign in/i }).first(), + ).not.toBeVisible(); + + await expect(page.getByText(user.email!)).toBeVisible(); + await expect(page.getByText(user.slug!)).toBeVisible(); + await expect(page.getByText(new RegExp(user.name!))).toBeVisible(); }); From f690bf6d4f6885eb368976b39adda5e34ebab9a5 Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Mon, 31 Mar 2025 17:42:16 -0400 Subject: [PATCH 048/168] Increase retries and workers for CI in Playwright configuration --- playwright.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index 21b6e8f9..ac46cdbf 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -16,9 +16,9 @@ export default defineConfig({ /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, + retries: process.env.CI ? 5 : 0, /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, + workers: process.env.CI ? 2 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: "html", From 9234dd215c99ef05b1e1b9a4e00abb7ca72d962c Mon Sep 17 00:00:00 2001 From: Davis Becker <143132652+BanEvading@users.noreply.github.com> Date: Mon, 31 Mar 2025 18:29:54 -0400 Subject: [PATCH 049/168] almost got it working. --- lib/client/StatsMath.ts | 15 +++++++++++++++ lib/games.ts | 8 ++++++++ 2 files changed, 23 insertions(+) diff --git a/lib/client/StatsMath.ts b/lib/client/StatsMath.ts index 855d0b68..79dcf30f 100644 --- a/lib/client/StatsMath.ts +++ b/lib/client/StatsMath.ts @@ -1,4 +1,5 @@ import { QuantData, Report } from "../Types"; +import {Reefscape} from "../games" export const SpeakerAutoPoints = 5; export const SpeakerTeleopPoints = 2; @@ -146,3 +147,17 @@ export function ComparativePercentMulti( return results; } + +export function GetMinimum( + quantitativeReports: Report[], + stat: string, +) { + if (!quantitativeReports) return 0; + let minimum = 0; + for (let repo of quantitativeReports) { + if (repo.data.AutoCoralScoredLevelOne > minimum) { + minimum = repo.data["AutoCoralScoredLevelOne"]; + } + } + return minimum; +} diff --git a/lib/games.ts b/lib/games.ts index fff434e3..a676a429 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -22,6 +22,7 @@ import { AmpAutoPoints, AmpTeleopPoints, BooleanAverage, + GetMinimum, MostCommonValue, NumericalTotal, Round, @@ -29,6 +30,7 @@ import { SpeakerTeleopPoints, TrapPoints, } from "./client/StatsMath"; +import { report } from "process"; function getBaseBadges( pitReport: Pitreport | undefined, @@ -1324,6 +1326,12 @@ namespace Reefscape { sections: { Auto: [ { key: "AutoMovedPastStaringLine", label: "Avg Auto Moves Past Start" }, + { + label: "Min Auto L1 Coral", + get(pitData, quantitativeReports) { + GetMinimum(quantitativeReports, "AutoCoralScoredLevelOne") + }, + }, { key: "AutoCoralScoredLevelOne", label: "Avg Amt Of Coral Scored Level One Auto", From bab7bae4dc3bf51bc56f74939e61880b4a9639df Mon Sep 17 00:00:00 2001 From: Davis Becker <143132652+BanEvading@users.noreply.github.com> Date: Mon, 31 Mar 2025 18:30:23 -0400 Subject: [PATCH 050/168] export Reeefscape namespace --- lib/games.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/games.ts b/lib/games.ts index a676a429..95ed324f 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1167,7 +1167,7 @@ export namespace IntoTheDeep { ); } -namespace Reefscape { +export namespace Reefscape { export class QuantitativeData extends QuantData { AutoMovedPastStartingline: boolean = false; From 3122e3b243787589f6936529ee8084db1671888e Mon Sep 17 00:00:00 2001 From: Davis Becker <143132652+BanEvading@users.noreply.github.com> Date: Mon, 31 Mar 2025 18:34:13 -0400 Subject: [PATCH 051/168] got it /functional/ still lots of work to do --- lib/games.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/games.ts b/lib/games.ts index 95ed324f..e17ca0f4 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1329,7 +1329,7 @@ export namespace Reefscape { { label: "Min Auto L1 Coral", get(pitData, quantitativeReports) { - GetMinimum(quantitativeReports, "AutoCoralScoredLevelOne") + return GetMinimum(quantitativeReports!, "AutoCoralScoredLevelOne"); }, }, { From eae73323ac4d15f4575e5fdc6b83cb9fe2659b13 Mon Sep 17 00:00:00 2001 From: Davis Becker <143132652+BanEvading@users.noreply.github.com> Date: Mon, 31 Mar 2025 18:39:09 -0400 Subject: [PATCH 052/168] adding getMaximum --- lib/client/StatsMath.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/client/StatsMath.ts b/lib/client/StatsMath.ts index 79dcf30f..dc480847 100644 --- a/lib/client/StatsMath.ts +++ b/lib/client/StatsMath.ts @@ -1,5 +1,5 @@ import { QuantData, Report } from "../Types"; -import {Reefscape} from "../games" +import { Reefscape } from "../games"; export const SpeakerAutoPoints = 5; export const SpeakerTeleopPoints = 2; @@ -161,3 +161,17 @@ export function GetMinimum( } return minimum; } + +export function GetMaximum( + quantitativeReports: Report[], + stat: string, +) { + if (!quantitativeReports) return 0; + let minimum = 0; + for (let repo of quantitativeReports) { + if (repo.data.AutoCoralScoredLevelOne > minimum) { + minimum = repo.data["AutoCoralScoredLevelOne"]; + } + } + return minimum; +} \ No newline at end of file From ca22b80ee8438cf88f8a0bf5598cbd1ab6b6fc77 Mon Sep 17 00:00:00 2001 From: Davis Becker <143132652+BanEvading@users.noreply.github.com> Date: Mon, 31 Mar 2025 18:39:37 -0400 Subject: [PATCH 053/168] fixing the math in GetMinimum --- lib/client/StatsMath.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/client/StatsMath.ts b/lib/client/StatsMath.ts index dc480847..d992fb3c 100644 --- a/lib/client/StatsMath.ts +++ b/lib/client/StatsMath.ts @@ -153,9 +153,9 @@ export function GetMinimum( stat: string, ) { if (!quantitativeReports) return 0; - let minimum = 0; + let minimum = quantitativeReports[0].data["AutoCoralScoredLevelOne"]; for (let repo of quantitativeReports) { - if (repo.data.AutoCoralScoredLevelOne > minimum) { + if (repo.data.AutoCoralScoredLevelOne < minimum) { minimum = repo.data["AutoCoralScoredLevelOne"]; } } @@ -174,4 +174,4 @@ export function GetMaximum( } } return minimum; -} \ No newline at end of file +} From ee987e343c7c6c3227739e8b44e5917ffca34d75 Mon Sep 17 00:00:00 2001 From: Davis Becker <143132652+BanEvading@users.noreply.github.com> Date: Mon, 31 Mar 2025 18:42:03 -0400 Subject: [PATCH 054/168] switch from hardcode to parameter for determining calculated stat --- lib/client/StatsMath.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/client/StatsMath.ts b/lib/client/StatsMath.ts index d992fb3c..f347332d 100644 --- a/lib/client/StatsMath.ts +++ b/lib/client/StatsMath.ts @@ -153,10 +153,10 @@ export function GetMinimum( stat: string, ) { if (!quantitativeReports) return 0; - let minimum = quantitativeReports[0].data["AutoCoralScoredLevelOne"]; + let minimum = quantitativeReports[0].data[stat]; for (let repo of quantitativeReports) { if (repo.data.AutoCoralScoredLevelOne < minimum) { - minimum = repo.data["AutoCoralScoredLevelOne"]; + minimum = repo.data[stat]; } } return minimum; From 3ccbbe199a40bf2cb37e5482e7a668f64c3ae067 Mon Sep 17 00:00:00 2001 From: Davis Becker <143132652+BanEvading@users.noreply.github.com> Date: Mon, 31 Mar 2025 18:43:49 -0400 Subject: [PATCH 055/168] Add comments explaining what the functions do. --- lib/client/StatsMath.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/client/StatsMath.ts b/lib/client/StatsMath.ts index f347332d..a0e78d97 100644 --- a/lib/client/StatsMath.ts +++ b/lib/client/StatsMath.ts @@ -148,6 +148,7 @@ export function ComparativePercentMulti( return results; } +//Takes a list of Quantitative reports and a stat and returns the minimum value recorded for said stat export function GetMinimum( quantitativeReports: Report[], stat: string, @@ -162,6 +163,7 @@ export function GetMinimum( return minimum; } +//Takes a list of Quantitative reports and a stat and returns the maximum value recorded for said stat export function GetMaximum( quantitativeReports: Report[], stat: string, From fab5527477f088eef5a39215f899486a20507615 Mon Sep 17 00:00:00 2001 From: Davis Becker <143132652+BanEvading@users.noreply.github.com> Date: Mon, 31 Mar 2025 18:45:01 -0400 Subject: [PATCH 056/168] Change GetMaximum variable name from minimum to maximum --- lib/client/StatsMath.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/client/StatsMath.ts b/lib/client/StatsMath.ts index a0e78d97..4b54538a 100644 --- a/lib/client/StatsMath.ts +++ b/lib/client/StatsMath.ts @@ -169,11 +169,11 @@ export function GetMaximum( stat: string, ) { if (!quantitativeReports) return 0; - let minimum = 0; + let maximum = 0; for (let repo of quantitativeReports) { - if (repo.data.AutoCoralScoredLevelOne > minimum) { - minimum = repo.data["AutoCoralScoredLevelOne"]; + if (repo.data.AutoCoralScoredLevelOne > maximum) { + maximum = repo.data["AutoCoralScoredLevelOne"]; } } - return minimum; + return maximum; } From e5069984f548926beae8152acb8b129afe01e74e Mon Sep 17 00:00:00 2001 From: Davis Becker <143132652+BanEvading@users.noreply.github.com> Date: Tue, 1 Apr 2025 16:03:40 -0400 Subject: [PATCH 057/168] Move labels below. --- lib/games.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/games.ts b/lib/games.ts index e17ca0f4..520dd7c7 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -31,6 +31,7 @@ import { TrapPoints, } from "./client/StatsMath"; import { report } from "process"; +import { GetMaximum } from './client/StatsMath'; function getBaseBadges( pitReport: Pitreport | undefined, @@ -1326,6 +1327,10 @@ export namespace Reefscape { sections: { Auto: [ { key: "AutoMovedPastStaringLine", label: "Avg Auto Moves Past Start" }, + { + key: "AutoCoralScoredLevelOne", + label: "Avg Amt Of Coral Scored Level One Auto", + }, { label: "Min Auto L1 Coral", get(pitData, quantitativeReports) { @@ -1333,8 +1338,10 @@ export namespace Reefscape { }, }, { - key: "AutoCoralScoredLevelOne", - label: "Avg Amt Of Coral Scored Level One Auto", + label: "Max Auto L1 Coral", + get(pitData, quantitativeReports) { + return GetMaximum(quantitativeReports!, "AutoCoralScoredLevelOne"); + }, }, { key: "AutoCoralScoredLevelTwo", From e481a18bc0773a2277252617c0e36cadd54fb58d Mon Sep 17 00:00:00 2001 From: Davis Becker <143132652+BanEvading@users.noreply.github.com> Date: Tue, 1 Apr 2025 16:19:31 -0400 Subject: [PATCH 058/168] commit --- lib/games.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/games.ts b/lib/games.ts index 520dd7c7..8ea8a794 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1329,24 +1329,37 @@ export namespace Reefscape { { key: "AutoMovedPastStaringLine", label: "Avg Auto Moves Past Start" }, { key: "AutoCoralScoredLevelOne", - label: "Avg Amt Of Coral Scored Level One Auto", + label: ">>Avg Amt Of Coral Scored Level One Auto", }, { - label: "Min Auto L1 Coral", + label: ">>>>>Min Auto L1 Coral", get(pitData, quantitativeReports) { return GetMinimum(quantitativeReports!, "AutoCoralScoredLevelOne"); }, }, { - label: "Max Auto L1 Coral", + label: ">>>>>Max Auto L1 Coral", get(pitData, quantitativeReports) { return GetMaximum(quantitativeReports!, "AutoCoralScoredLevelOne"); }, }, { key: "AutoCoralScoredLevelTwo", - label: "Avg Amt Of Coral Scored Level Two Auto", + label: ">>Avg Amt Of Coral Scored Level Two Auto", }, + { + label: ">>>>>Min Auto L2 Coral", + get(pitData, quantitativeReports) { + return GetMinimum(quantitativeReports!, "AutoCoralScoredLevelTwo"); + }, + }, + { + label: ">>>>>Max Auto L2 Coral", + get(pitData, quantitativeReports) { + return GetMaximum(quantitativeReports!, "AutoCoralScoredLevelTwo"); + }, + }, + {label:""}, { key: "AutoCoralScoredLevelThree", label: "Avg Amt Of Coral Scored Level Three Auto", From a5953b994d0ce5c4f72dd0568d8696c5fbc764d7 Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Tue, 1 Apr 2025 16:26:26 -0400 Subject: [PATCH 059/168] Don't add BASE_URL twice --- lib/testutils/TestUtils.ts | 26 +++++++++++++++++++++++--- package-lock.json | 16 ++++++++-------- package.json | 2 +- playwright.config.ts | 3 ++- tests/e2e/misc.spec.ts | 6 +----- tests/e2e/profile.spec.ts | 4 ++++ 6 files changed, 39 insertions(+), 18 deletions(-) diff --git a/lib/testutils/TestUtils.ts b/lib/testutils/TestUtils.ts index 3ff8963a..fe8437b4 100644 --- a/lib/testutils/TestUtils.ts +++ b/lib/testutils/TestUtils.ts @@ -167,9 +167,14 @@ export namespace PlaywrightUtils { export function getTestClientApi() { const api = new ClientApi(); - // Relative requests don't work in Playwright apparentl - api.requestHelper.baseUrl = - process.env.BASE_URL_FOR_PLAYWRIGHT + api.requestHelper.baseUrl; + // Relative requests don't work in Playwright apparently + if ( + process.env.BASE_URL_FOR_PLAYWRIGHT && + !api.requestHelper.baseUrl.startsWith(process.env.BASE_URL_FOR_PLAYWRIGHT) + ) { + api.requestHelper.baseUrl = + process.env.BASE_URL_FOR_PLAYWRIGHT + api.requestHelper.baseUrl; + } return api; } @@ -177,6 +182,10 @@ export namespace PlaywrightUtils { export async function signUp(context: BrowserContext) { const { sessionToken, user } = await getTestClientApi().testSignIn(); + if (!sessionToken || !user) { + throw new Error("Failed to sign in"); + } + await signIn(context, sessionToken); return { @@ -199,4 +208,15 @@ export namespace PlaywrightUtils { }, ]); } + + export async function getUser(context: BrowserContext) { + const res = await context.request.get("/api/auth/session"); + + if (res.ok()) { + const { user } = await res.json(); + return user as User; + } else { + throw new Error("Failed to get user"); + } + } } diff --git a/package-lock.json b/package-lock.json index 4759a53c..23839044 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,7 +51,7 @@ "ts-node": "^10.9.2", "tsx": "^4.19.3", "typescript": "5.7.3", - "unified-api-nextjs": "^1.0.9" + "unified-api-nextjs": "^1.1.2" }, "devDependencies": { "@eslint/eslintrc": "^3.2.0", @@ -11269,20 +11269,20 @@ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" }, "node_modules/unified-api": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/unified-api/-/unified-api-1.0.19.tgz", - "integrity": "sha512-8fQ/fnOTHtEzNTQPPQ+vlFP3jq4vje+7D8adn1yh2TETIkEvgaVvxFbfG4BKyYyp1ZRJxvLH42kYz14WlU8/6A==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/unified-api/-/unified-api-1.1.3.tgz", + "integrity": "sha512-Q16K/2QggHcFMTMSguTmodae//+cbUdxGhL9+Qi0aUWVJrRsMF35nrH6Lf97nhCZFJ5SqoQuhWiVbVy37t3lzg==", "dependencies": { "omit-call-signature": "^1.0.16" } }, "node_modules/unified-api-nextjs": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/unified-api-nextjs/-/unified-api-nextjs-1.0.10.tgz", - "integrity": "sha512-EkW0g3shBuLkbCkX/I3uQHq+ug31rzsHNGEXnOZc2UHypaD5OfQjKV5irwwGtc0iucwYzkDvKKd8aX6dgXXTkQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/unified-api-nextjs/-/unified-api-nextjs-1.1.2.tgz", + "integrity": "sha512-D8rzvYzuEwXnQjqNqUG9/YsFLRqqFtzs/cl9ZKh710heNpBvktVDfGC6w+PSVhx3WvrPSQ6YHdHRTo4In8KDDQ==", "dependencies": { "next": "^15.1.2", - "unified-api": "^1.0.19" + "unified-api": "^1.1.3" } }, "node_modules/update-browserslist-db": { diff --git a/package.json b/package.json index 066245ab..16213bed 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "ts-node": "^10.9.2", "tsx": "^4.19.3", "typescript": "5.7.3", - "unified-api-nextjs": "^1.0.9" + "unified-api-nextjs": "^1.1.2" }, "devDependencies": { "@eslint/eslintrc": "^3.2.0", diff --git a/playwright.config.ts b/playwright.config.ts index ac46cdbf..d7f6ec8e 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -77,6 +77,7 @@ export default defineConfig({ command: "npm run e2e-start-server", url: baseURL, reuseExistingServer: !process.env.CI, - timeout: 5 * 60 * 1000, // 5 minutes + timeout: 5 * 60 * 1000, // 5 minutes, + stdout: "pipe", }, }); diff --git a/tests/e2e/misc.spec.ts b/tests/e2e/misc.spec.ts index f26a12f6..66bdc091 100644 --- a/tests/e2e/misc.spec.ts +++ b/tests/e2e/misc.spec.ts @@ -15,12 +15,8 @@ test("Sign up function signs up", async ({ page, context }) => { expect(sessionToken).toBeDefined(); expect(sessionToken).not.toBe(""); - const res = await context.request.get("/api/auth/session"); - - const foundUser = (await res.json()).user; - + const foundUser = await PlaywrightUtils.getUser(context); if (foundUser) foundUser.id = user.id; // ID mismatches are normal - expect(res.status()).toBe(200); expect(foundUser).toEqual(user as any); }); diff --git a/tests/e2e/profile.spec.ts b/tests/e2e/profile.spec.ts index b82ac4d7..bd37705c 100644 --- a/tests/e2e/profile.spec.ts +++ b/tests/e2e/profile.spec.ts @@ -1,3 +1,4 @@ +import { wait } from "@/lib/client/ClientUtils"; import { PlaywrightUtils } from "@/lib/testutils/TestUtils"; import { test, expect } from "@playwright/test"; @@ -14,6 +15,9 @@ test("Displays user information when signed in", async ({ page, context }) => { await page.goto("/profile"); + const foundUser = await PlaywrightUtils.getUser(context); + expect(foundUser._id).toEqual(user._id); + await expect( page.getByRole("heading", { name: /sign in/i }).first(), ).not.toBeVisible(); From 3a421558c51aa43dbc3dd463450dfd70b21a3f38 Mon Sep 17 00:00:00 2001 From: Davis Becker <143132652+BanEvading@users.noreply.github.com> Date: Tue, 1 Apr 2025 16:36:43 -0400 Subject: [PATCH 060/168] Add more stats --- lib/games.ts | 45 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/lib/games.ts b/lib/games.ts index 8ea8a794..72989a73 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -31,7 +31,7 @@ import { TrapPoints, } from "./client/StatsMath"; import { report } from "process"; -import { GetMaximum } from './client/StatsMath'; +import { GetMaximum } from "./client/StatsMath"; function getBaseBadges( pitReport: Pitreport | undefined, @@ -1329,45 +1329,74 @@ export namespace Reefscape { { key: "AutoMovedPastStaringLine", label: "Avg Auto Moves Past Start" }, { key: "AutoCoralScoredLevelOne", - label: ">>Avg Amt Of Coral Scored Level One Auto", + label: "Avg Amt Of Coral Scored Level One Auto", }, { - label: ">>>>>Min Auto L1 Coral", + label: "> Min Auto L1 Coral", get(pitData, quantitativeReports) { return GetMinimum(quantitativeReports!, "AutoCoralScoredLevelOne"); }, }, { - label: ">>>>>Max Auto L1 Coral", + label: "> Max Auto L1 Coral", get(pitData, quantitativeReports) { return GetMaximum(quantitativeReports!, "AutoCoralScoredLevelOne"); }, }, { key: "AutoCoralScoredLevelTwo", - label: ">>Avg Amt Of Coral Scored Level Two Auto", + label: "Avg Amt Of Coral Scored Level Two Auto", }, { - label: ">>>>>Min Auto L2 Coral", + label: "> Min Auto L2 Coral", get(pitData, quantitativeReports) { return GetMinimum(quantitativeReports!, "AutoCoralScoredLevelTwo"); }, }, { - label: ">>>>>Max Auto L2 Coral", + label: "> Max Auto L2 Coral", get(pitData, quantitativeReports) { return GetMaximum(quantitativeReports!, "AutoCoralScoredLevelTwo"); }, }, - {label:""}, { key: "AutoCoralScoredLevelThree", label: "Avg Amt Of Coral Scored Level Three Auto", }, + { + label: "> Min Auto L3 Coral", + get(pitData, quantitativeReports) { + return GetMinimum( + quantitativeReports!, + "AutoCoralScoredLevelThree", + ); + }, + }, + { + label: "> Max Auto L3 Coral", + get(pitData, quantitativeReports) { + return GetMaximum( + quantitativeReports!, + "AutoCoralScoredLevelThree", + ); + }, + }, { key: "AutoCoralScoredLevelFour", label: "Avg Amt Of Coral Scored Level Four Auto", }, + { + label: "> Min Auto L4 Coral", + get(pitData, quantitativeReports) { + return GetMinimum(quantitativeReports!, "AutoCoralScoredLevelFour"); + }, + }, + { + label: "> Max Auto L4 Coral", + get(pitData, quantitativeReports) { + return GetMaximum(quantitativeReports!, "AutoCoralScoredLevelFour"); + }, + }, { label: "Avg Auto Coral", get(pitData, quantitativeReports) { From a571a31363b6f442145aa1ac092dde04318c3f5b Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Tue, 1 Apr 2025 16:38:39 -0400 Subject: [PATCH 061/168] Shard playwright CI tests --- .github/workflows/ci.yml | 24 +----------- .github/workflows/e2e_test.yml | 70 ++++++++++++++++++++++++++++++++++ playwright.config.ts | 4 +- 3 files changed, 73 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/e2e_test.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 37b1c909..6a7cd91e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,26 +68,4 @@ jobs: run: npm run lint e2e_test: - timeout-minutes: 60 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: lts/* - - name: Install dependencies - run: npm ci - - name: Install Playwright Browsers - run: npx playwright install --with-deps - - name: Start MongoDB - uses: supercharge/mongodb-github-action@1.12.0 - with: - mongodb-version: "8.0" - - name: Run Playwright tests - run: npm run e2e - - uses: actions/upload-artifact@v4 - if: ${{ !cancelled() }} - with: - name: playwright-report - path: playwright-report/ - retention-days: 30 + uses: ./.github/workflows/e2e_test.yml diff --git a/.github/workflows/e2e_test.yml b/.github/workflows/e2e_test.yml new file mode 100644 index 00000000..4cdc843d --- /dev/null +++ b/.github/workflows/e2e_test.yml @@ -0,0 +1,70 @@ +name: Playwright Tests +on: [workflow_dispatch, workflow_call] +jobs: + e2e_tests: + timeout-minutes: 60 + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + shardIndex: [1, 2, 3, 4] + shardTotal: [4] + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: lts/* + + - name: Install dependencies + run: npm ci + + - name: Install Playwright browsers + run: npx playwright install --with-deps + + - name: Run Playwright tests + run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} + + - name: Start MongoDB + uses: supercharge/mongodb-github-action@1.12.0 + with: + mongodb-version: "8.0" + + - name: Upload blob report to GitHub Actions Artifacts + if: ${{ !cancelled() }} + uses: actions/upload-artifact@v4 + with: + name: blob-report-${{ matrix.shardIndex }} + path: blob-report + retention-days: 1 + + merge_reports: + # Merge reports after playwright-tests, even if some shards have failed + if: ${{ !cancelled() }} + needs: [e2e_tests] + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: Install dependencies + run: npm ci + + - name: Download blob reports from GitHub Actions Artifacts + uses: actions/download-artifact@v4 + with: + path: all-blob-reports + pattern: blob-report-* + merge-multiple: true + + - name: Merge into HTML Report + run: npx playwright merge-reports --reporter html ./all-blob-reports + + - name: Upload HTML report + uses: actions/upload-artifact@v4 + with: + name: html-report--attempt-${{ github.run_attempt }} + path: playwright-report + retention-days: 14 diff --git a/playwright.config.ts b/playwright.config.ts index d7f6ec8e..c0a79019 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -18,9 +18,9 @@ export default defineConfig({ /* Retry on CI only */ retries: process.env.CI ? 5 : 0, /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 2 : undefined, + workers: process.env.CI ? 4 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: "html", + reporter: process.env.CI ? "blob" : "html", globalSetup: require.resolve("./lib/testutils/PlaywrightSetup"), From cdb45f2ac6ef3d3e2c960339e51400f6b77efc44 Mon Sep 17 00:00:00 2001 From: Davis Becker <143132652+BanEvading@users.noreply.github.com> Date: Tue, 1 Apr 2025 16:38:48 -0400 Subject: [PATCH 062/168] Remove hardcode in GetMax --- lib/client/StatsMath.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/client/StatsMath.ts b/lib/client/StatsMath.ts index 4b54538a..0055d475 100644 --- a/lib/client/StatsMath.ts +++ b/lib/client/StatsMath.ts @@ -172,7 +172,7 @@ export function GetMaximum( let maximum = 0; for (let repo of quantitativeReports) { if (repo.data.AutoCoralScoredLevelOne > maximum) { - maximum = repo.data["AutoCoralScoredLevelOne"]; + maximum = repo.data[stat]; } } return maximum; From 65e6b133951a2be5ec63d2f4b16ef9dc3269fb13 Mon Sep 17 00:00:00 2001 From: Davis Becker <143132652+BanEvading@users.noreply.github.com> Date: Tue, 1 Apr 2025 16:39:33 -0400 Subject: [PATCH 063/168] Change hardcode in check to based on stat --- lib/client/StatsMath.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/client/StatsMath.ts b/lib/client/StatsMath.ts index 0055d475..1b7f22c4 100644 --- a/lib/client/StatsMath.ts +++ b/lib/client/StatsMath.ts @@ -156,7 +156,7 @@ export function GetMinimum( if (!quantitativeReports) return 0; let minimum = quantitativeReports[0].data[stat]; for (let repo of quantitativeReports) { - if (repo.data.AutoCoralScoredLevelOne < minimum) { + if (repo.data[stat] < minimum) { minimum = repo.data[stat]; } } @@ -171,7 +171,7 @@ export function GetMaximum( if (!quantitativeReports) return 0; let maximum = 0; for (let repo of quantitativeReports) { - if (repo.data.AutoCoralScoredLevelOne > maximum) { + if (repo.data[stat] > maximum) { maximum = repo.data[stat]; } } From a78dc1fc0f74a83e25e785631b08903b7828fd26 Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Tue, 1 Apr 2025 16:43:09 -0400 Subject: [PATCH 064/168] Update Playwright test command to use npm script --- .github/workflows/e2e_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e_test.yml b/.github/workflows/e2e_test.yml index 4cdc843d..32305dfd 100644 --- a/.github/workflows/e2e_test.yml +++ b/.github/workflows/e2e_test.yml @@ -23,7 +23,7 @@ jobs: run: npx playwright install --with-deps - name: Run Playwright tests - run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} + run: npm run e2e --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} - name: Start MongoDB uses: supercharge/mongodb-github-action@1.12.0 From fe778512dc6fcc5dcf95729b96e9b21b7630917a Mon Sep 17 00:00:00 2001 From: Davis Becker <143132652+BanEvading@users.noreply.github.com> Date: Tue, 1 Apr 2025 16:46:24 -0400 Subject: [PATCH 065/168] Complete min/max coverage for auto --- lib/games.ts | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/lib/games.ts b/lib/games.ts index 72989a73..0182fba1 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1419,14 +1419,50 @@ export namespace Reefscape { key: "AutoAlgaeRemovedFromReef", label: "Avg Amt of Algae Removed From Reef", }, + { + label: "> Min Algae Removed From Reef", + get(pitData, quantitativeReports) { + return GetMinimum(quantitativeReports!, "AutoAlgaeRemovedFromReef"); + }, + }, + { + label: "> Max Algae Removed From Reef", + get(pitData, quantitativeReports) { + return GetMaximum(quantitativeReports!, "AutoAlgaeRemovedFromReef"); + }, + }, { key: "AutoAlgaeScoredProcessor", label: "Avg Amt of Algae Scored Processor Auto", }, + { + label: "> Min Algae Scored In Processor", + get(pitData, quantitativeReports) { + return GetMinimum(quantitativeReports!, "AutoAlgaeScoredProcessor"); + }, + }, + { + label: "> Max Algae Scored In Processor", + get(pitData, quantitativeReports) { + return GetMaximum(quantitativeReports!, "AutoAlgaeScoredProcessor"); + }, + }, { key: "AutoAlgaeScoredNet", label: "Avg Amt of Algae Scored Net Auto", }, + { + label: "> Min Algae Scored In Net", + get(pitData, quantitativeReports) { + return GetMinimum(quantitativeReports!, "AutoAlgaeScoredNet"); + }, + }, + { + label: "> Max Algae Scored In Net", + get(pitData, quantitativeReports) { + return GetMaximum(quantitativeReports!, "AutoAlgaeScoredNet"); + }, + }, ], Teleop: [ { key: "GroundIntake", label: "Has Ground Intake?" }, From e3924a96aff2b4972551006078ed7f768db21cc3 Mon Sep 17 00:00:00 2001 From: Davis Becker <143132652+BanEvading@users.noreply.github.com> Date: Tue, 1 Apr 2025 16:46:41 -0400 Subject: [PATCH 066/168] Remove faulty stat --- lib/games.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/games.ts b/lib/games.ts index 0182fba1..99a336bf 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1326,7 +1326,6 @@ export namespace Reefscape { const statsLayout: StatsLayout = { sections: { Auto: [ - { key: "AutoMovedPastStaringLine", label: "Avg Auto Moves Past Start" }, { key: "AutoCoralScoredLevelOne", label: "Avg Amt Of Coral Scored Level One Auto", From 36389f9ada03afebf2139fb2226c41564ea740ae Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Tue, 1 Apr 2025 16:47:30 -0400 Subject: [PATCH 067/168] Update CI workflow to set permissions and modify Playwright test command --- .github/workflows/ci.yml | 3 +++ .github/workflows/e2e_test.yml | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6a7cd91e..987b7490 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,3 +69,6 @@ jobs: e2e_test: uses: ./.github/workflows/e2e_test.yml + permissions: + contents: read + pull-requests: write diff --git a/.github/workflows/e2e_test.yml b/.github/workflows/e2e_test.yml index 32305dfd..1cf1cfcd 100644 --- a/.github/workflows/e2e_test.yml +++ b/.github/workflows/e2e_test.yml @@ -22,14 +22,14 @@ jobs: - name: Install Playwright browsers run: npx playwright install --with-deps - - name: Run Playwright tests - run: npm run e2e --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} - - name: Start MongoDB uses: supercharge/mongodb-github-action@1.12.0 with: mongodb-version: "8.0" + - name: Run Playwright tests + run: npx cross-env NODE_ENV=test playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} + - name: Upload blob report to GitHub Actions Artifacts if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 From 51c018dc663f1f675f52a4be3b228bf56ed10a32 Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Tue, 1 Apr 2025 16:51:36 -0400 Subject: [PATCH 068/168] Bump version to 1.3.0, add note about requiring individual shard in GitHub --- .github/workflows/e2e_test.yml | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e_test.yml b/.github/workflows/e2e_test.yml index 1cf1cfcd..e79336ca 100644 --- a/.github/workflows/e2e_test.yml +++ b/.github/workflows/e2e_test.yml @@ -7,6 +7,7 @@ jobs: strategy: fail-fast: false matrix: + # Make sure to require each shard in GitHub! shardIndex: [1, 2, 3, 4] shardTotal: [4] steps: diff --git a/package.json b/package.json index 16213bed..68384fe4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sj3", - "version": "1.2.26", + "version": "1.3.0", "private": true, "repository": "https://github.com/Decatur-Robotics/Gearbox", "license": "CC BY-NC-SA 4.0", From e2bf7e6eda8ed15279e76b37cd87a1083467a3f0 Mon Sep 17 00:00:00 2001 From: Davis Becker <143132652+BanEvading@users.noreply.github.com> Date: Tue, 1 Apr 2025 16:55:39 -0400 Subject: [PATCH 069/168] Complete coverage for teleop. --- lib/games.ts | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/lib/games.ts b/lib/games.ts index 99a336bf..73d6ef2f 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1469,18 +1469,90 @@ export namespace Reefscape { key: "TeleopCoralScoredLevelOne", label: "Avg Amt Of Coral Scored Level One Teleop", }, + { + label: "> Min L1 Coral Scored", + get(pitData, quantitativeReports) { + return GetMinimum( + quantitativeReports!, + "TeleopCoralScoredLevelOne", + ); + }, + }, + { + label: "> Max L1 Coral Scored", + get(pitData, quantitativeReports) { + return GetMaximum( + quantitativeReports!, + "TeleopCoralScoredLevelOne", + ); + }, + }, { key: "TeleopCoralScoredLevelTwo", label: "Avg Amt Of Coral Scored Level Two Teleop", }, + { + label: "> Min L2 Coral Scored", + get(pitData, quantitativeReports) { + return GetMinimum( + quantitativeReports!, + "TeleopCoralScoredLevelTwo", + ); + }, + }, + { + label: "> Max L2 Coral Scored", + get(pitData, quantitativeReports) { + return GetMaximum( + quantitativeReports!, + "TeleopCoralScoredLevelTwo", + ); + }, + }, { key: "TeleopCoralScoredLevelThree", label: "Avg Amt Of Coral Scored Level Three Teleop", }, + { + label: "> Min L3 Coral Scored", + get(pitData, quantitativeReports) { + return GetMinimum( + quantitativeReports!, + "TeleopCoralScoredLevelThree", + ); + }, + }, + { + label: "> Max L3 Coral Scored", + get(pitData, quantitativeReports) { + return GetMaximum( + quantitativeReports!, + "TeleopCoralScoredLevelThree", + ); + }, + }, { key: "TeleopCoralScoredLevelFour", label: "Avg Amt Of Coral Scored Level Four Teleop", }, + { + label: "> Min L4 Coral Scored", + get(pitData, quantitativeReports) { + return GetMinimum( + quantitativeReports!, + "TeleopCoralScoredLevelFour", + ); + }, + }, + { + label: "> Max L4 Coral Scored", + get(pitData, quantitativeReports) { + return GetMaximum( + quantitativeReports!, + "TeleopCoralScoredLevelFour", + ); + }, + }, { label: "Avg Teleop Coral", get(pitData, quantitativeReports) { @@ -1503,14 +1575,62 @@ export namespace Reefscape { key: "TeleopAlgaeRemovedFromReef", label: "Avg Amt of Algae Removed From Reef", }, + { + label: "> Min Algae Removed From Reef", + get(pitData, quantitativeReports) { + return GetMinimum( + quantitativeReports!, + "TeleopAlgaeRemovedFromReef", + ); + }, + }, + { + label: "> Max Algae Removed From Reef", + get(pitData, quantitativeReports) { + return GetMaximum( + quantitativeReports!, + "TeleopAlgaeRemovedFromReef", + ); + }, + }, { key: "TeleopAlgaeScoredProcessor", label: "Avg Amt of Algae Scored Processor Teleop", }, + { + label: "> Min Algae Scored In Processor", + get(pitData, quantitativeReports) { + return GetMinimum( + quantitativeReports!, + "TeleopAlgaeScoredProcessor", + ); + }, + }, + { + label: "> Max Algae Scored In Processor", + get(pitData, quantitativeReports) { + return GetMaximum( + quantitativeReports!, + "TeleopAlgaeScoredProcessor", + ); + }, + }, { key: "TeleopAlgaeScoredNet", label: "Avg Amt of Algae Scored Net Teleop", }, + { + label: "> Min Algae Scored In Net", + get(pitData, quantitativeReports) { + return GetMinimum(quantitativeReports!, "TeleopAlgaeScorednet"); + }, + }, + { + label: "> Max Algae Scored In Net", + get(pitData, quantitativeReports) { + return GetMaximum(quantitativeReports!, "TeleopAlgaeScorednet"); + }, + }, ], }, getGraphDots: function ( From 71f1ea5cf5252a421ee0982d44b382102feb01ab Mon Sep 17 00:00:00 2001 From: Davis Becker <143132652+BanEvading@users.noreply.github.com> Date: Tue, 1 Apr 2025 17:03:49 -0400 Subject: [PATCH 070/168] Fix Breaking Typos --- lib/games.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/games.ts b/lib/games.ts index 73d6ef2f..da8e08ac 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1622,13 +1622,13 @@ export namespace Reefscape { { label: "> Min Algae Scored In Net", get(pitData, quantitativeReports) { - return GetMinimum(quantitativeReports!, "TeleopAlgaeScorednet"); + return GetMinimum(quantitativeReports!, "TeleopAlgaeScoredNet"); }, }, { label: "> Max Algae Scored In Net", get(pitData, quantitativeReports) { - return GetMaximum(quantitativeReports!, "TeleopAlgaeScorednet"); + return GetMaximum(quantitativeReports!, "TeleopAlgaeScoredNet"); }, }, ], From 8754455f1e6d078db3d168efd43d76e70de7a088 Mon Sep 17 00:00:00 2001 From: Davis Becker <143132652+BanEvading@users.noreply.github.com> Date: Tue, 1 Apr 2025 17:04:18 -0400 Subject: [PATCH 071/168] Remove invalid stat --- lib/games.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/games.ts b/lib/games.ts index da8e08ac..54507b65 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1464,7 +1464,6 @@ export namespace Reefscape { }, ], Teleop: [ - { key: "GroundIntake", label: "Has Ground Intake?" }, { key: "TeleopCoralScoredLevelOne", label: "Avg Amt Of Coral Scored Level One Teleop", From bfcd3952c3959078e3eaba09a112c2d1df24cb04 Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Tue, 1 Apr 2025 17:13:24 -0400 Subject: [PATCH 072/168] Repeat tests in CI --- pages/profile.tsx | 2 ++ playwright.config.ts | 1 + tests/e2e/profile.spec.ts | 22 +++++++++++++++++++++- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/pages/profile.tsx b/pages/profile.tsx index 8bc972f5..886d8448 100644 --- a/pages/profile.tsx +++ b/pages/profile.tsx @@ -103,12 +103,14 @@ export default function Profile(props: { teamList: Team[] }) { setNewName(e.target.value)} defaultValue={newName} + placeholder="New Name" className="input" /> ) : (

{user?.name}

)} + +
+ + + ); +} diff --git a/pages/profile.tsx b/pages/profile.tsx index 886d8448..c7624d80 100644 --- a/pages/profile.tsx +++ b/pages/profile.tsx @@ -22,6 +22,8 @@ import { signOut } from "next-auth/react"; import XpProgressBar from "@/components/XpProgressBar"; import { HiPencilAlt } from "react-icons/hi"; import toast from "react-hot-toast"; +import EditAvatarModal from "@/components/EditAvatarModal"; +import { close } from "fs"; const api = new ClientApi(); @@ -43,6 +45,8 @@ export default function Profile(props: { teamList: Team[] }) { const [editingName, setEditingName] = useState(false); const [newName, setNewName] = useState(); + const [editingAvatar, setEditingAvatar] = useState(false); + useEffect(() => { const loadTeams = async () => { setLoadingTeams(true); @@ -69,6 +73,10 @@ export default function Profile(props: { teamList: Team[] }) { Analytics.requestedToJoinTeam(teamNumber, user?.name ?? "Unknown User"); }; + async function toggleEditingAvatarModal() { + setEditingAvatar(!editingAvatar) + } + async function toggleEditingName() { setEditingName(!editingName); @@ -123,6 +131,12 @@ export default function Profile(props: { teamList: Team[] }) { >
+
+ {editingAvatar && + + } ); } From 5526712dc4738e99370ba5b1dd380934407ac000 Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Thu, 3 Apr 2025 18:36:12 -0400 Subject: [PATCH 085/168] Ctrl + S --- components/EditAvatarModal.tsx | 6 +++++- pages/profile.tsx | 10 +++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/components/EditAvatarModal.tsx b/components/EditAvatarModal.tsx index e457960f..045fffcc 100644 --- a/components/EditAvatarModal.tsx +++ b/components/EditAvatarModal.tsx @@ -16,7 +16,11 @@ export default function EditAvatarModal(props: { async function updateAvatar() { toast.promise( api.changePFP(newAvatar).then(() => location.reload()), - { loading: "Updating profile picture...", success: "Successfully updated profile picture!", error: "Failed to update profile picture!" }, + { + loading: "Updating profile picture...", + success: "Successfully updated profile picture!", + error: "Failed to update profile picture!", + }, ); } diff --git a/pages/profile.tsx b/pages/profile.tsx index c7624d80..b079f5e4 100644 --- a/pages/profile.tsx +++ b/pages/profile.tsx @@ -74,7 +74,7 @@ export default function Profile(props: { teamList: Team[] }) { }; async function toggleEditingAvatarModal() { - setEditingAvatar(!editingAvatar) + setEditingAvatar(!editingAvatar); } async function toggleEditingName() { @@ -284,12 +284,12 @@ export default function Profile(props: { teamList: Team[] }) {
- {editingAvatar && + {editingAvatar && ( - } + )} ); } From 5aeba61d05934bb241c85c13e23d3a29b0f0b498 Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Fri, 4 Apr 2025 08:56:15 -0400 Subject: [PATCH 086/168] Add e2e test for avatar editing functionality --- tests/e2e/profile.spec.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/e2e/profile.spec.ts b/tests/e2e/profile.spec.ts index 9c89506c..6613b9a6 100644 --- a/tests/e2e/profile.spec.ts +++ b/tests/e2e/profile.spec.ts @@ -1,3 +1,4 @@ +import Card from "@/components/Card"; import { PlaywrightUtils } from "@/lib/testutils/TestUtils"; import { test, expect } from "@playwright/test"; @@ -41,3 +42,18 @@ test.describe("Edit user name", () => { await expect(page.getByText("New Name")).toBeVisible(); }); }); + +test.describe('Edit Avatar', () => { + + test("Edit Avatar button displays popup", async ({ page }) => { + await PlaywrightUtils.signUp(page); + + await page.goto("/profile") + + const editAvatarButton = page.getByRole("button", { name: 'Edit Avatar' }) + await editAvatarButton.click(); + + const editAvatarPopup = page.getByTitle('Edit Avatar') + await expect(editAvatarPopup).toBeVisible(); + }); +}); From e79b2e657852e68a70cf003e76a334daab7778fd Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Fri, 4 Apr 2025 09:21:28 -0400 Subject: [PATCH 087/168] Ctrl + S & added Title attribute to card --- components/Card.tsx | 1 + tests/e2e/profile.spec.ts | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/Card.tsx b/components/Card.tsx index b584664a..7ae27ac0 100644 --- a/components/Card.tsx +++ b/components/Card.tsx @@ -14,6 +14,7 @@ export default function Card(props: CardProps) { return (
{color ? ( diff --git a/tests/e2e/profile.spec.ts b/tests/e2e/profile.spec.ts index 6613b9a6..684e67f4 100644 --- a/tests/e2e/profile.spec.ts +++ b/tests/e2e/profile.spec.ts @@ -43,17 +43,16 @@ test.describe("Edit user name", () => { }); }); -test.describe('Edit Avatar', () => { - +test.describe("Edit Avatar", () => { test("Edit Avatar button displays popup", async ({ page }) => { await PlaywrightUtils.signUp(page); - await page.goto("/profile") + await page.goto("/profile"); - const editAvatarButton = page.getByRole("button", { name: 'Edit Avatar' }) + const editAvatarButton = page.getByRole("button", { name: "Edit Avatar" }); await editAvatarButton.click(); - const editAvatarPopup = page.getByTitle('Edit Avatar') + const editAvatarPopup = page.getByTitle("Edit Avatar"); await expect(editAvatarPopup).toBeVisible(); }); }); From e1660bb960cf1123335aacef189bf502e5a34d77 Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Fri, 4 Apr 2025 10:12:20 -0400 Subject: [PATCH 088/168] Add altText prop to Avatar component and add remaining e2e tests for Avatar editing --- components/Avatar.tsx | 3 +- components/EditAvatarModal.tsx | 2 + tests/e2e/profile.spec.ts | 74 ++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/components/Avatar.tsx b/components/Avatar.tsx index d34af4dd..04aef25d 100644 --- a/components/Avatar.tsx +++ b/components/Avatar.tsx @@ -13,6 +13,7 @@ export default function Avatar(props: { className?: string | undefined; online?: boolean; gearSize?: number; + altText?: string; }) { const { session, status } = useCurrentSession(); const user = props.user ?? session?.user; @@ -34,7 +35,7 @@ export default function Avatar(props: { > {"Avatar"}
diff --git a/components/EditAvatarModal.tsx b/components/EditAvatarModal.tsx index 045fffcc..bb53d6fb 100644 --- a/components/EditAvatarModal.tsx +++ b/components/EditAvatarModal.tsx @@ -37,6 +37,7 @@ export default function EditAvatarModal(props: {
@@ -44,6 +45,7 @@ export default function EditAvatarModal(props: { defaultValue={props.currentImg} className="input" onChange={(e) => setNewAvatar(e.target.value)} + placeholder="Enter new avatar url" />
+
+
+ {myMatches + .filter((matchData) => showSubmittedReports || !matchData.completed) + .map((matchData) => ( + + ))} +
+ + + ); +} diff --git a/components/competition/CompHeaderCard.tsx b/components/competition/CompHeaderCard.tsx index 2222446d..012fd8ca 100644 --- a/components/competition/CompHeaderCard.tsx +++ b/components/competition/CompHeaderCard.tsx @@ -1,13 +1,31 @@ import { NotLinkedToTba } from "@/lib/client/ClientUtils"; -import { Competition } from "@/lib/Types"; +import { Competition, Match, Report } from "@/lib/Types"; +import { useState } from "react"; import { BiExport } from "react-icons/bi"; +import { FaCalendarDay } from "react-icons/fa"; import { MdAutoGraph, MdQueryStats, MdCoPresent } from "react-icons/md"; +import ViewMatchesModal from "../ViewMatchesModal"; +import { User } from "../../lib/Types"; export default function CompHeaderCard({ comp, + matches, + reports, + user, + matchPathway, }: { comp: Competition | undefined; + matches: Match[]; + reports: Report[]; + user: User | null; + matchPathway: string; }) { + const [viewMatches, setViewMatches] = useState(false); + + async function toggleViewMatches() { + setViewMatches(!viewMatches); + } + return (
@@ -36,8 +54,24 @@ export default function CompHeaderCard({ > Pit Stats +
+
+ {viewMatches && user && ( + + )}
); } diff --git a/pages/[teamSlug]/[seasonSlug]/[competitonSlug]/index.tsx b/pages/[teamSlug]/[seasonSlug]/[competitonSlug]/index.tsx index 3a01e4ee..ecb101e1 100644 --- a/pages/[teamSlug]/[seasonSlug]/[competitonSlug]/index.tsx +++ b/pages/[teamSlug]/[seasonSlug]/[competitonSlug]/index.tsx @@ -405,7 +405,13 @@ export default function CompetitionIndex({ >
- + Date: Fri, 4 Apr 2025 16:02:49 -0400 Subject: [PATCH 093/168] Add key prop to ViewMatchCard in ViewMatchesModal --- components/ViewMatchesModal.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/components/ViewMatchesModal.tsx b/components/ViewMatchesModal.tsx index 8ea27ff2..54d68b21 100644 --- a/components/ViewMatchesModal.tsx +++ b/components/ViewMatchesModal.tsx @@ -101,8 +101,11 @@ export default function ViewMatchesModal(props: {
{myMatches .filter((matchData) => showSubmittedReports || !matchData.completed) - .map((matchData) => ( - + .map((matchData, index) => ( + ))}
From 0baab922ccbba44c8b475f7b05bae7a4a0fe6f5c Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Fri, 4 Apr 2025 16:26:13 -0400 Subject: [PATCH 094/168] Add export schedule as CSV, style my matches modal --- components/ViewMatchesModal.tsx | 12 +-- .../competition/InsightsAndSettingsCard.tsx | 39 +++++++- lib/api/ClientApi.ts | 95 ++++++++++++++++++- 3 files changed, 137 insertions(+), 9 deletions(-) diff --git a/components/ViewMatchesModal.tsx b/components/ViewMatchesModal.tsx index 54d68b21..fce0e314 100644 --- a/components/ViewMatchesModal.tsx +++ b/components/ViewMatchesModal.tsx @@ -6,6 +6,7 @@ import { report } from "process"; import Checkbox from "./forms/Checkboxes"; import { CheckmarkIcon } from "react-hot-toast"; import { useState } from "react"; +import { toDict } from "@/lib/client/ClientUtils"; type MatchData = { number: number; @@ -17,7 +18,10 @@ type MatchData = { function ViewMatchCard(props: MatchData) { return ( - + {props.message} { setExportPending(true); - const res = await api.exportCompAsCsv(comp?._id!).catch((e) => { + const res = await api.exportCompDataAsCsv(comp?._id!).catch((e) => { console.error(e); return { csv: undefined }; }); @@ -82,6 +82,31 @@ export default function InsightsAndSettingsCard(props: { setExportPending(false); }; + async function exportScheduleAsCsv() { + setExportPending(true); + + const res = await api.exportCompScheduleAsCsv(comp?._id!).catch((e) => { + console.error(e); + return { csv: undefined }; + }); + + if (!res) { + console.error("failed to export"); + } + + if (res.csv) { + download( + `${comp?.name ?? "Competition"}Schedule.csv`, + res.csv, + "text/csv", + ); + } else { + console.error("No CSV data returned from server"); + } + + setExportPending(false); + } + const createMatch = async () => { try { await api.createMatch( @@ -255,6 +280,18 @@ export default function InsightsAndSettingsCard(props: { "Export Scouting Data as CSV" )} +
{ }, }); - exportCompAsCsv = createNextRoute< + exportCompDataAsCsv = createNextRoute< [string], { csv: string }, ApiDependencies, @@ -1180,6 +1184,93 @@ export default class ClientApi extends NextApiTemplate { }, }); + exportCompScheduleAsCsv = createNextRoute< + [string], + { csv: string }, + ApiDependencies, + { team: Team; comp: Competition } + >({ + isAuthorized: (req, res, deps, [compId]) => + AccessLevels.IfCompOwner(req, res, deps, compId), + handler: async ( + req, + res, + { db: dbPromise, userPromise }, + { team, comp }, + [compId], + ) => { + const db = await dbPromise; + + const matches = await db.findObjects(CollectionId.Matches, { + _id: { $in: comp.matches.map((matchId) => new ObjectId(matchId)) }, + }); + const reports = await db.findObjects(CollectionId.Reports, { + match: { $in: matches.map((match) => match?._id?.toString()) }, + }); + + if (reports.length == 0) { + return res + .status(200) + .send({ error: "No reports found for competition" }); + } + + const users = await db.findObjects(CollectionId.Users, { + _id: { + $in: reports + .map((r) => r.user) + .concat(matches.map((m) => m.subjectiveScouter)) + .flat() + .map((id) => new ObjectId(id)), + }, + }); + + const reportsById = toDict(reports); + const usersById = toDict(users); + + interface Row { + matchNumber: string; + quantScouters: string[]; + subjectiveScouter: string; + } + + const rows: Row[] = [ + // Headers + { + matchNumber: "Match #", + quantScouters: matches[0].reports.map( + (_, index) => `Scouter ${index + 1}`, + ), + subjectiveScouter: "Subjective Scouter", + }, + ]; + + for (const match of matches) { + rows.push({ + matchNumber: match.number.toString(), + quantScouters: match.reports.map((id) => + reportsById[id].user ? usersById[reportsById[id].user].name! : "", + ), + subjectiveScouter: match.subjectiveScouter + ? usersById[match.subjectiveScouter].name! + : "", + }); + } + + const headers = Object.values(rows[0]).flat(); + + let csv = ""; + for (const row of rows) { + csv += + Object.values(row) + .flat() + .map((str) => str.replace(",", "")) + .join(",") + "\n"; + } + + res.status(200).send({ csv }); + }, + }); + teamCompRanking = createNextRoute< [string, number], { place: number | string; max: number | string }, From 9dbd7af6c06ee18e9da80f6121e29ccfaf127802 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 18:32:59 +0000 Subject: [PATCH 095/168] [npm]: Bump @eslint/js from 9.18.0 to 9.24.0 Bumps [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) from 9.18.0 to 9.24.0. - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/commits/v9.24.0/packages/js) --- updated-dependencies: - dependency-name: "@eslint/js" dependency-version: 9.24.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 19 +++++++++++++++---- package.json | 2 +- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index cb39210a..9f11f538 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,7 +55,7 @@ }, "devDependencies": { "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "^9.17.0", + "@eslint/js": "^9.24.0", "@jest/globals": "^29.7.0", "@playwright/test": "^1.51.1", "@types/formidable": "^3.4.5", @@ -1264,9 +1264,11 @@ } }, "node_modules/@eslint/js": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", - "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.24.0.tgz", + "integrity": "sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==", + "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -5515,6 +5517,15 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/@eslint/js": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", + "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/eslint/node_modules/eslint-visitor-keys": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", diff --git a/package.json b/package.json index f4c555ba..7c331027 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ }, "devDependencies": { "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "^9.17.0", + "@eslint/js": "^9.24.0", "@jest/globals": "^29.7.0", "@playwright/test": "^1.51.1", "@types/formidable": "^3.4.5", From a6ae06931f3f661b518f5d6c560a7e218417f244 Mon Sep 17 00:00:00 2001 From: Gearbox Bot Date: Mon, 7 Apr 2025 18:33:15 +0000 Subject: [PATCH 096/168] 1.3.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9f11f538..5d0e2dd4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sj3", - "version": "1.3.1", + "version": "1.3.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sj3", - "version": "1.3.1", + "version": "1.3.2", "license": "CC BY-NC-SA 4.0", "dependencies": { "dependencies": "^0.0.1", diff --git a/package.json b/package.json index 7c331027..18fd9fc2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sj3", - "version": "1.3.1", + "version": "1.3.2", "private": true, "repository": "https://github.com/Decatur-Robotics/Gearbox", "license": "CC BY-NC-SA 4.0", From 860d331e52a136af16f23f82064e6245839ed981 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 18:45:01 +0000 Subject: [PATCH 097/168] [npm]: Bump resend from 4.1.2 to 4.2.0 Bumps [resend](https://github.com/resendlabs/resend-node) from 4.1.2 to 4.2.0. - [Release notes](https://github.com/resendlabs/resend-node/releases) - [Commits](https://github.com/resendlabs/resend-node/compare/v4.1.2...v4.2.0) --- updated-dependencies: - dependency-name: resend dependency-version: 4.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 214 +++++++++------------------------------------- package.json | 2 +- 2 files changed, 42 insertions(+), 174 deletions(-) diff --git a/package-lock.json b/package-lock.json index cb39210a..8ec9889d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,7 +45,7 @@ "react-icons": "^5.5.0", "react-p5": "^1.4.1", "react-qr-code": "^2.0.15", - "resend": "^4.1.2", + "resend": "^4.2.0", "rollbar": "^2.26.4", "string-similarity-js": "^2.1.4", "ts-node": "^10.9.2", @@ -2133,11 +2133,6 @@ "node": ">= 8" } }, - "node_modules/@one-ini/wasm": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", - "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==" - }, "node_modules/@panva/hkdf": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", @@ -2210,12 +2205,13 @@ "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==" }, "node_modules/@react-email/render": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.1.tgz", - "integrity": "sha512-W3gTrcmLOVYnG80QuUp22ReIT/xfLsVJ+n7ghSlG2BITB8evNABn1AO2rGQoXuK84zKtDAlxCdm3hRyIpZdGSA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.5.tgz", + "integrity": "sha512-CA69HYXPk21HhtAXATIr+9JJwpDNmAFCvdMUjWmeoD1+KhJ9NAxusMRxKNeibdZdslmq3edaeOKGbdQ9qjK8LQ==", + "license": "MIT", "dependencies": { "html-to-text": "9.0.5", - "js-beautify": "^1.14.11", + "prettier": "3.4.2", "react-promise-suspense": "0.3.4" }, "engines": { @@ -2226,6 +2222,21 @@ "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc" } }, + "node_modules/@react-email/render/node_modules/prettier": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/@restart/hooks": { "version": "0.4.16", "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", @@ -2294,6 +2305,7 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz", "integrity": "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==", + "license": "MIT", "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" @@ -3079,14 +3091,6 @@ "react-dom": "^17 || ^18 || ^19" } }, - "node_modules/abbrev": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", - "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/acorn": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", @@ -4094,15 +4098,6 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, - "node_modules/config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, "node_modules/console-polyfill": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/console-polyfill/-/console-polyfill-0.3.0.tgz", @@ -4508,6 +4503,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -4526,12 +4522,14 @@ "type": "github", "url": "https://github.com/sponsors/fb55" } - ] + ], + "license": "BSD-2-Clause" }, "node_modules/domhandler": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.3.0" }, @@ -4546,6 +4544,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -4580,53 +4579,6 @@ "safer-buffer": "^2.1.0" } }, - "node_modules/editorconfig": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", - "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", - "dependencies": { - "@one-ini/wasm": "0.1.1", - "commander": "^10.0.0", - "minimatch": "9.0.1", - "semver": "^7.5.3" - }, - "bin": { - "editorconfig": "bin/editorconfig" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/editorconfig/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/editorconfig/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "engines": { - "node": ">=14" - } - }, - "node_modules/editorconfig/node_modules/minimatch": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", - "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", @@ -4702,6 +4654,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -6259,6 +6212,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz", "integrity": "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==", + "license": "MIT", "dependencies": { "@selderee/plugin-htmlparser2": "^0.11.0", "deepmerge": "^4.3.1", @@ -6281,6 +6235,7 @@ "url": "https://github.com/sponsors/fb55" } ], + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", @@ -6389,11 +6344,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, "node_modules/internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -7857,75 +7807,6 @@ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" }, - "node_modules/js-beautify": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz", - "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==", - "dependencies": { - "config-chain": "^1.1.13", - "editorconfig": "^1.0.4", - "glob": "^10.3.3", - "js-cookie": "^3.0.5", - "nopt": "^7.2.0" - }, - "bin": { - "css-beautify": "js/bin/css-beautify.js", - "html-beautify": "js/bin/html-beautify.js", - "js-beautify": "js/bin/js-beautify.js" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/js-beautify/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/js-beautify/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/js-beautify/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/js-cookie": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", - "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", - "engines": { - "node": ">=14" - } - }, "node_modules/js-sha1": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/js-sha1/-/js-sha1-0.6.0.tgz", @@ -8078,6 +7959,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz", "integrity": "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==", + "license": "MIT", "funding": { "url": "https://ko-fi.com/killymxi" } @@ -8620,20 +8502,6 @@ "node": ">=6.0.0" } }, - "node_modules/nopt": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", - "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", - "dependencies": { - "abbrev": "^2.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -8956,6 +8824,7 @@ "version": "0.12.1", "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz", "integrity": "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==", + "license": "MIT", "dependencies": { "leac": "^0.6.0", "peberminta": "^0.9.0" @@ -9018,6 +8887,7 @@ "version": "0.9.0", "resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz", "integrity": "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==", + "license": "MIT", "funding": { "url": "https://ko-fi.com/killymxi" } @@ -9425,11 +9295,6 @@ "react": ">=0.14.0" } }, - "node_modules/proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" - }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -9705,6 +9570,7 @@ "version": "0.3.4", "resolved": "https://registry.npmjs.org/react-promise-suspense/-/react-promise-suspense-0.3.4.tgz", "integrity": "sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^2.0.1" } @@ -9712,7 +9578,8 @@ "node_modules/react-promise-suspense/node_modules/fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==" + "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==", + "license": "MIT" }, "node_modules/react-qr-code": { "version": "2.0.15", @@ -9911,12 +9778,12 @@ } }, "node_modules/resend": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/resend/-/resend-4.1.2.tgz", - "integrity": "sha512-km0btrAj/BqIaRlS+SoLNMaCAUUWEgcEvZpycfVvoXEwAHCxU+vp/ikxPgKRkyKyiR2iDcdUq5uIBTDK9oSSSQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/resend/-/resend-4.2.0.tgz", + "integrity": "sha512-s6ogU+BBYH1H6Zl926cpddtLRAJWeFv5cIKcuHLWk1QYhFTbpFJlhqx31pnN2f0CB075KFSrc1Xf6HG690wzuw==", "license": "MIT", "dependencies": { - "@react-email/render": "1.0.1" + "@react-email/render": "1.0.5" }, "engines": { "node": ">=18" @@ -10126,6 +9993,7 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz", "integrity": "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==", + "license": "MIT", "dependencies": { "parseley": "^0.12.0" }, diff --git a/package.json b/package.json index f4c555ba..a53b720c 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "react-icons": "^5.5.0", "react-p5": "^1.4.1", "react-qr-code": "^2.0.15", - "resend": "^4.1.2", + "resend": "^4.2.0", "rollbar": "^2.26.4", "string-similarity-js": "^2.1.4", "ts-node": "^10.9.2", From 58946df244b0eeb17f16ca299db0f37a9f3c2e07 Mon Sep 17 00:00:00 2001 From: Gearbox Bot Date: Mon, 7 Apr 2025 18:45:15 +0000 Subject: [PATCH 098/168] 1.3.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8ec9889d..2d9310c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sj3", - "version": "1.3.1", + "version": "1.3.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sj3", - "version": "1.3.1", + "version": "1.3.2", "license": "CC BY-NC-SA 4.0", "dependencies": { "dependencies": "^0.0.1", diff --git a/package.json b/package.json index a53b720c..3bf8913e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sj3", - "version": "1.3.1", + "version": "1.3.2", "private": true, "repository": "https://github.com/Decatur-Robotics/Gearbox", "license": "CC BY-NC-SA 4.0", From 7c4a9aad3363e062ecc5e2c5a228008267086f38 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 18:53:35 +0000 Subject: [PATCH 099/168] [npm]: Bump eslint-config-next from 15.2.2 to 15.2.4 Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 15.2.2 to 15.2.4. - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/commits/v15.2.4/packages/eslint-config-next) --- updated-dependencies: - dependency-name: eslint-config-next dependency-version: 15.2.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 16 ++++++++-------- package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index cb39210a..2a23ac4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "bson": "^5.0.0", "dotenv": "^16.4.7", "eslint": "9.18.0", - "eslint-config-next": "15.2.2", + "eslint-config-next": "15.2.4", "formidable": "^3.5.2", "jose": "^6.0.10", "levenary": "^1.1.1", @@ -1937,9 +1937,9 @@ "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "15.2.2", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.2.2.tgz", - "integrity": "sha512-1+BzokFuFQIfLaRxUKf2u5In4xhPV7tUgKcK53ywvFl6+LXHWHpFkcV7VNeKlyQKUotwiq4fy/aDNF9EiUp4RQ==", + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.2.4.tgz", + "integrity": "sha512-O8ScvKtnxkp8kL9TpJTTKnMqlkZnS+QxwoQnJwPGBxjBbzd6OVVPEJ5/pMNrktSyXQD/chEfzfFzYLM6JANOOQ==", "license": "MIT", "dependencies": { "fast-glob": "3.3.1" @@ -4998,12 +4998,12 @@ } }, "node_modules/eslint-config-next": { - "version": "15.2.2", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.2.2.tgz", - "integrity": "sha512-g34RI7RFS4HybYFwGa/okj+8WZM+/fy+pEM+aqRQoVvM4gQhKrd4wIEddKmlZfWD75j8LTwB5zwkmNv3DceH1A==", + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.2.4.tgz", + "integrity": "sha512-v4gYjd4eYIme8qzaJItpR5MMBXJ0/YV07u7eb50kEnlEmX7yhOjdUdzz70v4fiINYRjLf8X8TbogF0k7wlz6sA==", "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "15.2.2", + "@next/eslint-plugin-next": "15.2.4", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", diff --git a/package.json b/package.json index f4c555ba..e2db688c 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "dependencies": "^0.0.1", "dotenv": "^16.4.7", "eslint": "9.18.0", - "eslint-config-next": "15.2.2", + "eslint-config-next": "15.2.4", "formidable": "^3.5.2", "jose": "^6.0.10", "levenary": "^1.1.1", From fa719f0f8a64e055bb732f334c3d1a68d78cfb27 Mon Sep 17 00:00:00 2001 From: Gearbox Bot Date: Mon, 7 Apr 2025 18:53:47 +0000 Subject: [PATCH 100/168] 1.3.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2a23ac4e..09fe76f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sj3", - "version": "1.3.1", + "version": "1.3.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sj3", - "version": "1.3.1", + "version": "1.3.2", "license": "CC BY-NC-SA 4.0", "dependencies": { "dependencies": "^0.0.1", diff --git a/package.json b/package.json index e2db688c..afec3314 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sj3", - "version": "1.3.1", + "version": "1.3.2", "private": true, "repository": "https://github.com/Decatur-Robotics/Gearbox", "license": "CC BY-NC-SA 4.0", From 7995a3fd79bccfde4b1a7f7578509c814f19d8bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 18:56:44 +0000 Subject: [PATCH 101/168] [npm]: Bump @serwist/next from 9.0.11 to 9.0.13 Bumps [@serwist/next](https://github.com/serwist/serwist) from 9.0.11 to 9.0.13. - [Release notes](https://github.com/serwist/serwist/releases) - [Commits](https://github.com/serwist/serwist/compare/@serwist/next@9.0.11...@serwist/next@9.0.13) --- updated-dependencies: - dependency-name: "@serwist/next" dependency-version: 9.0.13 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 99 ++++++++++++++++++++++++++++------------------- package.json | 2 +- 2 files changed, 60 insertions(+), 41 deletions(-) diff --git a/package-lock.json b/package-lock.json index cb39210a..b1333439 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "dependencies": "^0.0.1", "@next-auth/mongodb-adapter": "^1.1.3", - "@serwist/next": "^9.0.11", + "@serwist/next": "^9.0.13", "@types/http-proxy": "^1.17.15", "@types/react-dom": "18.3.1", "@types/socket.io-client": "^3.0.0", @@ -2303,15 +2303,16 @@ } }, "node_modules/@serwist/build": { - "version": "9.0.11", - "resolved": "https://registry.npmjs.org/@serwist/build/-/build-9.0.11.tgz", - "integrity": "sha512-mYBBpm6hWN40J/Sj2aNncVgl/o7Ch/CfyHHl3XvEecbowRuJNv8BfIch+Tvwlfd1cqBqNc65kCYvCL2XJms8DA==", + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@serwist/build/-/build-9.0.13.tgz", + "integrity": "sha512-Hoc6llxFmnsE8z5Cs95UmRRhRyoNh44OdrMWWPPX8BpW19z0CK/qnBquptjyBIe46jjoOxsPHK0Tt7oZOV4Mbw==", + "license": "MIT", "dependencies": { "common-tags": "1.8.2", "glob": "10.4.5", "pretty-bytes": "6.1.1", "source-map": "0.8.0-beta.0", - "zod": "3.23.8" + "zod": "3.24.2" }, "engines": { "node": ">=18.0.0" @@ -2329,6 +2330,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -2337,6 +2339,7 @@ "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -2356,6 +2359,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -2370,6 +2374,7 @@ "version": "0.8.0-beta.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "license": "BSD-3-Clause", "dependencies": { "whatwg-url": "^7.0.0" }, @@ -2381,6 +2386,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "license": "MIT", "dependencies": { "punycode": "^2.1.0" } @@ -2388,12 +2394,14 @@ "node_modules/@serwist/build/node_modules/webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "license": "BSD-2-Clause" }, "node_modules/@serwist/build/node_modules/whatwg-url": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "license": "MIT", "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", @@ -2401,17 +2409,18 @@ } }, "node_modules/@serwist/next": { - "version": "9.0.11", - "resolved": "https://registry.npmjs.org/@serwist/next/-/next-9.0.11.tgz", - "integrity": "sha512-Aa26qgJxnaxbGK8JYAWqAPeDcyfu0xjYUPpvSfc3d9GrIl9G9cdflkcsvcbmJGnOb3Oc4tHN4wncYO6cIKb2wQ==", - "dependencies": { - "@serwist/build": "9.0.11", - "@serwist/webpack-plugin": "9.0.11", - "@serwist/window": "9.0.11", - "chalk": "5.3.0", + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@serwist/next/-/next-9.0.13.tgz", + "integrity": "sha512-nI2N4oSEHJGH0YUsE4m1obPfudO3DZ/pXiHPaisw+28YjgkMqD6ePfhzeHGO07ahPmIUiyHca9VNO8OfarbQ1Q==", + "license": "MIT", + "dependencies": { + "@serwist/build": "9.0.13", + "@serwist/webpack-plugin": "9.0.13", + "@serwist/window": "9.0.13", + "chalk": "5.4.1", "glob": "10.4.5", - "serwist": "9.0.11", - "zod": "3.23.8" + "serwist": "9.0.13", + "zod": "3.24.2" }, "engines": { "node": ">=18.0.0" @@ -2435,9 +2444,10 @@ } }, "node_modules/@serwist/next/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -2479,13 +2489,14 @@ } }, "node_modules/@serwist/webpack-plugin": { - "version": "9.0.11", - "resolved": "https://registry.npmjs.org/@serwist/webpack-plugin/-/webpack-plugin-9.0.11.tgz", - "integrity": "sha512-ZTxVJqv7nKlPWmhC3oMt7tZc0ssnd7SYr6L8zZLLF7ltJUub9TMyh1K1zL5TJtsr8DyfALNFPwPA/s2yjYSk+w==", + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@serwist/webpack-plugin/-/webpack-plugin-9.0.13.tgz", + "integrity": "sha512-Z+Eve8dckM2FulRCa7Cj7VCF3EOP7QkRA76tI742olF7J2sYZSm3t9Ex13jDxTMmYiU1AxLq6V6gEMIdRAetVw==", + "license": "MIT", "dependencies": { - "@serwist/build": "9.0.11", + "@serwist/build": "9.0.13", "pretty-bytes": "6.1.1", - "zod": "3.23.8" + "zod": "3.24.2" }, "engines": { "node": ">=18.0.0" @@ -2504,12 +2515,13 @@ } }, "node_modules/@serwist/window": { - "version": "9.0.11", - "resolved": "https://registry.npmjs.org/@serwist/window/-/window-9.0.11.tgz", - "integrity": "sha512-hUJVNxZbqfnJbUTfuJew7SR3f1KBIv/4nwmP1YRxjR6EMthUfN4bVSLzWLW8/u3h5bNaVh7sOhopUVC8m1HTCQ==", + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@serwist/window/-/window-9.0.13.tgz", + "integrity": "sha512-Cf3RizPuFInDcLt0P1Y5QzG1sA5mW131/PZfMYE3yBuNUSGNgOQGlYuLdwDOWPHgECYoVb/da8pspdQNKs0O5g==", + "license": "MIT", "dependencies": { "@types/trusted-types": "2.0.7", - "serwist": "9.0.11" + "serwist": "9.0.13" }, "peerDependencies": { "typescript": ">=5.0.0" @@ -3028,7 +3040,8 @@ "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" }, "node_modules/@types/warning": { "version": "3.0.3", @@ -4085,6 +4098,7 @@ "version": "1.8.2", "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "license": "MIT", "engines": { "node": ">=4.0.0" } @@ -6312,9 +6326,10 @@ } }, "node_modules/idb": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/idb/-/idb-8.0.0.tgz", - "integrity": "sha512-l//qvlAKGmQO31Qn7xdzagVPPaHTxXx199MhrAFuVBTPqydcPYBWjkrbv4Y0ktB+GmWOiwHl237UUOrLmQxLvw==" + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/idb/-/idb-8.0.2.tgz", + "integrity": "sha512-CX70rYhx7GDDQzwwQMDwF6kDRQi5vVs6khHUumDrMecBylKkwvZ8HWvKV08AGb7VbpoGCWUQ4aHzNDgoUiOIUg==", + "license": "ISC" }, "node_modules/idb-wrapper": { "version": "1.7.2", @@ -8164,7 +8179,8 @@ "node_modules/lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "license": "MIT" }, "node_modules/loose-envify": { "version": "1.4.0", @@ -9378,6 +9394,7 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", + "license": "MIT", "engines": { "node": "^14.13.1 || >=16.0.0" }, @@ -10146,11 +10163,12 @@ } }, "node_modules/serwist": { - "version": "9.0.11", - "resolved": "https://registry.npmjs.org/serwist/-/serwist-9.0.11.tgz", - "integrity": "sha512-oUuyOuIFZoj+ZYdwWnPYVsndOw5CoGsTy106mHJ2wxjnz01TrDhg2l26yrsoJek1qT/Doi0pzknYLLXTIFs5og==", + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/serwist/-/serwist-9.0.13.tgz", + "integrity": "sha512-BF3bmzYdOVT2lF3iHV0044NqTO6q6GAiqrYpc7L9EPYQXZHOy22WajKaHLvCdvpm2Jpji4SsxUL8/uC1WSCZ5g==", + "license": "MIT", "dependencies": { - "idb": "8.0.0" + "idb": "8.0.2" }, "peerDependencies": { "typescript": ">=5.0.0" @@ -11753,9 +11771,10 @@ } }, "node_modules/zod": { - "version": "3.23.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", - "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "version": "3.24.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", + "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index f4c555ba..6c719f7f 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@next-auth/mongodb-adapter": "^1.1.3", - "@serwist/next": "^9.0.11", + "@serwist/next": "^9.0.13", "@types/http-proxy": "^1.17.15", "@types/react-dom": "18.3.1", "@types/socket.io-client": "^3.0.0", From 6bbd1f8852d6c4ab6184f28e6a1d55d7b4db8aee Mon Sep 17 00:00:00 2001 From: Gearbox Bot Date: Mon, 7 Apr 2025 18:56:57 +0000 Subject: [PATCH 102/168] 1.3.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index b1333439..cb8782f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sj3", - "version": "1.3.1", + "version": "1.3.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sj3", - "version": "1.3.1", + "version": "1.3.2", "license": "CC BY-NC-SA 4.0", "dependencies": { "dependencies": "^0.0.1", diff --git a/package.json b/package.json index 6c719f7f..543a8d56 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sj3", - "version": "1.3.1", + "version": "1.3.2", "private": true, "repository": "https://github.com/Decatur-Robotics/Gearbox", "license": "CC BY-NC-SA 4.0", From 32cad93388856f3c78c5f058e0c438e0482f1da7 Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Wed, 9 Apr 2025 22:17:03 -0400 Subject: [PATCH 103/168] Add comment to adapter and fix? Slack profile callback --- lib/Auth.ts | 16 +++------------- lib/DbInterfaceAuthAdapter.ts | 2 ++ 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/lib/Auth.ts b/lib/Auth.ts index 813f972d..22e15fdd 100644 --- a/lib/Auth.ts +++ b/lib/Auth.ts @@ -57,20 +57,12 @@ export const AuthenticationOptions: AuthOptions = { clientId: process.env.NEXT_PUBLIC_SLACK_CLIENT_ID as string, clientSecret: process.env.SLACK_CLIENT_SECRET as string, allowDangerousEmailAccountLinking: true, + /** + * @returns Data used to create the user object in the DB + */ profile: async (profile) => { logger.debug("Slack profile:", profile); - const existing = await ( - await cachedDb - ).findObject(CollectionId.Users, { email: profile.email }); - - if (existing) { - existing.slackId = profile.sub; - existing.id = profile.sub; - console.log("Found existing user:", existing); - return existing; - } - const user = new User( profile.name, profile.email, @@ -84,8 +76,6 @@ export const AuthenticationOptions: AuthOptions = { 1, ); - user.id = profile.sub; - return user; }, }), diff --git a/lib/DbInterfaceAuthAdapter.ts b/lib/DbInterfaceAuthAdapter.ts index b2ca5758..a5a4c0b8 100644 --- a/lib/DbInterfaceAuthAdapter.ts +++ b/lib/DbInterfaceAuthAdapter.ts @@ -15,6 +15,8 @@ import Logger from "./client/Logger"; import { RollbarInterface } from "./client/RollbarUtils"; /** + * Should match the MongoDB adapter as closely as possible + * (https://github.com/nextauthjs/next-auth/blob/main/packages/adapter-mongodb/src/index.ts). * @tested_by tests/lib/DbInterfaceAuthAdapter.test.ts */ export default function DbInterfaceAuthAdapter( From fdbf54c6c897b17b66d7aa73bb4256be13a9dfae Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Wed, 9 Apr 2025 22:23:57 -0400 Subject: [PATCH 104/168] Fix "Profile id is missing in Slack OAuth profile response" --- lib/Auth.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Auth.ts b/lib/Auth.ts index 22e15fdd..f1859a33 100644 --- a/lib/Auth.ts +++ b/lib/Auth.ts @@ -76,6 +76,9 @@ export const AuthenticationOptions: AuthOptions = { 1, ); + // We need the 'id' field to avoid the error "Profile id is missing in Slack OAuth profile response" + user.id = new ObjectId().toString(); + return user; }, }), From 140a5a29b5536e924a1a0839a6fcd5eb7541bee2 Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Thu, 10 Apr 2025 17:55:22 -0400 Subject: [PATCH 105/168] Conform auth adapter to mongo adapter --- lib/Auth.ts | 71 +-- lib/DbInterfaceAuthAdapter.ts | 452 +++--------------- lib/Types.ts | 1 + lib/client/dbinterfaces/CachedDbInterface.ts | 29 ++ lib/client/dbinterfaces/DbInterface.ts | 22 +- .../dbinterfaces/InMemoryDbInterface.ts | 29 ++ .../dbinterfaces/LocalStorageDbInterface.ts | 29 ++ package-lock.json | 8 +- package.json | 2 +- 9 files changed, 208 insertions(+), 435 deletions(-) diff --git a/lib/Auth.ts b/lib/Auth.ts index f1859a33..1a9eedfa 100644 --- a/lib/Auth.ts +++ b/lib/Auth.ts @@ -20,6 +20,28 @@ const cachedDb = getDatabase(); const rollbar = getRollbar(); const adapter = DbInterfaceAuthAdapter(cachedDb, rollbar, logger); +async function createUser(profile: any) { + const user = new User( + profile.name, + profile.email, + profile.picture, + false, + await GenerateSlug(await cachedDb, CollectionId.Users, profile.name), + [], + [], + profile.sub, + 10, + 1, + ); + + user._id = new ObjectId() as any; + + // We need the 'id' field to avoid the error "Profile id is missing in OAuth profile response" + user.id = user._id!.toString(); + + return user; +} + export const AuthenticationOptions: AuthOptions = { secret: process.env.NEXTAUTH_SECRET, providers: [ @@ -27,60 +49,11 @@ export const AuthenticationOptions: AuthOptions = { clientId: process.env.GOOGLE_ID, clientSecret: process.env.GOOGLE_SECRET, allowDangerousEmailAccountLinking: true, - profile: async (profile) => { - logger.debug("Google profile:", profile); - - const existingUser = await ( - await cachedDb - ).findObject(CollectionId.Users, { email: profile.email }); - if (existingUser) { - existingUser.id = profile.sub; - return existingUser; - } - - const user = new User( - profile.name, - profile.email, - profile.picture, - false, - await GenerateSlug(await cachedDb, CollectionId.Users, profile.name), - [], - [], - ); - - user.id = profile.sub; - - return user; - }, }), SlackProvider({ clientId: process.env.NEXT_PUBLIC_SLACK_CLIENT_ID as string, clientSecret: process.env.SLACK_CLIENT_SECRET as string, allowDangerousEmailAccountLinking: true, - /** - * @returns Data used to create the user object in the DB - */ - profile: async (profile) => { - logger.debug("Slack profile:", profile); - - const user = new User( - profile.name, - profile.email, - profile.picture, - false, - await GenerateSlug(await cachedDb, CollectionId.Users, profile.name), - [], - [], - profile.sub, - 10, - 1, - ); - - // We need the 'id' field to avoid the error "Profile id is missing in Slack OAuth profile response" - user.id = new ObjectId().toString(); - - return user; - }, }), Email({ server: { diff --git a/lib/DbInterfaceAuthAdapter.ts b/lib/DbInterfaceAuthAdapter.ts index a5a4c0b8..714050b0 100644 --- a/lib/DbInterfaceAuthAdapter.ts +++ b/lib/DbInterfaceAuthAdapter.ts @@ -1,4 +1,4 @@ -import { format, MongoDBAdapter } from "@next-auth/mongodb-adapter"; +import { _id, format, MongoDBAdapter } from "@next-auth/mongodb-adapter"; import { Adapter, AdapterAccount, @@ -13,9 +13,10 @@ import { GenerateSlug } from "./Utils"; import { ObjectId } from "bson"; import Logger from "./client/Logger"; import { RollbarInterface } from "./client/RollbarUtils"; +import { Profile } from "next-auth"; /** - * Should match the MongoDB adapter as closely as possible + * Should match the MongoDB adapter as closely as possible * (https://github.com/nextauthjs/next-auth/blob/main/packages/adapter-mongodb/src/index.ts). * @tested_by tests/lib/DbInterfaceAuthAdapter.test.ts */ @@ -29,264 +30,97 @@ export default function DbInterfaceAuthAdapter( new Logger(["AUTH"], false); const adapter: Adapter = { + /** + * @param data returns from the profile callback of the auth provider + */ createUser: async (data: Record) => { - const db = await dbPromise; - - const adapterUser = format.to(data); - adapterUser._id = data["_id"] as any; - - logger.debug("Creating user:", adapterUser.name); - - // Check if user already exists - const existingUser = await db.findObject(CollectionId.Users, { - email: adapterUser.email, - }); - - if (existingUser) { - // If user exists, return existing user - logger.warn("User already exists:", existingUser.name); - rollbar.warn("User already exists when creating user", { - existingUser, - data, - }); - return format.from(existingUser); - } - - logger.debug("Creating user:", adapterUser); + const profile = format.to(data); const user = new User( - adapterUser.name ?? "Unknown", - adapterUser.email, - adapterUser.image ?? process.env.DEFAULT_IMAGE, + profile.name!, + profile.email, + profile.image, false, await GenerateSlug( - db, + await dbPromise, CollectionId.Users, - adapterUser.name ?? "Unknown", + profile.name ?? profile.email!, ), [], [], - adapterUser.id, - 0, + profile.sub, + 10, 1, ); - user._id = new ObjectId(adapterUser._id) as any; + user._id = new ObjectId() as any; - const dbUser = await db.addObject(CollectionId.Users, user); - logger.info("Created user:", dbUser._id!.toString()); - return format.from(dbUser); + // We need the 'id' field to avoid the error "Profile id is missing in OAuth profile response" + user.id = user._id!.toString(); + + await (await dbPromise).addObject(CollectionId.Users, user); + return format.from(user); }, getUser: async (id: string) => { - const db = await dbPromise; - - if (id.length !== 24) return null; - - logger.debug("Getting user:", id); - - const user = await db.findObjectById( - CollectionId.Users, - new ObjectId(id), - ); - - if (!user) { - logger.warn("User not found:", id); - return null; - } - user.id = user._id!.toString()!; + const user = await ( + await dbPromise + ).findObjectById(CollectionId.Users, _id(id)); + if (!user) return null; return format.from(user); }, getUserByEmail: async (email: string) => { - const db = await dbPromise; - - logger.debug("Getting user by email:", email); - - const user = await db.findObject(CollectionId.Users, { email }); - - if (!user) { - logger.warn("User not found by email:", email); - return null; - } - - user.id = user._id!.toString()!; + const user = await ( + await dbPromise + ).findObject(CollectionId.Users, { + email, + }); + if (!user) return null; return format.from(user); }, getUserByAccount: async ( providerAccountId: Pick, ) => { const db = await dbPromise; - - logger.debug("Getting user by account:", providerAccountId); - const account = await db.findObject(CollectionId.Accounts, { - providerAccountId: providerAccountId.providerAccountId, + providerAccountId, }); - - if (!account) { - logger.warn( - "Account not found by providerAccountId:", - providerAccountId.providerAccountId, - ); - return null; - } - + if (!account) return null; const user = await db.findObjectById( CollectionId.Users, - account.userId as any as ObjectId, - ); - - if (!user) { - logger.warn("User not found:", account.userId); - return null; - } - - logger.debug( - "Found user by account: Account", - providerAccountId.providerAccountId, - "=> User", - user._id, - user.name, + new ObjectId(account.userId), ); - - user.id = user._id!.toString()!; + if (!user) return null; return format.from(user); }, updateUser: async ( data: Partial & Pick, ) => { - const db = await dbPromise; const { _id, ...user } = format.to(data); + const db = await dbPromise; - if (!_id) { - logger.error("User ID not found when updating user:", user); - rollbar.error("User ID not found when updating user", { - user, - }); - - return format.from(user); - } - - logger.debug("Updating user:", _id); - - const existing = await db.findObjectById( - CollectionId.Users, - new ObjectId(_id), - ); - - if (!existing) { - logger.error("User not found:", _id); - rollbar.error("User not found when updating user", { - _id, - user, - }); - return format.from(user); - } - - user.id = existing._id!.toString()!; - - await db.updateObjectById( + const result = await db.findObjectAndUpdate( CollectionId.Users, - new ObjectId(_id), - user as Partial, + _id, + user as unknown as Partial, ); - return format.from({ ...existing, ...user, _id: _id }); + return format.from(result!); }, deleteUser: async (id: string) => { + const userId = _id(id); const db = await dbPromise; - - logger.log("Deleting user:", id); - - const user = await db.findObjectById( - CollectionId.Users, - new ObjectId(id), - ); - if (!user) { - logger.error("User not found:", id); - rollbar.error("User not found when deleting user", { - id, - }); - return null; - } - - const account = await db.findObject(CollectionId.Accounts, { - userId: user._id, - }); - - const sessions = await db.findObjects(CollectionId.Sessions, { - userId: user._id, - }); - - const promises: Promise[] = [ - db.deleteObjectById(CollectionId.Users, user._id as any as ObjectId), - ]; - - if (account) { - promises.push( - db.deleteObjectById(CollectionId.Accounts, new ObjectId(account._id)), - ); - } - - if (sessions.length) { - promises.push( - Promise.all( - sessions.map((session) => - db.deleteObjectById( - CollectionId.Sessions, - new ObjectId(session._id), - ), - ), - ), - ); - } - - await Promise.all(promises); - - return format.from(user); + await Promise.all([ + db.deleteObjects(CollectionId.Accounts, { userId: userId as any }), + db.deleteObjects(CollectionId.Sessions, { userId: userId as any }), + db.deleteObjectById(CollectionId.Users, userId), + ]); }, /** * Creates an account */ linkAccount: async (data: Record) => { - const db = await dbPromise; - const account = format.to(data); - account.userId = data["userId"] as any; // userId gets overwritten for some reason - - logger.debug( - "Linking account: providerAccountId", - account.providerAccountId, - "userId:", - account.userId, - ); - - const existingAccount = await db.findObject(CollectionId.Accounts, { - providerAccountId: account.providerAccountId, - }); - - if (existingAccount) { - logger.warn( - "Account already exists:", - existingAccount.providerAccountId, - ); - rollbar.warn("Account already exists when linking account", { - account, - }); - - let formattedAccount: AdapterAccount; - - // Sometimes gives an error about not finding toHexString. - try { - formattedAccount = format.from(existingAccount); - } catch (e) { - account.userId = new ObjectId(account.userId) as any; - formattedAccount = format.from(account); - } - return formattedAccount; - } - - await db.addObject(CollectionId.Accounts, account); - + await (await dbPromise).addObject(CollectionId.Accounts, account); return account; }, /** @@ -295,211 +129,69 @@ export default function DbInterfaceAuthAdapter( unlinkAccount: async ( providerAccountId: Pick, ) => { - const db = await dbPromise; - - logger.debug("Unlinking account:", providerAccountId.providerAccountId); - - const account = await db.findObject(CollectionId.Accounts, { - providerAccountId: providerAccountId.providerAccountId, - }); - - if (!account) { - logger.warn( - "Account not found by providerAccountId:", - providerAccountId.providerAccountId, - ); - rollbar.warn("Account not found when unlinking account", { - providerAccountId, - }); - return null; - } - - await db.deleteObjectById( - CollectionId.Accounts, - new ObjectId(account._id), - ); - - return format.from(account); + const account = await ( + await dbPromise + ).findObjectAndDelete(CollectionId.Accounts, providerAccountId); + return format.from(account!); }, getSessionAndUser: async (sessionToken: string) => { const db = await dbPromise; - const session = await db.findObject(CollectionId.Sessions, { sessionToken, }); - - if (!session) { - // Weirdly, this is ok. - logger.warn("Session not found:", sessionToken); - return null; - } + if (!session) return null; const user = await db.findObjectById( CollectionId.Users, new ObjectId(session.userId), ); - - if (!user) { - logger.warn("User not found:", session.userId); - return null; - } - - logger.debug( - "Got session and user. Session Token:", - sessionToken, - "User:", - user._id, - user.name, - ); + if (!user) return null; return { + user: format.from(user), session: format.from(session), - user: { - ...format.from(user), - _id: user._id, - }, }; }, createSession: async (data: Record) => { - const db = await dbPromise; - const session = format.to(data); - session.userId = data["userId"] as any; // userId gets overwritten for some reason - - if (!session.userId) { - logger.error("User ID not found in session:", session); - rollbar.error("User ID not found in session when creating session", { - session, - }); - - const dbSession = await db.addObject( - CollectionId.Sessions, - session as unknown as Session, - ); - - return format.from(dbSession); - } - - logger.debug( - "Creating session:", - session._id, - "with user", - session.userId, - ); - - const user = await db.findObjectById( - CollectionId.Users, - new ObjectId(session.userId), - ); - - if (!user) { - logger.warn("User not found:", session.userId); - rollbar.warn("User not found", { - session, - }); - } else session.userId = user._id as any; - - const dbSession = await db.addObject( - CollectionId.Sessions, - session as unknown as Session, - ); - - return format.from(dbSession); + await (await dbPromise).addObject(CollectionId.Sessions, session as any); + return format.from(session); }, updateSession: async ( data: Partial & Pick, ) => { - const db = await dbPromise; const { _id, ...session } = format.to(data); - session.userId = data["userId"] as any; // userId gets overwritten for some reason - - logger.debug("Updating session:", session.sessionToken); - - const existing = await db.findObject(CollectionId.Sessions, { + const updatedSession = await ( + await dbPromise + ).findObjectAndUpdate(CollectionId.Sessions, _id, { sessionToken: session.sessionToken, }); - - if (!existing) { - logger.error("Session not found:", session.sessionToken); - rollbar.error("Session not found when updating session", { - session, - }); - return null; - } - - if (session.userId) { - session.userId = new ObjectId(session.userId) as any; - } - - await db.updateObjectById( - CollectionId.Sessions, - new ObjectId(existing._id), - session as unknown as Partial, - ); - - return format.from({ ...existing, ...data }); + return format.from(updatedSession!); }, deleteSession: async (sessionToken: string) => { - const db = await dbPromise; - - logger.debug("Deleting session:", sessionToken); - - const session = await db.findObject(CollectionId.Sessions, { + const session = await ( + await dbPromise + ).findObjectAndDelete(CollectionId.Sessions, { sessionToken, }); - - if (!session) { - logger.warn("Session not found:", sessionToken); - rollbar.warn("Session not found when deleting session", { - sessionToken, - }); - return null; - } - - await db.deleteObjectById( - CollectionId.Sessions, - new ObjectId(session._id), - ); - - return format.from(session); + return format.from(session!); }, createVerificationToken: async (token: VerificationToken) => { - const db = await dbPromise; - - logger.debug("Creating verification token:", token.identifier); - - await db.addObject( - CollectionId.VerificationTokens, - format.to(token) as VerificationToken, - ); + await ( + await dbPromise + ).addObject(CollectionId.VerificationTokens, format.to(token) as any); return token; }, useVerificationToken: async (token: { identifier: string; token: string; }) => { - const db = await dbPromise; - - logger.info("Using verification token:", token.identifier); - - const existing = await db.findObject(CollectionId.VerificationTokens, { - token: token.token, - }); - - if (!existing) { - logger.warn("Verification token not found:", token.token); - rollbar.warn("Verification token not found when using token", { - token, - }); - return null; - } - - await db.deleteObjectById( - CollectionId.VerificationTokens, - new ObjectId(existing._id), - ); - - return format.from(existing); + const verificationToken = await ( + await dbPromise + ).findObjectAndDelete(CollectionId.VerificationTokens, token); + if (!verificationToken) return null; + const { _id, ...rest } = verificationToken; + return rest; }, }; diff --git a/lib/Types.ts b/lib/Types.ts index 8127ae02..0fe59fe5 100644 --- a/lib/Types.ts +++ b/lib/Types.ts @@ -54,6 +54,7 @@ export class User implements NextAuthUser { onboardingComplete: boolean = false; resendContactId: string | undefined = undefined; lastSignInDateTime: Date | undefined = undefined; + emailVerified: Date | null = null; constructor( name: string | undefined, diff --git a/lib/client/dbinterfaces/CachedDbInterface.ts b/lib/client/dbinterfaces/CachedDbInterface.ts index 2ae214e5..0903e9f5 100644 --- a/lib/client/dbinterfaces/CachedDbInterface.ts +++ b/lib/client/dbinterfaces/CachedDbInterface.ts @@ -74,4 +74,33 @@ export default class CachedDbInterface >(collection: TId, slug: string): Promise { return findObjectBySlugLookUp(this, collection, slug); } + + addOrUpdateObject( + collection: TId, + object: CollectionIdToType, + ): Promise> { + return super.addOrUpdateObject(collection, object); + } + + findObjectAndUpdate( + collection: TId, + id: ObjectId, + newValues: Partial>, + ): Promise | undefined> { + return super.findObjectAndUpdate(collection, id, newValues); + } + + deleteObjects( + collection: TId, + query: object, + ): Promise { + return super.deleteObjects(collection, query); + } + + findObjectAndDelete( + collection: TId, + query: object, + ): Promise | undefined> { + return super.findObjectAndDelete(collection, query); + } } diff --git a/lib/client/dbinterfaces/DbInterface.ts b/lib/client/dbinterfaces/DbInterface.ts index 698a8a63..29bc3172 100644 --- a/lib/client/dbinterfaces/DbInterface.ts +++ b/lib/client/dbinterfaces/DbInterface.ts @@ -4,7 +4,6 @@ import CollectionId, { SluggedCollectionId, } from "../CollectionId"; import { default as BaseDbInterface } from "mongo-anywhere/DbInterface"; -import slugToId from "@/lib/slugToId"; export type WithStringOrObjectIdId = Omit & { _id?: ObjectId | string; @@ -63,4 +62,25 @@ export default interface DbInterface collection: CollectionId, query: object, ): Promise; + + addOrUpdateObject( + collection: TId, + object: CollectionIdToType, + ): Promise>; + + findObjectAndUpdate( + collection: TId, + id: ObjectId, + newValues: Partial>, + ): Promise | undefined>; + + deleteObjects( + collection: TId, + query: object, + ): Promise; + + findObjectAndDelete( + collection: TId, + query: object, + ): Promise | undefined>; } diff --git a/lib/client/dbinterfaces/InMemoryDbInterface.ts b/lib/client/dbinterfaces/InMemoryDbInterface.ts index 779f931f..306a7204 100644 --- a/lib/client/dbinterfaces/InMemoryDbInterface.ts +++ b/lib/client/dbinterfaces/InMemoryDbInterface.ts @@ -65,4 +65,33 @@ export default class InMemoryDbInterface >(collection: TId, slug: string): Promise { return findObjectBySlugLookUp(this, collection, slug); } + + addOrUpdateObject( + collection: TId, + object: CollectionIdToType, + ): Promise> { + return super.addOrUpdateObject(collection, object); + } + + findObjectAndUpdate( + collection: TId, + id: ObjectId, + newValues: Partial>, + ): Promise | undefined> { + return super.findObjectAndUpdate(collection, id, newValues); + } + + deleteObjects( + collection: TId, + query: object, + ): Promise { + return super.deleteObjects(collection, query); + } + + findObjectAndDelete( + collection: TId, + query: object, + ): Promise | undefined> { + return super.findObjectAndDelete(collection, query); + } } diff --git a/lib/client/dbinterfaces/LocalStorageDbInterface.ts b/lib/client/dbinterfaces/LocalStorageDbInterface.ts index b7975a2d..a5f962a2 100644 --- a/lib/client/dbinterfaces/LocalStorageDbInterface.ts +++ b/lib/client/dbinterfaces/LocalStorageDbInterface.ts @@ -65,4 +65,33 @@ export default class LocalStorageDbInterface >(collection: TId, slug: string): Promise { return findObjectBySlugLookUp(this, collection, slug); } + + addOrUpdateObject( + collection: TId, + object: CollectionIdToType, + ): Promise> { + return super.addOrUpdateObject(collection, object); + } + + findObjectAndUpdate( + collection: TId, + id: ObjectId, + newValues: Partial>, + ): Promise | undefined> { + return super.findObjectAndUpdate(collection, id, newValues); + } + + deleteObjects( + collection: TId, + query: object, + ): Promise { + return super.deleteObjects(collection, query); + } + + findObjectAndDelete( + collection: TId, + query: object, + ): Promise | undefined> { + return super.findObjectAndDelete(collection, query); + } } diff --git a/package-lock.json b/package-lock.json index 71f9e500..244398b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "jose": "^6.0.10", "levenary": "^1.1.1", "minimongo": "^7.0.0", - "mongo-anywhere": "^1.1.15", + "mongo-anywhere": "^1.2.0", "mongodb": "^5.0.0", "next": "^15.2.4", "next-auth": "^4.24.11", @@ -8231,9 +8231,9 @@ } }, "node_modules/mongo-anywhere": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/mongo-anywhere/-/mongo-anywhere-1.1.15.tgz", - "integrity": "sha512-wN6E/jN0lae5EqAeaAaE5fdUdb+ZchZKib3FWGOOOQUYZvTv2ino9Aii3GK+uIMxNdnm6N//B0bEGEeCNbBy1g==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mongo-anywhere/-/mongo-anywhere-1.2.0.tgz", + "integrity": "sha512-0D7eooYwPY1Z4ZhTmPExKGOOfQHSEU8GpmFPqSQKlJx90vHaY0P/ABwiycjRfDRxrhg1XI8JrNdwJoWFvvsuxA==", "dependencies": { "bson": "^5.0.0", "minimongo": "^6.19.0", diff --git a/package.json b/package.json index 9c4bdceb..222d16b7 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "jose": "^6.0.10", "levenary": "^1.1.1", "minimongo": "^7.0.0", - "mongo-anywhere": "^1.1.15", + "mongo-anywhere": "^1.2.0", "mongodb": "^5.0.0", "next": "^15.2.4", "next-auth": "^4.24.11", From 721ffb500c8cd95e35a5e19d8f51c4d4f26da011 Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Thu, 10 Apr 2025 21:15:44 -0400 Subject: [PATCH 106/168] Fix unit tests --- lib/Auth.ts | 22 -- lib/DbInterfaceAuthAdapter.ts | 95 ++++++-- lib/Types.ts | 2 +- lib/Utils.ts | 1 + tests/unit/lib/DbInterfaceAuthAdapter.test.ts | 228 +++--------------- 5 files changed, 107 insertions(+), 241 deletions(-) diff --git a/lib/Auth.ts b/lib/Auth.ts index 1a9eedfa..d0a3e5f7 100644 --- a/lib/Auth.ts +++ b/lib/Auth.ts @@ -20,28 +20,6 @@ const cachedDb = getDatabase(); const rollbar = getRollbar(); const adapter = DbInterfaceAuthAdapter(cachedDb, rollbar, logger); -async function createUser(profile: any) { - const user = new User( - profile.name, - profile.email, - profile.picture, - false, - await GenerateSlug(await cachedDb, CollectionId.Users, profile.name), - [], - [], - profile.sub, - 10, - 1, - ); - - user._id = new ObjectId() as any; - - // We need the 'id' field to avoid the error "Profile id is missing in OAuth profile response" - user.id = user._id!.toString(); - - return user; -} - export const AuthenticationOptions: AuthOptions = { secret: process.env.NEXTAUTH_SECRET, providers: [ diff --git a/lib/DbInterfaceAuthAdapter.ts b/lib/DbInterfaceAuthAdapter.ts index 714050b0..f75cf7da 100644 --- a/lib/DbInterfaceAuthAdapter.ts +++ b/lib/DbInterfaceAuthAdapter.ts @@ -15,6 +15,44 @@ import Logger from "./client/Logger"; import { RollbarInterface } from "./client/RollbarUtils"; import { Profile } from "next-auth"; +function formatTo(obj: T | undefined) { + if (!obj) return undefined; + + const formatted = format.to(obj); + + if ("_id" in obj) { + formatted._id = obj._id as ObjectId; + } + + if ("id" in obj) { + (formatted as any).id = obj.id; + } + + if ("userId" in obj) { + (formatted as any).userId = obj.userId; + } + + return formatted; +} + +function formatFrom(obj: T | undefined) { + if (!obj) return undefined; + + const formatted = format.from(obj); + if ("_id" in obj) { + (formatted as any)._id = obj._id as ObjectId; + } + if ("id" in obj) { + (formatted as any).id = obj.id; + } + + if ("userId" in obj) { + (formatted as any).userId = obj.userId; + } + + return formatted; +} + /** * Should match the MongoDB adapter as closely as possible * (https://github.com/nextauthjs/next-auth/blob/main/packages/adapter-mongodb/src/index.ts). @@ -34,17 +72,17 @@ export default function DbInterfaceAuthAdapter( * @param data returns from the profile callback of the auth provider */ createUser: async (data: Record) => { - const profile = format.to(data); + const profile = formatTo(data)!; const user = new User( - profile.name!, + profile.name ?? profile.email ?? "Unknown User", profile.email, profile.image, false, await GenerateSlug( await dbPromise, CollectionId.Users, - profile.name ?? profile.email!, + profile.name ?? profile.email ?? "Unknown User", ), [], [], @@ -59,14 +97,14 @@ export default function DbInterfaceAuthAdapter( user.id = user._id!.toString(); await (await dbPromise).addObject(CollectionId.Users, user); - return format.from(user); + return formatFrom(user); }, getUser: async (id: string) => { const user = await ( await dbPromise ).findObjectById(CollectionId.Users, _id(id)); if (!user) return null; - return format.from(user); + return formatFrom(user) as AdapterUser; }, getUserByEmail: async (email: string) => { const user = await ( @@ -75,27 +113,28 @@ export default function DbInterfaceAuthAdapter( email, }); if (!user) return null; - return format.from(user); + return formatFrom(user) as AdapterUser; }, getUserByAccount: async ( providerAccountId: Pick, ) => { const db = await dbPromise; - const account = await db.findObject(CollectionId.Accounts, { + const account = await db.findObject( + CollectionId.Accounts, providerAccountId, - }); + ); if (!account) return null; const user = await db.findObjectById( CollectionId.Users, new ObjectId(account.userId), ); if (!user) return null; - return format.from(user); + return formatFrom(user) as AdapterUser; }, updateUser: async ( data: Partial & Pick, ) => { - const { _id, ...user } = format.to(data); + const { _id, ...user } = formatTo(data as any)!; const db = await dbPromise; const result = await db.findObjectAndUpdate( @@ -104,7 +143,7 @@ export default function DbInterfaceAuthAdapter( user as unknown as Partial, ); - return format.from(result!); + return formatFrom(result!) as AdapterUser; }, deleteUser: async (id: string) => { const userId = _id(id); @@ -119,7 +158,7 @@ export default function DbInterfaceAuthAdapter( * Creates an account */ linkAccount: async (data: Record) => { - const account = format.to(data); + const account = formatTo(data as any)!; await (await dbPromise).addObject(CollectionId.Accounts, account); return account; }, @@ -132,7 +171,7 @@ export default function DbInterfaceAuthAdapter( const account = await ( await dbPromise ).findObjectAndDelete(CollectionId.Accounts, providerAccountId); - return format.from(account!); + return formatFrom(account!) ?? null; }, getSessionAndUser: async (sessionToken: string) => { const db = await dbPromise; @@ -148,25 +187,35 @@ export default function DbInterfaceAuthAdapter( if (!user) return null; return { - user: format.from(user), - session: format.from(session), + user: formatFrom(user) as any as AdapterUser, + session: formatFrom(session) as any as AdapterSession, }; }, createSession: async (data: Record) => { - const session = format.to(data); + const session = formatTo(data as any)!; await (await dbPromise).addObject(CollectionId.Sessions, session as any); - return format.from(session); + return formatFrom(session)!; }, updateSession: async ( data: Partial & Pick, ) => { - const { _id, ...session } = format.to(data); - const updatedSession = await ( - await dbPromise - ).findObjectAndUpdate(CollectionId.Sessions, _id, { + const { _id, ...session } = formatTo(data as any)!; + + const db = await dbPromise; + const existing = await db.findObject(CollectionId.Sessions, { sessionToken: session.sessionToken, }); - return format.from(updatedSession!); + + await db.updateObjectById( + CollectionId.Sessions, + existing?._id as any, + session as any, + ); + return formatFrom({ + _id, + ...existing, + ...session, + }) as any as AdapterSession; }, deleteSession: async (sessionToken: string) => { const session = await ( @@ -174,7 +223,7 @@ export default function DbInterfaceAuthAdapter( ).findObjectAndDelete(CollectionId.Sessions, { sessionToken, }); - return format.from(session!); + return formatFrom(session!) as any as AdapterSession; }, createVerificationToken: async (token: VerificationToken) => { await ( diff --git a/lib/Types.ts b/lib/Types.ts index 0fe59fe5..a25a741e 100644 --- a/lib/Types.ts +++ b/lib/Types.ts @@ -54,7 +54,7 @@ export class User implements NextAuthUser { onboardingComplete: boolean = false; resendContactId: string | undefined = undefined; lastSignInDateTime: Date | undefined = undefined; - emailVerified: Date | null = null; + emailVerified: Date | undefined = undefined; constructor( name: string | undefined, diff --git a/lib/Utils.ts b/lib/Utils.ts index 09503f38..e794fa8b 100644 --- a/lib/Utils.ts +++ b/lib/Utils.ts @@ -113,6 +113,7 @@ export async function populateMissingUserFields( level: user.level ?? 0, resendContactId: user.resendContactId ?? undefined, lastSignInDateTime: user.lastSignInDateTime ?? undefined, + emailVerified: user.emailVerified ?? undefined, }; if (user._id) (filled as User)._id = user._id as unknown as string; diff --git a/tests/unit/lib/DbInterfaceAuthAdapter.test.ts b/tests/unit/lib/DbInterfaceAuthAdapter.test.ts index d1cf93be..c53e8517 100644 --- a/tests/unit/lib/DbInterfaceAuthAdapter.test.ts +++ b/tests/unit/lib/DbInterfaceAuthAdapter.test.ts @@ -84,21 +84,6 @@ describe(prototype.createUser.name, () => { expect(foundUser?.name).toBeDefined(); expect(foundUser?.image).toBeDefined(); }); - - test("Does not create a new user if one already exists", async () => { - const { db, adapter } = await getDeps(); - - const user = { - email: "test@gmail.com", - }; - - await adapter.createUser(user); - await adapter.createUser(user); - - expect( - await db.countObjects(CollectionId.Users, { email: user.email }), - ).toBe(1); - }); }); describe(prototype.getUser!.name, () => { @@ -207,7 +192,10 @@ describe(prototype.getUserByAccount!.name, () => { await db.addObject(CollectionId.Accounts, account); - const foundUser = await adapter.getUserByAccount!(account); + const foundUser = await adapter.getUserByAccount!({ + provider: account.provider, + providerAccountId: account.providerAccountId, + }); expect(foundUser).toMatchObject(addedUser); }); @@ -222,7 +210,10 @@ describe(prototype.getUserByAccount!.name, () => { userId: new ObjectId() as any, }; - const user = await adapter.getUserByAccount!(account); + const user = await adapter.getUserByAccount!({ + provider: account.provider, + providerAccountId: account.providerAccountId, + }); expect(user).toBeNull(); }); @@ -239,7 +230,10 @@ describe(prototype.getUserByAccount!.name, () => { await db.addObject(CollectionId.Accounts, account); - const user = await adapter.getUserByAccount!(account); + const user = await adapter.getUserByAccount!({ + provider: account.provider, + providerAccountId: account.providerAccountId, + }); expect(user).toBeNull(); }); @@ -269,33 +263,10 @@ describe(prototype.updateUser!.name, () => { email: user.email, }); - expect(foundUser).toMatchObject(updatedUser); - }); + // Not sure how id behaves, don't use it + foundUser!.id = foundUser!._id!.toString(); - test("Errors if not given an _id", async () => { - const { adapter, rollbar } = await getDeps(); - - const user = { - name: "Test User", - email: "test@gmail.com", - }; - - await adapter.updateUser!(user as any); - - expect(rollbar.error).toHaveBeenCalled(); - }); - - test("Errors if the user doesn't exist", async () => { - const { adapter, rollbar } = await getDeps(); - - const user = { - name: "Test User", - email: "test@gmail.com", - }; - - await adapter.updateUser!(user as any); - - expect(rollbar.error).toHaveBeenCalled(); + expect(foundUser).toMatchObject(updatedUser); }); test("Returns the updated user without their _id", async () => { @@ -321,21 +292,6 @@ describe(prototype.updateUser!.name, () => { expect(returnedUser).toMatchObject(expectedUser); }); - - test("Errors if no _id is provided", async () => { - const { adapter, db, rollbar } = await getDeps(); - - const user = { - name: "Test User", - email: "test@gmail.com", - }; - - await db.addObject(CollectionId.Users, user as any); - - await adapter.updateUser!({ name: "Test User 2" } as any); - - expect(rollbar.error).toHaveBeenCalled(); - }); }); describe(prototype.deleteUser!.name, () => { @@ -359,15 +315,6 @@ describe(prototype.deleteUser!.name, () => { expect(foundUser).toBeUndefined(); }); - test("Errors but returns null if the user doesn't exist", async () => { - const { adapter, rollbar } = await getDeps(); - - const user = await adapter.deleteUser!(new ObjectId().toString()); - - expect(user).toBeNull(); - expect(rollbar.error).toHaveBeenCalled(); - }); - test("Deletes the user's account", async () => { const { db, adapter } = await getDeps(); @@ -460,24 +407,6 @@ describe(prototype.linkAccount!.name, () => { expect(foundAccount).toEqual(account); }); - test("Warns if the account already exists", async () => { - const { adapter, db, rollbar } = await getDeps(); - - const account: Account = { - _id: new ObjectId(), - provider: "test", - type: "oauth", - providerAccountId: "1234567890", - userId: new ObjectId() as any, - }; - - await db.addObject(CollectionId.Accounts, account); - - await adapter.linkAccount!(account); - - expect(rollbar.warn).toHaveBeenCalled(); - }); - test("Does not create another account if one already exists", async () => { const { db, adapter } = await getDeps(); @@ -544,21 +473,6 @@ describe(prototype.unlinkAccount!.name, () => { expect(foundAccount).toBeUndefined(); }); - test("Warns if the account doesn't exist", async () => { - const { adapter, rollbar } = await getDeps(); - - const account: Account = { - provider: "test", - type: "oauth", - providerAccountId: "1234567890", - userId: new ObjectId() as any, - }; - - await adapter.unlinkAccount!(account); - - expect(rollbar.warn).toHaveBeenCalled(); - }); - test("Returns null if the account doesn't exist", async () => { const { adapter } = await getDeps(); @@ -689,33 +603,6 @@ describe(prototype.createSession!.name, () => { expect(foundSession?.userId).toEqual(session.userId); }); - - test("Errors if not given a userId", async () => { - const { adapter, rollbar } = await getDeps(); - - const session: AdapterSession = { - sessionToken: "1234567890", - userId: undefined as any, - expires: new Date(), - }; - - await adapter.createSession!(session); - expect(rollbar.error).toHaveBeenCalled(); - }); - - test("Warns if the user doesn't exist", async () => { - const { adapter, rollbar } = await getDeps(); - - const session: AdapterSession = { - sessionToken: "1234567890", - userId: new ObjectId() as any, - expires: new Date(), - }; - - await adapter.createSession!(session); - - expect(rollbar.warn).toHaveBeenCalled(); - }); }); describe(prototype.updateSession!.name, () => { @@ -727,21 +614,17 @@ describe(prototype.updateSession!.name, () => { name: "Test User", email: "test@gmail.com", }; - const { _id: userId } = await db.addObject(CollectionId.Users, user as any); + const session: AdapterSession = { sessionToken: "1234567890", userId: userId as any, expires: new Date(), }; - - const { _id: sessionId } = await db.addObject( - CollectionId.Sessions, - session as any, - ); + await db.addObject(CollectionId.Sessions, session as any); const updatedSession = { - sessionToken: "1234567890", + sessionToken: session.sessionToken, userId: new ObjectId() as any, }; @@ -753,34 +636,6 @@ describe(prototype.updateSession!.name, () => { expect(foundSession?.userId).toEqual(updatedSession.userId); }); - - test("Errors if not given a sessionToken", async () => { - const { adapter, rollbar } = await getDeps(); - - const session: AdapterSession = { - sessionToken: undefined as any, - userId: new ObjectId() as any, - expires: new Date(), - }; - - await adapter.updateSession!(session); - - expect(rollbar.error).toHaveBeenCalled(); - }); - - test("Errors if the session doesn't exist", async () => { - const { adapter, rollbar } = await getDeps(); - - const session: AdapterSession = { - sessionToken: "1234567890", - userId: new ObjectId() as any, - expires: new Date(), - }; - - await adapter.updateSession!(session); - - expect(rollbar.error).toHaveBeenCalled(); - }); }); describe(prototype.deleteSession!.name, () => { @@ -801,10 +656,7 @@ describe(prototype.deleteSession!.name, () => { expires: new Date(), }; - const { _id: sessionId } = await db.addObject( - CollectionId.Sessions, - session as any, - ); + await db.addObject(CollectionId.Sessions, session as any); await adapter.deleteSession!(session.sessionToken); @@ -815,14 +667,6 @@ describe(prototype.deleteSession!.name, () => { expect(foundSession).toBeUndefined(); }); - test("Warns if the session doesn't exist", async () => { - const { adapter, rollbar } = await getDeps(); - - await adapter.deleteSession!("1234567890"); - - expect(rollbar.warn).toHaveBeenCalled(); - }); - test("Does not delete the user", async () => { const { db, adapter } = await getDeps(); @@ -882,32 +726,39 @@ describe(prototype.createVerificationToken!.name, () => { describe(prototype.useVerificationToken!.name, () => { test("Returns token", async () => { + const { adapter, db } = await getDeps(); + const testToken = { identifier: "hi", expires: new Date(), token: "hello", }; - - const { adapter, db } = await getDeps(); - await db.addObject(CollectionId.VerificationTokens, testToken); - const foundToken = await adapter.useVerificationToken!(testToken); + + const foundToken = await adapter.useVerificationToken!({ + identifier: testToken.identifier, + token: testToken.token, + }); expect(foundToken?.identifier).toBe(testToken.identifier); expect(foundToken?.token).toBe(testToken.token); }); test("Token is removed from database", async () => { + const { adapter, db } = await getDeps(); + const testToken = { identifier: "hi", expires: new Date(), token: "hello", }; + await db.addObject(CollectionId.VerificationTokens, testToken); - const { adapter, db } = await getDeps(); + await adapter.useVerificationToken!({ + identifier: testToken.identifier, + token: testToken.token, + }); - await db.addObject(CollectionId.VerificationTokens, testToken); - await adapter.useVerificationToken!(testToken); const foundToken = await db.findObject(CollectionId.VerificationTokens, { identifier: testToken.identifier, token: testToken.token, @@ -915,17 +766,4 @@ describe(prototype.useVerificationToken!.name, () => { expect(foundToken).toBeUndefined(); }); - - test("Warns if token doesn't exist", async () => { - const testToken = { - identifier: "hi", - expires: new Date(), - token: "hello", - }; - const { adapter, rollbar } = await getDeps(); - - await adapter.useVerificationToken!(testToken); - - expect(rollbar.warn).toHaveBeenCalled(); - }); }); From d2e624ca18392d9525a033c7df3ad4259e4f24d4 Mon Sep 17 00:00:00 2001 From: Gearbox Bot Date: Fri, 11 Apr 2025 01:16:23 +0000 Subject: [PATCH 107/168] 1.3.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 244398b0..28b1d8dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sj3", - "version": "1.3.2", + "version": "1.3.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sj3", - "version": "1.3.2", + "version": "1.3.3", "license": "CC BY-NC-SA 4.0", "dependencies": { "dependencies": "^0.0.1", diff --git a/package.json b/package.json index 222d16b7..9fe24735 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sj3", - "version": "1.3.2", + "version": "1.3.3", "private": true, "repository": "https://github.com/Decatur-Robotics/Gearbox", "license": "CC BY-NC-SA 4.0", From 9793ab0a47c8a6ea59b70dea8ee966c412496550 Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Thu, 10 Apr 2025 21:22:58 -0400 Subject: [PATCH 108/168] Fix sign in --- lib/DbInterfaceAuthAdapter.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/DbInterfaceAuthAdapter.ts b/lib/DbInterfaceAuthAdapter.ts index f75cf7da..ee3a0a7c 100644 --- a/lib/DbInterfaceAuthAdapter.ts +++ b/lib/DbInterfaceAuthAdapter.ts @@ -29,13 +29,14 @@ function formatTo(obj: T | undefined) { } if ("userId" in obj) { - (formatted as any).userId = obj.userId; + (formatted as any).userId = new ObjectId((obj.userId as any).toString()); } return formatted; } function formatFrom(obj: T | undefined) { + console.log("formatFrom", obj); if (!obj) return undefined; const formatted = format.from(obj); From 776abe83a1ba9bf0f30a1ea24ef7eff07d2cb3e3 Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Sun, 13 Apr 2025 15:23:39 -0400 Subject: [PATCH 109/168] Increase E2E retries in CI --- playwright.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index a8c50cd6..b6dabf87 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -16,8 +16,8 @@ export default defineConfig({ /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, /* Retry on CI only */ - retries: process.env.CI ? 5 : 0, - repeatEach: process.env.CI ? 5 : 1, + retries: process.env.CI ? 5 : 1, + repeatEach: process.env.CI ? 25 : 1, /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 4 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ From d554ff1aaa2811b1e5a77171fea09ab993248c85 Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Sun, 13 Apr 2025 15:40:12 -0400 Subject: [PATCH 110/168] Decreate CI E2E retries --- playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright.config.ts b/playwright.config.ts index b6dabf87..66c9923d 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -17,7 +17,7 @@ export default defineConfig({ forbidOnly: !!process.env.CI, /* Retry on CI only */ retries: process.env.CI ? 5 : 1, - repeatEach: process.env.CI ? 25 : 1, + repeatEach: process.env.CI ? 10 : 1, /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 4 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ From c4c04063b6674ae338206d1e847aa36d2e82f757 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 17:24:55 +0000 Subject: [PATCH 111/168] [npm]: Bump dotenv from 16.4.7 to 16.5.0 Bumps [dotenv](https://github.com/motdotla/dotenv) from 16.4.7 to 16.5.0. - [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md) - [Commits](https://github.com/motdotla/dotenv/compare/v16.4.7...v16.5.0) --- updated-dependencies: - dependency-name: dotenv dependency-version: 16.5.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 9 +++++---- package.json | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 71f9e500..d7d320af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "bootstrap": "^5.3.3", "browser-image-compression": "^2.0.2", "bson": "^5.0.0", - "dotenv": "^16.4.7", + "dotenv": "^16.5.0", "eslint": "9.18.0", "eslint-config-next": "15.2.4", "formidable": "^3.5.2", @@ -4571,9 +4571,10 @@ } }, "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "license": "BSD-2-Clause", "engines": { "node": ">=12" }, diff --git a/package.json b/package.json index 9c4bdceb..b4b4a09d 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "browser-image-compression": "^2.0.2", "bson": "^5.0.0", "dependencies": "^0.0.1", - "dotenv": "^16.4.7", + "dotenv": "^16.5.0", "eslint": "9.18.0", "eslint-config-next": "15.2.4", "formidable": "^3.5.2", From 80f0a99603e30e7fe64c00dd12fac96481ebe865 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 17:25:01 +0000 Subject: [PATCH 112/168] [npm]: Bump prettier from 3.5.0 to 3.5.3 Bumps [prettier](https://github.com/prettier/prettier) from 3.5.0 to 3.5.3. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.5.0...3.5.3) --- updated-dependencies: - dependency-name: prettier dependency-version: 3.5.3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 71f9e500..026116e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,7 +67,7 @@ "daisyui": "^4.12.22", "jest": "^29.7.0", "postcss": "^8.5.3", - "prettier": "3.5.0", + "prettier": "3.5.3", "serwist": "^9.0.11", "tailwindcss": "^3.4.17", "ts-jest": "^29.2.5" @@ -9256,9 +9256,9 @@ } }, "node_modules/prettier": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.0.tgz", - "integrity": "sha512-quyMrVt6svPS7CjQ9gKb3GLEX/rl3BCL2oa/QkNcXv4YNVBC9olt3s+H7ukto06q7B1Qz46PbrKLO34PR6vXcA==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true, "license": "MIT", "bin": { diff --git a/package.json b/package.json index 9c4bdceb..4b1c9625 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "daisyui": "^4.12.22", "jest": "^29.7.0", "postcss": "^8.5.3", - "prettier": "3.5.0", + "prettier": "3.5.3", "serwist": "^9.0.11", "tailwindcss": "^3.4.17", "ts-jest": "^29.2.5" From 4d6f0112649727f210bd568f621c393735098302 Mon Sep 17 00:00:00 2001 From: Gearbox Bot Date: Mon, 14 Apr 2025 17:25:10 +0000 Subject: [PATCH 113/168] 1.3.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d7d320af..ebac76d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sj3", - "version": "1.3.2", + "version": "1.3.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sj3", - "version": "1.3.2", + "version": "1.3.3", "license": "CC BY-NC-SA 4.0", "dependencies": { "dependencies": "^0.0.1", diff --git a/package.json b/package.json index b4b4a09d..e9604b9a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sj3", - "version": "1.3.2", + "version": "1.3.3", "private": true, "repository": "https://github.com/Decatur-Robotics/Gearbox", "license": "CC BY-NC-SA 4.0", From c9fd083cdc9724e20132a1e5564eca0630af675e Mon Sep 17 00:00:00 2001 From: Gearbox Bot Date: Mon, 14 Apr 2025 17:25:13 +0000 Subject: [PATCH 114/168] 1.3.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 026116e1..cffd82ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sj3", - "version": "1.3.2", + "version": "1.3.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sj3", - "version": "1.3.2", + "version": "1.3.3", "license": "CC BY-NC-SA 4.0", "dependencies": { "dependencies": "^0.0.1", diff --git a/package.json b/package.json index 4b1c9625..1563ecfd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sj3", - "version": "1.3.2", + "version": "1.3.3", "private": true, "repository": "https://github.com/Decatur-Robotics/Gearbox", "license": "CC BY-NC-SA 4.0", From 5c9390a09428dee8eef7547e9c99bc5a0fae08b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 17:25:21 +0000 Subject: [PATCH 115/168] [npm]: Bump react-chartjs-2 from 5.2.0 to 5.3.0 Bumps [react-chartjs-2](https://github.com/reactchartjs/react-chartjs-2) from 5.2.0 to 5.3.0. - [Release notes](https://github.com/reactchartjs/react-chartjs-2/releases) - [Changelog](https://github.com/reactchartjs/react-chartjs-2/blob/master/CHANGELOG.md) - [Commits](https://github.com/reactchartjs/react-chartjs-2/compare/v5.2.0...v5.3.0) --- updated-dependencies: - dependency-name: react-chartjs-2 dependency-version: 5.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 11 ++++++----- package.json | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 71f9e500..2e77ee2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,7 @@ "react": "18.3.1", "react-beautiful-dnd": "^13.1.1", "react-bootstrap": "^2.10.9", - "react-chartjs-2": "^5.2.0", + "react-chartjs-2": "^5.3.0", "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-dom": "18.3.1", @@ -9463,12 +9463,13 @@ } }, "node_modules/react-chartjs-2": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz", - "integrity": "sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.0.tgz", + "integrity": "sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==", + "license": "MIT", "peerDependencies": { "chart.js": "^4.1.1", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/react-dnd": { diff --git a/package.json b/package.json index 9c4bdceb..7ee54590 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "react": "18.3.1", "react-beautiful-dnd": "^13.1.1", "react-bootstrap": "^2.10.9", - "react-chartjs-2": "^5.2.0", + "react-chartjs-2": "^5.3.0", "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-dom": "18.3.1", From 41293848f6a1b60a4defc88cbb53c69bca59dc12 Mon Sep 17 00:00:00 2001 From: Gearbox Bot Date: Mon, 14 Apr 2025 17:25:38 +0000 Subject: [PATCH 116/168] 1.3.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2e77ee2d..56da78bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sj3", - "version": "1.3.2", + "version": "1.3.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sj3", - "version": "1.3.2", + "version": "1.3.3", "license": "CC BY-NC-SA 4.0", "dependencies": { "dependencies": "^0.0.1", diff --git a/package.json b/package.json index 7ee54590..136a8ec2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sj3", - "version": "1.3.2", + "version": "1.3.3", "private": true, "repository": "https://github.com/Decatur-Robotics/Gearbox", "license": "CC BY-NC-SA 4.0", From cb32644050b1673f1de09f5b48c1221339dff63b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 17:25:45 +0000 Subject: [PATCH 117/168] [npm]: Bump typescript from 5.7.3 to 5.8.3 Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.7.3 to 5.8.3. - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release-publish.yml) - [Commits](https://github.com/microsoft/TypeScript/compare/v5.7.3...v5.8.3) --- updated-dependencies: - dependency-name: typescript dependency-version: 5.8.3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 9 +++++---- package.json | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 71f9e500..ef6d5024 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,7 +50,7 @@ "string-similarity-js": "^2.1.4", "ts-node": "^10.9.2", "tsx": "^4.19.3", - "typescript": "5.7.3", + "typescript": "5.8.3", "unified-api-nextjs": "^1.1.3" }, "devDependencies": { @@ -11116,9 +11116,10 @@ } }, "node_modules/typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 9c4bdceb..7671cc95 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "string-similarity-js": "^2.1.4", "ts-node": "^10.9.2", "tsx": "^4.19.3", - "typescript": "5.7.3", + "typescript": "5.8.3", "unified-api-nextjs": "^1.1.3" }, "devDependencies": { From 3f4984d30abc091724cb84b9d42d32539c27033c Mon Sep 17 00:00:00 2001 From: Gearbox Bot Date: Mon, 14 Apr 2025 17:26:09 +0000 Subject: [PATCH 118/168] 1.3.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ef6d5024..51d40232 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sj3", - "version": "1.3.2", + "version": "1.3.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sj3", - "version": "1.3.2", + "version": "1.3.3", "license": "CC BY-NC-SA 4.0", "dependencies": { "dependencies": "^0.0.1", diff --git a/package.json b/package.json index 7671cc95..6705ccbb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sj3", - "version": "1.3.2", + "version": "1.3.3", "private": true, "repository": "https://github.com/Decatur-Robotics/Gearbox", "license": "CC BY-NC-SA 4.0", From 286dcc90b6fe35c17a6bccc583102c655de330c6 Mon Sep 17 00:00:00 2001 From: Davis Becker <143132652+BanEvading@users.noreply.github.com> Date: Mon, 21 Apr 2025 12:57:04 -0400 Subject: [PATCH 119/168] Update createTeam.tsx --- pages/createTeam.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/createTeam.tsx b/pages/createTeam.tsx index 0859d0ac..6d957763 100644 --- a/pages/createTeam.tsx +++ b/pages/createTeam.tsx @@ -100,7 +100,7 @@ export default function CreateTeam() { > From e6d1baeb304434f1e701b0668696a0f0178c150e Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Thu, 1 May 2025 16:19:53 -0400 Subject: [PATCH 132/168] Add history section to README --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 92cf2931..fe8c3973 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,14 @@ See the [Gearbox-Terraform](https://github.com/Decatur-Robotics/Gearbox-Terrafor - Google OAuth (for authentication) - Slack OAuth (for authentication) +## History + +Gearbox is internally known as Scout Janssen 3 (SJ3). It is the third iteration of the Scout Janssen project, which started in 2019. A brief timeline of the project is as follows: + +- 2019-2020: [Scout Janssen 1](https://github.com/Decatur-Robotics/Scout-Janssen), developed by team members Hayden, Keon, and Carter. Used a Django backend with a plain HTML/CSS/JS frontend. +- 2020-2023: [Scout Janssen 2](https://github.com/Decatur-Robotics/Scout-Janssen-2), developed by Theo, Hayden, and Carter with help from Avyn and Renato. Unlike other iterations, SJ2 used GraphQL, Redis, and Socket.io. +- 2023-present: Gearbox (SJ3), developed by Theo, Renato, Davis, and Colin. + ## Contibutors From d8a48dc9c300aeb2504d5eaf440a7014e2f76164 Mon Sep 17 00:00:00 2001 From: Renato Dell'Osso Date: Thu, 1 May 2025 16:57:52 -0400 Subject: [PATCH 133/168] Revert "Conform auth adapter to Mongo adapter" --- lib/Auth.ts | 56 ++ lib/DbInterfaceAuthAdapter.ts | 510 +++++++++++++----- lib/Types.ts | 1 - lib/Utils.ts | 1 - lib/client/dbinterfaces/CachedDbInterface.ts | 29 - lib/client/dbinterfaces/DbInterface.ts | 22 +- .../dbinterfaces/InMemoryDbInterface.ts | 29 - .../dbinterfaces/LocalStorageDbInterface.ts | 29 - package-lock.json | 8 +- package.json | 2 +- tests/unit/lib/DbInterfaceAuthAdapter.test.ts | 228 ++++++-- 11 files changed, 640 insertions(+), 275 deletions(-) diff --git a/lib/Auth.ts b/lib/Auth.ts index d0a3e5f7..813f972d 100644 --- a/lib/Auth.ts +++ b/lib/Auth.ts @@ -27,11 +27,67 @@ export const AuthenticationOptions: AuthOptions = { clientId: process.env.GOOGLE_ID, clientSecret: process.env.GOOGLE_SECRET, allowDangerousEmailAccountLinking: true, + profile: async (profile) => { + logger.debug("Google profile:", profile); + + const existingUser = await ( + await cachedDb + ).findObject(CollectionId.Users, { email: profile.email }); + if (existingUser) { + existingUser.id = profile.sub; + return existingUser; + } + + const user = new User( + profile.name, + profile.email, + profile.picture, + false, + await GenerateSlug(await cachedDb, CollectionId.Users, profile.name), + [], + [], + ); + + user.id = profile.sub; + + return user; + }, }), SlackProvider({ clientId: process.env.NEXT_PUBLIC_SLACK_CLIENT_ID as string, clientSecret: process.env.SLACK_CLIENT_SECRET as string, allowDangerousEmailAccountLinking: true, + profile: async (profile) => { + logger.debug("Slack profile:", profile); + + const existing = await ( + await cachedDb + ).findObject(CollectionId.Users, { email: profile.email }); + + if (existing) { + existing.slackId = profile.sub; + existing.id = profile.sub; + console.log("Found existing user:", existing); + return existing; + } + + const user = new User( + profile.name, + profile.email, + profile.picture, + false, + await GenerateSlug(await cachedDb, CollectionId.Users, profile.name), + [], + [], + profile.sub, + 10, + 1, + ); + + user.id = profile.sub; + + return user; + }, }), Email({ server: { diff --git a/lib/DbInterfaceAuthAdapter.ts b/lib/DbInterfaceAuthAdapter.ts index ee3a0a7c..b2ca5758 100644 --- a/lib/DbInterfaceAuthAdapter.ts +++ b/lib/DbInterfaceAuthAdapter.ts @@ -1,4 +1,4 @@ -import { _id, format, MongoDBAdapter } from "@next-auth/mongodb-adapter"; +import { format, MongoDBAdapter } from "@next-auth/mongodb-adapter"; import { Adapter, AdapterAccount, @@ -13,50 +13,8 @@ import { GenerateSlug } from "./Utils"; import { ObjectId } from "bson"; import Logger from "./client/Logger"; import { RollbarInterface } from "./client/RollbarUtils"; -import { Profile } from "next-auth"; - -function formatTo(obj: T | undefined) { - if (!obj) return undefined; - - const formatted = format.to(obj); - - if ("_id" in obj) { - formatted._id = obj._id as ObjectId; - } - - if ("id" in obj) { - (formatted as any).id = obj.id; - } - - if ("userId" in obj) { - (formatted as any).userId = new ObjectId((obj.userId as any).toString()); - } - - return formatted; -} - -function formatFrom(obj: T | undefined) { - console.log("formatFrom", obj); - if (!obj) return undefined; - - const formatted = format.from(obj); - if ("_id" in obj) { - (formatted as any)._id = obj._id as ObjectId; - } - if ("id" in obj) { - (formatted as any).id = obj.id; - } - - if ("userId" in obj) { - (formatted as any).userId = obj.userId; - } - - return formatted; -} /** - * Should match the MongoDB adapter as closely as possible - * (https://github.com/nextauthjs/next-auth/blob/main/packages/adapter-mongodb/src/index.ts). * @tested_by tests/lib/DbInterfaceAuthAdapter.test.ts */ export default function DbInterfaceAuthAdapter( @@ -69,98 +27,264 @@ export default function DbInterfaceAuthAdapter( new Logger(["AUTH"], false); const adapter: Adapter = { - /** - * @param data returns from the profile callback of the auth provider - */ createUser: async (data: Record) => { - const profile = formatTo(data)!; + const db = await dbPromise; + + const adapterUser = format.to(data); + adapterUser._id = data["_id"] as any; + + logger.debug("Creating user:", adapterUser.name); + + // Check if user already exists + const existingUser = await db.findObject(CollectionId.Users, { + email: adapterUser.email, + }); + + if (existingUser) { + // If user exists, return existing user + logger.warn("User already exists:", existingUser.name); + rollbar.warn("User already exists when creating user", { + existingUser, + data, + }); + return format.from(existingUser); + } + + logger.debug("Creating user:", adapterUser); const user = new User( - profile.name ?? profile.email ?? "Unknown User", - profile.email, - profile.image, + adapterUser.name ?? "Unknown", + adapterUser.email, + adapterUser.image ?? process.env.DEFAULT_IMAGE, false, await GenerateSlug( - await dbPromise, + db, CollectionId.Users, - profile.name ?? profile.email ?? "Unknown User", + adapterUser.name ?? "Unknown", ), [], [], - profile.sub, - 10, + adapterUser.id, + 0, 1, ); - user._id = new ObjectId() as any; + user._id = new ObjectId(adapterUser._id) as any; - // We need the 'id' field to avoid the error "Profile id is missing in OAuth profile response" - user.id = user._id!.toString(); - - await (await dbPromise).addObject(CollectionId.Users, user); - return formatFrom(user); + const dbUser = await db.addObject(CollectionId.Users, user); + logger.info("Created user:", dbUser._id!.toString()); + return format.from(dbUser); }, getUser: async (id: string) => { - const user = await ( - await dbPromise - ).findObjectById(CollectionId.Users, _id(id)); - if (!user) return null; - return formatFrom(user) as AdapterUser; + const db = await dbPromise; + + if (id.length !== 24) return null; + + logger.debug("Getting user:", id); + + const user = await db.findObjectById( + CollectionId.Users, + new ObjectId(id), + ); + + if (!user) { + logger.warn("User not found:", id); + return null; + } + user.id = user._id!.toString()!; + return format.from(user); }, getUserByEmail: async (email: string) => { - const user = await ( - await dbPromise - ).findObject(CollectionId.Users, { - email, - }); - if (!user) return null; - return formatFrom(user) as AdapterUser; + const db = await dbPromise; + + logger.debug("Getting user by email:", email); + + const user = await db.findObject(CollectionId.Users, { email }); + + if (!user) { + logger.warn("User not found by email:", email); + return null; + } + + user.id = user._id!.toString()!; + return format.from(user); }, getUserByAccount: async ( providerAccountId: Pick, ) => { const db = await dbPromise; - const account = await db.findObject( - CollectionId.Accounts, - providerAccountId, - ); - if (!account) return null; + + logger.debug("Getting user by account:", providerAccountId); + + const account = await db.findObject(CollectionId.Accounts, { + providerAccountId: providerAccountId.providerAccountId, + }); + + if (!account) { + logger.warn( + "Account not found by providerAccountId:", + providerAccountId.providerAccountId, + ); + return null; + } + const user = await db.findObjectById( CollectionId.Users, - new ObjectId(account.userId), + account.userId as any as ObjectId, + ); + + if (!user) { + logger.warn("User not found:", account.userId); + return null; + } + + logger.debug( + "Found user by account: Account", + providerAccountId.providerAccountId, + "=> User", + user._id, + user.name, ); - if (!user) return null; - return formatFrom(user) as AdapterUser; + + user.id = user._id!.toString()!; + return format.from(user); }, updateUser: async ( data: Partial & Pick, ) => { - const { _id, ...user } = formatTo(data as any)!; const db = await dbPromise; + const { _id, ...user } = format.to(data); + + if (!_id) { + logger.error("User ID not found when updating user:", user); + rollbar.error("User ID not found when updating user", { + user, + }); + + return format.from(user); + } + + logger.debug("Updating user:", _id); - const result = await db.findObjectAndUpdate( + const existing = await db.findObjectById( CollectionId.Users, - _id, - user as unknown as Partial, + new ObjectId(_id), ); - return formatFrom(result!) as AdapterUser; + if (!existing) { + logger.error("User not found:", _id); + rollbar.error("User not found when updating user", { + _id, + user, + }); + return format.from(user); + } + + user.id = existing._id!.toString()!; + + await db.updateObjectById( + CollectionId.Users, + new ObjectId(_id), + user as Partial, + ); + + return format.from({ ...existing, ...user, _id: _id }); }, deleteUser: async (id: string) => { - const userId = _id(id); const db = await dbPromise; - await Promise.all([ - db.deleteObjects(CollectionId.Accounts, { userId: userId as any }), - db.deleteObjects(CollectionId.Sessions, { userId: userId as any }), - db.deleteObjectById(CollectionId.Users, userId), - ]); + + logger.log("Deleting user:", id); + + const user = await db.findObjectById( + CollectionId.Users, + new ObjectId(id), + ); + if (!user) { + logger.error("User not found:", id); + rollbar.error("User not found when deleting user", { + id, + }); + return null; + } + + const account = await db.findObject(CollectionId.Accounts, { + userId: user._id, + }); + + const sessions = await db.findObjects(CollectionId.Sessions, { + userId: user._id, + }); + + const promises: Promise[] = [ + db.deleteObjectById(CollectionId.Users, user._id as any as ObjectId), + ]; + + if (account) { + promises.push( + db.deleteObjectById(CollectionId.Accounts, new ObjectId(account._id)), + ); + } + + if (sessions.length) { + promises.push( + Promise.all( + sessions.map((session) => + db.deleteObjectById( + CollectionId.Sessions, + new ObjectId(session._id), + ), + ), + ), + ); + } + + await Promise.all(promises); + + return format.from(user); }, /** * Creates an account */ linkAccount: async (data: Record) => { - const account = formatTo(data as any)!; - await (await dbPromise).addObject(CollectionId.Accounts, account); + const db = await dbPromise; + + const account = format.to(data); + account.userId = data["userId"] as any; // userId gets overwritten for some reason + + logger.debug( + "Linking account: providerAccountId", + account.providerAccountId, + "userId:", + account.userId, + ); + + const existingAccount = await db.findObject(CollectionId.Accounts, { + providerAccountId: account.providerAccountId, + }); + + if (existingAccount) { + logger.warn( + "Account already exists:", + existingAccount.providerAccountId, + ); + rollbar.warn("Account already exists when linking account", { + account, + }); + + let formattedAccount: AdapterAccount; + + // Sometimes gives an error about not finding toHexString. + try { + formattedAccount = format.from(existingAccount); + } catch (e) { + account.userId = new ObjectId(account.userId) as any; + formattedAccount = format.from(account); + } + return formattedAccount; + } + + await db.addObject(CollectionId.Accounts, account); + return account; }, /** @@ -169,79 +293,211 @@ export default function DbInterfaceAuthAdapter( unlinkAccount: async ( providerAccountId: Pick, ) => { - const account = await ( - await dbPromise - ).findObjectAndDelete(CollectionId.Accounts, providerAccountId); - return formatFrom(account!) ?? null; + const db = await dbPromise; + + logger.debug("Unlinking account:", providerAccountId.providerAccountId); + + const account = await db.findObject(CollectionId.Accounts, { + providerAccountId: providerAccountId.providerAccountId, + }); + + if (!account) { + logger.warn( + "Account not found by providerAccountId:", + providerAccountId.providerAccountId, + ); + rollbar.warn("Account not found when unlinking account", { + providerAccountId, + }); + return null; + } + + await db.deleteObjectById( + CollectionId.Accounts, + new ObjectId(account._id), + ); + + return format.from(account); }, getSessionAndUser: async (sessionToken: string) => { const db = await dbPromise; + const session = await db.findObject(CollectionId.Sessions, { sessionToken, }); - if (!session) return null; + + if (!session) { + // Weirdly, this is ok. + logger.warn("Session not found:", sessionToken); + return null; + } const user = await db.findObjectById( CollectionId.Users, new ObjectId(session.userId), ); - if (!user) return null; + + if (!user) { + logger.warn("User not found:", session.userId); + return null; + } + + logger.debug( + "Got session and user. Session Token:", + sessionToken, + "User:", + user._id, + user.name, + ); return { - user: formatFrom(user) as any as AdapterUser, - session: formatFrom(session) as any as AdapterSession, + session: format.from(session), + user: { + ...format.from(user), + _id: user._id, + }, }; }, createSession: async (data: Record) => { - const session = formatTo(data as any)!; - await (await dbPromise).addObject(CollectionId.Sessions, session as any); - return formatFrom(session)!; + const db = await dbPromise; + + const session = format.to(data); + session.userId = data["userId"] as any; // userId gets overwritten for some reason + + if (!session.userId) { + logger.error("User ID not found in session:", session); + rollbar.error("User ID not found in session when creating session", { + session, + }); + + const dbSession = await db.addObject( + CollectionId.Sessions, + session as unknown as Session, + ); + + return format.from(dbSession); + } + + logger.debug( + "Creating session:", + session._id, + "with user", + session.userId, + ); + + const user = await db.findObjectById( + CollectionId.Users, + new ObjectId(session.userId), + ); + + if (!user) { + logger.warn("User not found:", session.userId); + rollbar.warn("User not found", { + session, + }); + } else session.userId = user._id as any; + + const dbSession = await db.addObject( + CollectionId.Sessions, + session as unknown as Session, + ); + + return format.from(dbSession); }, updateSession: async ( data: Partial & Pick, ) => { - const { _id, ...session } = formatTo(data as any)!; - const db = await dbPromise; + const { _id, ...session } = format.to(data); + session.userId = data["userId"] as any; // userId gets overwritten for some reason + + logger.debug("Updating session:", session.sessionToken); + const existing = await db.findObject(CollectionId.Sessions, { sessionToken: session.sessionToken, }); + if (!existing) { + logger.error("Session not found:", session.sessionToken); + rollbar.error("Session not found when updating session", { + session, + }); + return null; + } + + if (session.userId) { + session.userId = new ObjectId(session.userId) as any; + } + await db.updateObjectById( CollectionId.Sessions, - existing?._id as any, - session as any, - ); - return formatFrom({ - _id, - ...existing, - ...session, - }) as any as AdapterSession; + new ObjectId(existing._id), + session as unknown as Partial, + ); + + return format.from({ ...existing, ...data }); }, deleteSession: async (sessionToken: string) => { - const session = await ( - await dbPromise - ).findObjectAndDelete(CollectionId.Sessions, { + const db = await dbPromise; + + logger.debug("Deleting session:", sessionToken); + + const session = await db.findObject(CollectionId.Sessions, { sessionToken, }); - return formatFrom(session!) as any as AdapterSession; + + if (!session) { + logger.warn("Session not found:", sessionToken); + rollbar.warn("Session not found when deleting session", { + sessionToken, + }); + return null; + } + + await db.deleteObjectById( + CollectionId.Sessions, + new ObjectId(session._id), + ); + + return format.from(session); }, createVerificationToken: async (token: VerificationToken) => { - await ( - await dbPromise - ).addObject(CollectionId.VerificationTokens, format.to(token) as any); + const db = await dbPromise; + + logger.debug("Creating verification token:", token.identifier); + + await db.addObject( + CollectionId.VerificationTokens, + format.to(token) as VerificationToken, + ); return token; }, useVerificationToken: async (token: { identifier: string; token: string; }) => { - const verificationToken = await ( - await dbPromise - ).findObjectAndDelete(CollectionId.VerificationTokens, token); - if (!verificationToken) return null; - const { _id, ...rest } = verificationToken; - return rest; + const db = await dbPromise; + + logger.info("Using verification token:", token.identifier); + + const existing = await db.findObject(CollectionId.VerificationTokens, { + token: token.token, + }); + + if (!existing) { + logger.warn("Verification token not found:", token.token); + rollbar.warn("Verification token not found when using token", { + token, + }); + return null; + } + + await db.deleteObjectById( + CollectionId.VerificationTokens, + new ObjectId(existing._id), + ); + + return format.from(existing); }, }; diff --git a/lib/Types.ts b/lib/Types.ts index a25a741e..8127ae02 100644 --- a/lib/Types.ts +++ b/lib/Types.ts @@ -54,7 +54,6 @@ export class User implements NextAuthUser { onboardingComplete: boolean = false; resendContactId: string | undefined = undefined; lastSignInDateTime: Date | undefined = undefined; - emailVerified: Date | undefined = undefined; constructor( name: string | undefined, diff --git a/lib/Utils.ts b/lib/Utils.ts index e794fa8b..09503f38 100644 --- a/lib/Utils.ts +++ b/lib/Utils.ts @@ -113,7 +113,6 @@ export async function populateMissingUserFields( level: user.level ?? 0, resendContactId: user.resendContactId ?? undefined, lastSignInDateTime: user.lastSignInDateTime ?? undefined, - emailVerified: user.emailVerified ?? undefined, }; if (user._id) (filled as User)._id = user._id as unknown as string; diff --git a/lib/client/dbinterfaces/CachedDbInterface.ts b/lib/client/dbinterfaces/CachedDbInterface.ts index 0903e9f5..2ae214e5 100644 --- a/lib/client/dbinterfaces/CachedDbInterface.ts +++ b/lib/client/dbinterfaces/CachedDbInterface.ts @@ -74,33 +74,4 @@ export default class CachedDbInterface >(collection: TId, slug: string): Promise { return findObjectBySlugLookUp(this, collection, slug); } - - addOrUpdateObject( - collection: TId, - object: CollectionIdToType, - ): Promise> { - return super.addOrUpdateObject(collection, object); - } - - findObjectAndUpdate( - collection: TId, - id: ObjectId, - newValues: Partial>, - ): Promise | undefined> { - return super.findObjectAndUpdate(collection, id, newValues); - } - - deleteObjects( - collection: TId, - query: object, - ): Promise { - return super.deleteObjects(collection, query); - } - - findObjectAndDelete( - collection: TId, - query: object, - ): Promise | undefined> { - return super.findObjectAndDelete(collection, query); - } } diff --git a/lib/client/dbinterfaces/DbInterface.ts b/lib/client/dbinterfaces/DbInterface.ts index 29bc3172..698a8a63 100644 --- a/lib/client/dbinterfaces/DbInterface.ts +++ b/lib/client/dbinterfaces/DbInterface.ts @@ -4,6 +4,7 @@ import CollectionId, { SluggedCollectionId, } from "../CollectionId"; import { default as BaseDbInterface } from "mongo-anywhere/DbInterface"; +import slugToId from "@/lib/slugToId"; export type WithStringOrObjectIdId = Omit & { _id?: ObjectId | string; @@ -62,25 +63,4 @@ export default interface DbInterface collection: CollectionId, query: object, ): Promise; - - addOrUpdateObject( - collection: TId, - object: CollectionIdToType, - ): Promise>; - - findObjectAndUpdate( - collection: TId, - id: ObjectId, - newValues: Partial>, - ): Promise | undefined>; - - deleteObjects( - collection: TId, - query: object, - ): Promise; - - findObjectAndDelete( - collection: TId, - query: object, - ): Promise | undefined>; } diff --git a/lib/client/dbinterfaces/InMemoryDbInterface.ts b/lib/client/dbinterfaces/InMemoryDbInterface.ts index 306a7204..779f931f 100644 --- a/lib/client/dbinterfaces/InMemoryDbInterface.ts +++ b/lib/client/dbinterfaces/InMemoryDbInterface.ts @@ -65,33 +65,4 @@ export default class InMemoryDbInterface >(collection: TId, slug: string): Promise { return findObjectBySlugLookUp(this, collection, slug); } - - addOrUpdateObject( - collection: TId, - object: CollectionIdToType, - ): Promise> { - return super.addOrUpdateObject(collection, object); - } - - findObjectAndUpdate( - collection: TId, - id: ObjectId, - newValues: Partial>, - ): Promise | undefined> { - return super.findObjectAndUpdate(collection, id, newValues); - } - - deleteObjects( - collection: TId, - query: object, - ): Promise { - return super.deleteObjects(collection, query); - } - - findObjectAndDelete( - collection: TId, - query: object, - ): Promise | undefined> { - return super.findObjectAndDelete(collection, query); - } } diff --git a/lib/client/dbinterfaces/LocalStorageDbInterface.ts b/lib/client/dbinterfaces/LocalStorageDbInterface.ts index a5f962a2..b7975a2d 100644 --- a/lib/client/dbinterfaces/LocalStorageDbInterface.ts +++ b/lib/client/dbinterfaces/LocalStorageDbInterface.ts @@ -65,33 +65,4 @@ export default class LocalStorageDbInterface >(collection: TId, slug: string): Promise { return findObjectBySlugLookUp(this, collection, slug); } - - addOrUpdateObject( - collection: TId, - object: CollectionIdToType, - ): Promise> { - return super.addOrUpdateObject(collection, object); - } - - findObjectAndUpdate( - collection: TId, - id: ObjectId, - newValues: Partial>, - ): Promise | undefined> { - return super.findObjectAndUpdate(collection, id, newValues); - } - - deleteObjects( - collection: TId, - query: object, - ): Promise { - return super.deleteObjects(collection, query); - } - - findObjectAndDelete( - collection: TId, - query: object, - ): Promise | undefined> { - return super.findObjectAndDelete(collection, query); - } } diff --git a/package-lock.json b/package-lock.json index 78c99b46..e1d3d706 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "jose": "^6.0.10", "levenary": "^1.1.1", "minimongo": "^7.0.0", - "mongo-anywhere": "^1.2.0", + "mongo-anywhere": "^1.1.15", "mongodb": "^5.0.0", "next": "^15.2.4", "next-auth": "^4.24.11", @@ -8234,9 +8234,9 @@ } }, "node_modules/mongo-anywhere": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mongo-anywhere/-/mongo-anywhere-1.2.0.tgz", - "integrity": "sha512-0D7eooYwPY1Z4ZhTmPExKGOOfQHSEU8GpmFPqSQKlJx90vHaY0P/ABwiycjRfDRxrhg1XI8JrNdwJoWFvvsuxA==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/mongo-anywhere/-/mongo-anywhere-1.1.15.tgz", + "integrity": "sha512-wN6E/jN0lae5EqAeaAaE5fdUdb+ZchZKib3FWGOOOQUYZvTv2ino9Aii3GK+uIMxNdnm6N//B0bEGEeCNbBy1g==", "dependencies": { "bson": "^5.0.0", "minimongo": "^6.19.0", diff --git a/package.json b/package.json index 0e85e912..93953def 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "jose": "^6.0.10", "levenary": "^1.1.1", "minimongo": "^7.0.0", - "mongo-anywhere": "^1.2.0", + "mongo-anywhere": "^1.1.15", "mongodb": "^5.0.0", "next": "^15.2.4", "next-auth": "^4.24.11", diff --git a/tests/unit/lib/DbInterfaceAuthAdapter.test.ts b/tests/unit/lib/DbInterfaceAuthAdapter.test.ts index c53e8517..d1cf93be 100644 --- a/tests/unit/lib/DbInterfaceAuthAdapter.test.ts +++ b/tests/unit/lib/DbInterfaceAuthAdapter.test.ts @@ -84,6 +84,21 @@ describe(prototype.createUser.name, () => { expect(foundUser?.name).toBeDefined(); expect(foundUser?.image).toBeDefined(); }); + + test("Does not create a new user if one already exists", async () => { + const { db, adapter } = await getDeps(); + + const user = { + email: "test@gmail.com", + }; + + await adapter.createUser(user); + await adapter.createUser(user); + + expect( + await db.countObjects(CollectionId.Users, { email: user.email }), + ).toBe(1); + }); }); describe(prototype.getUser!.name, () => { @@ -192,10 +207,7 @@ describe(prototype.getUserByAccount!.name, () => { await db.addObject(CollectionId.Accounts, account); - const foundUser = await adapter.getUserByAccount!({ - provider: account.provider, - providerAccountId: account.providerAccountId, - }); + const foundUser = await adapter.getUserByAccount!(account); expect(foundUser).toMatchObject(addedUser); }); @@ -210,10 +222,7 @@ describe(prototype.getUserByAccount!.name, () => { userId: new ObjectId() as any, }; - const user = await adapter.getUserByAccount!({ - provider: account.provider, - providerAccountId: account.providerAccountId, - }); + const user = await adapter.getUserByAccount!(account); expect(user).toBeNull(); }); @@ -230,10 +239,7 @@ describe(prototype.getUserByAccount!.name, () => { await db.addObject(CollectionId.Accounts, account); - const user = await adapter.getUserByAccount!({ - provider: account.provider, - providerAccountId: account.providerAccountId, - }); + const user = await adapter.getUserByAccount!(account); expect(user).toBeNull(); }); @@ -263,12 +269,35 @@ describe(prototype.updateUser!.name, () => { email: user.email, }); - // Not sure how id behaves, don't use it - foundUser!.id = foundUser!._id!.toString(); - expect(foundUser).toMatchObject(updatedUser); }); + test("Errors if not given an _id", async () => { + const { adapter, rollbar } = await getDeps(); + + const user = { + name: "Test User", + email: "test@gmail.com", + }; + + await adapter.updateUser!(user as any); + + expect(rollbar.error).toHaveBeenCalled(); + }); + + test("Errors if the user doesn't exist", async () => { + const { adapter, rollbar } = await getDeps(); + + const user = { + name: "Test User", + email: "test@gmail.com", + }; + + await adapter.updateUser!(user as any); + + expect(rollbar.error).toHaveBeenCalled(); + }); + test("Returns the updated user without their _id", async () => { const { db, adapter } = await getDeps(); @@ -292,6 +321,21 @@ describe(prototype.updateUser!.name, () => { expect(returnedUser).toMatchObject(expectedUser); }); + + test("Errors if no _id is provided", async () => { + const { adapter, db, rollbar } = await getDeps(); + + const user = { + name: "Test User", + email: "test@gmail.com", + }; + + await db.addObject(CollectionId.Users, user as any); + + await adapter.updateUser!({ name: "Test User 2" } as any); + + expect(rollbar.error).toHaveBeenCalled(); + }); }); describe(prototype.deleteUser!.name, () => { @@ -315,6 +359,15 @@ describe(prototype.deleteUser!.name, () => { expect(foundUser).toBeUndefined(); }); + test("Errors but returns null if the user doesn't exist", async () => { + const { adapter, rollbar } = await getDeps(); + + const user = await adapter.deleteUser!(new ObjectId().toString()); + + expect(user).toBeNull(); + expect(rollbar.error).toHaveBeenCalled(); + }); + test("Deletes the user's account", async () => { const { db, adapter } = await getDeps(); @@ -407,6 +460,24 @@ describe(prototype.linkAccount!.name, () => { expect(foundAccount).toEqual(account); }); + test("Warns if the account already exists", async () => { + const { adapter, db, rollbar } = await getDeps(); + + const account: Account = { + _id: new ObjectId(), + provider: "test", + type: "oauth", + providerAccountId: "1234567890", + userId: new ObjectId() as any, + }; + + await db.addObject(CollectionId.Accounts, account); + + await adapter.linkAccount!(account); + + expect(rollbar.warn).toHaveBeenCalled(); + }); + test("Does not create another account if one already exists", async () => { const { db, adapter } = await getDeps(); @@ -473,6 +544,21 @@ describe(prototype.unlinkAccount!.name, () => { expect(foundAccount).toBeUndefined(); }); + test("Warns if the account doesn't exist", async () => { + const { adapter, rollbar } = await getDeps(); + + const account: Account = { + provider: "test", + type: "oauth", + providerAccountId: "1234567890", + userId: new ObjectId() as any, + }; + + await adapter.unlinkAccount!(account); + + expect(rollbar.warn).toHaveBeenCalled(); + }); + test("Returns null if the account doesn't exist", async () => { const { adapter } = await getDeps(); @@ -603,6 +689,33 @@ describe(prototype.createSession!.name, () => { expect(foundSession?.userId).toEqual(session.userId); }); + + test("Errors if not given a userId", async () => { + const { adapter, rollbar } = await getDeps(); + + const session: AdapterSession = { + sessionToken: "1234567890", + userId: undefined as any, + expires: new Date(), + }; + + await adapter.createSession!(session); + expect(rollbar.error).toHaveBeenCalled(); + }); + + test("Warns if the user doesn't exist", async () => { + const { adapter, rollbar } = await getDeps(); + + const session: AdapterSession = { + sessionToken: "1234567890", + userId: new ObjectId() as any, + expires: new Date(), + }; + + await adapter.createSession!(session); + + expect(rollbar.warn).toHaveBeenCalled(); + }); }); describe(prototype.updateSession!.name, () => { @@ -614,17 +727,21 @@ describe(prototype.updateSession!.name, () => { name: "Test User", email: "test@gmail.com", }; - const { _id: userId } = await db.addObject(CollectionId.Users, user as any); + const { _id: userId } = await db.addObject(CollectionId.Users, user as any); const session: AdapterSession = { sessionToken: "1234567890", userId: userId as any, expires: new Date(), }; - await db.addObject(CollectionId.Sessions, session as any); + + const { _id: sessionId } = await db.addObject( + CollectionId.Sessions, + session as any, + ); const updatedSession = { - sessionToken: session.sessionToken, + sessionToken: "1234567890", userId: new ObjectId() as any, }; @@ -636,6 +753,34 @@ describe(prototype.updateSession!.name, () => { expect(foundSession?.userId).toEqual(updatedSession.userId); }); + + test("Errors if not given a sessionToken", async () => { + const { adapter, rollbar } = await getDeps(); + + const session: AdapterSession = { + sessionToken: undefined as any, + userId: new ObjectId() as any, + expires: new Date(), + }; + + await adapter.updateSession!(session); + + expect(rollbar.error).toHaveBeenCalled(); + }); + + test("Errors if the session doesn't exist", async () => { + const { adapter, rollbar } = await getDeps(); + + const session: AdapterSession = { + sessionToken: "1234567890", + userId: new ObjectId() as any, + expires: new Date(), + }; + + await adapter.updateSession!(session); + + expect(rollbar.error).toHaveBeenCalled(); + }); }); describe(prototype.deleteSession!.name, () => { @@ -656,7 +801,10 @@ describe(prototype.deleteSession!.name, () => { expires: new Date(), }; - await db.addObject(CollectionId.Sessions, session as any); + const { _id: sessionId } = await db.addObject( + CollectionId.Sessions, + session as any, + ); await adapter.deleteSession!(session.sessionToken); @@ -667,6 +815,14 @@ describe(prototype.deleteSession!.name, () => { expect(foundSession).toBeUndefined(); }); + test("Warns if the session doesn't exist", async () => { + const { adapter, rollbar } = await getDeps(); + + await adapter.deleteSession!("1234567890"); + + expect(rollbar.warn).toHaveBeenCalled(); + }); + test("Does not delete the user", async () => { const { db, adapter } = await getDeps(); @@ -726,39 +882,32 @@ describe(prototype.createVerificationToken!.name, () => { describe(prototype.useVerificationToken!.name, () => { test("Returns token", async () => { - const { adapter, db } = await getDeps(); - const testToken = { identifier: "hi", expires: new Date(), token: "hello", }; - await db.addObject(CollectionId.VerificationTokens, testToken); - const foundToken = await adapter.useVerificationToken!({ - identifier: testToken.identifier, - token: testToken.token, - }); + const { adapter, db } = await getDeps(); + + await db.addObject(CollectionId.VerificationTokens, testToken); + const foundToken = await adapter.useVerificationToken!(testToken); expect(foundToken?.identifier).toBe(testToken.identifier); expect(foundToken?.token).toBe(testToken.token); }); test("Token is removed from database", async () => { - const { adapter, db } = await getDeps(); - const testToken = { identifier: "hi", expires: new Date(), token: "hello", }; - await db.addObject(CollectionId.VerificationTokens, testToken); - await adapter.useVerificationToken!({ - identifier: testToken.identifier, - token: testToken.token, - }); + const { adapter, db } = await getDeps(); + await db.addObject(CollectionId.VerificationTokens, testToken); + await adapter.useVerificationToken!(testToken); const foundToken = await db.findObject(CollectionId.VerificationTokens, { identifier: testToken.identifier, token: testToken.token, @@ -766,4 +915,17 @@ describe(prototype.useVerificationToken!.name, () => { expect(foundToken).toBeUndefined(); }); + + test("Warns if token doesn't exist", async () => { + const testToken = { + identifier: "hi", + expires: new Date(), + token: "hello", + }; + const { adapter, rollbar } = await getDeps(); + + await adapter.useVerificationToken!(testToken); + + expect(rollbar.warn).toHaveBeenCalled(); + }); }); From c4f9f52bf2f6e6ead1b9d6572fe6b90671ea701d Mon Sep 17 00:00:00 2001 From: Renato Dell'Osso Date: Thu, 15 May 2025 15:42:07 -0400 Subject: [PATCH 134/168] Change Dependabot assignee to Colin --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4f83cda2..01b9e600 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,7 +6,7 @@ version: 2 updates: - package-ecosystem: "npm" # See documentation for possible values directory: "/" # Location of package manifests - assignees: ["renatodellosso", "BanEvading"] + assignees: ["Tr01ler"] schedule: day: "monday" interval: "weekly" From 944e6bb86e8a3a1f8a95157ef97c1cefc84b63b5 Mon Sep 17 00:00:00 2001 From: Teknosquad5219 Date: Tue, 14 Oct 2025 16:11:13 -0400 Subject: [PATCH 135/168] Started with Decode quantData --- lib/games.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/games.ts b/lib/games.ts index 756ddc0d..6378bb62 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1888,6 +1888,18 @@ namespace Reefscape { ); } +namespace Decode { + export class QuantitativeData extends QuantData { + autoMovedPastStartingLine: boolean = false; + + autoArtifactsClassified: number = 0; + autoOverflowArtifacts: number = 0; + autoMotifArtifacts: number = 0; + + + } +} + export const games: { [id in GameId]: Game } = Object.freeze({ [GameId.Reefscape]: Reefscape.game, [GameId.IntoTheDeep]: IntoTheDeep.game, From 28b386185fd72130ee6a6a6e74ac1979508aaecb Mon Sep 17 00:00:00 2001 From: Teknosquad5219 Date: Tue, 14 Oct 2025 16:22:54 -0400 Subject: [PATCH 136/168] Quantdata done + Started on Enums --- lib/Enums.ts | 9 +++++++++ lib/games.ts | 16 +++++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/Enums.ts b/lib/Enums.ts index ccd7a6f0..c0391cc9 100644 --- a/lib/Enums.ts +++ b/lib/Enums.ts @@ -109,3 +109,12 @@ export namespace ReefscapeEnums { Shallow = "Shallow", } } + +export namespace DecodeEnums{ + export enum EndgameParkStatus { + No = "No", + Partial = "Partial", + Full = "Full", + TwoBotPark = "Two Bot Park" + } +} \ No newline at end of file diff --git a/lib/games.ts b/lib/games.ts index 6378bb62..68248b22 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1,6 +1,7 @@ import { Dot } from "@/components/stats/Heatmap"; import { CenterStageEnums, + DecodeEnums, Defense, FrcDrivetrain, IntakeTypes, @@ -1890,13 +1891,18 @@ namespace Reefscape { namespace Decode { export class QuantitativeData extends QuantData { - autoMovedPastStartingLine: boolean = false; + AutoMovedPastStartingLine: boolean = false; - autoArtifactsClassified: number = 0; - autoOverflowArtifacts: number = 0; - autoMotifArtifacts: number = 0; + AutoArtifactsClassified: number = 0; + AutoOverflowArtifacts: number = 0; + AutoMotifArtifacts: number = 0; - + TeleopArtifactsClassified: number = 0; + TeleopOverflowArtifacts: number = 0; + TeleopMotifArtifacts: number = 0; + + EndgameParkStatus: DecodeEnums.EndgameParkStatus = DecodeEnums.EndgameParkStatus.No; + EndgameDefenseStatus: Defense = Defense.None; } } From 83367fa882ea0a70b0dcf5f3fe9ea9ff9d66355d Mon Sep 17 00:00:00 2001 From: Teknosquad5219 Date: Tue, 14 Oct 2025 16:39:08 -0400 Subject: [PATCH 137/168] PitData --- lib/games.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/games.ts b/lib/games.ts index 68248b22..c7399e79 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1904,6 +1904,16 @@ namespace Decode { EndgameParkStatus: DecodeEnums.EndgameParkStatus = DecodeEnums.EndgameParkStatus.No; EndgameDefenseStatus: Defense = Defense.None; } + + export class PitData extends PitReportData { + CanScoreClassifier: boolean = false; + CanScoreDepot: boolean = false; + CanOpenGate: boolean = false; + + PointsScoredAuto: number = 0; + AutoAccountsForMotif: boolean = false; + CanParkWithOtherBots: boolean = false + } } export const games: { [id in GameId]: Game } = Object.freeze({ From 13e303eca59ea92f5d8013d39ecb47d8692f4747 Mon Sep 17 00:00:00 2001 From: Teknosquad5219 Date: Thu, 16 Oct 2025 15:47:34 -0400 Subject: [PATCH 138/168] pitReportLayout --- lib/games.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/games.ts b/lib/games.ts index c7399e79..255be0c5 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1909,11 +1909,24 @@ namespace Decode { CanScoreClassifier: boolean = false; CanScoreDepot: boolean = false; CanOpenGate: boolean = false; + CanParkWithOtherBots: boolean = false; PointsScoredAuto: number = 0; AutoAccountsForMotif: boolean = false; - CanParkWithOtherBots: boolean = false } + + const pitReportLayout: FormLayoutProps = { + Capabilities: [ + { key: "CanScoreClassifier", label: "Can Score Classifier?" }, + { key: "CanScoreDepot", label: "Can Score Depot?" }, + { key: "CanOpenGate", label: "Can Score Gate?" }, + { key: "CanParkWithOtherBots", label: "Can Park With Other Bots?" }, + ], + Auto: [ + { key: "PointsScoredAuto", label: "Average Auto Points" }, + { key: "AutoAccountsForMotif", label: "Auto Accounts For Motif?" }, + ], + }; } export const games: { [id in GameId]: Game } = Object.freeze({ From 44f34aea0f19eb8b3a965b8b5d111ab967bb7e6b Mon Sep 17 00:00:00 2001 From: Teknosquad5219 Date: Thu, 16 Oct 2025 16:36:09 -0400 Subject: [PATCH 139/168] Set up th rest of the parts needed for the game and started quant reports. --- lib/client/GameId.ts | 1 + lib/games.ts | 50 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/lib/client/GameId.ts b/lib/client/GameId.ts index d2f946cc..7eb6402a 100644 --- a/lib/client/GameId.ts +++ b/lib/client/GameId.ts @@ -3,6 +3,7 @@ export enum GameId { CenterStage = "CenterStage", IntoTheDeep = "IntoTheDeep", Reefscape = "Reefscape", + Decode = "Decode" } export const defaultGameId = GameId.Reefscape; diff --git a/lib/games.ts b/lib/games.ts index 255be0c5..564d9654 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1927,6 +1927,55 @@ namespace Decode { { key: "AutoAccountsForMotif", label: "Auto Accounts For Motif?" }, ], }; + + const quantitativeReportLayout: FormLayoutProps = { + Auto: [ + { key: "AutoMovedPastStart", label: "Moved Past Starting line"}, + [ + [ + { key: "AutoArtifactsClassified", label: "Artifacts Classified (Auto)"} + ], + [ + { key: "AutoOverflowArtifacts", label: "Overflow Artifacts (Auto)"} + ], + [ + { key: "AutoMotifArtifacts", label: "Motif Artifacts (Auto)"} + ] + ] + ], + Teleop: [ + + ], + "Post Match": ["EndgameDefenseStatus", "EndgameParkStatus"] + }; + + const statsLayout: StatsLayout = {} + + const pitStatsLayout: PitStatsLayout ={} + + function getBadges( + pitReport: Pitreport | undefined, + quantitativeReports: Report[] | undefined, + card: boolean, + ) {} + + function getAvgPoints(reports: Report[] | undefined) {} + + export const game = new Game( + "Decode", + 2026, + League.FTC, + QuantitativeData, + PitData, + pitReportLayout, + quantitativeReportLayout, + statsLayout, + pitStatsLayout, + "Decode", + "https://info.firstinspires.org/hs-fs/hubfs/2026%20Season/Season%20Assets/first_age_ftc_decode_logo_vertical_rgb_fullcolor.png?width=237&height=348&name=first_age_ftc_decode_logo_vertical_rgb_fullcolor.png", + "invert", + getBadges, + getAvgPoints, } export const games: { [id in GameId]: Game } = Object.freeze({ @@ -1934,4 +1983,5 @@ export const games: { [id in GameId]: Game } = Object.freeze({ [GameId.IntoTheDeep]: IntoTheDeep.game, [GameId.Crescendo]: Crescendo.game, [GameId.CenterStage]: CenterStage.game, + [GameId.Decode]: Decode.game, }); From b52f7be947ec62446f95921afe76ee257827cf34 Mon Sep 17 00:00:00 2001 From: Teknosquad5219 Date: Thu, 16 Oct 2025 16:58:40 -0400 Subject: [PATCH 140/168] Finished quantdata layout --- lib/games.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/games.ts b/lib/games.ts index 564d9654..cae34991 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1944,7 +1944,17 @@ namespace Decode { ] ], Teleop: [ - + [ + [ + { key: "TeleopArtifactsClassified", label: "Artifacts Classified (Teleop)"} + ], + [ + { key: "TeleopOverflowArtifacts", label: "Overflow Artifacts (Teleop)"} + ], + [ + { key: "TeleopMotifArtifacts", label: "Motif Artifacts (Teleop)"} + ] + ] ], "Post Match": ["EndgameDefenseStatus", "EndgameParkStatus"] }; From c3fa9c15232dea6548d45e6c8ecf819d7046de64 Mon Sep 17 00:00:00 2001 From: Teknosquad5219 Date: Thu, 6 Nov 2025 16:29:04 -0500 Subject: [PATCH 141/168] Stats layout done --- lib/games.ts | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 118 insertions(+), 2 deletions(-) diff --git a/lib/games.ts b/lib/games.ts index cae34991..2e8389b7 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1959,7 +1959,122 @@ namespace Decode { "Post Match": ["EndgameDefenseStatus", "EndgameParkStatus"] }; - const statsLayout: StatsLayout = {} + const statsLayout: StatsLayout = { + sections: { + Auto: [ + { + key: "AutoArtifactsClassified", + label: "Average Amt Of Artifacts Classified Auto", + }, + { + label: "> Min Artifacts Classified Auto", + get(pitData, quantitativeReports) { + return GetMinimum(quantitativeReports!, "AutoArtifactsClassified"); + }, + }, + { + label: "> Max Artifacts Classified Auto", + get(pitData, quantitativeReports) { + return GetMaximum(quantitativeReports!, "AutoArtifactsClassified"); + }, + }, + { + key: "AutoOverflowArtifacts", + label: "Average Amt Of Overflow Artifacts Classified Auto", + }, + { + label: "> Min Overflow Artifacts Classified Auto", + get(pitData, quantitativeReports) { + return GetMinimum(quantitativeReports!, "AutoOverflowArtifacts"); + }, + }, + { + label: "> Max Overflow Artifacts Classified Auto", + get(pitData, quantitativeReports) { + return GetMaximum(quantitativeReports!, "AutoOverflowArtifacts"); + }, + }, + { + key: "AutoMotifArtifacts", + label: "Average Amt Of Motif Artifacts Classified Auto", + }, + { + label: "> Min Motif Artifacts Classified Auto", + get(pitData, quantitativeReports) { + return GetMinimum(quantitativeReports!, "AutoMotifArtifacts"); + }, + }, + { + label: "> Max Motif Artifacts Classified Auto", + get(pitData, quantitativeReports) { + return GetMaximum(quantitativeReports!, "AutoMotifArtifacts"); + }, + }, + ], + Teleop: [ + { + key: "TeleopArtifactsClassified", + label: "Average Amt Of Artifacts Classified Teleop", + }, + { + label: "> Min Artifacts Classified Teleop", + get(pitData, quantitativeReports) { + return GetMinimum( + quantitativeReports!, + "TeleopArtifactsClassified", + ); + }, + }, + { + label: "> Max Artifacts Classified Teleop", + get(pitData, quantitativeReports) { + return GetMaximum( + quantitativeReports!, + "TeleopArtifactsClassified", + ); + }, + }, + { + key: "TeleopOverflowArtifacts", + label: "Average Amt Of Overflow Artifacts Classified Teleop", + }, + { + label: "> Min Overflow Artifacts Classified Teleop", + get(pitData, quantitativeReports) { + return GetMinimum(quantitativeReports!, "TeleopOverflowArtifacts"); + }, + }, + { + label: "> Max Overflow Artifacts Classified Teleop", + get(pitData, quantitativeReports) { + return GetMaximum(quantitativeReports!, "TeleopOverflowArtifacts"); + }, + }, + { + key: "TeleopMotifArtifacts", + label: "Average Amt Of Motif Artifacts Classified Teleop", + }, + { + label: "> Min Motif Artifacts Classified Teleop", + get(pitData, quantitativeReports) { + return GetMinimum(quantitativeReports!, "TeleopMotifArtifacts"); + }, + }, + { + label: "> Max Motif Artifacts Classified Teleop", + get(pitData, quantitativeReports) { + return GetMaximum(quantitativeReports!, "TeleopMotifArtifacts"); + }, + }, + ], + }, + getGraphDots: function ( + quantitativeReports: Report[], + pitReport?: Pitreport | undefined, + ): Dot[] { + return []; + }, + }; const pitStatsLayout: PitStatsLayout ={} @@ -1986,7 +2101,8 @@ namespace Decode { "invert", getBadges, getAvgPoints, -} + ); +}; export const games: { [id in GameId]: Game } = Object.freeze({ [GameId.Reefscape]: Reefscape.game, From 611fc489a91a0ac7db24d4d1966da9c40a6bb410 Mon Sep 17 00:00:00 2001 From: Teknosquad5219 Date: Wed, 19 Nov 2025 16:15:32 -0500 Subject: [PATCH 142/168] Added more pit stats! --- lib/Enums.ts | 7 +++++++ lib/games.ts | 25 ++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/lib/Enums.ts b/lib/Enums.ts index c0391cc9..c70b203c 100644 --- a/lib/Enums.ts +++ b/lib/Enums.ts @@ -117,4 +117,11 @@ export namespace DecodeEnums{ Full = "Full", TwoBotPark = "Two Bot Park" } + + export enum AutoCapabilities { + NoAuto = "No Auto", + MovePastStart = "Move Past Start", + ScoreOneArtifact = "Score One Artifact", + ScoreMultipleArtifacts = "Score Multiple Artifacts" + } } \ No newline at end of file diff --git a/lib/games.ts b/lib/games.ts index 2e8389b7..036f3a9e 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1911,8 +1911,13 @@ namespace Decode { CanOpenGate: boolean = false; CanParkWithOtherBots: boolean = false; - PointsScoredAuto: number = 0; + ArtifactsScoredAuto: number = 0; AutoAccountsForMotif: boolean = false; + AutoCapablilities: DecodeEnums.AutoCapabilities = DecodeEnums.AutoCapabilities.NoAuto; + AutoStrategy: string = "" + + ArtifactsScoredTeleop: number = 0; + GameStrategy: string = "" } const pitReportLayout: FormLayoutProps = { @@ -1923,8 +1928,14 @@ namespace Decode { { key: "CanParkWithOtherBots", label: "Can Park With Other Bots?" }, ], Auto: [ - { key: "PointsScoredAuto", label: "Average Auto Points" }, + { key: "ArtifactsScoredAuto", label: "Average Auto Artifacts" }, { key: "AutoAccountsForMotif", label: "Auto Accounts For Motif?" }, + { key: "AutoCapablilities", label: "Other Auto Scoring Capabilities"}, + { key: "AutoStrategy", label: "Auto Strategy"} + ], + Teleop: [ + { key: "ArtifactsScoredTeleop", label: "Average Teleop Artifacts"}, + { key: "TeleopStrategy", label: "Teleop Strategy"}, ], }; @@ -2076,7 +2087,15 @@ namespace Decode { }, }; - const pitStatsLayout: PitStatsLayout ={} + const pitStatsLayout: PitStatsLayout ={ + overallSlideStats: [], + individualSlideStats: [], + robotCapabilities:[], + graphStat:{ + label: "TeleopArtifactsClassified", + key: "TeleopArtifactsClassified" + } + } function getBadges( pitReport: Pitreport | undefined, From dfeb5b9f8e4a6aea1fb0af9225e905eedf232ae0 Mon Sep 17 00:00:00 2001 From: Teknosquad5219 Date: Wed, 19 Nov 2025 17:29:09 -0500 Subject: [PATCH 143/168] getBadges and pitStatsLayout finished --- lib/client/StatsMath.ts | 4 + lib/games.ts | 160 ++++++++++++++++++++++++++++++---------- 2 files changed, 127 insertions(+), 37 deletions(-) diff --git a/lib/client/StatsMath.ts b/lib/client/StatsMath.ts index 6fd009f7..280304b1 100644 --- a/lib/client/StatsMath.ts +++ b/lib/client/StatsMath.ts @@ -6,6 +6,10 @@ export const AmpAutoPoints = 2; export const AmpTeleopPoints = 1; export const TrapPoints = 5; +export const ArtifactPoints = 3; +export const MotifArtifactPoints = 5; +export const OverflowArtifactPoints = 1; + type Selector = ((r: T) => number) | (keyof T & string); function getSelection( diff --git a/lib/games.ts b/lib/games.ts index 036f3a9e..32cc14f2 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -22,10 +22,13 @@ import { GameId } from "./client/GameId"; import { AmpAutoPoints, AmpTeleopPoints, + ArtifactPoints, BooleanAverage, GetMinimum, MostCommonValue, + MotifArtifactPoints, NumericalTotal, + OverflowArtifactPoints, Round, SpeakerAutoPoints, SpeakerTeleopPoints, @@ -1901,7 +1904,8 @@ namespace Decode { TeleopOverflowArtifacts: number = 0; TeleopMotifArtifacts: number = 0; - EndgameParkStatus: DecodeEnums.EndgameParkStatus = DecodeEnums.EndgameParkStatus.No; + EndgameParkStatus: DecodeEnums.EndgameParkStatus = + DecodeEnums.EndgameParkStatus.No; EndgameDefenseStatus: Defense = Defense.None; } @@ -1913,11 +1917,11 @@ namespace Decode { ArtifactsScoredAuto: number = 0; AutoAccountsForMotif: boolean = false; - AutoCapablilities: DecodeEnums.AutoCapabilities = DecodeEnums.AutoCapabilities.NoAuto; - AutoStrategy: string = "" + AutoCapablilities: DecodeEnums.AutoCapabilities = + DecodeEnums.AutoCapabilities.NoAuto; + AutoStrategy: string = ""; - ArtifactsScoredTeleop: number = 0; - GameStrategy: string = "" + GameStrategy: string = ""; } const pitReportLayout: FormLayoutProps = { @@ -1930,44 +1934,44 @@ namespace Decode { Auto: [ { key: "ArtifactsScoredAuto", label: "Average Auto Artifacts" }, { key: "AutoAccountsForMotif", label: "Auto Accounts For Motif?" }, - { key: "AutoCapablilities", label: "Other Auto Scoring Capabilities"}, - { key: "AutoStrategy", label: "Auto Strategy"} - ], - Teleop: [ - { key: "ArtifactsScoredTeleop", label: "Average Teleop Artifacts"}, - { key: "TeleopStrategy", label: "Teleop Strategy"}, + { key: "AutoCapablilities", label: "Other Auto Scoring Capabilities" }, + { key: "AutoStrategy", label: "Auto Strategy" }, ], + Teleop: [{ key: "TeleopStrategy", label: "Teleop Strategy" }], }; const quantitativeReportLayout: FormLayoutProps = { Auto: [ - { key: "AutoMovedPastStart", label: "Moved Past Starting line"}, + { key: "AutoMovedPastStart", label: "Moved Past Starting line" }, [ [ - { key: "AutoArtifactsClassified", label: "Artifacts Classified (Auto)"} - ], - [ - { key: "AutoOverflowArtifacts", label: "Overflow Artifacts (Auto)"} + { + key: "AutoArtifactsClassified", + label: "Artifacts Classified (Auto)", + }, ], - [ - { key: "AutoMotifArtifacts", label: "Motif Artifacts (Auto)"} - ] - ] + [{ key: "AutoOverflowArtifacts", label: "Overflow Artifacts (Auto)" }], + [{ key: "AutoMotifArtifacts", label: "Motif Artifacts (Auto)" }], + ], ], Teleop: [ [ [ - { key: "TeleopArtifactsClassified", label: "Artifacts Classified (Teleop)"} + { + key: "TeleopArtifactsClassified", + label: "Artifacts Classified (Teleop)", + }, ], [ - { key: "TeleopOverflowArtifacts", label: "Overflow Artifacts (Teleop)"} + { + key: "TeleopOverflowArtifacts", + label: "Overflow Artifacts (Teleop)", + }, ], - [ - { key: "TeleopMotifArtifacts", label: "Motif Artifacts (Teleop)"} - ] - ] + [{ key: "TeleopMotifArtifacts", label: "Motif Artifacts (Teleop)" }], + ], ], - "Post Match": ["EndgameDefenseStatus", "EndgameParkStatus"] + "Post Match": ["EndgameDefenseStatus", "EndgameParkStatus"], }; const statsLayout: StatsLayout = { @@ -2087,21 +2091,103 @@ namespace Decode { }, }; - const pitStatsLayout: PitStatsLayout ={ - overallSlideStats: [], - individualSlideStats: [], - robotCapabilities:[], - graphStat:{ + const pitStatsLayout: PitStatsLayout = { + overallSlideStats: [ + { label: "Artifacts Scored Auto", key: "ArtifactsScoredAuto" }, + ], + individualSlideStats: [ + { + label: "Average Auto Points", + get: ( + pitReport: Pitreport | undefined, + quantitativeReports: Report[] | undefined, + ) => { + if (!quantitativeReports) { + return 0; + } + + const Artifacts = + NumericalTotal("AutoArtifactsClassified", quantitativeReports) * + ArtifactPoints; + + const MotifArtifacts = + NumericalTotal("AutoMotifArtifacts", quantitativeReports) * + MotifArtifactPoints; + + const OverflowArtifacts = + NumericalTotal("AutoOverflowArtifacts", quantitativeReports) * + OverflowArtifactPoints; + + return ( + (Artifacts + MotifArtifacts + OverflowArtifacts) / + quantitativeReports.length + ); + }, + }, + { + label: "Average Teleop Points", + get: ( + pitReport: Pitreport | undefined, + quantitativeReports: Report[] | undefined, + ) => { + if (!quantitativeReports) { + return 0; + } + + const Artifacts = + NumericalTotal("TeleopArtifactsClassified", quantitativeReports) * + ArtifactPoints; + + const MotifArtifacts = + NumericalTotal("TeleopMotifArtifacts", quantitativeReports) * + MotifArtifactPoints; + + const OverflowArtifacts = + NumericalTotal("TeleopOverflowArtifacts", quantitativeReports) * + OverflowArtifactPoints; + + return ( + (Artifacts + MotifArtifacts + OverflowArtifacts) / + quantitativeReports.length + ); + }, + }, + ], + robotCapabilities: [ + { key: "CanScoreClassifier", label: "Can Score Classifier?" }, + { key: "CanScoreDepot", label: "Can Score Depot?" }, + { key: "CanOpenGate", label: "Can Open Gate?" }, + { key: "CanParkWithOtherBots", label: "Can Park With Other Bots" }, + ], + graphStat: { label: "TeleopArtifactsClassified", - key: "TeleopArtifactsClassified" - } - } + key: "TeleopArtifactsClassified", + }, + }; function getBadges( pitReport: Pitreport | undefined, quantitativeReports: Report[] | undefined, card: boolean, - ) {} + ) { + const badges: Badge[] = getBaseBadges(pitReport, quantitativeReports); + + if (pitReport?.data?.CanOpenGate) + badges.push({ text: "Can Open Gate", color: "info" }); + if (pitReport?.data?.CanScoreDepot) + badges.push({ text: "Can Score Depot", color: "secondary" }); + if (pitReport?.data?.CanScoreClassifier) + badges.push({ text: "Can Score Classifier", color: "primary" }); + if (pitReport?.data?.CanParkWithOtherBots) + badges.push({ text: "Can Double Park", color: "success" }); + + if ( + !(pitReport?.data?.CanScoreDepot || pitReport?.data?.CanScoreClassifier) + ) + badges.push({ text: "Cannot Score", color: "warning" }); + + return badges; + } function getAvgPoints(reports: Report[] | undefined) {} @@ -2121,7 +2207,7 @@ namespace Decode { getBadges, getAvgPoints, ); -}; +} export const games: { [id in GameId]: Game } = Object.freeze({ [GameId.Reefscape]: Reefscape.game, From 1968a99e0acc655fb8c50363a9c35d56389365d9 Mon Sep 17 00:00:00 2001 From: Teknosquad5219 Date: Thu, 20 Nov 2025 16:24:20 -0500 Subject: [PATCH 144/168] getAvgPoints and the inclusion of the depot --- lib/client/StatsMath.ts | 1 + lib/games.ts | 60 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/lib/client/StatsMath.ts b/lib/client/StatsMath.ts index 280304b1..61b49d71 100644 --- a/lib/client/StatsMath.ts +++ b/lib/client/StatsMath.ts @@ -9,6 +9,7 @@ export const TrapPoints = 5; export const ArtifactPoints = 3; export const MotifArtifactPoints = 5; export const OverflowArtifactPoints = 1; +export const DepotArtifactPoints = 1; type Selector = ((r: T) => number) | (keyof T & string); diff --git a/lib/games.ts b/lib/games.ts index 32cc14f2..4639f9ee 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -24,6 +24,7 @@ import { AmpTeleopPoints, ArtifactPoints, BooleanAverage, + DepotArtifactPoints, GetMinimum, MostCommonValue, MotifArtifactPoints, @@ -1903,6 +1904,7 @@ namespace Decode { TeleopArtifactsClassified: number = 0; TeleopOverflowArtifacts: number = 0; TeleopMotifArtifacts: number = 0; + TeleopDepotArtifacts: number = 0; EndgameParkStatus: DecodeEnums.EndgameParkStatus = DecodeEnums.EndgameParkStatus.No; @@ -1969,6 +1971,7 @@ namespace Decode { }, ], [{ key: "TeleopMotifArtifacts", label: "Motif Artifacts (Teleop)" }], + [{ key: "TeleopDepotArtifacts", label: "Depot Artifacts (Teleop)" }], ], ], "Post Match": ["EndgameDefenseStatus", "EndgameParkStatus"], @@ -2081,6 +2084,22 @@ namespace Decode { return GetMaximum(quantitativeReports!, "TeleopMotifArtifacts"); }, }, + { + key: "TeleopDepotArtifacts", + label: "Average Amt of Artifacts In Depot Teleop", + }, + { + label: "> Min Depot Artifacts Teleop", + get(pitData, quantitativeReports) { + return GetMinimum(quantitativeReports!, "TeleopDepotArtifacts"); + }, + }, + { + label: "> Max Depot Artifacts Teleop", + get(pitData, quantitativeReports) { + return GetMaximum(quantitativeReports!, "TeleopDepotArtifacts"); + }, + }, ], }, getGraphDots: function ( @@ -2146,8 +2165,12 @@ namespace Decode { NumericalTotal("TeleopOverflowArtifacts", quantitativeReports) * OverflowArtifactPoints; + const DepotArtifacts = + NumericalTotal("TeleopDepotArtifacts", quantitativeReports) * + DepotArtifactPoints; + return ( - (Artifacts + MotifArtifacts + OverflowArtifacts) / + (Artifacts + MotifArtifacts + OverflowArtifacts + DepotArtifacts) / quantitativeReports.length ); }, @@ -2189,7 +2212,40 @@ namespace Decode { return badges; } - function getAvgPoints(reports: Report[] | undefined) {} + function getAvgPoints(reports: Report[] | undefined) { + if (!reports) return 0; + + let totalPoints = 0; + + for (const report of reports.map((r) => r.data)) { + switch (report.EndgameParkStatus) { + case DecodeEnums.EndgameParkStatus.No: + break; + case DecodeEnums.EndgameParkStatus.Partial: + totalPoints += 5; + break; + case DecodeEnums.EndgameParkStatus.Full: + totalPoints += 10; + break; + case DecodeEnums.EndgameParkStatus.TwoBotPark: + totalPoints += 20; + break; + } + + totalPoints += + (report.AutoArtifactsClassified + report.TeleopArtifactsClassified) * + ArtifactPoints; + totalPoints += + (report.AutoMotifArtifacts + report.TeleopMotifArtifacts) * + MotifArtifactPoints; + totalPoints += + (report.AutoOverflowArtifacts + report.TeleopOverflowArtifacts) * + OverflowArtifactPoints; + totalPoints += report.TeleopDepotArtifacts * DepotArtifactPoints; + } + + return totalPoints / Math.max(reports.length, 1); + } export const game = new Game( "Decode", From aa05b897fe08cb9f5d77d7711aede908bfb83cb2 Mon Sep 17 00:00:00 2001 From: Teknosquad5219 Date: Tue, 9 Dec 2025 15:44:00 -0500 Subject: [PATCH 145/168] Updates --- package-lock.json | 1264 ++++++++++++++++++++++++++++----------------- package.json | 2 +- 2 files changed, 783 insertions(+), 483 deletions(-) diff --git a/package-lock.json b/package-lock.json index e1d3d706..a9f01f2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "minimongo": "^7.0.0", "mongo-anywhere": "^1.1.15", "mongodb": "^5.0.0", - "next": "^15.2.4", + "next": "^15.2.6", "next-auth": "^4.24.11", "next-seo": "^6.6.0", "omit-call-signature": "^1.0.15", @@ -107,13 +107,15 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", - "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.24.7", - "picocolors": "^1.0.0" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" @@ -338,19 +340,21 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", - "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -365,109 +369,28 @@ } }, "node_modules/@babel/helpers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", - "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", - "dev": true, - "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "@babel/types": "^7.28.5" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", - "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", - "dev": true, "bin": { "parser": "bin/babel-parser.js" }, @@ -710,14 +633,15 @@ } }, "node_modules/@babel/template": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", - "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -754,14 +678,14 @@ } }, "node_modules/@babel/types": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", - "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -793,6 +717,16 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@emnapi/runtime": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", @@ -1260,104 +1194,564 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.24.0.tgz", + "integrity": "sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", + "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.13.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", + "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" } }, - "node_modules/@eslint/js": { - "version": "9.24.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.24.0.tgz", - "integrity": "sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==", - "dev": true, - "license": "MIT", + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" } }, - "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", - "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", - "dependencies": { - "@eslint/core": "^0.10.0", - "levn": "^0.4.1" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" } }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18.18.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" } }, - "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "license": "Apache-2.0", + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" + "@emnapi/runtime": "^1.7.0" }, "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "url": "https://opencollective.com/libvips" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=12.22" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "url": "https://opencollective.com/libvips" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", - "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", - "license": "Apache-2.0", + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=18.18" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", - "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", "cpu": [ "x64" ], @@ -1453,10 +1847,11 @@ } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -1933,9 +2328,9 @@ } }, "node_modules/@next/env": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.2.4.tgz", - "integrity": "sha512-+SFtMgoiYP3WoSswuNmxJOCwi06TdWE733D+WPjpXIe4LXGULwEaofiiAy6kbS0+XjM5xF5n3lKuBwN2SnqD9g==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.7.tgz", + "integrity": "sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -1976,9 +2371,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.2.4.tgz", - "integrity": "sha512-1AnMfs655ipJEDC/FHkSr0r3lXBgpqKo4K1kiwfUf3iE68rDFXZ1TtHdMvf7D0hMItgDZ7Vuq3JgNMbt/+3bYw==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.7.tgz", + "integrity": "sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==", "cpu": [ "arm64" ], @@ -1992,9 +2387,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.2.4.tgz", - "integrity": "sha512-3qK2zb5EwCwxnO2HeO+TRqCubeI/NgCe+kL5dTJlPldV/uwCnUgC7VbEzgmxbfrkbjehL4H9BPztWOEtsoMwew==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.7.tgz", + "integrity": "sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==", "cpu": [ "x64" ], @@ -2008,9 +2403,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.2.4.tgz", - "integrity": "sha512-HFN6GKUcrTWvem8AZN7tT95zPb0GUGv9v0d0iyuTb303vbXkkbHDp/DxufB04jNVD+IN9yHy7y/6Mqq0h0YVaQ==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.7.tgz", + "integrity": "sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==", "cpu": [ "arm64" ], @@ -2024,9 +2419,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.2.4.tgz", - "integrity": "sha512-Oioa0SORWLwi35/kVB8aCk5Uq+5/ZIumMK1kJV+jSdazFm2NzPDztsefzdmzzpx5oGCJ6FkUC7vkaUseNTStNA==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.7.tgz", + "integrity": "sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==", "cpu": [ "arm64" ], @@ -2040,9 +2435,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.2.4.tgz", - "integrity": "sha512-yb5WTRaHdkgOqFOZiu6rHV1fAEK0flVpaIN2HB6kxHVSy/dIajWbThS7qON3W9/SNOH2JWkVCyulgGYekMePuw==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.7.tgz", + "integrity": "sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==", "cpu": [ "x64" ], @@ -2056,9 +2451,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.2.4.tgz", - "integrity": "sha512-Dcdv/ix6srhkM25fgXiyOieFUkz+fOYkHlydWCtB0xMST6X9XYI3yPDKBZt1xuhOytONsIFJFB08xXYsxUwJLw==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.7.tgz", + "integrity": "sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==", "cpu": [ "x64" ], @@ -2072,9 +2467,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.2.4.tgz", - "integrity": "sha512-dW0i7eukvDxtIhCYkMrZNQfNicPDExt2jPb9AZPpL7cfyUo7QSNl1DjsHjmmKp6qNAqUESyT8YFl/Aw91cNJJg==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.7.tgz", + "integrity": "sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==", "cpu": [ "arm64" ], @@ -2088,9 +2483,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.2.4.tgz", - "integrity": "sha512-SbnWkJmkS7Xl3kre8SdMF6F/XDh1DTFEhp0jRTj/uB8iPKoU2bb2NDfcu+iifv1+mxQEd1g2vvSxcZbXSKyWiQ==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.7.tgz", + "integrity": "sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==", "cpu": [ "x64" ], @@ -2174,13 +2569,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.52.0.tgz", - "integrity": "sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", + "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.52.0" + "playwright": "1.57.0" }, "bin": { "playwright": "cli.js" @@ -2324,16 +2719,16 @@ } }, "node_modules/@serwist/build": { - "version": "9.0.13", - "resolved": "https://registry.npmjs.org/@serwist/build/-/build-9.0.13.tgz", - "integrity": "sha512-Hoc6llxFmnsE8z5Cs95UmRRhRyoNh44OdrMWWPPX8BpW19z0CK/qnBquptjyBIe46jjoOxsPHK0Tt7oZOV4Mbw==", + "version": "9.2.3", + "resolved": "https://registry.npmjs.org/@serwist/build/-/build-9.2.3.tgz", + "integrity": "sha512-UU38GDsTerzoCRDIT5v62W/CcTMLfGZm/tAa+u8XLBU0y0f2aJ2GCfsHnI1eXhEuWvt4Y7Imx/uG2ww2KWIBWQ==", "license": "MIT", "dependencies": { "common-tags": "1.8.2", - "glob": "10.4.5", + "glob": "10.5.0", "pretty-bytes": "6.1.1", "source-map": "0.8.0-beta.0", - "zod": "3.24.2" + "zod": "4.1.12" }, "engines": { "node": ">=18.0.0" @@ -2348,18 +2743,18 @@ } }, "node_modules/@serwist/build/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/@serwist/build/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -2395,6 +2790,7 @@ "version": "0.8.0-beta.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "deprecated": "The work that was done in this beta branch won't be included in future versions", "license": "BSD-3-Clause", "dependencies": { "whatwg-url": "^7.0.0" @@ -2430,18 +2826,18 @@ } }, "node_modules/@serwist/next": { - "version": "9.0.13", - "resolved": "https://registry.npmjs.org/@serwist/next/-/next-9.0.13.tgz", - "integrity": "sha512-nI2N4oSEHJGH0YUsE4m1obPfudO3DZ/pXiHPaisw+28YjgkMqD6ePfhzeHGO07ahPmIUiyHca9VNO8OfarbQ1Q==", + "version": "9.2.3", + "resolved": "https://registry.npmjs.org/@serwist/next/-/next-9.2.3.tgz", + "integrity": "sha512-sbStIaen8H6ZhNCtfxAuZ2haujJe8nPFRBiq5KQFYYi5iyFrDGLpYICmRTBP6Oc1pQBmsAqbBZzIjjC60d+7zg==", "license": "MIT", "dependencies": { - "@serwist/build": "9.0.13", - "@serwist/webpack-plugin": "9.0.13", - "@serwist/window": "9.0.13", - "chalk": "5.4.1", - "glob": "10.4.5", - "serwist": "9.0.13", - "zod": "3.24.2" + "@serwist/build": "9.2.3", + "@serwist/webpack-plugin": "9.2.3", + "@serwist/window": "9.2.3", + "chalk": "5.6.2", + "glob": "10.5.0", + "serwist": "9.2.3", + "zod": "4.1.12" }, "engines": { "node": ">=18.0.0" @@ -2457,17 +2853,18 @@ } }, "node_modules/@serwist/next/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/@serwist/next/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -2477,9 +2874,10 @@ } }, "node_modules/@serwist/next/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -2499,6 +2897,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -2510,14 +2909,14 @@ } }, "node_modules/@serwist/webpack-plugin": { - "version": "9.0.13", - "resolved": "https://registry.npmjs.org/@serwist/webpack-plugin/-/webpack-plugin-9.0.13.tgz", - "integrity": "sha512-Z+Eve8dckM2FulRCa7Cj7VCF3EOP7QkRA76tI742olF7J2sYZSm3t9Ex13jDxTMmYiU1AxLq6V6gEMIdRAetVw==", + "version": "9.2.3", + "resolved": "https://registry.npmjs.org/@serwist/webpack-plugin/-/webpack-plugin-9.2.3.tgz", + "integrity": "sha512-y1Ag1a7LARo/LtPVRSrfGO5PG4BZdc1NiMGAq+CGlBH/PJa7RQimo4iq9C94pjxusEsP4yHnsDQ06+GVTQ6gww==", "license": "MIT", "dependencies": { - "@serwist/build": "9.0.13", + "@serwist/build": "9.2.3", "pretty-bytes": "6.1.1", - "zod": "3.24.2" + "zod": "4.1.12" }, "engines": { "node": ">=18.0.0" @@ -2536,13 +2935,13 @@ } }, "node_modules/@serwist/window": { - "version": "9.0.13", - "resolved": "https://registry.npmjs.org/@serwist/window/-/window-9.0.13.tgz", - "integrity": "sha512-Cf3RizPuFInDcLt0P1Y5QzG1sA5mW131/PZfMYE3yBuNUSGNgOQGlYuLdwDOWPHgECYoVb/da8pspdQNKs0O5g==", + "version": "9.2.3", + "resolved": "https://registry.npmjs.org/@serwist/window/-/window-9.2.3.tgz", + "integrity": "sha512-6bK01QAaFI+oXTmDSQHvsw0kTTNlj79PFkox+0Z9XYNqTVLoXY9C6BFmZNvxNxEinOqCSm7cms4I5V54E9/1XA==", "license": "MIT", "dependencies": { "@types/trusted-types": "2.0.7", - "serwist": "9.0.13" + "serwist": "9.2.3" }, "peerDependencies": { "typescript": ">=5.0.0" @@ -2582,11 +2981,6 @@ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" - }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -3693,9 +4087,10 @@ "integrity": "sha512-N/toHA87JcmAHPqU8Qt7YnhFK6W2WUpdq5M1k/JqLdTqtts7sHEMZhFjFWTvvR2poKF7Qki0qknhIPIr5I7TIQ==" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3788,17 +4183,6 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -4047,20 +4431,6 @@ "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", "dev": true }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "license": "MIT", - "optional": true, - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4077,17 +4447,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "license": "MIT", - "optional": true, - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -4352,15 +4711,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/decache": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/decache/-/decache-3.1.0.tgz", - "integrity": "sha512-p7D6wJ5EJFFq1CcF2lu1XeqKFLBob8jRQGNAvFLTsV3CbSKBl3VtliAVlUIGz2i9H6kEFnI2Amaft5ZopIG2Fw==", - "optional": true, - "dependencies": { - "find": "^0.2.4" - } - }, "node_modules/dedent": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", @@ -4448,9 +4798,9 @@ } }, "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", "optional": true, "engines": { @@ -5193,9 +5543,9 @@ } }, "node_modules/eslint-config-next/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -5747,10 +6097,11 @@ } }, "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -5778,15 +6129,6 @@ "node": ">=8" } }, - "node_modules/find": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/find/-/find-0.2.9.tgz", - "integrity": "sha512-7a4/LCiInB9xYMnAUEjLilL9FKclwbwK7VlXw+h5jMvT2TDFeYFCHM24O1XdnC/on/hx8mxVO3FTQkyHZnOghQ==", - "optional": true, - "dependencies": { - "traverse-chain": "~0.1.0" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -5852,6 +6194,20 @@ "node": "*" } }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, "node_modules/formidable": { "version": "3.5.4", "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", @@ -6295,9 +6651,9 @@ } }, "node_modules/idb": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/idb/-/idb-8.0.2.tgz", - "integrity": "sha512-CX70rYhx7GDDQzwwQMDwF6kDRQi5vVs6khHUumDrMecBylKkwvZ8HWvKV08AGb7VbpoGCWUQ4aHzNDgoUiOIUg==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/idb/-/idb-8.0.3.tgz", + "integrity": "sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==", "license": "ISC" }, "node_modules/idb-wrapper": { @@ -6394,10 +6750,14 @@ "loose-envify": "^1.0.0" } }, - "node_modules/ip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", - "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==" + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", + "engines": { + "node": ">= 12" + } }, "node_modules/is-array-buffer": { "version": "3.0.4", @@ -7847,9 +8207,9 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -8359,15 +8719,13 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" }, "node_modules/next": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/next/-/next-15.2.4.tgz", - "integrity": "sha512-VwL+LAaPSxEkd3lU2xWbgEOtrM8oedmyhBqaVNmgKB+GvZlCy9rgaEc+y2on0wv+l0oSFqLtYD6dcC1eAedUaQ==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.7.tgz", + "integrity": "sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==", "license": "MIT", "dependencies": { - "@next/env": "15.2.4", - "@swc/counter": "0.1.3", + "@next/env": "15.5.7", "@swc/helpers": "0.5.15", - "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" @@ -8379,19 +8737,19 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.2.4", - "@next/swc-darwin-x64": "15.2.4", - "@next/swc-linux-arm64-gnu": "15.2.4", - "@next/swc-linux-arm64-musl": "15.2.4", - "@next/swc-linux-x64-gnu": "15.2.4", - "@next/swc-linux-x64-musl": "15.2.4", - "@next/swc-win32-arm64-msvc": "15.2.4", - "@next/swc-win32-x64-msvc": "15.2.4", - "sharp": "^0.33.5" + "@next/swc-darwin-arm64": "15.5.7", + "@next/swc-darwin-x64": "15.5.7", + "@next/swc-linux-arm64-gnu": "15.5.7", + "@next/swc-linux-arm64-musl": "15.5.7", + "@next/swc-linux-x64-gnu": "15.5.7", + "@next/swc-linux-x64-musl": "15.5.7", + "@next/swc-win32-arm64-msvc": "15.5.7", + "@next/swc-win32-x64-msvc": "15.5.7", + "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.41.2", + "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", @@ -8413,9 +8771,9 @@ } }, "node_modules/next-auth": { - "version": "4.24.11", - "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.11.tgz", - "integrity": "sha512-pCFXzIDQX7xmHFs4KVH4luCjaCbuPRtZ9oBUjUhOk84mZ9WVPf94n87TxYI4rSRf9HmfHEF8Yep3JrYDVOo3Cw==", + "version": "4.24.13", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.13.tgz", + "integrity": "sha512-sgObCfcfL7BzIK76SS5TnQtc3yo2Oifp/yIpfv6fMfeBOiBJkDWF3A2y9+yqnmJ4JKc2C+nMjSjmgDeTwgN1rQ==", "license": "ISC", "dependencies": { "@babel/runtime": "^7.20.13", @@ -8429,9 +8787,9 @@ "uuid": "^8.3.2" }, "peerDependencies": { - "@auth/core": "0.34.2", - "next": "^12.2.5 || ^13 || ^14 || ^15", - "nodemailer": "^6.6.5", + "@auth/core": "0.34.3", + "next": "^12.2.5 || ^13 || ^14 || ^15 || ^16", + "nodemailer": "^7.0.7", "react": "^17.0.2 || ^18 || ^19", "react-dom": "^17.0.2 || ^18 || ^19" }, @@ -8521,17 +8879,6 @@ "node": ">=0.6.0" } }, - "node_modules/nodemailer": { - "version": "6.9.16", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz", - "integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==", - "license": "MIT-0", - "optional": true, - "peer": true, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -9026,13 +9373,13 @@ } }, "node_modules/playwright": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz", - "integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.52.0" + "playwright-core": "1.57.0" }, "bin": { "playwright": "cli.js" @@ -9045,9 +9392,9 @@ } }, "node_modules/playwright-core": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz", - "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", + "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", "devOptional": true, "license": "Apache-2.0", "bin": { @@ -9780,19 +10127,6 @@ "resolved": "https://registry.npmjs.org/request-ip/-/request-ip-3.3.0.tgz", "integrity": "sha512-cA6Xh6e0fDBBBwH77SLJaJPBmD3nWVAcF9/XAcsrIHdjhFzFiB5aNQFytdjCGPezU3ROwrR11IddKAM08vohxA==" }, - "node_modules/request/node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, "node_modules/request/node_modules/uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", @@ -9901,9 +10235,10 @@ "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" }, "node_modules/rollbar": { - "version": "2.26.4", - "resolved": "https://registry.npmjs.org/rollbar/-/rollbar-2.26.4.tgz", - "integrity": "sha512-JKmrj6riYm9ZPJisgxljgH4uCsvjMHDHXrinDF7aAFaP+eoF51HomVPtLcDTYLsrJ568aKVNLUhedFajONBwSg==", + "version": "2.26.5", + "resolved": "https://registry.npmjs.org/rollbar/-/rollbar-2.26.5.tgz", + "integrity": "sha512-4Of0ALl5+CU2glyDy5dWMRRy9Ty81DrY2r46ucbqjtCikbgHoWJNGXbQUWpDaLxsc8Q71LT/yj1bPb9NHbJIFQ==", + "license": "MIT", "dependencies": { "async": "~3.2.3", "console-polyfill": "0.3.0", @@ -9912,9 +10247,6 @@ "lru-cache": "~2.2.1", "request-ip": "~3.3.0", "source-map": "^0.5.7" - }, - "optionalDependencies": { - "decache": "^3.0.5" } }, "node_modules/rollbar/node_modules/lru-cache": { @@ -10036,9 +10368,9 @@ } }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -10048,12 +10380,12 @@ } }, "node_modules/serwist": { - "version": "9.0.13", - "resolved": "https://registry.npmjs.org/serwist/-/serwist-9.0.13.tgz", - "integrity": "sha512-BF3bmzYdOVT2lF3iHV0044NqTO6q6GAiqrYpc7L9EPYQXZHOy22WajKaHLvCdvpm2Jpji4SsxUL8/uC1WSCZ5g==", + "version": "9.2.3", + "resolved": "https://registry.npmjs.org/serwist/-/serwist-9.2.3.tgz", + "integrity": "sha512-menorUH09SpLvU7R25TvG0TuQ12MUDpQ5NKGohbtREF8YI+0D++Abn59XCmvvMp2n740rBizIkeqWq0m6U+++g==", "license": "MIT", "dependencies": { - "idb": "8.0.2" + "idb": "8.0.3" }, "peerDependencies": { "typescript": ">=5.0.0" @@ -10096,16 +10428,16 @@ } }, "node_modules/sharp": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", - "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", "hasInstallScript": true, "license": "Apache-2.0", "optional": true, "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.3", - "semver": "^7.6.3" + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" }, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" @@ -10114,25 +10446,30 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.5", - "@img/sharp-darwin-x64": "0.33.5", - "@img/sharp-libvips-darwin-arm64": "1.0.4", - "@img/sharp-libvips-darwin-x64": "1.0.4", - "@img/sharp-libvips-linux-arm": "1.0.5", - "@img/sharp-libvips-linux-arm64": "1.0.4", - "@img/sharp-libvips-linux-s390x": "1.0.4", - "@img/sharp-libvips-linux-x64": "1.0.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", - "@img/sharp-libvips-linuxmusl-x64": "1.0.4", - "@img/sharp-linux-arm": "0.33.5", - "@img/sharp-linux-arm64": "0.33.5", - "@img/sharp-linux-s390x": "0.33.5", - "@img/sharp-linux-x64": "0.33.5", - "@img/sharp-linuxmusl-arm64": "0.33.5", - "@img/sharp-linuxmusl-x64": "0.33.5", - "@img/sharp-wasm32": "0.33.5", - "@img/sharp-win32-ia32": "0.33.5", - "@img/sharp-win32-x64": "0.33.5" + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" } }, "node_modules/shebang-command": { @@ -10182,23 +10519,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "license": "MIT", - "optional": true, - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "license": "MIT", - "optional": true - }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -10250,15 +10570,16 @@ } }, "node_modules/socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "license": "MIT", "dependencies": { - "ip": "^2.0.0", + "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" }, "engines": { - "node": ">= 10.13.0", + "node": ">= 10.0.0", "npm": ">= 3.0.0" } }, @@ -10349,14 +10670,6 @@ "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -10629,19 +10942,21 @@ } }, "node_modules/sucrase/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/sucrase/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, + "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -10794,15 +11109,6 @@ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -10837,12 +11143,6 @@ "node": ">=12" } }, - "node_modules/traverse-chain": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz", - "integrity": "sha512-up6Yvai4PYKhpNp5PkYtx50m3KbwQrqDwbuZP/ItyL64YEWHAvH6Md83LFLV/GRSk/BoUVwwgUzX6SOQSbsfAg==", - "optional": true - }, "node_modules/ts-api-utils": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", @@ -11657,9 +11957,9 @@ } }, "node_modules/zod": { - "version": "3.24.2", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", - "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", + "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 93953def..bcef52a9 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "minimongo": "^7.0.0", "mongo-anywhere": "^1.1.15", "mongodb": "^5.0.0", - "next": "^15.2.4", + "next": "^15.2.6", "next-auth": "^4.24.11", "next-seo": "^6.6.0", "omit-call-signature": "^1.0.15", From 8ae79981d174c2ea3aee2a523840e643d471f2df Mon Sep 17 00:00:00 2001 From: Tr01ler Date: Fri, 2 Jan 2026 16:17:45 -0500 Subject: [PATCH 146/168] Random updates that don't fix the larger issue --- components/Container.tsx | 2 +- components/TeamCard.tsx | 4 +-- package-lock.json | 54 ++++++++++++++++++++++++++++++++-------- package.json | 3 ++- pages/profile.tsx | 2 +- 5 files changed, 49 insertions(+), 16 deletions(-) diff --git a/components/Container.tsx b/components/Container.tsx index 0cc63f42..e9bb660a 100644 --- a/components/Container.tsx +++ b/components/Container.tsx @@ -317,7 +317,7 @@ export default function Container(props: ContainerProps) { "w-16 h-16 btn btn-ghost " + (selected ? "border-2 border-accent" : "border-2") } - key={team._id.toString()} + key={team._id?.toString()} onClick={() => { setSelectedTeamIndex(index); }} diff --git a/components/TeamCard.tsx b/components/TeamCard.tsx index 1475843f..6982a443 100644 --- a/components/TeamCard.tsx +++ b/components/TeamCard.tsx @@ -6,13 +6,13 @@ export default function TeamCard(props: { team: Team | undefined }) { return (

{team?.league} {team?.alliance ? "Alliance" : "Team"}{" "} {team?.number} -{" "} - {team?.users.length} members + {team?.users?.length} members

); diff --git a/package-lock.json b/package-lock.json index a9f01f2b..c112372b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "next": "^15.2.6", "next-auth": "^4.24.11", "next-seo": "^6.6.0", + "nodemailer": "^7.0.12", "omit-call-signature": "^1.0.15", "react": "18.3.1", "react-beautiful-dnd": "^13.1.1", @@ -60,7 +61,7 @@ "@playwright/test": "^1.52.0", "@types/formidable": "^3.4.5", "@types/jest": "^29.5.14", - "@types/node": "^22.13.13", + "@types/node": "^22.19.3", "@types/react": "^18.3.8", "autoprefixer": "^10.4.21", "cross-env": "^7.0.3", @@ -135,6 +136,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", "dev": true, + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", @@ -2306,8 +2308,7 @@ "node_modules/@kurkle/color": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", - "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==", - "peer": true + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" }, "node_modules/@mongodb-js/saslprep": { "version": "1.1.9", @@ -2574,6 +2575,7 @@ "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "playwright": "1.57.0" }, @@ -2588,6 +2590,7 @@ "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -3382,12 +3385,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.13.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.13.tgz", - "integrity": "sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ==", + "version": "22.19.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", + "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", "license": "MIT", + "peer": true, "dependencies": { - "undici-types": "~6.20.0" + "undici-types": "~6.21.0" } }, "node_modules/@types/p5": { @@ -3512,6 +3516,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4135,6 +4140,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -4595,7 +4601,8 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "peer": true }, "node_modules/culori": { "version": "3.3.0", @@ -5271,6 +5278,7 @@ "version": "9.18.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -5444,6 +5452,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.17.0.tgz", "integrity": "sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.17.0", "@typescript-eslint/types": "8.17.0", @@ -5650,6 +5659,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", @@ -7286,6 +7296,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -8178,6 +8189,7 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "devOptional": true, + "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -8635,6 +8647,7 @@ "version": "5.9.2", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.2.tgz", "integrity": "sha512-H60HecKO4Bc+7dhOv4sJlgvenK4fQNqqUIlXxZYQNbfEWSALGAwGoyJd/0Qwk4TttFXUOHJ2ZJQe/52ScaUwtQ==", + "peer": true, "dependencies": { "bson": "^5.5.0", "mongodb-connection-string-url": "^2.6.0", @@ -8723,6 +8736,7 @@ "resolved": "https://registry.npmjs.org/next/-/next-15.5.7.tgz", "integrity": "sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==", "license": "MIT", + "peer": true, "dependencies": { "@next/env": "15.5.7", "@swc/helpers": "0.5.15", @@ -8775,6 +8789,7 @@ "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.13.tgz", "integrity": "sha512-sgObCfcfL7BzIK76SS5TnQtc3yo2Oifp/yIpfv6fMfeBOiBJkDWF3A2y9+yqnmJ4JKc2C+nMjSjmgDeTwgN1rQ==", "license": "ISC", + "peer": true, "dependencies": { "@babel/runtime": "^7.20.13", "@panva/hkdf": "^1.0.2", @@ -8879,6 +8894,16 @@ "node": ">=0.6.0" } }, + "node_modules/nodemailer": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.12.tgz", + "integrity": "sha512-H+rnK5bX2Pi/6ms3sN4/jRQvYSMltV6vqup/0SFOrxYYY/qoNvhXPlYq3e+Pm9RFJRwrMGbMIwi81M4dxpomhA==", + "license": "MIT-0", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -9456,6 +9481,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -9584,6 +9610,7 @@ "version": "10.24.3", "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz", "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -9758,6 +9785,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -9866,6 +9894,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -11225,6 +11254,7 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -11426,6 +11456,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11468,9 +11499,10 @@ "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" }, "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" }, "node_modules/unified-api": { "version": "1.1.4", diff --git a/package.json b/package.json index bcef52a9..0c6a7baf 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "next": "^15.2.6", "next-auth": "^4.24.11", "next-seo": "^6.6.0", + "nodemailer": "^7.0.12", "omit-call-signature": "^1.0.15", "react": "18.3.1", "react-beautiful-dnd": "^13.1.1", @@ -71,7 +72,7 @@ "@playwright/test": "^1.52.0", "@types/formidable": "^3.4.5", "@types/jest": "^29.5.14", - "@types/node": "^22.13.13", + "@types/node": "^22.19.3", "@types/react": "^18.3.8", "autoprefixer": "^10.4.21", "cross-env": "^7.0.3", diff --git a/pages/profile.tsx b/pages/profile.tsx index c2f74d32..7a7746bd 100644 --- a/pages/profile.tsx +++ b/pages/profile.tsx @@ -198,7 +198,7 @@ export default function Profile(props: { teamList: Team[] }) { From 8fc11dafb77cfff1c95d55c54c1030b0271388f8 Mon Sep 17 00:00:00 2001 From: Tr01ler Date: Mon, 12 Jan 2026 18:14:06 -0500 Subject: [PATCH 147/168] Ruined the post match screen??? --- lib/api/ClientApi.ts | 2 +- lib/games.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/api/ClientApi.ts b/lib/api/ClientApi.ts index e3d5c45b..25fc2f5c 100644 --- a/lib/api/ClientApi.ts +++ b/lib/api/ClientApi.ts @@ -214,7 +214,7 @@ export default class ClientApi extends NextApiTemplate { if (number <= 0) { return res.status(200).send(undefined); } - + console.log("Getting autofill data for team:", number, league); res .status(200) .send( diff --git a/lib/games.ts b/lib/games.ts index 4639f9ee..20edf131 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1970,6 +1970,8 @@ namespace Decode { label: "Overflow Artifacts (Teleop)", }, ], + ], + [ [{ key: "TeleopMotifArtifacts", label: "Motif Artifacts (Teleop)" }], [{ key: "TeleopDepotArtifacts", label: "Depot Artifacts (Teleop)" }], ], From 81b7608eb26e94f3168cde6aa28a364ea4272243 Mon Sep 17 00:00:00 2001 From: Tr01ler Date: Mon, 12 Jan 2026 20:47:02 -0500 Subject: [PATCH 148/168] Reorganized quantdata --- lib/games.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/games.ts b/lib/games.ts index 20edf131..faa1ba1f 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1970,10 +1970,20 @@ namespace Decode { label: "Overflow Artifacts (Teleop)", }, ], + [ + { + key: "TeleopMotifArtifacts", + label: "Motif Artifacts (Teleop)", + }, + ], ], [ - [{ key: "TeleopMotifArtifacts", label: "Motif Artifacts (Teleop)" }], - [{ key: "TeleopDepotArtifacts", label: "Depot Artifacts (Teleop)" }], + [ + { + key: "TeleopDepotArtifacts", + label: "Depot Artifacts (Teleop)", + }, + ], ], ], "Post Match": ["EndgameDefenseStatus", "EndgameParkStatus"], From 94d508b6fe15f30c07fa10f7a1f218c331d0fd6b Mon Sep 17 00:00:00 2001 From: Tr01ler Date: Wed, 14 Jan 2026 18:07:06 -0500 Subject: [PATCH 149/168] Fixed post match page, added field images --- lib/Enums.ts | 4 ++-- lib/Layout.ts | 6 ++++++ lib/games.ts | 23 ++++++----------------- public/fields/DecodeBlue.png | Bin 0 -> 59451 bytes public/fields/DecodeRed.png | Bin 0 -> 59451 bytes 5 files changed, 14 insertions(+), 19 deletions(-) create mode 100644 public/fields/DecodeBlue.png create mode 100644 public/fields/DecodeRed.png diff --git a/lib/Enums.ts b/lib/Enums.ts index c70b203c..5818c67a 100644 --- a/lib/Enums.ts +++ b/lib/Enums.ts @@ -115,13 +115,13 @@ export namespace DecodeEnums{ No = "No", Partial = "Partial", Full = "Full", - TwoBotPark = "Two Bot Park" + TwoBotPark = "Two Bot Park", } export enum AutoCapabilities { NoAuto = "No Auto", MovePastStart = "Move Past Start", ScoreOneArtifact = "Score One Artifact", - ScoreMultipleArtifacts = "Score Multiple Artifacts" + ScoreMultipleArtifacts = "Score Multiple Artifacts", } } \ No newline at end of file diff --git a/lib/Layout.ts b/lib/Layout.ts index 01ef80d1..da676ca8 100644 --- a/lib/Layout.ts +++ b/lib/Layout.ts @@ -10,6 +10,7 @@ import { IntoTheDeepEnums, FtcDrivetrain, ReefscapeEnums, + DecodeEnums, } from "./Enums"; import { PitReportData, QuantData, Pitreport, Report, League } from "./Types"; @@ -217,6 +218,8 @@ export function keyToType( ReefscapeEnums.Climbing, ReefscapeEnums.DriveThroughDeepCage, ReefscapeEnums.EndgameClimbStatus, + DecodeEnums.AutoCapabilities, + DecodeEnums.EndgameParkStatus, ]; if (key === "Defense") return Defense; @@ -231,6 +234,9 @@ export function keyToType( if (key == "DriveThroughDeepCage") return ReefscapeEnums.DriveThroughDeepCage; if (key == "EndgameClimbStatus") return ReefscapeEnums.EndgameClimbStatus; + if(key == "EndgameParkStatusDecode") return DecodeEnums.EndgameParkStatus; + if(key == "AutoAbilities") return DecodeEnums.AutoCapabilities; + for (const e of enums) { if (Object.values(e).includes(exampleData[key])) return e; } diff --git a/lib/games.ts b/lib/games.ts index faa1ba1f..4c2aebb0 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1906,9 +1906,8 @@ namespace Decode { TeleopMotifArtifacts: number = 0; TeleopDepotArtifacts: number = 0; - EndgameParkStatus: DecodeEnums.EndgameParkStatus = - DecodeEnums.EndgameParkStatus.No; - EndgameDefenseStatus: Defense = Defense.None; + EndgameParkStatusDecode: DecodeEnums.EndgameParkStatus = DecodeEnums.EndgameParkStatus.No; + EndgameDefense: Defense = Defense.None; } export class PitData extends PitReportData { @@ -1919,11 +1918,9 @@ namespace Decode { ArtifactsScoredAuto: number = 0; AutoAccountsForMotif: boolean = false; - AutoCapablilities: DecodeEnums.AutoCapabilities = + AutoAbilities: DecodeEnums.AutoCapabilities = DecodeEnums.AutoCapabilities.NoAuto; - AutoStrategy: string = ""; - - GameStrategy: string = ""; + } const pitReportLayout: FormLayoutProps = { @@ -1936,10 +1933,8 @@ namespace Decode { Auto: [ { key: "ArtifactsScoredAuto", label: "Average Auto Artifacts" }, { key: "AutoAccountsForMotif", label: "Auto Accounts For Motif?" }, - { key: "AutoCapablilities", label: "Other Auto Scoring Capabilities" }, - { key: "AutoStrategy", label: "Auto Strategy" }, + { key: "AutoAbilities", label: "Other Auto Scoring Capabilities" }, ], - Teleop: [{ key: "TeleopStrategy", label: "Teleop Strategy" }], }; const quantitativeReportLayout: FormLayoutProps = { @@ -1963,8 +1958,6 @@ namespace Decode { key: "TeleopArtifactsClassified", label: "Artifacts Classified (Teleop)", }, - ], - [ { key: "TeleopOverflowArtifacts", label: "Overflow Artifacts (Teleop)", @@ -1975,10 +1968,6 @@ namespace Decode { key: "TeleopMotifArtifacts", label: "Motif Artifacts (Teleop)", }, - ], - ], - [ - [ { key: "TeleopDepotArtifacts", label: "Depot Artifacts (Teleop)", @@ -1986,7 +1975,7 @@ namespace Decode { ], ], ], - "Post Match": ["EndgameDefenseStatus", "EndgameParkStatus"], + Endgame: ["Defense", "EndgameParkStatusDecode"], }; const statsLayout: StatsLayout = { diff --git a/public/fields/DecodeBlue.png b/public/fields/DecodeBlue.png new file mode 100644 index 0000000000000000000000000000000000000000..4ece8d32406511ee9e3fe87602226b3e7949d554 GIT binary patch literal 59451 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
h0}*b)sF9dV@L}e#We`n3 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 r!Ev~bkvepH;^EQ~uc7^1c65b{Wd7iZB-53 Date: Wed, 14 Jan 2026 23:31:57 +0000 Subject: [PATCH 150/168] 1.3.5 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c112372b..9d3d3dc2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sj3", - "version": "1.3.4", + "version": "1.3.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sj3", - "version": "1.3.4", + "version": "1.3.5", "license": "CC BY-NC-SA 4.0", "dependencies": { "dependencies": "^0.0.1", diff --git a/package.json b/package.json index 0c6a7baf..41c2f309 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sj3", - "version": "1.3.4", + "version": "1.3.5", "private": true, "repository": "https://github.com/Decatur-Robotics/Gearbox", "license": "CC BY-NC-SA 4.0", From 75715b22cd94cdcc652b99b266209292cdc60df5 Mon Sep 17 00:00:00 2001 From: Tr01ler Date: Wed, 14 Jan 2026 18:34:11 -0500 Subject: [PATCH 151/168] Prettier --- lib/Enums.ts | 4 ++-- lib/games.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Enums.ts b/lib/Enums.ts index 5818c67a..79c45916 100644 --- a/lib/Enums.ts +++ b/lib/Enums.ts @@ -110,7 +110,7 @@ export namespace ReefscapeEnums { } } -export namespace DecodeEnums{ +export namespace DecodeEnums { export enum EndgameParkStatus { No = "No", Partial = "Partial", @@ -124,4 +124,4 @@ export namespace DecodeEnums{ ScoreOneArtifact = "Score One Artifact", ScoreMultipleArtifacts = "Score Multiple Artifacts", } -} \ No newline at end of file +} diff --git a/lib/games.ts b/lib/games.ts index 4c2aebb0..839e2a4b 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1906,7 +1906,8 @@ namespace Decode { TeleopMotifArtifacts: number = 0; TeleopDepotArtifacts: number = 0; - EndgameParkStatusDecode: DecodeEnums.EndgameParkStatus = DecodeEnums.EndgameParkStatus.No; + EndgameParkStatusDecode: DecodeEnums.EndgameParkStatus = + DecodeEnums.EndgameParkStatus.No; EndgameDefense: Defense = Defense.None; } @@ -1920,7 +1921,6 @@ namespace Decode { AutoAccountsForMotif: boolean = false; AutoAbilities: DecodeEnums.AutoCapabilities = DecodeEnums.AutoCapabilities.NoAuto; - } const pitReportLayout: FormLayoutProps = { From f7efa2d3c289f770ddcbd8b74f0fe0b0f196d59c Mon Sep 17 00:00:00 2001 From: Tr01ler Date: Fri, 16 Jan 2026 17:27:04 -0500 Subject: [PATCH 152/168] Made all of the fake users named after numbers since the website we were using before changed --- .env.test | 8 +++++--- lib/dev/FakeData.ts | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.env.test b/.env.test index f373e69d..29d9257d 100644 --- a/.env.test +++ b/.env.test @@ -1,4 +1,4 @@ -NEXTAUTH_URL=http://localhost:3000/ +NEXTAUTH_URL=http://localhost:3000/api/ NEXTAUTH_SECRET=testsecret NEXT_PUBLIC_API_URL=/api/ @@ -9,12 +9,14 @@ TOA_URL=https://example.com TOA_APP_ID=123 TOA_KEY=456 +API_URL=/api/ +API_KEY=gearboxiscool + DEFAULT_IMAGE=https://example.com/default.jpg BASE_URL_FOR_PLAYWRIGHT=http://localhost:3000/ ENABLE_TEST_SIGNIN_ROUTE=true -FALLBACK_MONGODB_URI=mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000 - +FALLBACK_MONGODB_URI=mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+2.5.10 ENV_FILE=.env.test DB=playwright_tests diff --git a/lib/dev/FakeData.ts b/lib/dev/FakeData.ts index 9782f082..c9e71d49 100644 --- a/lib/dev/FakeData.ts +++ b/lib/dev/FakeData.ts @@ -9,8 +9,8 @@ import { ObjectId } from "bson"; import CollectionId from "../client/CollectionId"; import DbInterface from "../client/dbinterfaces/DbInterface"; -const firstNameMaleURL = "https://www.randomlists.com/data/names-male.json"; -const firstNameFemaleURL = "https://www.randomlists.com/data/names-female.json"; +const firstNameMaleURL = "https://www.randomlists.com/male-names"; +const firstNameFemaleURL = "https://www.randomlists.com/female-names"; var cachedFirstNames: string[] = []; var cachedLastNames: string[] = []; @@ -43,7 +43,7 @@ export async function fakeUser( db: DbInterface, teamId: ObjectId | undefined, ): Promise { - const name = await randomName(); + const name = String(Math.random() * 101); const user = new User( name, "totallyrealemail@gmail.com", From 615915ca0ef4d43a5350ad84fb9431fcee04c508 Mon Sep 17 00:00:00 2001 From: Tr01ler Date: Fri, 16 Jan 2026 17:37:43 -0500 Subject: [PATCH 153/168] Prettier claims to have changed 176 files despite having only actually changed 2. --- .github/workflows/increment_version.yml | 2 +- LICENSE.md | 314 ++++++++++++------------ eslint.config.mjs | 2 +- lib/Layout.ts | 4 +- lib/client/GameId.ts | 2 +- styles/globals.css | 21 +- 6 files changed, 168 insertions(+), 177 deletions(-) diff --git a/.github/workflows/increment_version.yml b/.github/workflows/increment_version.yml index a021286e..46ca6d3d 100644 --- a/.github/workflows/increment_version.yml +++ b/.github/workflows/increment_version.yml @@ -10,7 +10,7 @@ on: jobs: increment: runs-on: ubuntu-latest - if: + if: steps: - name: Checkout uses: actions/checkout@v4 diff --git a/LICENSE.md b/LICENSE.md index cbe5ad16..11976172 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -67,90 +67,88 @@ Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. - Section 1 -- Definitions. - a. Adapted Material means material subject to Copyright and Similar - Rights that is derived from or based upon the Licensed Material - and in which the Licensed Material is translated, altered, - arranged, transformed, or otherwise modified in a manner requiring - permission under the Copyright and Similar Rights held by the - Licensor. For purposes of this Public License, where the Licensed - Material is a musical work, performance, or sound recording, - Adapted Material is always produced where the Licensed Material is - synched in timed relation with a moving image. - - b. Adapter's License means the license You apply to Your Copyright - and Similar Rights in Your contributions to Adapted Material in - accordance with the terms and conditions of this Public License. - - c. BY-NC-SA Compatible License means a license listed at - creativecommons.org/compatiblelicenses, approved by Creative - Commons as essentially the equivalent of this Public License. - - d. Copyright and Similar Rights means copyright and/or similar rights - closely related to copyright including, without limitation, - performance, broadcast, sound recording, and Sui Generis Database - Rights, without regard to how the rights are labeled or - categorized. For purposes of this Public License, the rights - specified in Section 2(b)(1)-(2) are not Copyright and Similar - Rights. - - e. Effective Technological Measures means those measures that, in the - absence of proper authority, may not be circumvented under laws - fulfilling obligations under Article 11 of the WIPO Copyright - Treaty adopted on December 20, 1996, and/or similar international - agreements. - - f. Exceptions and Limitations means fair use, fair dealing, and/or - any other exception or limitation to Copyright and Similar Rights - that applies to Your use of the Licensed Material. - - g. License Elements means the license attributes listed in the name - of a Creative Commons Public License. The License Elements of this - Public License are Attribution, NonCommercial, and ShareAlike. - - h. Licensed Material means the artistic or literary work, database, - or other material to which the Licensor applied this Public - License. - - i. Licensed Rights means the rights granted to You subject to the - terms and conditions of this Public License, which are limited to - all Copyright and Similar Rights that apply to Your use of the - Licensed Material and that the Licensor has authority to license. - - j. Licensor means the individual(s) or entity(ies) granting rights - under this Public License. - - k. NonCommercial means not primarily intended for or directed towards - commercial advantage or monetary compensation. For purposes of - this Public License, the exchange of the Licensed Material for - other material subject to Copyright and Similar Rights by digital - file-sharing or similar means is NonCommercial provided there is - no payment of monetary compensation in connection with the - exchange. - - l. Share means to provide material to the public by any means or - process that requires permission under the Licensed Rights, such - as reproduction, public display, public performance, distribution, - dissemination, communication, or importation, and to make material - available to the public including in ways that members of the - public may access the material from a place and at a time - individually chosen by them. - - m. Sui Generis Database Rights means rights other than copyright - resulting from Directive 96/9/EC of the European Parliament and of - the Council of 11 March 1996 on the legal protection of databases, - as amended and/or succeeded, as well as other essentially - equivalent rights anywhere in the world. - - n. You means the individual or entity exercising the Licensed Rights - under this Public License. Your has a corresponding meaning. - +a. Adapted Material means material subject to Copyright and Similar +Rights that is derived from or based upon the Licensed Material +and in which the Licensed Material is translated, altered, +arranged, transformed, or otherwise modified in a manner requiring +permission under the Copyright and Similar Rights held by the +Licensor. For purposes of this Public License, where the Licensed +Material is a musical work, performance, or sound recording, +Adapted Material is always produced where the Licensed Material is +synched in timed relation with a moving image. + +b. Adapter's License means the license You apply to Your Copyright +and Similar Rights in Your contributions to Adapted Material in +accordance with the terms and conditions of this Public License. + +c. BY-NC-SA Compatible License means a license listed at +creativecommons.org/compatiblelicenses, approved by Creative +Commons as essentially the equivalent of this Public License. + +d. Copyright and Similar Rights means copyright and/or similar rights +closely related to copyright including, without limitation, +performance, broadcast, sound recording, and Sui Generis Database +Rights, without regard to how the rights are labeled or +categorized. For purposes of this Public License, the rights +specified in Section 2(b)(1)-(2) are not Copyright and Similar +Rights. + +e. Effective Technological Measures means those measures that, in the +absence of proper authority, may not be circumvented under laws +fulfilling obligations under Article 11 of the WIPO Copyright +Treaty adopted on December 20, 1996, and/or similar international +agreements. + +f. Exceptions and Limitations means fair use, fair dealing, and/or +any other exception or limitation to Copyright and Similar Rights +that applies to Your use of the Licensed Material. + +g. License Elements means the license attributes listed in the name +of a Creative Commons Public License. The License Elements of this +Public License are Attribution, NonCommercial, and ShareAlike. + +h. Licensed Material means the artistic or literary work, database, +or other material to which the Licensor applied this Public +License. + +i. Licensed Rights means the rights granted to You subject to the +terms and conditions of this Public License, which are limited to +all Copyright and Similar Rights that apply to Your use of the +Licensed Material and that the Licensor has authority to license. + +j. Licensor means the individual(s) or entity(ies) granting rights +under this Public License. + +k. NonCommercial means not primarily intended for or directed towards +commercial advantage or monetary compensation. For purposes of +this Public License, the exchange of the Licensed Material for +other material subject to Copyright and Similar Rights by digital +file-sharing or similar means is NonCommercial provided there is +no payment of monetary compensation in connection with the +exchange. + +l. Share means to provide material to the public by any means or +process that requires permission under the Licensed Rights, such +as reproduction, public display, public performance, distribution, +dissemination, communication, or importation, and to make material +available to the public including in ways that members of the +public may access the material from a place and at a time +individually chosen by them. + +m. Sui Generis Database Rights means rights other than copyright +resulting from Directive 96/9/EC of the European Parliament and of +the Council of 11 March 1996 on the legal protection of databases, +as amended and/or succeeded, as well as other essentially +equivalent rights anywhere in the world. + +n. You means the individual or entity exercising the Licensed Rights +under this Public License. Your has a corresponding meaning. Section 2 -- Scope. - a. License grant. +a. License grant. 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, @@ -211,7 +209,7 @@ Section 2 -- Scope. the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). - b. Other rights. +b. Other rights. 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, @@ -233,13 +231,12 @@ Section 2 -- Scope. the Licensed Material is used other than for NonCommercial purposes. - Section 3 -- License Conditions. Your exercise of the Licensed Rights is expressly made subject to the following conditions. - a. Attribution. +a. Attribution. 1. If You Share the Licensed Material (including in modified form), You must: @@ -280,7 +277,7 @@ following conditions. information required by Section 3(a)(1)(A) to the extent reasonably practicable. - b. ShareAlike. +b. ShareAlike. In addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply. @@ -299,69 +296,66 @@ following conditions. Measures to, Adapted Material that restrict exercise of the rights granted under the Adapter's License You apply. - Section 4 -- Sui Generis Database Rights. Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: - a. for the avoidance of doubt, Section 2(a)(1) grants You the right - to extract, reuse, reproduce, and Share all or a substantial - portion of the contents of the database for NonCommercial purposes - only; +a. for the avoidance of doubt, Section 2(a)(1) grants You the right +to extract, reuse, reproduce, and Share all or a substantial +portion of the contents of the database for NonCommercial purposes +only; - b. if You include all or a substantial portion of the database - contents in a database in which You have Sui Generis Database - Rights, then the database in which You have Sui Generis Database - Rights (but not its individual contents) is Adapted Material, - including for purposes of Section 3(b); and +b. if You include all or a substantial portion of the database +contents in a database in which You have Sui Generis Database +Rights, then the database in which You have Sui Generis Database +Rights (but not its individual contents) is Adapted Material, +including for purposes of Section 3(b); and - c. You must comply with the conditions in Section 3(a) if You Share - all or a substantial portion of the contents of the database. +c. You must comply with the conditions in Section 3(a) if You Share +all or a substantial portion of the contents of the database. For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. - Section 5 -- Disclaimer of Warranties and Limitation of Liability. - a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE - EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS - AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF - ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, - IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, - WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR - PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, - ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT - KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT - ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. - - b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE - TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, - NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, - INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, - COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR - USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN - ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR - DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR - IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. - - c. The disclaimer of warranties and limitation of liability provided - above shall be interpreted in a manner that, to the extent - possible, most closely approximates an absolute disclaimer and - waiver of all liability. - +a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE +EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS +AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF +ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, +IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, +WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, +ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT +KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT +ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + +b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE +TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, +NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, +INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, +COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR +USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN +ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR +DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR +IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + +c. The disclaimer of warranties and limitation of liability provided +above shall be interpreted in a manner that, to the extent +possible, most closely approximates an absolute disclaimer and +waiver of all liability. Section 6 -- Term and Termination. - a. This Public License applies for the term of the Copyright and - Similar Rights licensed here. However, if You fail to comply with - this Public License, then Your rights under this Public License - terminate automatically. +a. This Public License applies for the term of the Copyright and +Similar Rights licensed here. However, if You fail to comply with +this Public License, then Your rights under this Public License +terminate automatically. - b. Where Your right to use the Licensed Material has terminated under - Section 6(a), it reinstates: +b. Where Your right to use the Licensed Material has terminated under +Section 6(a), it reinstates: 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the @@ -373,47 +367,45 @@ Section 6 -- Term and Termination. right the Licensor may have to seek remedies for Your violations of this Public License. - c. For the avoidance of doubt, the Licensor may also offer the - Licensed Material under separate terms or conditions or stop - distributing the Licensed Material at any time; however, doing so - will not terminate this Public License. - - d. Sections 1, 5, 6, 7, and 8 survive termination of this Public - License. +c. For the avoidance of doubt, the Licensor may also offer the +Licensed Material under separate terms or conditions or stop +distributing the Licensed Material at any time; however, doing so +will not terminate this Public License. +d. Sections 1, 5, 6, 7, and 8 survive termination of this Public +License. Section 7 -- Other Terms and Conditions. - a. The Licensor shall not be bound by any additional or different - terms or conditions communicated by You unless expressly agreed. - - b. Any arrangements, understandings, or agreements regarding the - Licensed Material not stated herein are separate from and - independent of the terms and conditions of this Public License. +a. The Licensor shall not be bound by any additional or different +terms or conditions communicated by You unless expressly agreed. +b. Any arrangements, understandings, or agreements regarding the +Licensed Material not stated herein are separate from and +independent of the terms and conditions of this Public License. Section 8 -- Interpretation. - a. For the avoidance of doubt, this Public License does not, and - shall not be interpreted to, reduce, limit, restrict, or impose - conditions on any use of the Licensed Material that could lawfully - be made without permission under this Public License. - - b. To the extent possible, if any provision of this Public License is - deemed unenforceable, it shall be automatically reformed to the - minimum extent necessary to make it enforceable. If the provision - cannot be reformed, it shall be severed from this Public License - without affecting the enforceability of the remaining terms and - conditions. - - c. No term or condition of this Public License will be waived and no - failure to comply consented to unless expressly agreed to by the - Licensor. - - d. Nothing in this Public License constitutes or may be interpreted - as a limitation upon, or waiver of, any privileges and immunities - that apply to the Licensor or You, including from the legal - processes of any jurisdiction or authority. +a. For the avoidance of doubt, this Public License does not, and +shall not be interpreted to, reduce, limit, restrict, or impose +conditions on any use of the Licensed Material that could lawfully +be made without permission under this Public License. + +b. To the extent possible, if any provision of this Public License is +deemed unenforceable, it shall be automatically reformed to the +minimum extent necessary to make it enforceable. If the provision +cannot be reformed, it shall be severed from this Public License +without affecting the enforceability of the remaining terms and +conditions. + +c. No term or condition of this Public License will be waived and no +failure to comply consented to unless expressly agreed to by the +Licensor. + +d. Nothing in this Public License constitutes or may be interpreted +as a limitation upon, or waiver of, any privileges and immunities +that apply to the Licensor or You, including from the legal +processes of any jurisdiction or authority. ======================================================================= diff --git a/eslint.config.mjs b/eslint.config.mjs index e0462f2b..e8345844 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -20,7 +20,7 @@ const config = [ }, }, { - // Ignores has to go in its own config object + // Ignores has to go in its own config object ignores: ["coverage/**/*", ".next/**/*"], }, ]; diff --git a/lib/Layout.ts b/lib/Layout.ts index da676ca8..841b9625 100644 --- a/lib/Layout.ts +++ b/lib/Layout.ts @@ -234,8 +234,8 @@ export function keyToType( if (key == "DriveThroughDeepCage") return ReefscapeEnums.DriveThroughDeepCage; if (key == "EndgameClimbStatus") return ReefscapeEnums.EndgameClimbStatus; - if(key == "EndgameParkStatusDecode") return DecodeEnums.EndgameParkStatus; - if(key == "AutoAbilities") return DecodeEnums.AutoCapabilities; + if (key == "EndgameParkStatusDecode") return DecodeEnums.EndgameParkStatus; + if (key == "AutoAbilities") return DecodeEnums.AutoCapabilities; for (const e of enums) { if (Object.values(e).includes(exampleData[key])) return e; diff --git a/lib/client/GameId.ts b/lib/client/GameId.ts index 7eb6402a..2dcbdb15 100644 --- a/lib/client/GameId.ts +++ b/lib/client/GameId.ts @@ -3,7 +3,7 @@ export enum GameId { CenterStage = "CenterStage", IntoTheDeep = "IntoTheDeep", Reefscape = "Reefscape", - Decode = "Decode" + Decode = "Decode", } export const defaultGameId = GameId.Reefscape; diff --git a/styles/globals.css b/styles/globals.css index d1cd3c0e..df011e23 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -2,27 +2,26 @@ @tailwind components; @tailwind utilities; - @layer components { - .background-grid { - background-size: 50px 50px; - background-image: - linear-gradient(to right, white 1px, transparent 1px), - linear-gradient(to bottom, white 1px, transparent 1px); - } + .background-grid { + background-size: 50px 50px; + background-image: + linear-gradient(to right, white 1px, transparent 1px), + linear-gradient(to bottom, white 1px, transparent 1px); + } } @keyframes float { 0% { - box-shadow: 0 5px 15px 0px rgba(0,0,0,0.6); + box-shadow: 0 5px 15px 0px rgba(0, 0, 0, 0.6); transform: translatey(0px); } 50% { - box-shadow: 0 25px 15px 0px rgba(0,0,0,0.2); + box-shadow: 0 25px 15px 0px rgba(0, 0, 0, 0.2); transform: translatey(-20px); } 100% { - box-shadow: 0 5px 15px 0px rgba(0,0,0,0.6); + box-shadow: 0 5px 15px 0px rgba(0, 0, 0, 0.6); transform: translatey(0px); } } @@ -56,4 +55,4 @@ border-radius: 10px; } -*/ \ No newline at end of file +*/ From d91a0624dc2dd4ab208dc6f3ca79b0574dd94935 Mon Sep 17 00:00:00 2001 From: Cullen Date: Tue, 20 Jan 2026 17:55:21 -0500 Subject: [PATCH 154/168] Add Rebuilt game enums and data structures for scoring and capabilities --- lib/Enums.ts | 29 ++++++ lib/client/GameId.ts | 1 + lib/games.ts | 238 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 268 insertions(+) diff --git a/lib/Enums.ts b/lib/Enums.ts index ccd7a6f0..2bd17041 100644 --- a/lib/Enums.ts +++ b/lib/Enums.ts @@ -109,3 +109,32 @@ export namespace ReefscapeEnums { Shallow = "Shallow", } } +export namespace RebuiltEnums { + export enum Climbing { + No = "No", + FirstLevel = "First Level", + SecondLevel = "Second Level", + ThirdLevel = "Third Level", + } + + export enum DriveOverBump{ + No = "No", + Slow="Slow", + Fast="Fast", + } + + export enum EndgameClimbStatus { + None = "None", + FirstLevel = "First Level", + SecondLevel = "Second Level", + ThirdLevel = "Third Level", + } + + export enum AutoCapabilities { + NoAuto = "No Auto", + MovePastStart = "Move Past Start", + ClimbLevelOne = "Climb Level One", + ScoreOneOrMoreFuel = "Score One Or More Fuel", + ScoreFuelAndClimb = "Score Fuel And Climb", + } +} \ No newline at end of file diff --git a/lib/client/GameId.ts b/lib/client/GameId.ts index d2f946cc..0227eed5 100644 --- a/lib/client/GameId.ts +++ b/lib/client/GameId.ts @@ -3,6 +3,7 @@ export enum GameId { CenterStage = "CenterStage", IntoTheDeep = "IntoTheDeep", Reefscape = "Reefscape", + Rebuilt = "Rebuilt", } export const defaultGameId = GameId.Reefscape; diff --git a/lib/games.ts b/lib/games.ts index 756ddc0d..eb5af4dc 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -5,6 +5,7 @@ import { FrcDrivetrain, IntakeTypes, IntoTheDeepEnums, + RebuiltEnums, ReefscapeEnums, } from "./Enums"; import { Badge, FormLayoutProps, PitStatsLayout, StatsLayout } from "./Layout"; @@ -32,6 +33,7 @@ import { } from "./client/StatsMath"; import { report } from "process"; import { GetMaximum } from "./client/StatsMath"; +import { getMaxListeners } from "events"; function getBaseBadges( pitReport: Pitreport | undefined, @@ -1888,7 +1890,243 @@ namespace Reefscape { ); } +namespace Rebuilt { + export class QuantitativeData extends QuantData { + AutoScoredOnePoint: number = 0; + AutoScoredFivePoint: number = 0; + AutoScoredTenPoint: number = 0; + AutoClimbedLevelOne: boolean = false; + + TeleopScoredOnePoint: number = 0; + TeleopScoredFivePoint: number = 0; + TeleopScoredTenPoint: number = 0; + EngameDefenseStatus: Defense = Defense.None; + EndgameClimbStatus: RebuiltEnums.EndgameClimbStatus = + RebuiltEnums.EndgameClimbStatus.None; + } + export class PitData extends PitReportData { + GroundIntake: boolean = false; + CanDriveOverBump: boolean = false; + CanDriveUnderTrench: boolean = false; + CanDeClimb: boolean = false; + CanScoreFuel: boolean = false; + FuelScoredAuto: number = 0; + AutoCapabilities: RebuiltEnums.AutoCapabilities = + RebuiltEnums.AutoCapabilities.NoAuto; + Climing: RebuiltEnums.Climbing = RebuiltEnums.Climbing.No; + } + const pitReportLayout: FormLayoutProps = { + Capabilities: [ + { key: "GroundIntake", label: "Has Ground Intake?" }, + { key: "CanDriveOverBump", label: "Can Drive Over Bump?" }, + { key: "CanDriveUnderTrench", label: "Can Drive Under Trench?" }, + { key: "CanDeClimb", label: "Can De-Climb?" }, + { key: "CanScoreFuel", label: "Can Score Fuel?" }, + { key: "Climing", label: "Climbing?" }, + ], + "Auto (Describe more in comments)": [ + { key: "AutoCapabilities", label: "Auto Capabilities?" }, + { key: "FuelScoredAuto", label: "Average Fuel Scored In Auto" }, + ], + }; + const quantitativeReportLayout: FormLayoutProps = { + Auto: [ + { key: "AutoScoredOnePoint", label: "1 Point Scored (Auto)" }, + { key: "AutoScoredFivePoint", label: "5 Point Scared (Auto)" }, + { key: "AutoScoredTenPoint", label: "10 Point Scored (Auto)" }, + { key: "AutoClimbedLevelOne", label: "Climbed Level One (Auto)" }, + ], + Teleop: [ + { key: "TeleopScoredOnePoint", label: "1 Point Scored (Teleop)" }, + { key: "TeleopScoredFivePoint", label: "5 Point Scored (Teleop)" }, + { key: "TeleopScoredTenPoint", label: "10 Point Scored (Teleop)" }, + ], + "Post Match": ["EndgameClimbStatus", "Defense"], + }; + const statsLayout: StatsLayout = { + sections: { + Auto: [ + { + key: "AutoScoredOnePoint", + label: "Avg Amt Of Fuel Scored In Increments Of One Auto", + }, + { + label: "> Min Auto One Point Fuel", + get(pitData, quantitativeReports) { + return GetMinimum(quantitativeReports!, "AutoScoredOnePoint"); + }, + }, + { + label: "> Max Auto One Point Fuel", + get(pitData, quantitativeReports) { + return GetMaximum(quantitativeReports!, "AutoScoredOnePoint"); + }, + }, + { + key: "AutoScoredFivePoint", + label: "Avg Amt Of Fuel Scored In Increments Of Five Auto", + }, + { + label: "> Min Auto Five Point Fuel", + get(pitData, quantitativeReports) { + return GetMinimum(quantitativeReports!, "AutoScoredFivePoint"); + }, + }, + { + label: "> Max Auto five Point Fuel", + get(pitData, quantitativeReports) { + return GetMaximum(quantitativeReports!, "AutoScoredFivePoint"); + }, + }, + { + key: "AutoScoredTenPoint", + label: "Avg Amt Of Fuel Scored In Increments Of Ten Auto", + }, + { + label: "> Min Auto Ten Point Fuel", + get(pitData, quantitativeReports) { + return GetMinimum(quantitativeReports!, "autoScoredTenPoint"); + }, + }, + { + label: "> Max Auto Ten Point Fuel", + get(pitData, quantitativeReports) { + return GetMaximum(quantitativeReports!, "autoScoredTenPoint"); + }, + }, + { + label: "Average Auto Fuel", + get(pitData, quantitativeReports) { + if (!quantitativeReports) return 0; + + return ( + quantitativeReports?.reduce( + (acc, report) => + acc + + report.data.AutoScoredOnePoint + + report.data.AutoScoredFivePoint + + report.data.AutoScoredTenPoint, + 0, + ) / quantitativeReports?.length + ); + }, + }, + ], + Teleop: [ + { + key: "TeleopScoredOnePoint", + label: "Teleop Scored One Point Fuel", + }, + { + label: "< Min Teleop Scored One Point Fuel", + get(pitData, quantitativeReports) { + return GetMinimum(quantitativeReports!, "TeleopScoredOnePoint"); + }, + }, + { + label: "< Max Teleop Scored One Point Fuel", + get(pitData, quantitativeReports) { + return GetMaximum(quantitativeReports!, "TeleopScoredOnePoint"); + }, + }, + { + key: "TeleopScoredFivePoint", + label: "Teleop Scored Five Point Fuel", + }, + { + label: "< Min Teleop Scored Five Point Fuel", + get(pitData, quantitativeReports) { + return GetMinimum(quantitativeReports!, "TeleopScoredFivePoint"); + }, + }, + { + label: "< Maximum Teleop Scored Five Point Fuel", + get(pitData, quantitativeReports) { + return GetMaximum(quantitativeReports!, "TeleopScoredFivePoint"); + }, + }, + { + key: "TeleopScoredTenPoint", + label: "Teleop Scored Ten Point Fuel", + }, + { + label: "< Min Teleop Scored Ten Point Fuel", + get(pitData, quantitativeReports) { + return GetMinimum(quantitativeReports!, "TeleopScoredTenPoint"); + }, + }, + { + label: "< Maximum Teleop Scored Ten Point Fuel", + get(pitData, quantitativeReports) { + return GetMaximum(quantitativeReports!, "TeleopScoredTenPoint"); + }, + }, + { + label: "Average Teleop Fuel", + get(pitData, quantitativeReports) { + if (!quantitativeReports) return 0; + + return ( + quantitativeReports?.reduce( + (acc, report) => + acc + + report.data.TeleopScoredOnePoint + + report.data.TeleopScoredFivePoint + + report.data.TeleopScoredTenPoint, + 0, + ) / quantitativeReports?.length + ); + }, + }, + ], + }, + getGraphDots: function ( + quantitativeReports: Report[], + pitReport?: Pitreport | undefined, + ): Dot[] { + return []; + }, + }; + const pitStatsLayout: PitStatsLayout = { + overallSlideStats: [ + { + key: "FuelScoredAuto", + label: "Amount of fuel scored in auto", + }, + ], + individualSlideStats: [ + { + label: "Average Auto Points", + get: ( + pitReport: Pitreport | undefined, + quantitativeReports: Report[] | undefined, + ) => { + if (!quantitativeReports) return 0; + + const OneFuelScored = NumericalTotal( + "AutoScoredOnePoint", + quantitativeReports, + ); + const FiveFuelScored = NumericalTotal( + "AutoScoredFivePoint", + quantitativeReports, + ); + const TenFuelScored = NumericalTotal( + "AutoScoredTenPoint", + quantitativeReports, + ); + return ( + (OneFuelScored + FiveFuelScored + TenFuelScored) / + quantitativeReports.length + ); + }, + }, + ], + }; +} + export const games: { [id in GameId]: Game } = Object.freeze({ + [GameId.Rebuilt]: Rebuilt.game, [GameId.Reefscape]: Reefscape.game, [GameId.IntoTheDeep]: IntoTheDeep.game, [GameId.Crescendo]: Crescendo.game, From 7cb85ca0ee481e436d84f4c242c052cc35d0ca38 Mon Sep 17 00:00:00 2001 From: Cullen Date: Thu, 22 Jan 2026 17:41:05 -0500 Subject: [PATCH 155/168] 1/22 commit --- lib/Enums.ts | 12 ++++---- lib/games.ts | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 86 insertions(+), 8 deletions(-) diff --git a/lib/Enums.ts b/lib/Enums.ts index 2bd17041..92352f52 100644 --- a/lib/Enums.ts +++ b/lib/Enums.ts @@ -112,9 +112,9 @@ export namespace ReefscapeEnums { export namespace RebuiltEnums { export enum Climbing { No = "No", - FirstLevel = "First Level", - SecondLevel = "Second Level", - ThirdLevel = "Third Level", + FirstLevel = "FirstLevel", + SecondLevel = "SecondLevel", + ThirdLevel = "ThirdLevel", } export enum DriveOverBump{ @@ -125,9 +125,9 @@ export namespace RebuiltEnums { export enum EndgameClimbStatus { None = "None", - FirstLevel = "First Level", - SecondLevel = "Second Level", - ThirdLevel = "Third Level", + FirstLevel = "FirstLevel", + SecondLevel = "SecondLevel", + ThirdLevel = "ThirdLevel", } export enum AutoCapabilities { diff --git a/lib/games.ts b/lib/games.ts index eb5af4dc..63ef1979 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1895,11 +1895,13 @@ namespace Rebuilt { AutoScoredOnePoint: number = 0; AutoScoredFivePoint: number = 0; AutoScoredTenPoint: number = 0; + AutoTotalScored: number = 0; AutoClimbedLevelOne: boolean = false; TeleopScoredOnePoint: number = 0; TeleopScoredFivePoint: number = 0; TeleopScoredTenPoint: number = 0; + TeleopTotalScored: number=0 EngameDefenseStatus: Defense = Defense.None; EndgameClimbStatus: RebuiltEnums.EndgameClimbStatus = RebuiltEnums.EndgameClimbStatus.None; @@ -1913,7 +1915,7 @@ namespace Rebuilt { FuelScoredAuto: number = 0; AutoCapabilities: RebuiltEnums.AutoCapabilities = RebuiltEnums.AutoCapabilities.NoAuto; - Climing: RebuiltEnums.Climbing = RebuiltEnums.Climbing.No; + Climbing: RebuiltEnums.Climbing = RebuiltEnums.Climbing.No; } const pitReportLayout: FormLayoutProps = { Capabilities: [ @@ -1922,7 +1924,7 @@ namespace Rebuilt { { key: "CanDriveUnderTrench", label: "Can Drive Under Trench?" }, { key: "CanDeClimb", label: "Can De-Climb?" }, { key: "CanScoreFuel", label: "Can Score Fuel?" }, - { key: "Climing", label: "Climbing?" }, + { key: "Climbing", label: "Climbing?" }, ], "Auto (Describe more in comments)": [ { key: "AutoCapabilities", label: "Auto Capabilities?" }, @@ -2121,10 +2123,86 @@ namespace Rebuilt { ); }, }, + { + label: "Average Teleop Points", + get: ( + pitReport: Pitreport | undefined, + quantitativeReports: Report[] | undefined, + ) => { + if (!quantitativeReports) return 0; + + const TeleopOneFuelScored = NumericalTotal( + "TeleopScoredOnePoint", + quantitativeReports, + ); + const TeleopFiveFuelScored = NumericalTotal( + "TeleopScoredFivePoint", + quantitativeReports, + ); + const TeleopTenFuelScored = NumericalTotal( + "TeleopScoredTenPoint", + quantitativeReports, + ); + return ( + (TeleopOneFuelScored + TeleopFiveFuelScored + TeleopTenFuelScored) / + quantitativeReports.length + ); + }, + }, + { + label: "Ave Endgame Stats", + get: ( + pitReport: Pitreport | undefined, + quantitativeReports: Report[] | undefined, + ) => { + if (!quantitativeReports) return 0; + + const climb = NumericalTotal( + "EndGameClimbStatus", + quantitativeReports, + ); + return Round(climb) / quantitativeReports.length; + }, + }, ], + RobotCapabilites: [ + { key: "GroundIntake", label: "Has Ground Intake?" }, + { key: "CanDriveOverBump", label: "Can Drive Over Bump?" }, + { key: "CanDriveUnderTrench", label: "Can Drive Under Trench?" }, + { key: "CanDeClimb", label: "Can De-Climb?" }, + { key: "CanScoreFuel", label: "Can Score Fuel?" }, + { key: "Climbing", label: "Climbing?" }, + ], + GraphStat: [ + ] + }; + function getBadges( + pitReport: Pitreport | undefined, + quantitativeReports: Report[] | undefined, + card: boolean, + ) { + const badges: Badge[] = getBaseBadges(pitReport, quantitativeReports); + if (pitReport?.data?.GroundIntake) + badges.push({ text: "Can Use Ground Intake", color: "primary" }); + if (pitReport?.data?.CanDriveOverBump) + badges.push({ text: "Can Drive Over Bump", color: "accent"}); + if (pitReport?.data?.CanDriveUnderTrench) + badges.push({ text: "Can Drive Under Trench", color: "accent"}); + if (pitReport?.data?.CanDeClimb) + badges.push({ text: "Can Declimb", color:"accent"}); + if (pitReport?.data?.CanScoreFuel) + badges.push({ text: "Can Score Fuel", color: "accent"}); + + if ((pitReport?.data?.Climbing = RebuiltEnums.Climbing.FirstLevel)) + badges.push({ text: "Can Climb First Level", color: "accent" }); + else if ((pitReport?.data?.Climbing = RebuiltEnums.Climbing.SecondLevel)) + badges.push({ text: "Can Climb Second Level", color: "accent" }); + else if ((pitReport?.data?.Climbing = RebuiltEnums.Climbing.ThirdLevel)) + badges.push({ text: "Can Climb Third Level", color: "accent" }); }; } + export const games: { [id in GameId]: Game } = Object.freeze({ [GameId.Rebuilt]: Rebuilt.game, [GameId.Reefscape]: Reefscape.game, From 34ac314a7488576546aab70a0d7d648c8bac22ab Mon Sep 17 00:00:00 2001 From: Cullen Date: Mon, 2 Feb 2026 16:14:18 -0500 Subject: [PATCH 156/168] feb 2nd --- components/stats/SmallGraph.tsx | 21 ++++++--- lib/Enums.ts | 6 +++ lib/Layout.ts | 12 +++++ lib/api/ClientApi.ts | 2 +- lib/client/GameId.ts | 2 +- lib/games.ts | 75 +++++++++++++++++++++++++------- public/fields/RebuiltBlue.png | Bin 0 -> 45890 bytes public/fields/RebuiltRed.png | Bin 0 -> 45049 bytes 8 files changed, 95 insertions(+), 23 deletions(-) create mode 100644 public/fields/RebuiltBlue.png create mode 100644 public/fields/RebuiltRed.png diff --git a/components/stats/SmallGraph.tsx b/components/stats/SmallGraph.tsx index 31e6dd35..cd349a13 100644 --- a/components/stats/SmallGraph.tsx +++ b/components/stats/SmallGraph.tsx @@ -1,4 +1,4 @@ -import { Defense, ReefscapeEnums } from "@/lib/Enums"; +import { Defense, RebuiltEnums } from "@/lib/Enums"; import { Report } from "@/lib/Types"; import ClientApi from "@/lib/api/ClientApi"; @@ -87,15 +87,24 @@ export default function SmallGraph(props: { return 1; } } else if (key === "EndgameClimbStatus") { + ///switch (data) { + //case ReefscapeEnums.EndgameClimbStatus.None: + //return 0; + //case ReefscapeEnums.EndgameClimbStatus.Park: + //return 0.33; + //case ReefscapeEnums.EndgameClimbStatus.High: + //return 0.66; + //case ReefscapeEnums.EndgameClimbStatus.Low: + //return 1; switch (data) { - case ReefscapeEnums.EndgameClimbStatus.None: + case RebuiltEnums.EndgameClimbStatus.None: return 0; - case ReefscapeEnums.EndgameClimbStatus.Park: + case RebuiltEnums.EndgameClimbStatus.FirstLevel: return 0.33; - case ReefscapeEnums.EndgameClimbStatus.High: + case RebuiltEnums.EndgameClimbStatus.SecondLevel: return 0.66; - case ReefscapeEnums.EndgameClimbStatus.Low: - return 1; + case RebuiltEnums.EndgameClimbStatus.ThirdLevel: + return 1; } } return data; diff --git a/lib/Enums.ts b/lib/Enums.ts index 92352f52..d95465ca 100644 --- a/lib/Enums.ts +++ b/lib/Enums.ts @@ -123,6 +123,12 @@ export namespace RebuiltEnums { Fast="Fast", } + export enum DriveUnderTrench{ + No= "No", + Slow="Slow", + Fast="Fast", + } + export enum EndgameClimbStatus { None = "None", FirstLevel = "FirstLevel", diff --git a/lib/Layout.ts b/lib/Layout.ts index 01ef80d1..e961e962 100644 --- a/lib/Layout.ts +++ b/lib/Layout.ts @@ -10,6 +10,7 @@ import { IntoTheDeepEnums, FtcDrivetrain, ReefscapeEnums, + RebuiltEnums, } from "./Enums"; import { PitReportData, QuantData, Pitreport, Report, League } from "./Types"; @@ -217,6 +218,11 @@ export function keyToType( ReefscapeEnums.Climbing, ReefscapeEnums.DriveThroughDeepCage, ReefscapeEnums.EndgameClimbStatus, + RebuiltEnums.AutoCapabilities, + RebuiltEnums.Climbing, + RebuiltEnums.DriveOverBump, + RebuiltEnums.DriveUnderTrench, + RebuiltEnums.EndgameClimbStatus ]; if (key === "Defense") return Defense; @@ -231,6 +237,12 @@ export function keyToType( if (key == "DriveThroughDeepCage") return ReefscapeEnums.DriveThroughDeepCage; if (key == "EndgameClimbStatus") return ReefscapeEnums.EndgameClimbStatus; + if (key == "AutoCapabilities") return RebuiltEnums.AutoCapabilities; + if (key == "climbing") return RebuiltEnums.Climbing; + if (key == "DriveOverBump") return RebuiltEnums.DriveOverBump; + if (key == "DiveUnderTrench")return RebuiltEnums.DriveUnderTrench; + if (key == "EndgameClimbStatus") return RebuiltEnums.EndgameClimbStatus; + for (const e of enums) { if (Object.values(e).includes(exampleData[key])) return e; } diff --git a/lib/api/ClientApi.ts b/lib/api/ClientApi.ts index e3d5c45b..5e26cac7 100644 --- a/lib/api/ClientApi.ts +++ b/lib/api/ClientApi.ts @@ -42,7 +42,7 @@ import { assignScoutersToCompetitionMatches, generateReportsForMatch, } from "../CompetitionHandling"; -import { CenterStage, Crescendo, games, IntoTheDeep } from "../games"; +import { CenterStage, Crescendo, games, IntoTheDeep, Rebuilt } from "../games"; import { Statbotics } from "../Statbotics"; import { TheBlueAlliance } from "../TheBlueAlliance"; import { SlackNotLinkedError } from "./Errors"; diff --git a/lib/client/GameId.ts b/lib/client/GameId.ts index 0227eed5..4897b965 100644 --- a/lib/client/GameId.ts +++ b/lib/client/GameId.ts @@ -6,4 +6,4 @@ export enum GameId { Rebuilt = "Rebuilt", } -export const defaultGameId = GameId.Reefscape; +export const defaultGameId = GameId.Rebuilt; diff --git a/lib/games.ts b/lib/games.ts index 63ef1979..57c84e97 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1890,7 +1890,7 @@ namespace Reefscape { ); } -namespace Rebuilt { +export namespace Rebuilt { export class QuantitativeData extends QuantData { AutoScoredOnePoint: number = 0; AutoScoredFivePoint: number = 0; @@ -1901,7 +1901,7 @@ namespace Rebuilt { TeleopScoredOnePoint: number = 0; TeleopScoredFivePoint: number = 0; TeleopScoredTenPoint: number = 0; - TeleopTotalScored: number=0 + TeleopTotalScored: number = 0; EngameDefenseStatus: Defense = Defense.None; EndgameClimbStatus: RebuiltEnums.EndgameClimbStatus = RebuiltEnums.EndgameClimbStatus.None; @@ -1942,6 +1942,7 @@ namespace Rebuilt { { key: "TeleopScoredOnePoint", label: "1 Point Scored (Teleop)" }, { key: "TeleopScoredFivePoint", label: "5 Point Scored (Teleop)" }, { key: "TeleopScoredTenPoint", label: "10 Point Scored (Teleop)" }, + { key: "TeleopTotalScored", label:"Total Fuel Scored (Teleop)"} ], "Post Match": ["EndgameClimbStatus", "Defense"], }; @@ -2165,7 +2166,7 @@ namespace Rebuilt { }, }, ], - RobotCapabilites: [ + robotCapabilities: [ { key: "GroundIntake", label: "Has Ground Intake?" }, { key: "CanDriveOverBump", label: "Can Drive Over Bump?" }, { key: "CanDriveUnderTrench", label: "Can Drive Under Trench?" }, @@ -2173,8 +2174,10 @@ namespace Rebuilt { { key: "CanScoreFuel", label: "Can Score Fuel?" }, { key: "Climbing", label: "Climbing?" }, ], - GraphStat: [ - ] + graphStat: { + label: "Average Fuel Scored In Hopper", + key: "TeleopTotalPoints", + }, }; function getBadges( pitReport: Pitreport | undefined, @@ -2185,23 +2188,65 @@ namespace Rebuilt { if (pitReport?.data?.GroundIntake) badges.push({ text: "Can Use Ground Intake", color: "primary" }); if (pitReport?.data?.CanDriveOverBump) - badges.push({ text: "Can Drive Over Bump", color: "accent"}); + badges.push({ text: "Can Drive Over Bump", color: "accent" }); if (pitReport?.data?.CanDriveUnderTrench) - badges.push({ text: "Can Drive Under Trench", color: "accent"}); + badges.push({ text: "Can Drive Under Trench", color: "accent" }); if (pitReport?.data?.CanDeClimb) - badges.push({ text: "Can Declimb", color:"accent"}); - if (pitReport?.data?.CanScoreFuel) - badges.push({ text: "Can Score Fuel", color: "accent"}); + badges.push({ text: "Can Declimb", color: "accent" }); + if (!pitReport?.data?.CanScoreFuel) + badges.push({ text: "Can't Score Fuel", color: "warning" }); - if ((pitReport?.data?.Climbing = RebuiltEnums.Climbing.FirstLevel)) + if (pitReport?.data?.Climbing === RebuiltEnums.Climbing.FirstLevel) badges.push({ text: "Can Climb First Level", color: "accent" }); - else if ((pitReport?.data?.Climbing = RebuiltEnums.Climbing.SecondLevel)) + else if (pitReport?.data?.Climbing === RebuiltEnums.Climbing.SecondLevel) badges.push({ text: "Can Climb Second Level", color: "accent" }); - else if ((pitReport?.data?.Climbing = RebuiltEnums.Climbing.ThirdLevel)) + else if (pitReport?.data?.Climbing === RebuiltEnums.Climbing.ThirdLevel) badges.push({ text: "Can Climb Third Level", color: "accent" }); - }; -} + return badges; + } + function getAvgPoints(reports: Report[] | undefined){ + if (!reports) return 0; + + let totalPoints=0; + + for (const report of reports.map((r) => r.data)) { + switch (report.EndgameClimbStatus) { + case RebuiltEnums.EndgameClimbStatus.None: + break; + case RebuiltEnums.EndgameClimbStatus.FirstLevel: + totalPoints+=10 + break; + case RebuiltEnums.EndgameClimbStatus.SecondLevel: + totalPoints+=20 + break; + case RebuiltEnums.EndgameClimbStatus.ThirdLevel: + totalPoints+=30 + break; + } + totalPoints+= (report.AutoScoredOnePoint+report.TeleopScoredOnePoint); + totalPoints+= (report.AutoScoredFivePoint+report.TeleopScoredFivePoint)*5; + totalPoints+= (report.AutoScoredTenPoint+report.TeleopScoredTenPoint)*10; + } + return totalPoints/Math.max(reports.length,1); + } + export const game = new Game( + "Rebuilt", + 2026, + League.FRC, + QuantitativeData, + PitData, + pitReportLayout, + quantitativeReportLayout, + statsLayout, + pitStatsLayout, + "Rebuilt", + "https://www.firstinspires.org/hs-fs/hubfs/image-library/web/frc_rebuilt_1240x860.webp?width=630", + "invert", //Ask Colin + getBadges, + getAvgPoints, + ); +} export const games: { [id in GameId]: Game } = Object.freeze({ [GameId.Rebuilt]: Rebuilt.game, diff --git a/public/fields/RebuiltBlue.png b/public/fields/RebuiltBlue.png new file mode 100644 index 0000000000000000000000000000000000000000..68cf5423cf8b0f37e973568909bace41ffda1133 GIT binary patch literal 45890 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 bIvxIhG|jE7kryk000000NkvXXu0mjfYCO7Y literal 0 HcmV?d00001 diff --git a/public/fields/RebuiltRed.png b/public/fields/RebuiltRed.png new file mode 100644 index 0000000000000000000000000000000000000000..d668194961584d6eecab1c72935518b486f12176 GIT binary patch literal 45049 zcmV*!Ks&#QP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>DuZ~GXK~#8N?ETrZ zB*~rMi+z6X9uc{ot@f?EdT%t)XaJ3EFaT!bG7C8z8n4JJn(0XoW0D^92gpp~e?Z;K zs~0`cOr$iFC>k$y=U$OUVld+Hk~09Nv7`5{>Z-2w>`QJD;qE^@ctoC4O9OqTt9u4B z`7QIxlaZN`k(vJW-{QYTzW@F2>+k>m@AKMgud%YSLS5H<{D@>y(Vvbv@%jd9cPqxv zoa6dq%amCLQ7AF{fIV`)L#U>6JP1Xk!kHO$m;nZBI|z;#1;ru=B7&mny#*i;o9iQ^&SYuDWC~#)tw%^KG2#yil9rZ+Af_Sd zp95haEfq09T_NB5-uLty-}nYU`N>ZZ5v+Zm)#D>Y#F>ggIpXXO?{Ma3#qKB0^XAj5 zlz9%pFckqc45~;cL@EoV3)Cikxh|S!1^Tn#{Ul8UB2fd@3W!HFAQHetG(?OyWSV32 z;qU!^6Hrlt@dT#ez(|frhLQmpv=S#a(?>$HV1#A`28n6am5*~0DOe&ddhGiwRPASO zA5BDzq{ANPYMk+?LLG$=4M;|o_b{$tRs|v!6=}{PAn7|3O*A?@`^N+oaxoAi@~v-u zOTYQeZ}P?)Z{VD}|EV7nBFK#1bdO6v+Th&HnypWqgu`HF5B1B3H zmGjgM$FpFt?=x|KHrgMcoqUI+Q34PVQKHW9p~6}vFD%X|HqOXmh1I0G-48JaBM)Ae zq692ubhz#S?_o45@!pW_WpRV-t=ZU@Kg{zqX> z62%yUF=l?A7$d3?BcKYZ2FMUo5XmWPi;spthT@30Bx%Hu6g851$Dt(pxF7^vEHSZ> zH2?PB{##ys^;J5Z&i&6l29hKYKqUYHg9Vi|fN1=Dl|);eSOJgNDZS}Fmw&v;g_||& z&!6K*PcJde8q!#cU|61MB8^ij7x2w|%!6Rs_iv{B!)}`^J#wriNV-NL&i_V}>IaHQ zM72biF&G$DSGx@QhOU{A=?IH&&fY-NS-QO*&ZUb??~<-UfuK1h18ly^?q122Ya8tD z*H}A5$*FyNeTono+vZRa6+kpm=3$@pgWk61I(p0iDi5C9pcHnGPd{VG*l%9lexh`? zs1a2qbB;XEa9M^i%?}j?Lxu)R5U?&MN|&8I<@${gcQ(Lu`JBhkqAxYFd@groG5`h8{utD5DS`PLpM!QGegXn;bfo7Kic5@P0z*) z=lJon%S^IHJ+o;=U7bNuM=GaO*3U_kMbj9@4}RdIx7>eB4XOoT5h8-06GySA+Vo8E z7@c5rmzAZOCoT>+u__qd13zw7zw|Sq3Q-Y-!EivolVhDpHX8vEFbDytYC-c=Hg+xF z`^h?+dj&PCEU%vD@{`X}6b`fots0qQKut3lJZi!NpYgc24}2%{TP1fHK_lP)?)TUq z9gsW6FyG~i&qCkrfzYU4O5%k!wpb*~9KC*zZnuMo;6n{w=`z5IP{squ9g|S7KU?A3 zuZ-9}=;LKbFp9bn(NyVzv_Q2>G!bu0vKYw&7)`|T1_FQxqF{sI$N)oagvtq46zivC zt@@~nMnwcf%spSo{ojS4P*+%{$SbeBqTl$&H+c2cSIM&MLy*Q|LI)MayIrtTGT*`Z z9?o}A6DYGyYF7~@huFjVewwH<#h4NO=>eC1yvg~SfsGf=@{?y)nB*A*!^|WjaCHV9 zAE~WR-m|$)(^@nS|M}lG4`oz=ksPhN1GSoIhk zVI@H0!$Z>-EHM#hX*i(QEwEOJv4)tuU`7Km25jyKW`$cj9lrI0Z8k?M#OxerFFwcT zUj8EeK>=}w#EL{;2hkD{R6Hs*$o-%3xVH~}Z>kVtBy*0kobd;L@J;UCy^C`h%h_GN z@|XAv+~8yhep0@eyeC#T!#U~KXNx@d8w#fC_2chJ6N%z*v!9Dnpb&)D2Q zL2Xv?#-nZ*YL{Ht-jIF-YpKLhilH`2$U^$Tx`Cw=1$j^LQd5b? z8^w#J*P>LSR1zUK1gm%=6_JWSFi=ZmCXvz_W~R&L%<}e~2^&+#)b!}Ap62vpmynL5 zmVhVXjo_`J5<@9MZ5)*;9`YHFd;8$A;CcjCpjnR@mbb3mW^Yy#Y!~ZhoHi@{N-BNKx)f^!17fHio{s7--41+__^-P>mFH1{9= zomf19GED!>?xqKz5>cs$rbLZL1-w9MVCIte#25@|1;|uzDp<7`+Tl>KL}O5~II%c2 zSS(g77L)w?V$kLmv1oHY8b>ttXWZU3@I`w98zSc%b5j3EjgysC-3)goeZ^+ zXaZhpYAva?qNb#xq|}m%S#v#+N@<3Ch}#E`O{$8~tYlnPOzTJ)m0Ar!3~DXn9O_6T zvuGx0ZqdvVtxyBO3Yw+Qu6agu4lh8*q6PRYqy@nSB!jF2dDkSDzIa5E)4+&eq!FkX zXy!=}OR$z;9l;nPX@2Dz>DDv42RjV+HaM}n!P&iamd88T;0a>W9C*Lm2j+}LjX_Ps z*zo8i4JcL>r;r7Q4LK$b5$%Cy>7!~Oic^500L01BzFI>q_} zx^~7|-_Umv<0qIf!-N_W0^$RNfYcuGHMubq#w6`Jc&I9*_D~1ZMRwl zCJ-%a%ga1@@hpQ33N6XCrX!I;BAG-oJh?;)0tNoj4-+UP&~=LSGj3dcgW3KTITc-Y zxwJB&&FzZKx8Egh zZ{pV02v#V>r{7tt`5d}`UK||-C3zfE=)t3tG@@Xmkb6s~>d-5fD9QoGcTvrVYLe$9 z2qq|^o)^SaIE5F4aXXeRV-^bIYBk;P)-Xp zEFr97SjEU1MShBY?>xiq1q!nQzJpeVSSnh2q^yz3BXvOPh}3E0m%2vk8hj0YhWP16 zGIBhHq>+m!$2SX;#{o`;SXsfz3f8RPWDUzIPEL^7Q)Koe>W5UbF23wST*9(OCTm!` zMpm4r(>q76d!F2`LF^H0g)(4+;=JNKIB&4tCc{2NGG90IV205dk&;?!DpOIKnqWOf zXXLX3Iy>tO@800-t=n9_waMA_JM_!R{C9U+-!tx?7x&JQ8slq>*GDO7B36|=2%Wk^ zRu7Qa$4icP0W~EyOfb;{5l>*|DyD8iZ6{c*D9VW4nWAN&TpMsOET}~YQK`{no<{|e zy-(MmJv~#*DIbL=|;`L&!lgjC2sw1=DHvH!Q{qV$35SJ_c%ECtEdm-hSsD#*-1&7;+<=SYAQsQO6}p zKcEgnye?6P^tsD=z_jWS%?f$vJT5;?9hVtRJ5+U_Iu4oo4zn<1Z`@~6u5k0FML9`T z_lTM&&t|k}%+T0@(7^^zH0a5TvsV!xKh5sBA*FMKs8j+NA@_no2|{v9 zHVzJH_C<~hC2gjG!)Q`5+jA)nFOFu*9>EjpeF~YfI>^XONnMW-le}jjh$au3CT??L zZIzYP75d$R>2%E2)&>U$yBGm$Ezw7^d`N8u+}@lrqld|sxcKJZ-vGjhp;IJp32C7Y}<~3lTw8an3QBPWi)cev>GmwC11ep?WWcg{{yQaA8G^#xS)J(zD>6~hj znBioGV}#L&ieqLRQ)ifFLNJk`kDQ%MIep_U{kN`g;?4%=e9e%^YyE;(fAv>*Z*3Kq zW$8CQGPmDvp3`VjhJsj?50d8S3y+c_9tpgQF=C9tIfpTZ5CRnweD454&FRT`vYjD8 zmg$~dLr*Jp*k!4%=+)q&qqf4tDYXrV)MO!&k88S{Gm2?sw$^8QYJkr&$`A-5U<4Zk zqlq-pM37cBY$idoavZ${4ma2CL&Okps9KCB7iQWpY&OL(M3Xg}oF0S6X@%2Sv!NNA z{ek8`ZHUuM$xcy_CPPc<~hPtd6Po`*;6k#D#qEsLbrH7SMC_`d!jbo`yq><390v01B)=Y6x zG3t^fYaEp^h{7-gF4TeIjcbg)^`|UtZEykT5qN#6!|mVxb+*o(qSNURLcn{EGy$gE zH_z`yv!AEtLDCpwh%w&t;-jGut+=`$DuK!z(5Z5s+)EF>=iOGKwo84jnql$@&#CB6s zn=kqBI*n?%8lM(xS2XuP{=gwVVb# zNZ~FiAlMLMq^dmK&H!sIDpQmSRVAoJLm$-w)eIWasPRp7dAe4i#^T9?kOhaRBa%^> z^xY4{&>v0M|KU%#{y+a0%ANPWs``+(M zF$81Nf_gt`T5+31ZX!%O+Aq2#(NXf4VCecD7pE8b#MMu5@%9sBqam)}WwJCSQqezv ze%T@S9U_jIO<@gLRIF53Q}OA`sBPy|Ea z9VS{7OKmJ(1Pz|PbM(E3=@@GiTusT8iL;Dl09hB2v~U0zBdtR6zEHq7=DuaC!O=CgcTg- z{T!e_>qGyP@2f>@)WP&wBT>?|r5HnbU(%?6NP3;B5Stm9H0)&aF@OJ*3Xl{!+iZNN zN7K_{6Zzl?CgQ~qH6sfKrzx_%`D*9q6ai5MswQ5oVrnXER*;uz5#q@Fk`MNE5hSGV z;C|8!heL{@V0n3&wY4=?R#qsAVy>38sRQ17VvO@#9PcB9oU>c!8T{XKF8}Z`1_xaX zhVAYqvusMIE^xjN+DSXdHj+nW5Wr~)r3<74q?CfyT&yNFsM+1R2gyJ?I<1aAm~MXB z7ouomm4xOzi4bXJPZK2&iAI~CKQL;WP1!`4c#q7JLy6O?gsC79Dcgd+oH9d1VN&rHl`E^6~g?JJ&4eR z(pWkCj2imAv_!wWKA^NhCKy z=CyI2STuSjCSZ#kQ|67mn!II3676CWI`oh!2F>*cJW;~Eq{;J~GiT0_Wf_CPfR&XM zhQlGwIlT9H@3GdBWf?I>y!SZg(tU+m9Orf};QqV7^FO%EQoV#h*d5$tHkhEbA!>n_ zf>{3H1R9*xxN-!z&HIrisXeV0k~Eh@G)TW^j1g3@1dPOVOGq1^7(_FayonMs1X0v@g7K8rP>aPz zL*Y~MK$Nrq2Z?B1v1BbvrQ%9YW(-aP2fn1rGU{SLbQwe?Xh0;H9vYMC{Jv_uDAD+b zOPaDQF~(371xrgyEH5up6a@fvU6W-QXU?2qFc{2ND`S#>CuA8Lo__Zs_adz3Cv8pF(HOmfexIG_j>y*hb9N@JNgsIB6nl846i*#<)%RS8s@e3JAd z`zAT7CCkyjaQ2H(-T4k^3a(88g zX}=2yVQ$kT%^+$k?Ren(0wpvCO|n-sk=Uqb z3XRU4;^fj|>oIdl!#%25dVXWVx7R)YFYP@TI>}C~T~fQGc{T5F++WR5YZ=VGMsj6EFEkS6M3=zZf&08P@?8G}fU zdc|ms-Pxq`?%NpSaL<31{j5tZj+8ZHni>gpv&^M)N%|m3LAD{OPR6t5l@r%Dv2S0e zQ;+H3fr>l%kb}>Cft}?gFv*(-Y3?2p{N8W#=)L4YeF$mpf6-B#Jb98YeBlc$FE3M; z<(xF$dtBxa3#V_NX7Jw)xb(_nWT9Yhu+R0AukpsIAMw`tx7g~>P#q?3h&cf9bE7)> z(hl<)k~Vzw=RBO654+0ypZ%~~-sf0*#>4-0{-_>4kdSo5_D(~O!)u&B?Sc2WAMJUh zQQewn9(lpx%GVmkEe@~m{?BvV+edMZ_q)CS+z?T%Q{(}C>MGJJEGrwgIQ5tVG!c(?H7BaT<8R$!@WyqzlYM$xVs*yN z;TqR}@fW$hx|%$)jYCFFLvWIyXfby>H6J9^qIe&d^ZVh>gnobCP|@11&$SI>TrS08_s&He=9a#|tHLGp|=8J@{ket6Lf z{AEN$wL*+V=u=fnzi^yfe~(Ll{6`dnAvZtwRj&01%(CQSaWMt%BB}6;Y7=E{ELjlp z09k-&YU-@!L{;lkY>`_W`9na zgW5z&gl0r(tM(|Ov9V8%?2La19E&uqE4tI^%r`lAcX#>GkAB3De)Jo59ACV?P#Aw9D6sDDFs?7Qb1kLeC6GEV_Q>X`|(DtgTxOM9mw{PEW zf)gJBl^0mxr=TT@GT$?uKOjw{dTaM&0g8}9wOshKmy=+s)8NPFqsG2ri@$XArGVs+yFVyH7Z zZs++X=Mg0HMq15tfdxJkTFKIG@TSP~^jNEz*(l`VC?q3ON3NERWaNNW@u723nh>oI zswEwZG%Yze@?2x=RQn*fdv-$pwN`n*#sXY?-)(+t|4*fz8_&4M%weAH;Q{dTIguIDC29h*_9VH6`{W7xIA9rfIi$hhP1=EKj?W06Lh z$k=vpgg7C@4`o~}u)y)qNSQ`GlP2&sieVB0`*q2I0bN~2&YogC?6VQ8l-m9waV*k= z5SYzobLn!_u8Hw5ig~yx@5xvvDe3*T;++EKg%!t_TS?1FTG4P z=utlcZRl8}sj7<6Xhd0-b9Zy=O*1B?oi4DzM~M_ueq^S#tZA4tn@lK2Q~bE3_DcTL zCC+{I%bdIX6xB>0>ODIyjzyY>1!=ayo6>~OEU>^w3P}ajqZm}0aDCAxUER|Y7z8-w z9btdQ{)r{FmO7LN%CIcRnxY;b2FD^zp64ttFVpRI=V3Gp<9~sV6|HvG1Z%c7LECp# z1&S=AUv#lWhdn23N+e(?T&f-ZVR09%Q4gbH{I zGdxowrGtrj4$3jRlYRE$jHxtr9zG@W{FOyZ6szwuahzCcFDRKnx|4t9~Q?VjWmT8oOAO!&MXMCz@G!=glR@==3rC# zsvr?3%1|t;V#%$fXwHwDG)E;&d%q=&e6TWO3oP(w#@q*RWE^z{L`YR~n%dWeDl43oP(aA*RaDDQL4fmWYt#juk_O;DRz# zLuX?jcVmNme?-T79_Dav?ePz)Egfr?w^F7(`6AwIfsY&7^d*YsND^ivZG=?nHVcaL zk#6a^^Q|B7#=rYRc3yvnY*yj)FeU%bier&xfd&4&q4QjtBw|wGufu4RSl47~7-R)* zTC)3-H@WtozQxX4S13YT`u4+NL7D}APSH&MHiD)>Coiz7#Hyr-a7rB~Yhe&0J!_h7 z0v`?w(k$@v32Ad^QW5-lMyCeKx}>aUSd=BP^ij}2@O5JqeK;&gv%t?Cgd^22=K`mx z^KAr^J8Y35PQX{FpAt2oKdp(;F|Z)b0zY5SR9Um#)NF5QNE56OjWQ!JtxKY&7VaWY zi+=>WGz-!!@aK*BW=lfrQufUbzi&oc7oe>Xkiub7&7*WYq5@@A;#D!318bT#Ef5WO z4N25!YCxRY|E6wj5(N{}ZPVK$Nw4PlNA0EsX%_hNK~s5ENRk6hIscIqLsmH`U8J;; z$|{wG%EByDrnyquM4q}i9wbQ|CkBgPP%SmF8A*D-SkY<689YA) zgHvE08E{xz+#H$k-YB(IDw~>h88j7FSH%E9nwrqI?KP_N6zc_P7Wmkq4KZ5sB!gaX zQE(9o51kr%mC!E@{Zbf|uvEfwDJ++UrBYa`gkA+5pI%e=R8iB0)Ge*e@@zW@CGSBA zW(H;orbKN?v?boo2zG|ICABN@uEZZX9!yD;fTqT93oP(4L)vP~M<#UB*Ul;AK`4UI ztqt8;nqlZxhF&FU)P}+fd0T$AA)K4H-$>^UN7JK))b3GAEbXW;Qh`)NQxR=RG$m@% zF~Ng)kd#&s7o=I>&joF{B@t8-d0GXisV_4hNx5pCC#m9$sDK!}2wsFBf;W;vYZ?g_ z&C&R@WRJMe)^}D4ujC<;g@6mGvv&(2Nh%kGEC^XJsT&)hO+&*47Wi0kxG9+=Kx_z+ zNRS)>UWD2Rr4eRMm}W4^g;|!yG!v#dOfr~eP}xXrlC6?zJ;#PXiF8?G_dte_VPb|0 zjx1yt%>YXfix)>A1I-aF5G^pVK-4wYy|8H(_;Y~MWP3=nIj|m_;G0&*?VF_XwE`(6Kn$%rcDVrnuOM-JjWQ(CbwpR1%4WgG4qz{VhnW*5ChJF^)(&u>3PpmU9;qC zmVM1qT`{aH26e@-su|RtuCM5Zimp$`m#T`D80q_(ECj}r18klX(oPG(2>Yg@>It>0 zsdb96C1R#{8R6}O;HIdVp)x~c)_B`Wgc(DvS*ednnxpwNhZ&n~Cg-9Z?E*g=Dk(lp zjU=~ovqxeyko%f$S+QJtPETvjOlr=LYc7mRE{sbqj7lz!((UaGnMuW&Y0241 z$(cz-e{-LuSEEb<<}N1(f`UL;tco@QQN}@p|F9ox3BU1 zfBr*y-g5o?^XwG8q;9E_1=2h*H8v^|p|pmXOKOu*Qps$iqVXquC?5vIAapC7ZES_pxv zX%U!Z*}Sv!ytecGFIZrKhe1G1vRROb;K?+SSDx-<%H-{u;lPq`{l-pl`f`zHt?E{T74Kgp-+}=Y$YF9wj=#sOMrPtnF;EcKar0 zZ{6nX_4hb);@VXP@4ZKH5 zQ&klQ2M6<-XGhhvMU8+3J~&eQzMv^IB}I36vf#<(OQx_kQ!rc(w8sh(ya5Rxm}D#ait>J0pp+)pUV z5q&>qSdHn;(&*2|3}#~nvjcjwJ-XFC-Pr-l;}Oey2gf5#jFGyo@!sRTKT=e~U>-pW zg9ZL-K%%AqO>Nqm17*E}FVn(X>fu<6Mt+xk~r+$gy^WG#)|v>H;4x zl=Rb?n1~~Q+Ga-|sHvJUqoSmwWJ+WPOqww@N@*gM1iW|xHA=vVVl|-NqZ+WLiFH#5 z3Z)oIad?vvG$UXzI1Db?GEJ_IZH7TWEmnnYhz#pvk|xVC27>|4xw+S@bsU7?)5;-{ zXxdDnipUJH4(O2ij65io36@#Yeb*tHq2lmjsBMk6B}xq_-b7|D;>80sP=QQAMu<#6 zBl;)`^e*tRKmjifk3nLlcx)3nmX^=MrP4R|C8k||ZI0D|8Uw~s zhe)kTZ7fj?At(lmI)mgH;w%`!wq-ev>>VAQO^16qiU~C~RL3KYs?OE2)}A@)dNxK8 z16~x#ENZ5Nc!0^eXy}mFU5cPMMmWY~QOIHru^?(e5KAzDV5SHj6TusybV`r_S|ikm zlo%Odn4&7Ex<$bD&j}PuMNVxxSd=VQm=KB4QJFs87Km7kCUvZ6Cb31ZZDh2hRDw=O z!JS48(HJUYm_@L82d{$1P>MnF4k18pb0kKbgBU7esBvj&2I@vdJj7h5nKvj%p*hE% zX-8WJ9zEu4hK&yEbHrz84FrRz1m23Z$t`C7QmV6r>jk;!z@bfTdZ+ z>Lg=znz1~~Sf1r9%`%2%PQUKZ3q3m8r-*&B&_hC(iXIi6wAZu1&jp%TWsIZ+SV=aA zHma61UG+r3%t<3y5nMx1>D;1DQP%U89sVMd9Ag!!RaWkmlzEY1XH`d~J@T4rW+` z%`&?EKF(!xf|yn{Yb18nJZ8MN2R@J)Lv9^~&FC6S*xn-_m*g=? zvE+9P)Ee=c%H4XCh~wv^X*4Q@n7QMT#uzjAoV8NNd!N)Z0+lH#-IUr*8t+$0R*&h7 z@6z48LhrrTSb6t#PQCRO7v6l6$6tSqCtv#sPyOV_TzcbmPQLvXYwx_n+B;WSy>f-+ zt8cM<^=*b%-==@<9lF=wp?LQl@~iLAyLyemjhpm#x9L<<+#*=?=LecL5g-NmhL|i1 zV;wHfSv_%rUav>)pm0Lh!r~7Xmw1u2F4nBeN0>fj}f67;C{EN}5`dH}0s#;}v3-9gj3E(tb<|;B5D9R@YY0 zEK)M2He-+q2w3Oom_0h-F3VwqwR)e^)d6Q`V=m05T$+x#G~MUiY?l+&0c%yoYOSo) z6)W|a)%t+7dd!-iuDTn2Z?Jv@-{w)?!^o zZ!pB=8I5g^leCj^C|x9_l1Y*>*?iLz3NcjUF{9l*LRqFX0Syo$whn}?9o%?I79%2x zCsL#MCPE?TVduzF`1M@DIP*jc*9*{>Fy{&I#t8`!iJ?o5}*UcSWn zFF!@Ryu|fR#rRB*lP{g;@)s_1>iJ7d2A2InWbniZp84!^JoA|sC z(|X9-an#svH!_Ve^N46;45v?@=H%K*a>;ON!3$o6sD@fBvw>snrOQ0|n_uAh|LW`X zzWPbVFP%fbc8UJqeu0z!%P+C?cRtJXbLWVkKTq%Lk8}DrKgFqE|1|p28Qe=}IQ5lh zdFF+i3TscDrRWP$YbH{0fG0liNf!8v3P~yc(Ao^`24qyCq|MDbM8qV!gL#il zJ?ythx}WTwblf(cvsNORh75vY1e``P?sA;vDMIYD#8tze1)JJSh!WyRp^Io$FY zwkyM(NyVM%lzK2=>B2cm2^^GT#(qZVTKX4GQ1xNg%(&xs*{pWyUN}MQJJiA~*6iYm z!w!BSWPX02?XE|gp_%4sDr}h~(NUzWiq;{}l0>mhuxAQ)QQX{{c64tiArdh}G6Kmr z7YU*SNfJid(3yrfbBQxAHKl+d@(}OY{Um9js&gsSdeLHxbBP2(R7VIo#tv{U6)Qjn z%xbuP^A@ka{WkC2-r!&Y)5>vo>wvef-Q?wb^y~~}gEhbe+t3q&;n&EB!p_=q8@bRKu*^eG*i(s_YsW#6@s!`f^XU^|eCJ0$x|8i>$`y;UEAirymp;Ge&ZS&b;kNsc>Tr}Z@jz7x4!in(=BD$udo(R((^0K z_LNi-eu1AG9L~hXh0~O1YR8eM9Fn6A_&r2=tR+@ET07@q&rPG1Yz;T(glVhR$dQiG ziEc74_xS*6%CclOn^Be}RaMPX0qVMDXJ?1^-g}R-EYnVIBXLBM%g#AST&jt_6>0&S z1=datIdN)+=A6?)tHjc`ZLyu>YEJq4jG@G7BC;EIG`KjAK?|n{t zw)Wgf1*1{dq3^04IMg`BT^^;y35%b(G@*{$- zA!e(dwWC!5?|EZQ2Oc2J`Sa&_;)y5t#3w$%%P+soSHAKUzW(*E^YyQPov(iNtGw{S z3v|0(LI{VdS&T__tEU7>=>QRrpoB0XR#S$(jB+;O*{3h_*!k1cvwf!fTSz_S!s!)0 z_3Y!U^$a@MqTIj3*_E7^o_~zKQ!??C(=Fv$Mn2))wpQ zTim#D2Va95SOQ=%^g2VXzqii%);@Q(H`%MkOtoZvZ=3h-+~m&2I{T9`a}!l%d3l-D)m8faK1ET`>2z3L zUS?%=nKR}b?oTpSZmg0A2c4Q*?k8N8A97c&aC@@P#-1=8myCDsGMQG4E6c5oiuJ9U zvYw)pRKaj(W5)WXXMf*gm=b3bMz=jXuW!-Yu9%sOI~UKfyS$3ZQB8D#zsN9+)lnOZ zC(;wkV1JM08`l};j%s*H`JV|=&7cPz0u^!6?Aa|B8D z%b|(gOwi_!qsQ-g;C<0N_P|pfIB?JVJ^1KPdHi8XXwEsZEF;S@%#ma>V+_m7%bYuR zjx5XO&HzYzIOFkz-Q7KAvl_u*8*le?HX+Y*KJmhHeCbOs^T|&>%gTv9YbX1B`laXi z!WTcy3oksusZ)JiR`bkLm-yN*eu-cD>KA$OlTWg`TySb_nJ;|qv;6YczQz|m`x56) zpF~s$vw-@5*5N?~c7X*xB0NBvHm0mK@||<@4a}k_=ytn^$UN=2J>Kbb$y|X(18NbQ z)`{ut0ABIa~5<=VA(xpng<)5$blmw>efmpPod-?y^B0v{b7AdPcwzH!-(pN2NC zCInB2o~RycQ@UzIDe^u>3f{ePhwJZd^ZHM2vAYeiUSac&@ZQyuD{qZ?`>iRPJ3a2~ z!1awOZ{I4pdJD#3nHxJ}uB>nH)~(yTeS3?|X`n75Ri|V`$wBn2V_Obg-ZiI_2~#IzpXE!ReVWgH>Kvz*EvJ@xJa_prUwQd+{OZ@g#JSZWwsuszHACCQg}CsZ zE%347*rYk?Rcq@7BqC|5F?D3iNP(yxRZoahHaFj+H!!T7EWnP~8@cRVC=G*Zz=&cDgr<0cwHeci=lZp~$$oj~4tqP1YFcpjcFB!vBd)(Y;^xho-GhR= zJApgfGv2*9;>O*Y{i$PpZ^G@J9p1fjmn&CqF{+fxdSW*)iZi5BECkI09~+KE8jjdC z_m7m6R-=+CMzy^T^Snai^vn_gTLcdGG3sx8FG6-D`nyd4fAT zaOKuMfBK_a{NVL{wniCyHC*4?;seKtgbE5>1D(?V=~!6Y|W|DL)K382s+|m|27&9IJwg0 z{OL881{pCN5dDOuZimyWCs-W}$R$!uMpUziRzfb0Ocn`f3w(Sy7HMdR)1p<=TGzA7 z3nCV;>AoV2r+e5!cK8u6$$_Ec6*Gh7q6bxZU#Q&ze|2#r zv+Vxk4~;TAK8{72cE5&(Fh}o4po%b4!xV$hp_H0&7!flIQh-{Xd+JFpo?FK01Q%;M z#&F@}37)@vkyC}CD9708h_!ys<%_3S8V0aCRDPdczr%%#Yn(dSVc>+0t+6gr#}os% zz+WC5omV@Vh>`jIhmCt&*GK;T znP{^%+xWDj!-ye9#frrkOB9GwBYC9u@b0y{Y;8=~+SsR>OerTb?%v+!=FJ^8HYbcn zfpQjDzq`$yJ6qhoyUYHh!kfU>!3NiE-Q@bM4K}y;5JM`29X(a;k*xEOFLZ&QXWT;? z0W*(=ZfjQ1+)KR#=8u^~pmvg*hDfxfliTxIlY~;c0eV0G9yX4g^8=%*D)#sHsq4eo zvbMIf6T=`1YtgXkAupkbFfchXgS$6&xPEnm@BHZ-+}SFbG2qRcBfj^}Ca=7+#rJNE zxTyo)9K#RpO!(tB@AB<8*4daA>`XgczvcPyPd53{4{o!5+Y@~U4XecI5;iPDT%-pq zAZXMFn&UO0iVd~W2L93ZmvY22{6TOm(j0XJ9CZf77$HWi59D4j~sy?g~12 zmxIxS+v^+bO{YkwgLXSi1#a)}acyUt16NQFmYL=~Hm8wyZ||{l5ODblz1}J6GUN80 z1MX~07<-GiLsa^x=0^(eEbx~Z+7u&CZ8;wN7h_DsnLj_Fm0ThaBY2-W5Hu-0G*kEk z;aH@}^PJ&uIM20d9RMPN*T~Em_KmVaeBmsgeqqSPlb&U}%d_WteD0YueEp>-dGWCoR^5zKedSY6pW)?CU*_p&&(J#| zjEX6v-Y(PbE>p9M%A&Bw0yLGE4@HeOi+XE=x2l`A66lE9^+EUgjzyX_C4k1hIa+Bm z0x>gGnWb`umVbn!GVefn80o?E8pN=^?9m(TQh>DjZq^weoq^O6&t zn)6GBPd;{vm!G}FW6K>*7Rsp(JbB>+FMaAV7cZ|8`;mQfK*$fUSp~9aweeR4NyBr_Vy0zcQ@F$br&;vd-P&d}n&5-dsg-Ut*oW={FYs3lhw`Ppe{|0$?d8lLJ0fw8gJY5AXCV`Z!$8$o zYB(fLn zon=s5O_YWcAb4JC?(Q0#;2zxF65QS0-GWPScL?t8kl-+TzpdIITLs0;Wom9$ zci)zCp7-sCEFw=>J~-1%-I047b2s65i5~)5i?7( zRh(S8`6w?U3mbSIV80D*pth^B9tL`UyTpC?u-)Pto@vvD8E3Zk*j(&gX=zt;zJZO$V`i|ZN zSC{BMkKBx<3`)-g8nC`5gVkm%zwD||Pj~w*AofE<5i|ds?yAM$5n4~LF-yhBO1dFL(dkh*}c>Q=S`<*RT%K!KVLDCH|s$Jv0u3Ev`GTXMmbM&JhZQ zo*$0jpGLtC$fDd}*km(h#0`w+kL_kfYD-wI(f`!s)4sjJoIQ|Tk~zZd5^HPBX1g9M z!Mc<}^EPtauAqHI8QH@KWmX%(;1R1r9-UzG?U3JMj*pOJy8cQyYT$|Xb9e7JXOf?& zboAp(9${W=6vtimavqwh9%_-L=_!FIsVqkR#ZPI45}PPZ@V{0($JuWtG1fmK;>uNF z`YI$A(S!QD#k6%#b;7zOX)`l!0PL_&Ah9)j;i4xO!`rQuWPS>yEFeD{D~xRAEXY+3 zB|`Ry)yU<4(k~e-C7UrOYz8x_YAq}rp02!%J!Gb?EqURz;L^KaHmSVlK#r#toQ+}E zODJ{#Wu_TKH`)Yc-5D+pZH;V=SfItq9yd!Kb;yE$Y4|js;9+_=b+wgwlVC9KF>+XB z6_XFnzzHF1)|_5V37mACfAxic>3fOstw>!DKTA#Fg?j5G!z4Sh*<4=t~St z^Sz5#iW8zCXhD%jv~<-C5^;Ga){#y?NzJ$zdwqBak3rLrOmgbxR#s5(>dhJUCvuVh^j^zKZPu2`r-wK~AaJ!$p81Go)O{ zl)MXbu$?=O*Cd=9xcw0F-wpjauk)qF`gaQtvVhyL@WlwsU)6rjQ z!5Xxk`CpN;bw;270ke}e?}F+!Re$7QWFd|e6Mpe%8qZG_ry%DE4)osmhQb$>6KG!| z0l=K#huoJDiR*L|YojIB0vZ(YG=u@7py0WCF#O7m^n6OOE3?M2P(=z;)(0dXQfY$n zgRcpcDtTOUL@LM;JQySPXjo#sc?nJ~(LpMM)?<fiS_jRl zFN<8ffBsc;;>Vv&w%IRgb3_`$mp_{h_<`mv6-E>POsx~e6-xCgd;Ku zCCILUO9G^7bdyFMUEk#@{9}%64%$hf!P=Ur+gjEjfNs?$gZ9X2rwaG^uUQ_b^x4e0|OHG%KFxZ0~Kjcmg-biQ`Htxq^dZPtL;> zpQZFB9@aQOe%HTYkX{!hWO-X?!&xOi6=oo#lB*_(EUnb8q#2 z;avK-SL+|N4B2G4`Dy6$!~bCv$p>SJxjOt&hMs)V>x*3ef`Q( zWYlh*MbbVA!ehZ3!zWnhh6hEDc9>z?!l*!>qC_Tg&r#Y-7e>b$;+NTyD{r8c+R+>@ zDzBjUJyF`^J`=r7`{wN63QZ3hzDqo(vS0{=bkW^>i%{nb)sl}|n8%-$y8hvW^M^N$ zjn6gc@zw0_96ndY$&GaXi}-)EGh8SlOyPoNxFmXXYne;uTzURA4lW}#j`$>kLU#VX zL5t-cIK~Rd@f2{|(9GIpk{ibr*GWFLBHF=e&?lWU5llwQkQ71OI(^>Eq5~YP9UGvIc6XreBUbX$^vu1o}4l^g9v%j=U8y1Ca-fVRBpD=8^V7Hk9BsIM?oh$XFJ*50c{xA6T$>5PSt$omtEksyTa zpBxK@;VZ}O?ophQZ_K%~VZq~V>h{63s^4>>damEcyW2dVF@u%ORK+I&^uw?#(HFNUAA7IxAC-nHyGY8mL3-vc9S{%P|`%ZN` zA>8or1%LUT{?xp zk&XVZ`YB&oZr362klt-IATWbEaPpA{w)uy)de-!aiDYq3`rYGtu#6*AD7}E|ADy!C z5qUCXxB*NWFs;*=*Y7xKNAJYu3;ZGNrb>?z80$Km(jY}^Cfre$MqbM z6D_DQQW=0q>Uw3`FQxCBu>LZhiNjQVM@OXdj~&(AJNN1N7kf-A`H_`N763#ggW6_7 zgYGKZKZrWe(`t9k#N2u(9z6Z@*Zuu#*86Iz=jqBkGA{~Y61^A4_{fQ|wVcauzbpr3 zlI`*lMl4BMp8g^*p($4R%p551>EI@F)>})|{g=e`nw@(M`{DS-jX_;;Vg=CL`|$|IS}Df(G$2~`yEB^D1vI_rj@O@6bQ34Zqy>57 z@ZovKltqKs@zQCBcA;KHxmQb3p=tD_`dSdp*SO?^z4*ib`pElTF3mWbI=0CKKiMQ6 zP-5>R#y5Yq?N@h_{sExG#LG%uV>T85`bl~*FHTf2gMi(VGjk^SeD+KG{qlj8hNb># zyl3*l@246=42!@ER&K{5473S+GfhSiG%EMm>%(^;F_pI|_5;DL5`31ohdhPNboD_P;f#a@a z-)xW0jyd@$462;z@_oG*y!j^U%9@`ckI-CNV`Dx~=c;WCv+(r~+Q^x-3M_APj!XoT~ss#IVzSj0$07kir zs?G;I^)DgFXJ68}TeXV_lMWb7(vHxQqc7}6kcc~6(%I!0Tr~3NPq6XF8GLWoF`9>X zMylfQWj1J)jWGv<@D}jGW>TZ_nebXs^of|^`%3GGG*jv=6!YK6$ao95)lFhQqxeMh zcEb(fwQn#3V!!_~pcXlvKaO(!bB=PnV~}u}g()0w`Ezj>q)(>H8`V?H@X*OY{~(rL zN|~`$*OCl01ao2R^EA#m6bW1bt%|C!c@h>eCP*PIO5`JnKr*bT`Jd&CzNf}4 zzeBnT#{Nh&Y09$dd@bTRp*F;9{g2@{BOP%m%dDX7~5|E7``p?#u1&}cZ(rR4X+$)=#=73mz)bzJ0 zn~}G7=D&Zg|6Tpx6iqO&g?2c)xBI@)EuoQu2Xh3oSTxkEGE1_-D!q0k2?GkkV`xG}fM#T*Z8sgkO8 zK8)-lRvBH83oo)#&bNbg{O3}|IU!HBjTV2BwjP&DWE)(T86npQt^I-ZooKRE@jRUn z)ev6iV~2n&t$d>UKaH|>1f|RUdQ(GqqVT5Ll093wm6}VdZ8uwrnujg2!pM57?IWp> zYg~_=(@3nAex}C0XmQ$Ql&FFQKVLLWIRF#8suVHovQx z&RY)`PMjxFp3ea!2o-d2b zjw1NcL+Tn5DcUefmo_OTrq#E9@9)cNw#m)3$bY^}w6^d#<&$HD{})#>R1AY=RFA!+^qbP9uL)`Rr{P=A3r8#PhT9hrDWJ zE5GFH@dy99*h<-mW4Z;CFeIxxN3qKiEFtSa8oWA7|+}{9^w7(rmT%J$Fqzd}W50q)mgcqjNq$H>~($F}m41GDf|p9Gyo(7o?w zmbd=&exP+lzN2T|IG~C+pfXND&no2N;<1l{P7NF%X%~PUXZZUJT>#%quMt8|pDbF= zs+Lx6M3Aouh`Z+T=$^&A`pIgJ1JaVd1KNj_IW1~!MV{ltY#xb`MpKrp+ML-kBUk;x z3h>e4;Dskb>JBa8gprGEXUxe43PdBMT5e z&6QdFxO}!->V6U6rJ1gO81I>ysw0i<;CDX6I=Us>i-wN)o~yke*-l%NSh&Qhr+aWd zh_tO|ZOUkF@H@OHU!+j33|l@*&;Ny9=zad`cHv1b3g8(!y;E0Qf@TAvbBJf6<2!3W zi3t6PrH`*$kQd}!@5?@X`pF!52BRiNTwR+Z;yO2$68#fhEgK+W-oO~HpzC768M z)HKlMd<6EsXSA`gsj8`IXle>+#QAtD8H$OXwtfybu+APV6R?+8Ze*+tV!oC)v{Jn(|Vi+Bmt1mB7r4AAY z*SAR4t`#y0Fe`0zGLWc%{(3=+hG}KiH#7w`@(^Gl1c?VDz=p%B1eXn#4SsXf691e& z8Ba}a#7e|Q#B4M;QJl-AVJ+Kx^vOg+(o81j2vs&?sZ~f&)o&x5kmfP^3PNr8=N;qA zh12|ZHqT4u;H{~)rxV@vv6j&dtB_t(FSz2NKUpVDMKw^_itd+Tanu&WJltOiNdihZ z2k@@(WGO7MvcB3P<5iDdW7QkK4FmdLl6Q;U%doDKq-as_Q#x*4B~95VPQsyyu#Vg3 zU1(5D9IJ;u{a${rL>5)ic_>7<% z402r=)-F3$;NrTPAz{301Gb=%xXibQnfA{APBkP8RaR*^f3(+Jm6Q0Q0=|_QY{El9 z1oFyy>Qzho)T~XSt&W+qnO8vQ`K@7JvF2~Ce}eG^W#2y1oR2&_+3rG|#4$culF3vG zg=Y8N7rdH!NM%o`=QC)kO6Qr3`~};Q8O#>&dU~!GlO#?bCl{Llj*0Tjot&3TXql>~as`gULb3`@}tD}8>37#Ilr6A zo(cQcA^V2Lu5o|J7R2AP0`ZY?mY6+?pwDP&{}(Y@Xmo%?sqv zXP&j3m0UIMr%}%{q5Zzlh&65_ejda@f_cgt7fBVXTqc_4EZ&ut|IH7kM%Vz*&^eVFlVwD~*})Jm0%*oUjC&}=|Uobd|}@g;#aBmnR$6b}IiCm>Jnzi0pY`TczhVp#X<*Kp7KG9-h0(<8XOX0(CPb4dN< zpO>lnq|~3P zZ$`dsPW=oLmk>wKB!Z<1O!q1pIc8$n;gxU-uB1k-f;VPlo?cmB^=c}@NB;S@U0?tn z9HxR7JCqlJEjN4OuseWYyqEHR=KofEch>U`EVMVhUxoax9sn22W!9m(rY0DolrE-m zr%O_g$GZI~)z+v9Vq&=>$FTY&*GW?@in0cmT}oVH@2G;FfwGD+lT};Y?ZTf4ICLz^ ziPS|GSaCcN2C2@m`lhW7^4QRg?FCEHUno>y9Ahj$+Bm$SwCHY2l!r^l2P&B57tqqv z$w*j16n8G`?;q){Sm=Yy(U?36n{p`?;(LWCN;E?V09R4~&=LJ&{CmtX#wKvx7>3a|?>^iFQZ zq#3y*%D_71pGF{KO_!f*%ly0q_ftkjVYe3?r4l7x)fC$F86>RnQ*t;Orn>ws>clod z@ASbh>XV=-UT91+*3eYsvx4Y1cb8ruak4t?@mi0h*X|HLoa+%mlk1i8zi{QQXO71! zBkpvrIqP|$u`r`CJCOX9mN&?!knAD%0+SlE_i2f67zYh=sXR87|Ea(WiE)&1c0^&5 znqUI=JAi$f?gqoQZE{8HE1MKNUEVg=*7j2UpHiP{A$>yMoShT>pOqkV_5i>OXpx+_ zD27-c;+nlBJac+#q4PqGRb0h61Ek8(WEs&bapZpV6)%n~^di&b)YPYyjIAsA|NKOZ z4MTe>E%*<)K~Ruj;h@!zr0i+Oy=O9-!5(z}Z)TO1I2k@@F~JFf61-m5(j-}xbYdxq z9Knfi%*IN0cz)eA_uERN9%RhHS~IloE49U=r@cq7M4Xv1W9$g7g8M4PmP@Wjtm@Vpe@jJTLjf;e~yWZ#V=5S5V?z+M?qea;7y z+`t^4zebn`j&cPfDMHQ9GPmJ*Nu)aQQfcP!kI()YP~x8DnJ`n>^RbjM{SZypi&sa@ z6Qi7;kqj(WZsrloIXRO>dkGd08!L=G&7NixhP341$wF7cIQp3A$e+IoK`LLTu3o=z zasPUJ+k*HScwcZfFxqsQqSnqlmE}<)@S{pb2LfZ^H66cYU@Yot5t5gcfWC}?8$B3X zUirJ8cPs#w(MT!Jn@EDRj;=Liey|;5S?a4KCzjyq*s?ihY;~k7qKi-!(*;w_7wLQS z^2v>`7c`515@ISx9a4UwW&&ZyaH%*F)RSikI3EH9 zx&+==gkCIOM{{2h#u>%|BDM(u<=@U8f|<%UuFQJi|G!dk9v|~)5>jUQB`*Rmw+WLoFJXPbGYX5$$=WQr(PBzaXReH1GDBxtVIZ>kgo$g3`wrRXH7{o=lZLC#G#`gB^P}lgU#;t(+2u~`@0cxJJT(yA* zt$eA_3SB?2T?Yu2j^+BHcr*Nu#Z~ldHnR-_3pvH#;V>y0Z_+4qGolBqm@*awn5g`V za-a~)-|{#YRLaK@v(?ka_KfF6CIcdNVV*1go{bc%m1^tj`!*F>A;cZ=5U74LI3$`s zN%H?^|BhYT^UfRm9`OE6SVO~^k6J>RW_m^HV>t^#Up zqJjv!b0U>N{Bc{ho%@S5Tl*_gyezz?`#)&thGLwBXvbzKgcSK1Wc8zwbNj`?rV|qk zn;Z&Ag>h;5Xq5y)8R$~%X~iRcEb+2XDoI4ehOl06R5TsX5$9=CS$;Ui^2y&;lh^Rs z4JQr#A6%WmKA%Yc?TCH#3*;yFys4vFxvfaV)Kfa5O2@ z5`(!8MWHr<2K|7FZn0auB{cJb%fqng8J{VD06kjDh*dTYrB(Ez-H>q*io&Lv{pUnN z6&Wp1Sh3*h3MnV1i@^{RnmYv}y)qUh3)ykW?kQjPW$lGB>|%L>-i&u1XZaK&ME z%2=Cl!vcRUXjtKk7s~R)G|7+q9IN&`WJl_n@K-w z9L?k~*;!|f*)}5($X+5M4u0a9t%cqd;@*}VEje?;Xdv10<^ki;@&yjXGy|j!MGOH+7){qJ6aK7 z!zHXiu3~eGH$tQKyL!##LDGdV^f*3|`Y`}CM>>MLP$u6E>~m4SG?h56Qx2&9p-J%7 z0UHm2ObV0;E(X`YSo~<4aSk1wOwZbYw73iZVss>14EHN4vZC>KBJ&QCyd_t$pj$Toe8M`m|FgQcd>c1_*TrD3^h?zn~oH{7B!kevJoBUjcu%- z7&%z38kTRvBw%x_EjNw#JT&vm%B4ELux{z(_RpwV*73tAOci!kpgeRJnij&$21)-R z&W5$*v^WtuMXc7`z|gka5|tx^C^EX?auste8$oRTjaDcce6`BvKG?_9veV*TI>U{@ z6=(mcHD0Hn^fy5`*lxLV$%=^6IExH6c2Sy}evi++^=U76o0`1}@t~@pCc#LMP@%kp zT~s6AEL0skiApsk?J3P@ETOZ)h;49+UJpKt-!*6|Z=7QStx_70sDDbiFFlUt!-9h| z?G`VRl<^LaP2w2Bt7s>;R+~>(4>bo9-Rk<^{nFCarSOX=C#e;RX8HmdgUg?oF;-`;;hK!@B z7Kja((%`x1xy1Ftz$A`>aZkdB0-+BsK6yvbp3)*+7A!et|6b|nOT(~l(X?_t@t$=Y zd%K?~(T8C-KVk`Nfbod&bvh`A!hRHG{zxN-GI(Dff4%5=`^DpYZ)+_;E2@HZrvWLv zH*Yo&0JO_$J2BLkb|M+ExTB6K>P<~W%>!1y4XgUJfVR+wj2R-Mk6xvGC& zNX+uSnP1P=Sl{AEP43VFDcszU znF`jq?bY=QWDhbz;&X@kIDo`*jSATj+W&hpPeerYflm8k`&)=}j6p5;*MCKHdkX~D zSKOkymin2F`}JIN1a^8894}HJ3_)4A0VveL^&v{M;vv}qOG<0&_0>^lC-jJgm)N%& z6JOW#hm@2RB4)?Z97j9*#I;0UF0s)X4U{Zar#ejjwS05b;!~G0Z-J7HAjh6zTdQyd zbT_>z+AG#6B-vDUOz1b_Z1z$|-06AG9qkmqojaHAzY^=XJ<>OWENVWt^XuzdG)e5| zSMQfs?}ZFsYwGIjK@ZGtD|un)KlaF`YlMc&H`t|{k|YLAt$u!O>7I1;jwb1P;H6n& zzDDY~rh@GUSS|bd9-$!~sY9nurTb{R3Lik>mveMCp>QW`^f?$k5;Ky3%p4u`ZO~|N zXCxt~GO!?HE}E0($N2t+pnEA*L#KS{7KHXe6Soy^G%beXvkmq$xr-zP?=Sl}*XNOg zQo2g~H!soUWohhs>_3R+iy;F3y+fv{e0=V0U9Oq`s@H7Sy9N&@32nE!CzpUfn}Frc zJ@wUQ_h=?}?cLr(&GqF)$9lJC>IEO4?Z$?&v&~OJAl=jGn3)O3x6c2^X!z?yl}Nk3 zXenSznB>_fPUM}4@ODrv#b70kGWaCxge8gm))(;K>hPfFHT88E@<{S@ZXq;o|9)r> zpt`af91*f)oIn2+S#e3NwSN?H4S|dOl)IjgA`Z6rq6rfoKf%lPg8sdN`5KLqMVocf z@&y5X)VK&2%0h3b+_DvI`-YVN%japy9sJPJ+bpE?LZR&KmS3a4xJItl$vWLUvPLi7aqA4+)I=a>VQ16?qp2qLjPQ=HRSx&=kgvPf`xE}_Bae?B(#BTEZ^@Ax8A?pSCu9Nar(=?UH&{K+1#IF{8q|N z&1TY4Q0`I{x5PwN{;hj`ERt4HT67Hs2R9F0SRLrC&YcLxl~@9d~Pg=%IV?cwX%N{KfzFpwfb=C|y|G7)Drf==58_ zLr&qINz-grR@SxB`-#xbi~pl<_tTQ@D=;20>+0${>c>QL@{7T1*&Nkd=a!1NNK$f1 z(xZh`Fd}RY^WKhoW(d)6$Pj3_2oWL_?P`6^z`iE721s%$(@*u zocGUv-B}9nIuSI4Ao6lPCmyDOX_B5wN>*N_G{~3h`SzG&i>^a(FMp+^{_2xD2J*x3 z!s`P#9i=Koe#mwmhIEEAeMmrL@|+jGmv?rp?^Q?E?r4X8LmX?Ddqg%OOZPo>Fm8>S z{)VxO<7Yq?S;=i6`k70GFm=Sh2fL<6*wOLl%L`3aHkRIH75_?!HsyFDCQV$?j|;xF z*RBVMTDRXBcUn6^3l>T38@?gr%HXJ<@XC26liV*LB!iS@p(gJhszBQId0`}{bW_S? zzDUdZfNm}*pdTFUhI3K)GM9u&{Cd1~vjcglO}`{zb&YtDnHoE*O|<{cD03GX6cDdf zSIz~iH`v0a*Lp|S0dySZzt70EofA?*ezD{8zM9b6c?wfW`7$4*zQHC@A70}~RQ)*@ z!3SgG9SOj0z4CsAtUl1T{PF~%BC@e-S8p)vIS!uA!P}+levfoC-^cDRsWjni_*>uN zob=`myM$ZX2ZP)9w>R}SY2^&soOT=muf4;Xjlqj)Nz$;Ru|Bi+sWt!9THj}!rBdXt z)IP1ZV#J+K2tvM3r9}4@+i(h_r)13x4hCW2Qh(KC?$kMoWCp~ggxM%)A@EjF@L$SV ze?F97Zc`du1zs+7bm#Gz*g!-#Dfl1i-K8L!g<5}u6*yErAW_PGJgCQ ziza7NHU<>9VjJZiL#Z%BolpBk5YKjwqFFxn&J1qYm-2Rj0ZK2{irgb7pBa#1{V_ zg%)Y_*LV)%p9nY?Ok<_TXn2$nU(Qy_sH={?bdGw&xt?_2(q}I&KGFZC^6Q=4iuKEl-1u0UYPh?Hx_NNwlFNzOCpAY3N}1s5UPW}Emn8_ph(e16 z9hVgt&C1};VO@Ojg$l>X3ddaIi7QZM7QUvnyB^w38jO?-B3D6JHO)fg?|g*$$C#B# zW|#!djSc`s$f2^_xNp99bl%@AOH&@zIZyQcQ+hmV^dEb(-#jW4g4Pz>LE{uHLk|}V zoxOLCAA*S%ps{M=E{n_amA#J9qhC4&@psCd61DSfd(@&yTqxVLkczDex z#pClvMo*4E7|B`_HZ0!gY2V%4ue;8>G#Y-S%7A7=snPfD{AsSu#bPt7$f2nr0c=qA z50z}pG(g0$nbOK%7Hdk%E|SN;0w6u(ct*2#L~aZnm6>+sCv7&s8+H`aM+0jKRv4_+>BPWOY)XH zl^N3T($fFjK!c1k^3Z3)>KAm_C1?>m_;IABqA0PzpDEzf;F^D|_Wt?J8b|^C(u=?f zUGd}_h;~dX(W7nm3_sS8wZ>_cF?ZnxBrxulF5Gfgc|8?-mJQRS!Yejhczr91tYbrI zCi=2$a>Ka>{E1Ju_P;12;zA`l4OLa3ed1M+24I?=;0E53zuLRFKgzN=saP{&PBNX2 z<5?|i>|7}HjZworCst{pnBpSHD9Fe}643^fttQ2hDDnVf$@}`fGum)cW`fU%ddku> zVFOr1LwHS#3y2XP6n81D4RuX5`D=3UO#l~7AZ5yihkOha69cc>?#>hjW_mFg(8|Xe z?w@vWvoasl@$GL8qIy2;=3d_?{~{^BteV725=+Xi#fLEdI}b;j5lIZAE)$MV^0-hj zo=wQ36qD_b~ZHE7KS`EljnoGo@O(yW!O+5% zmOJKr0BP2%^A^a|nNFViTa76%t?reZaL9y--~D~`aJ`r%u1GR=cTfE<-hQ(y0YFG? zK2K~N9UXd|9`S&Cf}5M$VUHp3Iy*bN#QF|}kFVq0VDl5{LeK-KLhGbev~jZg$&>fU zb9kT={Vd)C`wop(Wfp&+aR80O&NG1BGz(CT?dE%bJx)CPF#=K9}FtNNmhEEr*VP$i&g1UVr}$S zkgS(IU2W@6?q(;E6s+KN;0ZXWQR#s6{Rzstm^-vX6vsZJ#_yR5B&A+(UJ_9g4{KU>qPQTw?9-bce zVxP8yQ6GWd|IC8{R*6GF+tAR$*Eid*N2zw%#Lg~yu`j@{Rw={0-ddRSP#C(&>s-~4 zSbafh4{=gD`OmI2Myv{qodb0~8^V;9_*vWV{+)CWl^}_+NXg?Nc3$xlaYnol^*z#T zr`36CS~hjTg^Y^zpy}a_;d*6wXe3kprM#XdfR+zU^Pe(BmgeZ{ObTSZ`TA9-A^vro zYq}OpDFpNI>^wfE?Kp&~MgVBofGva^d=zK?uMh+a__7zk&pJ~pT-=xPK&5Eye*GN# z&vav-*(Q9ZLTX6|FgghC`>+vm`IGunqUwK6CgVxj&a>{Dc>=>2az5DCcvd>l9fJiI zq?1{Zs#ysXcu1I^QLRdI(wN=iey<- zLzdxKLTfoz>C{%?wJ3hnRyuV3L0CEyiYpZsWCK|va}x&xwwVg$!Nm%V17G1||LseJ zv*sejH)fm7V0wh2|JQ))#Rc@Q;|G4lT98PBXJfCDgwZGpR?Qu-tuK*RGeFJZ$ZbBji zZMABmvwS;W=WuEPJ}Z1oFVYr&AyxT;1U@p(GuKv48C1<+yg>?N!~#cNoNvv~VAOq1 z!3ZCios2j;>T`}iY%CtUGw=$h@)K0HD2F4yNRB>ar_r6y)-Au&?(#;TmLR}!>DSb- z>9ShV`Rl}N9&RW)F&Rp|XA_wMuHN_3?9_1&_J{R4%qkX+z#1pHjpgYyi zcoQj1*(b^exnk$_V+f3zom{QwD&wA_XmQo@$H}=UgN=kqkL(KtYUF~se&B8g{X)Xn zYD}{&V~y;#>_|T2w|=C#sO8k+DqF=L)euFTz*th-p{ zip*F)FpV^A!}5!MJrNCI*JMCetf)mu2q`C%qi{A1=tH<#e@mFc8wYBkb0Rr(Z00z! z;mnu;$@vw+^vD8L%A>}Rb3C;S25?}8^t?-Vt?LJ1{lb;1wObi;L>>(?H)vmP8;bKP z5f74MyjkpXez=!V54?M>J@?-)suT|c{<+*$ zAJ1tZMjKzXDNaazN6ByY;Jnu+Gf%w$ z&efs_xjxuf1fGAHM^EH~X@v|aQH%^(%jfXWEa00f!+}=fD2DEOQ0UcV)FGsrdMf;{ z@qo*p?ZCg=2(lcje>>+|1CIvPjh;Z4#_${mm4cdjqLvA8Df?Ql2*5p8Y+Fe63l!Z1 zTfc`>b&mnfmt-~yWRXL_K#@AZmF?%V81VGlYtrT_vWB!$y~gfVBxkIfIvlb|qa zX}I3}!-|b3xtyI*U!Cx8qD6DgJ61zIs&=9GK<~x(s7jRF&5FKqTq9m^^RS8;M`H^S zN01d`RsKlDs>Ujp$DyoKrts>mOo~TDQ0j8`vfHd{HFs%O+cPt)E5p5YhpZLFX|rbR z((ClYIov{QO@UDQE|<|w{o`VZ7H=Vj!clX8-Ct@Il}~rDdSXdq0czVE9%g7}H+Zsk zWmRN&(HyGqy=8x<)a&nub~SWWZJvk{%;RYz(X2>-aWe-!9+wt|O5rG}47T}8<3ID} zP0r;%^ac_LLA-pi=+aO{#7?N`KPMR2a*cg|kQdLT(91Z;$XaR@d3D6@adqM49V+J|IK54*gZA9Ue!Q%#iG z(k6sdDz^6)G8r^7$!>;nz?o_~!~CsGJTA`l5*H5PR5)Z%l2W0xj{z!(*daI9N&gwY zRAf~A7!!*&q32{rm<}kIP_h(IB?&hRu|-6y$Tn96D(FW+x_L4ENZqPQl=XmupE*FB_E4&(UZw} zx!h{BXLG90QE9IgIiDb!AF=KIQz%JgHa2KgJi0aW;kLkmLQ!5VNTybS<$L(R&68__ z2gT9vpTzOrDANyRq%*hR3fR^tmH`JWb>BqWUjs*D&s@0fSxjZ`X`e+f-kd>wiBA6H zEi#?`(Lxu;gVP-ruxE_qMy%a4EmTFs?9~(nSC1f0a@YDt_x$UEr1e1fAo%`QwGe^# zdwU4YD_jq7J|ow!^2CsIcw>yHdg<$h8giKBGV92%F$u-VT+;{eXuxUd`<^i%b7nih zLLWZxSlGJ(*A8ExZlDmXwxzN6HaE~+n89@SolwjSYFn(A@252B;W(C_16GYOf2OKB z_Nq>eVNahNOV=2oy-)C##~Gi`9pTo&V5*^ss|DQRnZ(+NXGO_&!^ec7G7oVW=JjgW z^Jym-Mp=9U_r`~u|J;HLn{!wsb@?y(Q+%{{9XUd*Y(w1$gSB=oH{CL6t;k2!(J)9x z0w*l17W48nbH)&@sVH2Syo$#u!HVNhrNT|-s!8iYg1fD0DmI}CY8aW!Fn`3!1#1|X zyk-Q&=CXcSm>MmbpP^~F@Z=h%Eg67i)6A^;sB%KscPEM`F`v(OTkQjqz(A%**=t%J zIEg@ddZvxF_d%^FcN+sf{D*j96E!?PnBQ3SD~#DS{1l(CbW&g0F|vn_l0M}43YFy9=&QQHT^6JRQ3+0(NR&`8)rC~k0k@K`>l5ArISJ+41rd;$l*>!w#qeGvsB_VR-p0WCdZg<2yd{8nNDBAfmp%? zsYFJUX8d}}@nv3V#rygLHcTZllm`MhoQ+GtIViSIkPIC4NhIT7=AvK9jRGO0iEPTk z(I>nX5i2k%Lfl;10R~w%5P>s2Hy$0HmR^P~sRRbf>w6EDEU1C5S#d;NRtlLOo_8A5 zg_3{!SN?ZV8mL-s0M02p{u`)Zg}^p0Gpn;hw7WrZPPdf2ctBY?Dn=87l`N0YzCN#P zR$hH7j`*p4U zqvGYltrKGm!euhcXfA^4kZ5D*w-=sZIDssW)2XO_m)zj@k1sZG^79P{I?A-+iE{CC z(*|Ac;fDIOP&hvkISsU-2u!epZxGgg`N^K{)SR#?@_H2B43X0JS(PzGC5FUL1}3R9 zev}}~>)=5dw7d%WZr^*TYd_Q0j?|X`7Te{lG{EbIphyY5(f#v4+}soWwo8;?wy>m3 zAeT<5ZY64mX`QLMl9Yboka5o=?OpcE|8bbDvLxvss(|?^(S5Tt^FjOS-^s%>r4_-( zzW)8B^Gjv|e5LNQ=3tQ7K+wjuY8xrTU*lyJS z2}CWs(x(%8SwfOp+;~D)K%66p#K>p`sf(*XgH{9#X*OW0E$V;#AO3qm3)X6CCmf!P zu}MZZNy+kj?oNg#NJl9`9o=+VYm|+7&q4^)Rl!Tgukh0U{taIIV3*(m4_-lWOgH8ujjS|5I z0X(7|br4iY=mt&ZDiU#|1XhADyYrC#Nllem_TRe3!wUl(3H^MHPzfvLl$~P8{U7}) zQq&~dYp5$1C|ynFG)fdIO)0F#D@DInbSo93rZuENQ9D8X-hcTIL=d!A1gG%9#xU#! z?^s(t8X))pO*l3pNDw4Mr$Cw-UZ1?lZ~d$Ph;MxQ3KE3T+A*JO+~k8x@A2OCw;R$V zQPLm?9??~!o-Il9RfiyWB@`YcS;33OTF<5ZJG}ab{|~82IQjNp;eNME&>kVig9(nH z9U%yoghVNnFEQfjiDPwAGI{S~)=x@wC+GNE*SLS7!?+4$Sr;m}Skzp8){9J(!Z?d}P>E)o7@StD&tRp5Sqg;>bd5%bfUassl4L+aT?Y(0y55Y%YlZVd zQB{m)1=Cr9uN}^NN*^eynyRR9l>-mOWX8$KkkGiR6?X1w4$^qc=JtN$mQbPbH#UD? z;wuh?7DXbOBp9q1tdA?jL>YojnVOV|%{Wa{hH1uenl~-uw9{d<(&u=k!{MOE(Q23D zl`i96hq}|D3WBN~ouZ;Yn$kNwr1S70-QxrLrzZ@Gnk|*mlZ3Jk9C_h>P~0cs!1g%K zS2=0gFds0?2OQ-C_IrKq=P3`m23>nikPbzuQREo0Xwit)C~eR>CKnJ7QUyZcD9f5z zRa1H)h(cpXv>~+?kw9W&{(yEarKK<(^<{9aLkIpY~pYePhK0=tBlpX87n{hgzbOxKCk`T zpYR*szr}0c|B#J8`T+mqPjSNnsTCp#T6ZAnqw`f%zJ};7s4kigqH~;0sgmd%7-_{w zTXZL1C)9@72>P83OfV>k5~g+%BT$1T)0U1(NWH@NCNij%BBiI86nG!-uEu+ZN8zNx zJrXU@ERKlT5*9eLB+IW0qW3K3ddWq|yd!mv)OivgutA|csGyLrm?|bN&D1)>raiRn zVv-&@>4NPby*_qpi#oAL2&|3+*A8o5JFWQI-h@~7Mr*tyQ@*I(eqjT>Cvz08YOZ}8@;ukq3gH@I>23a`BKB57vOc_czrRU}FB*sFW= z*M(W6CBEVie5gRx5DM@WnGrH2Xy-@>B#j+on&rhNKo%64#3d({1iVrNG!>e0V!4|e z4z8>-dgC&uSGSpT6GRECXnGP5DeyhR$y%R-iyNH2aG7%VA}1(9mJ{+U27v-5coOe$ zs%B~{N?QLxbDfpUlnhz@w|@Y*BFgYuvPMufx(c`bCEqFWnMXAu%cX~KhE%HHmP;fve27cari z29>sqsX5Jr*>)fQ%5}ysT;ce_Iw$>{85$=FPmBmq;z)c&5-Lz7&Q^%7FjA9yA+HST zfBRqmPceWNO~(kL31FlKy~CzWdwU>h~#yR3^v4N(zRAh6VGYK z;Z4nkb8L=hTymb3Tld*`=M(hol!seF{o2c%UU`kfY`{2C%+N8}po(^s4VuggY49i< zweghJQ`&$MO~1^@>lFR^t2?~$+77S1vc;RPU*g*36|P_1;L4Q&ufMv>*I(MHZFZJ2j=&`!iBg>n5HRAEX6G9+O(&n|bAv3Le zc`>|ZiLW|D#e8IyiCJU3j?yRoP4GG<&2U<%wcxbO(=Ejo)7Zuq_h=M|p;nf$Hk@P$ zqfW+YW*KWmp%mVxxNetGk}Qqbbab0y z@(I(?ha4Zg!>y11m^*jhV>-PL;e_(!4tqD>b9#Im z2`8XNJb3sCckkb3G&;dajYv$(rj^3lCL!otEo%tVBv)9H=hq1_sfI|CT;q|ZqTR2e z2RA59P;sBvP^7|X?l|r`hIwF`3Qot*T5nO3;zGj2D~g~Iv1C4>TN-SkDFT!MOszbYqPRy@-a*0xrjwgIeDFRG_kMy8$A~|s znm(jDxl0&5U?oiHR;Rep1CI7T;^gQP%HknKvB!A)kfVdU9PHnxb_LcN^gNN2Fq_Sq z7uJ%c8FuC~rOQdpUkixB1dR!SgmI!opanKz_f zlX`{qsv%J%S8NIs6joxorl16`1!tlvrvy4qu~I5Jl|p-k!%|s`H6$r{@y;* z(J>D`{gC0|T`peSVrOfcoxwJnE0;-DUnJRnnf~rIvXwO;rIT*4yZt5?HeX_U>lzm> z+@L?Wz=e$qYzx3)fOy>F2$f?EfT$yG`~g=L8s8_jiJ+AyttvXfV>Ed0 zFu|ils7zc`W@^wifJP~U37UQ-45~m99L`h(U16xmyr<(pce`EgetMgafAV8)e)JA^ z?*AE>W3FA^;Of;)bZ5p%d5@VKV>&f?H(-(x!_h6!6LxmjSl>>_yCtXNJ*Lxfd@UZF zuVS20l7iMv0+Lp;$XxO)t(jjlv}>D{M0-L*(6K+TkY{XhOY1*syjoiHJPk&497y!0 z8H3h_5InVW)LKz$MX5k^Jia9%nkE_GnKMe$X6uZS5FJ1VlnPjpcF6QT=_IDq z(R#k9EQCPq>gJJ`SmN`ejj9nOE_s$HeFTlu8s`HVO(F?iSw`KIqfWwDYrL^viXotP zc6NB}wb%I0cfP~dzxEcBk>liO%HhE&r^Axz1d0jFCIMdqUQ!o(K01IXRp% zn*^%DGo4PEPG>Q2lN3;d5Z8S#vBVce>v|52dSXMIKk=%m77#pmh17ymhOoPhdFdKq zeVw8y67@x)_xAQk(wJ~yb#;rK3vZyqD*F#d96T(MYKuI%it>Gmsb)6ONWI2d?`0Cb z#mUi(;h|@IV(8}UdI7*{tNv*T2EerOTv=K`X_e zzs1hQ>uhdrlcfp9X!^ZX)&>{YT;F74V?e*(qmy^p+T3P)dz*g0kJcKI08LJ-r{zRj zVu|NLXl+As>D8 z5w~yO;^F=sR#!W`_WBE4zOs!?JkCw9R!DP0mK(Y~iwje-T(P^m#ny!Z>+Aid(qSxU z3yMI%*qDv8u9r5=5?>IFqo8#d&=~C|oQ3DiiUNb7n|AQVFw8BdgDz#BQ!f;T`XbO* zuU=(%GUVv!hqiCc-Ova-W+L*JIn@cS51t5)-k;Y0kjWa+R36x|RuBtFfqfA28%{W$`nF!eA zOG)>tUw!peZoF`V*Is*#*I$2~@%V(}<0B@gC8xs?lj#xD*^t?6h9X8Y&!!`0vlC8F zhwSg4aCCUWcsyi0K4ml-F`Lc^Dqyfkh?zN!UPdo3@deSwEw+^?TF-T30|XTa;&DFa z7Z(M?V#dtg#icq1$+0Wj86l`le{t(UOvwRW{bHv$?iIFYl9OeT?q0xqgwYtt~p84ytiA$2*O=0+v|fi$cxkMx$X9 zvrEMPl?1F+2%f4g5nr>KWn3Dp(M_Y;b~Z)&`JqRn5fAR)=hm%T+`W4jtrgdAT;kdb z8(e>3n=H?P!1jd|R#pu*De2^vPTpmupK{?+mn)Yy*jQgB&oX+w9^Gyi;xZnLF<5JH z&Yi#Z5=%Tclz^5-+R&(Ev2XxZz||FAJUZ26iKQPDc~PUQ3Ke4QfiDVu>Cz?c-M`N} zZ@kIXt?@_;gA!9bwZ6UZQiLt~?=lit3o7laDz*-D5JI zF+4qDJU!s_^pKO2lV?f2*G2GJ%8;bmiSyyk4T!vHvni6($s*4qAsY4DaGLl zlY0*-PfxL0#~dzS5L#1l?dlHS`tCQmcH=sw_v{~>a(Yy7|K3B6Ci@&0C+r;`6KV~G z!k3OoHKuf)@i_2sZ^*&X5YaJA!3U4hF`34!E~%ZPmv>N&+c`Qx;#i@iU0k}v63>9v zTh=Tq2r4Ggk3qnJSyAGFG|Ta$2@iho1K$5<|BTx|{1e=Gg#HqGo4$GTPbsS-X4R0v z<`&=l&fg$aS9tK@F_U9QWmC3ZdWDTkuQ1$)Y`RJ&>+D{C6O(Up>ytwc_B_UHva)%Z zd@x`-nc^#tSW7SpYb?I3h*95yZ%AW8j8+xQywcSYOMDh$-57@`kJC|~A*3-;D}vG% zNWf`!vp&5K-b4NSKcI84$Ewzk6~X;H&=2>H7)>hnj|x8g=>yg_ukz}vZ}HZfZ}Gy7 z>-4u)F#VJZySrT7y+%gLpufuc))p_m@)Fepe)XYWe&x4N_6noXDx$VI z9?mGM8FgI|q(+ISET&9mBc^`Fgo>a&Wi_TO#yB^lD#wgYPdGX{WHy^&b%L=PtCJ=R z4a|x*vBnZh{Axpgyzt0OV{T1^30+XrdlRNv|x_O_&y%FU!Fe?JX z(lHXnOcYfq3{NIJd~nEvhld;;4Jl?d&INoBLWudBm1>fqpTF`PaZ+`ejb@2o73k9C zbo!TB-FOw7?Q(JV8}tS@Nb-wldx0#w$ol$=bXTrWnl;8|g|T$WIv417cQAGxn{1MI zx7pmf%wVvARu(Vdo5bf@>n5M`7dOI|SmMtcTBSWI$*5Hi(Hm^;+GNnC%10${>MM%NAJJKpMLNYe)7oyAKp6VosS;! z!*_4<$A5a0_ddAK?T1t9?l#$t*Ek9}4-P9H9#-5xD7b%EvVUCjaQ~E}!&6R9M!4F~ z!>~o9ncQ3?&4Scf3ZNx^giSXtX*FVx%~4LKN&I5`<}FdTC<9I}6~M}E>{H&L$2TZdWF$ah_D@@yXo0l@@*8$QLtJNq{yoc0kx|35@?|gzyGe$et7^W%C2+~eX z5Ub_2%gDTr6PqU3xSmGWh<2bX+ABH&o#6=^5ATpwHAUW~+`ho^`WjRFxF=V^ylP+j z`;&eK(0R|0cP*WsCF@yq$Dn%_-Al0Dl(ds#^90E>LrQMW4!AoyVE^QZgW)mz!($GH z#~hp-b9^%7;lurC{A<++ns`a?;*{oNQf7%Ie#M|?vk}wTDU;a=)8d3_J!D)DnN%lC z>LIgo$aHqXaC*eO$sQjK@9@d-9UdIq;o<&W9vs}`;lVu~?muAv@PIpa?o!u|5ER~> zsb-%&PjjBjODyrr3oTxd;E~`7!QrLChZ%J!2%$hrg;q66d1~XB8Bf)u>C{bGf>@I( zl0$+bN%FXUM%siLZTY~@!ZIRYiC+z9#LUYaC{a;o3<2?uP&=q=d|6SIB}G+Hh@maLx53Y-rp4aP*fMdmpI&Tp4k;#Uh=NkVKEB^qCZnB+X^u{GG_@`WqB zu=^4(TzQ$RS6^cH%1d0i{36$PUgXBkOI+WL?b_~(T)lFG7hiaZJnvu>7z%!LS+=zL*`?MTpMVq zRKPLi+VNG^{+BCUd3zVD6ZW$MZeRGA4=%mUPp-VjQU4IHY*UKV#!T?@{35aHRqH5d z(;z&m*gaZfApR6T`^B-KB1-e$d;!Lj&fi}B$#}M5;K|PjQSw7vFru2Q`R(`g=&^qu z_`DGu0SPtAXcQglGSJHs*7xtR_3!_Hyw~UOn}3PB-5!OF;r}{VlnS6dMjT24wbnS5 zkoYua_%~C$F3~E%mcoWt3~t}z>bpN+FdlQdvdQ?3H~8p<8|-K4W9EY5%zGA-bv%w} zXei9Lzx|zmbhb;;?Q{V!+K{CgX_lfBixa_zfL75_tZC04n-CE3%M+hW#~5NdoVNK^1qQ@R2#A(o%;3W5P= zP+p6k|cN^@Dfl9tKS}Bqks~}?%@uv|C_II z`JIDC*k9C~Ju+q_Q+4NfV5*NJA6=ZQ>#(jKO;WQCO2w z6(wacBh3x5qPuGceLx|iW6^F6%4(EWIOmv7#wZlMULQ~d7q1iVm3HY}Ge`xD6|{lj zWJ2izo&Er;J5;lPc9yQ~U_{V(L{$V`Af}8ZOH_s?Y1UfALGM|&BC!bUnT&h%nBWCt zW5=&!uO&$>b?F#QCnTLNd7fi!f)9S4hFEJ8J6Cg0Aq2G6IPWQEC0d&}jiEIbFd?Ap z3SG=FvjXu#cViQ$Q!1qy&MIaO(v<-wO$csEA_aoSD;<{t#3#af50eS2B3ux~-rGMy z-Mvq*D5%FnuKtzZ=Ju;EvzI@fB3ZTdYgD{%tu?iC%%)R7(O>BkB*s)eMw$>p6KEOh zWVWv2cK_2{$4bDuz>Slu{ML_ti`PGXi+irEyKXja#H&038nD;*ldAn2wzl_P{W z0#C;ijMl8Ite~}NC~`*dM+O=cN*Ri}=3sb8jj+18Mv`RAMg@sjR=cZBX^9Z8qiceW zAxSF4nkq5n3S7DSz8{+cz(Fs|nLnrT$rD;?fCB91< z=^|0U5Jb>QQP(xo$&{cBy|oRrP0>Lx;<3SD#bJYwD!B@XR@6RlJe@GDD>^F!@_rv3 z6gB{8yix>prmoxI$!s921Ny-pKl~T}HxGXD4!zoOA?fhe-~5|Yf8o2FrdjjY&GV}G z9nFg>rzuJ+CgTai(;-?J);3lljqmF*(kP|o`rcgBF8u9jq|pH~2R9Bc^OxWL%e-~x zyJUWVM>y_3VC8kosNBORHKm0~78qx-nm}GztXMpnS_G4(Xo1;ef^(i;w;M}$gytb7 zR>*2Ilhis;X^-w$s;VYQEOlK|mSt2CyeH4IW)O(+aP1&~1}83N0SyW;q$Z;%YO10j zOA~Bj;tPI+G;MVhkD{Kxx3Y-REzh&&-6?dtz^)~F`_jaq=_1B3Do>b&imYc*S|Pz> zMUyIn4l$90G}%{TGKtt#+Pipu%)j=hAWlw;#3c4w8?@FGMZt7Bp_}HUSsJGqSRhFg z-5x(5@6DK~R?TJwSc~ec;FX2oqk1L*izib7h2VUFF$#<@D`(8A8EKwk(+r%&h7JKU zFJjx~XVwx3)s!pD!;(zIwhz#?h1)@ZE>;mNLI9TaH=H;ym!y?4LIH*fz2x!*vcsnr2xdQ7SuK?Q1K z7^j*^A}C2nYl}uBrnz}hZK2;-az}_OKSLymZpz9jVn6mun5i0_MY5zxLJ&bhi0Pxe z$24lEG}4r|+h|ZaptQn!5Yc352j_%Pd-AN?ET%i>Y>;T%weP1*Rpp(-`+(6JSJ%-7 zT+I2_8kqtFi*lB{lTs?jEKKpX##3Q*AWx>+dxyjc8hpM<&n zsx_ghMy9onHi;Kf6$c=JXdenhLDLSpk#Ru+iA@j@oO77eP#90GVm;P*l8gYR8B~k{ z6+B5|2spf}FcyLj_*!9ghkz!iGvWX!33Rdqf}@!Cgkvg$S? z8T6Do9FmenQK}N0)f85vLqg&Vfq*xTL;zn?0V>VW+E7&`Rv8j3+PAjB8KN7aMrlJ8 z3am`RVk3K~*lOkRgM&`4v0z@K}M1dhb zM-aiNXg5R|u|S$d2y;m!DiEXw15Rc*DX~gnm4RSTSVSyf=bZ!s3itU)gNo|ZQ{cTr z)9Cmj1Y!WL))r+n)E*UGFEN50g;91+7McY3ff}Qtmo!L-$?#=@r;PE3Dn***$r2Qx zNUfr(9YzHV0jMzuq8t)5TJ?drOrF-8+PnmZL&RZ3qPnL9r2-O4KofKyktb+NN=1oD zLu8ujYV$$KG(~Uq1BTeJ?C_Wy09dx~_1^(mJQZm>PtC97ge+aep|wZr-Z8x&gN zWG2eTdh&IKQ6;KA!Q6gGHmp$BFLHcwg~}R2fLcfQV(wxMc(22}T6C<674`h~(Vw>E zv3&}}G{40%?WibKXow1s7(v1eUkzDXQ{1?|&C0;w{4rV;jU@!p8uqM$Tr9p|?md!Fat zKK@SPeT!0vbJ#THU;WE}Nm*3{@91?WeDh6%Rl|lHQINz?DQUdG(XF4RDcx>|#3rD? z;>hb6Nf0nSN>6z{O_5Hh zjYo&1nR9SxRbix_lg8pmic@+Y?a@E1sb0Rs{VM|sYw@BejY3+|5bMr69q@C9LE8hK z^ctStMU-few9%0rqUuJ1#EvhbAOx%tj7$msgtax{g&XUvtOQhjM6PG(MqP_9_>45x zSn@n2%TgrxXcMX^eH0c$k6QX1o~Hc%4{kE8*YJ9i!Qe9A`qpofCKiPQT%5>Nfe>|% zm=X&Jq!RF^&%||+{0evW6~F(dpHkGT1havcE+T0p4soDV1cEN{rifL+8>f(N z|1N6&PD1lqqRQ7)j*j2xW6$~cwmt4_pP?pU2-N}RJKt#|0=^h@5K^z`R4IAY$Akp0 zJuWG!k{Pvic%!JT$J-g+O!0O~&?P~8P&LL^bPgtTCpGnEkHhthGF4OxibPY}sCfo0 z6jmsbKxsWrHxJmjVq2H&leg#hDIiX8#*rx!$LlD;1cj(LcYv-CH6_by25X8eEwFAv z$JL}RV7*|yV0^&%IN7$A<*ep8U1P~akqYD}G8H9mO@Y(Gq!vDXc*>+sz^>5gZ?L_y zi?J5b0@}y-6usT88XpbVX5W|DoCJcFsFLCN^xj>nP$2|X(uy1Fjyz0bp;%Az-0}HS zrOAyU*M>pbVUT5XttC^6Oe-=f5-S)RD=?E}OiIW7<0B-|)KmmpBDO%x1hJ!e^QMaT z)pk5ZoU@w$eAXTzN<*HYVhQ%g`^Dn+@qG}h5-6#k=GJ+BC=^+p(5X{QfS?`0ObKQR zltA5>;(?$7UOT+5sjZ{3o-&F1G(OgF7r|@6Y4BR`8k}xD8fSImV{6`vv?I>bx99h1 ziPzF1Q3a4_Ajf^t4QeI91cP=4<4tsZ`=;K!1d`xMf+Gp_x#m#y44F;v(=l#3LS`eF zO<*=A6l078jA&F)7-F=ARysN$LICk-aVT*yJ7jZTZRY&*&+~bp6P~$7*E3`~MimoOH9^-iR5?MF6DTKmS5s9rWm(}}jS3FsD?&NO zO@<^=Vx`1NfuW3UBdO8iFs)}yB$f^o!AR`XG0dq64{B68BOx9eno7`5dEQ6+#N+#v zNE1#KzC1~z+WLp0@!G@=ZVN;gXdFfqCgib$5DV~VJb?sAK_IPbR`(~Yj6B8G3J04R zRkJkJ>F8R_ohA+OX+q;UvI3dYq(NgvH68uw+edy5O%s|I>@fIPpI&TZhozWWlQ>94 z7^Ir*L6^16l2MaVW3-?(XdP$LjaC>{LVFJAOydfYsBm^Y<7TF3C9BvItKAh<6Rfu+!H_hO0ZC|y6Mc(Xq7c?ZG8qv}kl6nKE&8$k zaDMybeICUGt;b)UO@joICfKo78D0tK0%Lx?6?3i4d=v_4d&lbpBbNb$kAu z2w=5FTZ@tyQd1QLMICFrZ1ktR^`a+Z*w{4fM0j+dD5Wq~p^YL-lO`-h0EvSN3h^2* z36)Dpx)(S+as1#%A5*%VT2j1Z(Iq{1(!}=}UGR+x)!b{)O_T&pSON+i+V8jdjvJSD z^gKTPyo+0&xX&Z1M3{VuqybbDpk%G`S1ZCQ$1zSK)$+v0zVn37^$L;!BN*8${KJ zsvFzJfMno1n9uK}BMxR6&DYjfuFME1Wq3!d&Wr$2W5})->}RuB4+K zkTI$=9-at=^ax3Z($}DCjH(;e33+?@8#}!B@*1TYoSdY1_7CXoRZiQktwA!V4p^sCd;TM zMk9py4aT`HLAjD76c}Uhrb`i0Nc!j`rL0Q=j>HJy(1CbAWRcA1=;fn6`=-A{EJlEY z5XjPu+Bv*;7!6XFsD`X(q=|hX5-obvbQ2mCL-_@*K>HXmp_HMng(U9+mhrS^u)0oJ z){Vr7XFO}s-i*@=QQCy2Ky8#Xjq$G*#*xM@X-V_B`Uv2$z<{b2)Ia=(|L{vA4GWJK z5d-kFsOXYu=JKU^lJ-HB2pKch#wT2QcaP1zilf(d_~4}h)5KzfMM*pIMBjYtgFo|- ze7={_B91C&oxK^S;(6!6nemH5`6eRoDW3*Y?;Lpd}Bj^5nh));8sB|0d@aNf3apJPpmdYj+ zNkVBfUTH*|dHDC)v2okJj`-{2=UujC`+2ngA3n_gdl5m&?*IS*07*qoM6N<$g4f6L Ac>n+a literal 0 HcmV?d00001 From 9ea32ce2e62e156bd3b583e70245a95a481c11b6 Mon Sep 17 00:00:00 2001 From: Cullen Date: Mon, 23 Feb 2026 16:06:27 -0500 Subject: [PATCH 157/168] 2/23 --- components/stats/SmallGraph.tsx | 28 +- lib/Enums.ts | 32 +- lib/Layout.ts | 14 +- lib/games.ts | 107 ++- package-lock.json | 1330 +++++++++++++++++++------------ package.json | 3 +- 6 files changed, 942 insertions(+), 572 deletions(-) diff --git a/components/stats/SmallGraph.tsx b/components/stats/SmallGraph.tsx index cd349a13..ca66df4f 100644 --- a/components/stats/SmallGraph.tsx +++ b/components/stats/SmallGraph.tsx @@ -86,25 +86,25 @@ export default function SmallGraph(props: { case Defense.Full: return 1; } - } else if (key === "EndgameClimbStatus") { + } else if (key === "LevelClimbed") { ///switch (data) { - //case ReefscapeEnums.EndgameClimbStatus.None: - //return 0; - //case ReefscapeEnums.EndgameClimbStatus.Park: - //return 0.33; - //case ReefscapeEnums.EndgameClimbStatus.High: - //return 0.66; - //case ReefscapeEnums.EndgameClimbStatus.Low: - //return 1; + //case ReefscapeEnums.EndgameClimbStatus.None: + //return 0; + //case ReefscapeEnums.EndgameClimbStatus.Park: + //return 0.33; + //case ReefscapeEnums.EndgameClimbStatus.High: + //return 0.66; + //case ReefscapeEnums.EndgameClimbStatus.Low: + //return 1; switch (data) { - case RebuiltEnums.EndgameClimbStatus.None: + case RebuiltEnums.LevelClimbed.No: return 0; - case RebuiltEnums.EndgameClimbStatus.FirstLevel: + case RebuiltEnums.LevelClimbed.First: return 0.33; - case RebuiltEnums.EndgameClimbStatus.SecondLevel: + case RebuiltEnums.LevelClimbed.Second: return 0.66; - case RebuiltEnums.EndgameClimbStatus.ThirdLevel: - return 1; + case RebuiltEnums.LevelClimbed.Third: + return 1; } } return data; diff --git a/lib/Enums.ts b/lib/Enums.ts index d95465ca..20669b16 100644 --- a/lib/Enums.ts +++ b/lib/Enums.ts @@ -110,37 +110,37 @@ export namespace ReefscapeEnums { } } export namespace RebuiltEnums { - export enum Climbing { + export enum ClimbingAbilities { No = "No", FirstLevel = "FirstLevel", SecondLevel = "SecondLevel", ThirdLevel = "ThirdLevel", } - - export enum DriveOverBump{ + + export enum DriveOverBump { No = "No", - Slow="Slow", - Fast="Fast", + Slow = "Slow", + Fast = "Fast", } - export enum DriveUnderTrench{ - No= "No", - Slow="Slow", - Fast="Fast", + export enum DriveUnderTrench { + No = "No", + Slow = "Slow", + Fast = "Fast", } - export enum EndgameClimbStatus { - None = "None", - FirstLevel = "FirstLevel", - SecondLevel = "SecondLevel", - ThirdLevel = "ThirdLevel", + export enum LevelClimbed { + No = "No", + First = "First", + Second = "Second", + Third = "Third", } - export enum AutoCapabilities { + export enum AutoAbilities { NoAuto = "No Auto", MovePastStart = "Move Past Start", ClimbLevelOne = "Climb Level One", ScoreOneOrMoreFuel = "Score One Or More Fuel", ScoreFuelAndClimb = "Score Fuel And Climb", } -} \ No newline at end of file +} diff --git a/lib/Layout.ts b/lib/Layout.ts index e961e962..f18d101b 100644 --- a/lib/Layout.ts +++ b/lib/Layout.ts @@ -218,11 +218,11 @@ export function keyToType( ReefscapeEnums.Climbing, ReefscapeEnums.DriveThroughDeepCage, ReefscapeEnums.EndgameClimbStatus, - RebuiltEnums.AutoCapabilities, - RebuiltEnums.Climbing, + RebuiltEnums.AutoAbilities, + RebuiltEnums.ClimbingAbilities, RebuiltEnums.DriveOverBump, RebuiltEnums.DriveUnderTrench, - RebuiltEnums.EndgameClimbStatus + RebuiltEnums.LevelClimbed, ]; if (key === "Defense") return Defense; @@ -237,11 +237,11 @@ export function keyToType( if (key == "DriveThroughDeepCage") return ReefscapeEnums.DriveThroughDeepCage; if (key == "EndgameClimbStatus") return ReefscapeEnums.EndgameClimbStatus; - if (key == "AutoCapabilities") return RebuiltEnums.AutoCapabilities; - if (key == "climbing") return RebuiltEnums.Climbing; + if (key == "AutoAbilities") return RebuiltEnums.AutoAbilities; + if (key == "ClimbingAbilities") return RebuiltEnums.ClimbingAbilities; if (key == "DriveOverBump") return RebuiltEnums.DriveOverBump; - if (key == "DiveUnderTrench")return RebuiltEnums.DriveUnderTrench; - if (key == "EndgameClimbStatus") return RebuiltEnums.EndgameClimbStatus; + if (key == "DiveUnderTrench") return RebuiltEnums.DriveUnderTrench; + if (key == "LevelClimbed") return RebuiltEnums.LevelClimbed; for (const e of enums) { if (Object.values(e).includes(exampleData[key])) return e; diff --git a/lib/games.ts b/lib/games.ts index 57c84e97..ffcf2ca2 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -34,6 +34,7 @@ import { import { report } from "process"; import { GetMaximum } from "./client/StatsMath"; import { getMaxListeners } from "events"; +//import { ClimbingCapabilities, ClimbingAbilities } from './Enums'; function getBaseBadges( pitReport: Pitreport | undefined, @@ -1903,8 +1904,7 @@ export namespace Rebuilt { TeleopScoredTenPoint: number = 0; TeleopTotalScored: number = 0; EngameDefenseStatus: Defense = Defense.None; - EndgameClimbStatus: RebuiltEnums.EndgameClimbStatus = - RebuiltEnums.EndgameClimbStatus.None; + LevelClimbed: RebuiltEnums.LevelClimbed = RebuiltEnums.LevelClimbed.No; } export class PitData extends PitReportData { GroundIntake: boolean = false; @@ -1913,9 +1913,10 @@ export namespace Rebuilt { CanDeClimb: boolean = false; CanScoreFuel: boolean = false; FuelScoredAuto: number = 0; - AutoCapabilities: RebuiltEnums.AutoCapabilities = - RebuiltEnums.AutoCapabilities.NoAuto; - Climbing: RebuiltEnums.Climbing = RebuiltEnums.Climbing.No; + AutoAbilities: RebuiltEnums.AutoAbilities = + RebuiltEnums.AutoAbilities.NoAuto; + ClimbingAbilities: RebuiltEnums.ClimbingAbilities = + RebuiltEnums.ClimbingAbilities.No; } const pitReportLayout: FormLayoutProps = { Capabilities: [ @@ -1924,28 +1925,49 @@ export namespace Rebuilt { { key: "CanDriveUnderTrench", label: "Can Drive Under Trench?" }, { key: "CanDeClimb", label: "Can De-Climb?" }, { key: "CanScoreFuel", label: "Can Score Fuel?" }, - { key: "Climbing", label: "Climbing?" }, + { key: "ClimbingAbilites", label: "Climbing?" }, ], "Auto (Describe more in comments)": [ - { key: "AutoCapabilities", label: "Auto Capabilities?" }, + { key: "AutoAbilities", label: "Auto Capabilities?" }, { key: "FuelScoredAuto", label: "Average Fuel Scored In Auto" }, ], }; const quantitativeReportLayout: FormLayoutProps = { Auto: [ - { key: "AutoScoredOnePoint", label: "1 Point Scored (Auto)" }, - { key: "AutoScoredFivePoint", label: "5 Point Scared (Auto)" }, - { key: "AutoScoredTenPoint", label: "10 Point Scored (Auto)" }, + [ + [ + { key: "AutoScoredOnePoint", label: "1 Point Scored (Auto)" }, + { key: "AutoScoredFivePoint", label: "5 Point Scared (Auto)" }, + { key: "AutoScoredTenPoint", label: "10 Point Scored (Auto)" }, + ], + ], { key: "AutoClimbedLevelOne", label: "Climbed Level One (Auto)" }, ], Teleop: [ - { key: "TeleopScoredOnePoint", label: "1 Point Scored (Teleop)" }, - { key: "TeleopScoredFivePoint", label: "5 Point Scored (Teleop)" }, - { key: "TeleopScoredTenPoint", label: "10 Point Scored (Teleop)" }, - { key: "TeleopTotalScored", label:"Total Fuel Scored (Teleop)"} + [ + [ + { key: "TeleopScoredOnePoint", label: "1 Point Scored (Teleop)" }, + { key: "TeleopScoredFivePoint", label: "5 Point Scored (Teleop)" }, + { key: "TeleopScoredTenPoint", label: "10 Point Scored (Teleop)" }, + ], + ], ], - "Post Match": ["EndgameClimbStatus", "Defense"], + "Post Match": ["LevelClimbed", "Defense"], }; + function getTotalFuel(reports: Report[] | undefined) { + if (!reports) return 0; + + for (const report of reports.map((r) => r.data)) { + report.TeleopTotalScored += report.TeleopScoredOnePoint; + report.TeleopTotalScored += report.TeleopScoredFivePoint * 5; + report.TeleopTotalScored += report.TeleopScoredTenPoint * 10; + + report.AutoTotalScored += report.AutoScoredOnePoint; + report.AutoTotalScored += report.AutoScoredFivePoint * 5; + report.AutoTotalScored += report.AutoScoredFivePoint * 10; + } + } + getTotalFuel const statsLayout: StatsLayout = { sections: { Auto: [ @@ -2158,10 +2180,7 @@ export namespace Rebuilt { ) => { if (!quantitativeReports) return 0; - const climb = NumericalTotal( - "EndGameClimbStatus", - quantitativeReports, - ); + const climb = NumericalTotal("LevelClimbed", quantitativeReports); return Round(climb) / quantitativeReports.length; }, }, @@ -2172,7 +2191,7 @@ export namespace Rebuilt { { key: "CanDriveUnderTrench", label: "Can Drive Under Trench?" }, { key: "CanDeClimb", label: "Can De-Climb?" }, { key: "CanScoreFuel", label: "Can Score Fuel?" }, - { key: "Climbing", label: "Climbing?" }, + { key: "ClimbingAbilities", label: "Climbing?" }, ], graphStat: { label: "Average Fuel Scored In Hopper", @@ -2196,39 +2215,50 @@ export namespace Rebuilt { if (!pitReport?.data?.CanScoreFuel) badges.push({ text: "Can't Score Fuel", color: "warning" }); - if (pitReport?.data?.Climbing === RebuiltEnums.Climbing.FirstLevel) + if ( + pitReport?.data?.ClimbingAbilities === + RebuiltEnums.ClimbingAbilities.FirstLevel + ) badges.push({ text: "Can Climb First Level", color: "accent" }); - else if (pitReport?.data?.Climbing === RebuiltEnums.Climbing.SecondLevel) + else if ( + pitReport?.data?.ClimbingAbilities === + RebuiltEnums.ClimbingAbilities.SecondLevel + ) badges.push({ text: "Can Climb Second Level", color: "accent" }); - else if (pitReport?.data?.Climbing === RebuiltEnums.Climbing.ThirdLevel) + else if ( + pitReport?.data?.ClimbingAbilities === + RebuiltEnums.ClimbingAbilities.ThirdLevel + ) badges.push({ text: "Can Climb Third Level", color: "accent" }); return badges; } - function getAvgPoints(reports: Report[] | undefined){ + function getAvgPoints(reports: Report[] | undefined) { if (!reports) return 0; - let totalPoints=0; + let totalPoints = 0; for (const report of reports.map((r) => r.data)) { - switch (report.EndgameClimbStatus) { - case RebuiltEnums.EndgameClimbStatus.None: + switch (report.LevelClimbed) { + case RebuiltEnums.LevelClimbed.No: break; - case RebuiltEnums.EndgameClimbStatus.FirstLevel: - totalPoints+=10 + case RebuiltEnums.LevelClimbed.First: + totalPoints += 10; break; - case RebuiltEnums.EndgameClimbStatus.SecondLevel: - totalPoints+=20 + case RebuiltEnums.LevelClimbed.Second: + totalPoints += 20; break; - case RebuiltEnums.EndgameClimbStatus.ThirdLevel: - totalPoints+=30 + case RebuiltEnums.LevelClimbed.Third: + totalPoints += 30; break; } - totalPoints+= (report.AutoScoredOnePoint+report.TeleopScoredOnePoint); - totalPoints+= (report.AutoScoredFivePoint+report.TeleopScoredFivePoint)*5; - totalPoints+= (report.AutoScoredTenPoint+report.TeleopScoredTenPoint)*10; + totalPoints += report.AutoScoredOnePoint + report.TeleopScoredOnePoint; + totalPoints += + (report.AutoScoredFivePoint + report.TeleopScoredFivePoint) * 5; + totalPoints += + (report.AutoScoredTenPoint + report.TeleopScoredTenPoint) * 10; } - return totalPoints/Math.max(reports.length,1); + return totalPoints / Math.max(reports.length, 1); } export const game = new Game( "Rebuilt", @@ -2236,12 +2266,13 @@ export namespace Rebuilt { League.FRC, QuantitativeData, PitData, + //getTotalFuel, pitReportLayout, quantitativeReportLayout, statsLayout, pitStatsLayout, "Rebuilt", - "https://www.firstinspires.org/hs-fs/hubfs/image-library/web/frc_rebuilt_1240x860.webp?width=630", + "https://www.firstinspires.org/hs-fs/hubfs/image-library/web/frc_rebuilt_1240x860.webp?width=630", "invert", //Ask Colin getBadges, getAvgPoints, diff --git a/package-lock.json b/package-lock.json index e1d3d706..ceb4e729 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "next": "^15.2.4", "next-auth": "^4.24.11", "next-seo": "^6.6.0", + "nodemailer": "^7.0.13", "omit-call-signature": "^1.0.15", "react": "18.3.1", "react-beautiful-dnd": "^13.1.1", @@ -68,7 +69,7 @@ "jest": "^29.7.0", "postcss": "^8.5.3", "prettier": "3.5.3", - "serwist": "^9.0.11", + "serwist": "^9.0.13", "tailwindcss": "^3.4.17", "ts-jest": "^29.2.5" } @@ -107,13 +108,15 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", - "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.24.7", - "picocolors": "^1.0.0" + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" @@ -338,19 +341,21 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", - "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -365,109 +370,28 @@ } }, "node_modules/@babel/helpers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", - "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", - "dev": true, - "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "@babel/types": "^7.29.0" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", - "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", - "dev": true, "bin": { "parser": "bin/babel-parser.js" }, @@ -710,14 +634,15 @@ } }, "node_modules/@babel/template": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", - "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -754,14 +679,14 @@ } }, "node_modules/@babel/types": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", - "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -793,6 +718,16 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", @@ -1283,81 +1218,541 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", - "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", + "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", + "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.10.0", + "@eslint/core": "^0.13.0", "levn": "^0.4.1" }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", + "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" } }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18.18.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" } }, - "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18.18.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" } }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "license": "Apache-2.0", + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, "engines": { - "node": ">=18.18" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "url": "https://opencollective.com/libvips" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=12.22" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "url": "https://opencollective.com/libvips" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", - "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", - "license": "Apache-2.0", + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=18.18" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", - "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", "cpu": [ "x64" ], @@ -1453,10 +1848,11 @@ } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -1933,9 +2329,9 @@ } }, "node_modules/@next/env": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.2.4.tgz", - "integrity": "sha512-+SFtMgoiYP3WoSswuNmxJOCwi06TdWE733D+WPjpXIe4LXGULwEaofiiAy6kbS0+XjM5xF5n3lKuBwN2SnqD9g==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.12.tgz", + "integrity": "sha512-pUvdJN1on574wQHjaBfNGDt9Mz5utDSZFsIIQkMzPgNS8ZvT4H2mwOrOIClwsQOb6EGx5M76/CZr6G8i6pSpLg==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -1976,9 +2372,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.2.4.tgz", - "integrity": "sha512-1AnMfs655ipJEDC/FHkSr0r3lXBgpqKo4K1kiwfUf3iE68rDFXZ1TtHdMvf7D0hMItgDZ7Vuq3JgNMbt/+3bYw==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.12.tgz", + "integrity": "sha512-RnRjBtH8S8eXCpUNkQ+543DUc7ys8y15VxmFU9HRqlo9BG3CcBUiwNtF8SNoi2xvGCVJq1vl2yYq+3oISBS0Zg==", "cpu": [ "arm64" ], @@ -1992,9 +2388,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.2.4.tgz", - "integrity": "sha512-3qK2zb5EwCwxnO2HeO+TRqCubeI/NgCe+kL5dTJlPldV/uwCnUgC7VbEzgmxbfrkbjehL4H9BPztWOEtsoMwew==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.12.tgz", + "integrity": "sha512-nqa9/7iQlboF1EFtNhWxQA0rQstmYRSBGxSM6g3GxvxHxcoeqVXfGNr9stJOme674m2V7r4E3+jEhhGvSQhJRA==", "cpu": [ "x64" ], @@ -2008,9 +2404,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.2.4.tgz", - "integrity": "sha512-HFN6GKUcrTWvem8AZN7tT95zPb0GUGv9v0d0iyuTb303vbXkkbHDp/DxufB04jNVD+IN9yHy7y/6Mqq0h0YVaQ==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.12.tgz", + "integrity": "sha512-dCzAjqhDHwmoB2M4eYfVKqXs99QdQxNQVpftvP1eGVppamXh/OkDAwV737Zr0KPXEqRUMN4uCjh6mjO+XtF3Mw==", "cpu": [ "arm64" ], @@ -2024,9 +2420,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.2.4.tgz", - "integrity": "sha512-Oioa0SORWLwi35/kVB8aCk5Uq+5/ZIumMK1kJV+jSdazFm2NzPDztsefzdmzzpx5oGCJ6FkUC7vkaUseNTStNA==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.12.tgz", + "integrity": "sha512-+fpGWvQiITgf7PUtbWY1H7qUSnBZsPPLyyq03QuAKpVoTy/QUx1JptEDTQMVvQhvizCEuNLEeghrQUyXQOekuw==", "cpu": [ "arm64" ], @@ -2040,9 +2436,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.2.4.tgz", - "integrity": "sha512-yb5WTRaHdkgOqFOZiu6rHV1fAEK0flVpaIN2HB6kxHVSy/dIajWbThS7qON3W9/SNOH2JWkVCyulgGYekMePuw==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.12.tgz", + "integrity": "sha512-jSLvgdRRL/hrFAPqEjJf1fFguC719kmcptjNVDJl26BnJIpjL3KH5h6mzR4mAweociLQaqvt4UyzfbFjgAdDcw==", "cpu": [ "x64" ], @@ -2056,9 +2452,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.2.4.tgz", - "integrity": "sha512-Dcdv/ix6srhkM25fgXiyOieFUkz+fOYkHlydWCtB0xMST6X9XYI3yPDKBZt1xuhOytONsIFJFB08xXYsxUwJLw==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.12.tgz", + "integrity": "sha512-/uaF0WfmYqQgLfPmN6BvULwxY0dufI2mlN2JbOKqqceZh1G4hjREyi7pg03zjfyS6eqNemHAZPSoP84x17vo6w==", "cpu": [ "x64" ], @@ -2072,9 +2468,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.2.4.tgz", - "integrity": "sha512-dW0i7eukvDxtIhCYkMrZNQfNicPDExt2jPb9AZPpL7cfyUo7QSNl1DjsHjmmKp6qNAqUESyT8YFl/Aw91cNJJg==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.12.tgz", + "integrity": "sha512-xhsL1OvQSfGmlL5RbOmU+FV120urrgFpYLq+6U8C6KIym32gZT6XF/SDE92jKzzlPWskkbjOKCpqk5m4i8PEfg==", "cpu": [ "arm64" ], @@ -2088,9 +2484,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.2.4.tgz", - "integrity": "sha512-SbnWkJmkS7Xl3kre8SdMF6F/XDh1DTFEhp0jRTj/uB8iPKoU2bb2NDfcu+iifv1+mxQEd1g2vvSxcZbXSKyWiQ==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.12.tgz", + "integrity": "sha512-Z1Dh6lhFkxvBDH1FoW6OU/L6prYwPSlwjLiZkExIAh8fbP6iI/M7iGTQAJPYJ9YFlWobCZ1PHbchFhFYb2ADkw==", "cpu": [ "x64" ], @@ -2174,13 +2570,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.52.0.tgz", - "integrity": "sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==", + "version": "1.58.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.1.tgz", + "integrity": "sha512-6LdVIUERWxQMmUSSQi0I53GgCBYgM2RpGngCPY7hSeju+VrKjq3lvs7HpJoPbDiY5QM5EYRtRX5fvrinnMAz3w==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.52.0" + "playwright": "1.58.1" }, "bin": { "playwright": "cli.js" @@ -2324,16 +2720,17 @@ } }, "node_modules/@serwist/build": { - "version": "9.0.13", - "resolved": "https://registry.npmjs.org/@serwist/build/-/build-9.0.13.tgz", - "integrity": "sha512-Hoc6llxFmnsE8z5Cs95UmRRhRyoNh44OdrMWWPPX8BpW19z0CK/qnBquptjyBIe46jjoOxsPHK0Tt7oZOV4Mbw==", + "version": "9.5.4", + "resolved": "https://registry.npmjs.org/@serwist/build/-/build-9.5.4.tgz", + "integrity": "sha512-FTiNsNb3luKsLIxjKCvkPiqFZSbx7yVNOFGSUhp4lyfzgnelT1M3/lMC88kLiak90emkuFjSkQgwa6OnyhMZlQ==", "license": "MIT", "dependencies": { + "@serwist/utils": "9.5.4", "common-tags": "1.8.2", - "glob": "10.4.5", + "glob": "10.5.0", "pretty-bytes": "6.1.1", "source-map": "0.8.0-beta.0", - "zod": "3.24.2" + "zod": "4.3.6" }, "engines": { "node": ">=18.0.0" @@ -2348,18 +2745,19 @@ } }, "node_modules/@serwist/build/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/@serwist/build/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -2395,6 +2793,7 @@ "version": "0.8.0-beta.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "deprecated": "The work that was done in this beta branch won't be included in future versions", "license": "BSD-3-Clause", "dependencies": { "whatwg-url": "^7.0.0" @@ -2430,56 +2829,55 @@ } }, "node_modules/@serwist/next": { - "version": "9.0.13", - "resolved": "https://registry.npmjs.org/@serwist/next/-/next-9.0.13.tgz", - "integrity": "sha512-nI2N4oSEHJGH0YUsE4m1obPfudO3DZ/pXiHPaisw+28YjgkMqD6ePfhzeHGO07ahPmIUiyHca9VNO8OfarbQ1Q==", + "version": "9.5.4", + "resolved": "https://registry.npmjs.org/@serwist/next/-/next-9.5.4.tgz", + "integrity": "sha512-bkPkvMs4GfNV3C3tSZg9O5mb+B7Rq8zLpkZNF2hV96zep6d/ujA7yBp8o8bD1rmLb7I++ifsIMQzH2a+vVc/ag==", "license": "MIT", "dependencies": { - "@serwist/build": "9.0.13", - "@serwist/webpack-plugin": "9.0.13", - "@serwist/window": "9.0.13", - "chalk": "5.4.1", - "glob": "10.4.5", - "serwist": "9.0.13", - "zod": "3.24.2" + "@serwist/build": "9.5.4", + "@serwist/utils": "9.5.4", + "@serwist/webpack-plugin": "9.5.4", + "@serwist/window": "9.5.4", + "browserslist": "4.28.1", + "glob": "10.5.0", + "kolorist": "1.8.0", + "semver": "7.7.3", + "serwist": "9.5.4", + "zod": "4.3.6" }, "engines": { "node": ">=18.0.0" }, "peerDependencies": { + "@serwist/cli": "^9.5.4", "next": ">=14.0.0", + "react": ">=18.0.0", "typescript": ">=5.0.0" }, "peerDependenciesMeta": { + "@serwist/cli": { + "optional": true + }, "typescript": { "optional": true } } }, "node_modules/@serwist/next/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, - "node_modules/@serwist/next/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/@serwist/next/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -2499,6 +2897,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -2509,15 +2908,30 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@serwist/utils": { + "version": "9.5.4", + "resolved": "https://registry.npmjs.org/@serwist/utils/-/utils-9.5.4.tgz", + "integrity": "sha512-uyriGQF1qjNEHXXfsd8XJ5kfK3/MezEaUw//XdHjZeJ0LvLamrgnLJGQQoyJqUfEPCiJ4jJwc4uYMB9LjLiHxA==", + "license": "MIT", + "peerDependencies": { + "browserslist": ">=4" + }, + "peerDependenciesMeta": { + "browserslist": { + "optional": true + } + } + }, "node_modules/@serwist/webpack-plugin": { - "version": "9.0.13", - "resolved": "https://registry.npmjs.org/@serwist/webpack-plugin/-/webpack-plugin-9.0.13.tgz", - "integrity": "sha512-Z+Eve8dckM2FulRCa7Cj7VCF3EOP7QkRA76tI742olF7J2sYZSm3t9Ex13jDxTMmYiU1AxLq6V6gEMIdRAetVw==", + "version": "9.5.4", + "resolved": "https://registry.npmjs.org/@serwist/webpack-plugin/-/webpack-plugin-9.5.4.tgz", + "integrity": "sha512-Zmrce/fuKIBYPOFlCSutIk2rE3949ihFjrXiiR3SNOhnBhxYDOcf6oz3zXFQDLAaih8wdjDL4PlzPEqKg1OmJA==", "license": "MIT", "dependencies": { - "@serwist/build": "9.0.13", + "@serwist/build": "9.5.4", + "@serwist/utils": "9.5.4", "pretty-bytes": "6.1.1", - "zod": "3.24.2" + "zod": "4.3.6" }, "engines": { "node": ">=18.0.0" @@ -2536,13 +2950,13 @@ } }, "node_modules/@serwist/window": { - "version": "9.0.13", - "resolved": "https://registry.npmjs.org/@serwist/window/-/window-9.0.13.tgz", - "integrity": "sha512-Cf3RizPuFInDcLt0P1Y5QzG1sA5mW131/PZfMYE3yBuNUSGNgOQGlYuLdwDOWPHgECYoVb/da8pspdQNKs0O5g==", + "version": "9.5.4", + "resolved": "https://registry.npmjs.org/@serwist/window/-/window-9.5.4.tgz", + "integrity": "sha512-52t2G+TgiWDdRwGG0ArU28uy6/oQYICQfNLHs4ywybyS6mHy3BxHFl+JjB5vhg8znIG1LMpGvOmS5b7AuPVYDw==", "license": "MIT", "dependencies": { "@types/trusted-types": "2.0.7", - "serwist": "9.0.13" + "serwist": "9.5.4" }, "peerDependencies": { "typescript": ">=5.0.0" @@ -2582,11 +2996,6 @@ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" - }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -3648,6 +4057,15 @@ "zxing-wasm": "^2.0.1" } }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -3693,9 +4111,10 @@ "integrity": "sha512-N/toHA87JcmAHPqU8Qt7YnhFK6W2WUpdq5M1k/JqLdTqtts7sHEMZhFjFWTvvR2poKF7Qki0qknhIPIr5I7TIQ==" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3721,10 +4140,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", - "dev": true, + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "funding": [ { "type": "opencollective", @@ -3741,10 +4159,11 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -3788,17 +4207,6 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -3844,9 +4252,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001703", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001703.tgz", - "integrity": "sha512-kRlAGTRWgPsOj7oARC9m1okJEXdL/8fekFVcxA8Hl7GH4r/sN4OJn/i6Flde373T50KS7Y37oFbMwlE8+F42kQ==", + "version": "1.0.30001768", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001768.tgz", + "integrity": "sha512-qY3aDRZC5nWPgHUgIB84WL+nySuo19wk0VJpp/XI9T34lrvkyhRvNVOFJOp2kxClQhiFBu+TaUSudf6oa3vkSA==", "funding": [ { "type": "opencollective", @@ -4047,20 +4455,6 @@ "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", "dev": true }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "license": "MIT", - "optional": true, - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4077,17 +4471,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "license": "MIT", - "optional": true, - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -4352,15 +4735,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/decache": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/decache/-/decache-3.1.0.tgz", - "integrity": "sha512-p7D6wJ5EJFFq1CcF2lu1XeqKFLBob8jRQGNAvFLTsV3CbSKBl3VtliAVlUIGz2i9H6kEFnI2Amaft5ZopIG2Fw==", - "optional": true, - "dependencies": { - "find": "^0.2.4" - } - }, "node_modules/dedent": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", @@ -4448,9 +4822,9 @@ } }, "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", "optional": true, "engines": { @@ -4482,9 +4856,10 @@ "dev": true }, "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } @@ -4620,10 +4995,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.114", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.114.tgz", - "integrity": "sha512-DFptFef3iktoKlFQK/afbo274/XNWD00Am0xa7M8FZUepHlHT8PEuiNBoRfFHbH1okqN58AlhbJ4QTkcnXorjA==", - "dev": true, + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", "license": "ISC" }, "node_modules/emittery": { @@ -4901,7 +5275,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "engines": { "node": ">=6" } @@ -5193,9 +5566,9 @@ } }, "node_modules/eslint-config-next/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -5747,10 +6120,11 @@ } }, "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -5778,15 +6152,6 @@ "node": ">=8" } }, - "node_modules/find": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/find/-/find-0.2.9.tgz", - "integrity": "sha512-7a4/LCiInB9xYMnAUEjLilL9FKclwbwK7VlXw+h5jMvT2TDFeYFCHM24O1XdnC/on/hx8mxVO3FTQkyHZnOghQ==", - "optional": true, - "dependencies": { - "traverse-chain": "~0.1.0" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -5852,6 +6217,20 @@ "node": "*" } }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, "node_modules/formidable": { "version": "3.5.4", "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", @@ -6295,9 +6674,9 @@ } }, "node_modules/idb": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/idb/-/idb-8.0.2.tgz", - "integrity": "sha512-CX70rYhx7GDDQzwwQMDwF6kDRQi5vVs6khHUumDrMecBylKkwvZ8HWvKV08AGb7VbpoGCWUQ4aHzNDgoUiOIUg==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/idb/-/idb-8.0.3.tgz", + "integrity": "sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==", "license": "ISC" }, "node_modules/idb-wrapper": { @@ -6394,10 +6773,14 @@ "loose-envify": "^1.0.0" } }, - "node_modules/ip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", - "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==" + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", + "engines": { + "node": ">= 12" + } }, "node_modules/is-array-buffer": { "version": "3.0.4", @@ -7847,9 +8230,9 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -7966,6 +8349,12 @@ "node": ">=6" } }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "license": "MIT" + }, "node_modules/language-subtag-registry": { "version": "0.3.23", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", @@ -8057,9 +8446,10 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" }, "node_modules/lodash.memoize": { "version": "4.1.2", @@ -8359,15 +8749,13 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" }, "node_modules/next": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/next/-/next-15.2.4.tgz", - "integrity": "sha512-VwL+LAaPSxEkd3lU2xWbgEOtrM8oedmyhBqaVNmgKB+GvZlCy9rgaEc+y2on0wv+l0oSFqLtYD6dcC1eAedUaQ==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.12.tgz", + "integrity": "sha512-Fi/wQ4Etlrn60rz78bebG1i1SR20QxvV8tVp6iJspjLUSHcZoeUXCt+vmWoEcza85ElZzExK/jJ/F6SvtGktjA==", "license": "MIT", "dependencies": { - "@next/env": "15.2.4", - "@swc/counter": "0.1.3", + "@next/env": "15.5.12", "@swc/helpers": "0.5.15", - "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" @@ -8379,19 +8767,19 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.2.4", - "@next/swc-darwin-x64": "15.2.4", - "@next/swc-linux-arm64-gnu": "15.2.4", - "@next/swc-linux-arm64-musl": "15.2.4", - "@next/swc-linux-x64-gnu": "15.2.4", - "@next/swc-linux-x64-musl": "15.2.4", - "@next/swc-win32-arm64-msvc": "15.2.4", - "@next/swc-win32-x64-msvc": "15.2.4", - "sharp": "^0.33.5" + "@next/swc-darwin-arm64": "15.5.12", + "@next/swc-darwin-x64": "15.5.12", + "@next/swc-linux-arm64-gnu": "15.5.12", + "@next/swc-linux-arm64-musl": "15.5.12", + "@next/swc-linux-x64-gnu": "15.5.12", + "@next/swc-linux-x64-musl": "15.5.12", + "@next/swc-win32-arm64-msvc": "15.5.12", + "@next/swc-win32-x64-msvc": "15.5.12", + "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.41.2", + "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", @@ -8413,9 +8801,9 @@ } }, "node_modules/next-auth": { - "version": "4.24.11", - "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.11.tgz", - "integrity": "sha512-pCFXzIDQX7xmHFs4KVH4luCjaCbuPRtZ9oBUjUhOk84mZ9WVPf94n87TxYI4rSRf9HmfHEF8Yep3JrYDVOo3Cw==", + "version": "4.24.13", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.13.tgz", + "integrity": "sha512-sgObCfcfL7BzIK76SS5TnQtc3yo2Oifp/yIpfv6fMfeBOiBJkDWF3A2y9+yqnmJ4JKc2C+nMjSjmgDeTwgN1rQ==", "license": "ISC", "dependencies": { "@babel/runtime": "^7.20.13", @@ -8429,9 +8817,9 @@ "uuid": "^8.3.2" }, "peerDependencies": { - "@auth/core": "0.34.2", - "next": "^12.2.5 || ^13 || ^14 || ^15", - "nodemailer": "^6.6.5", + "@auth/core": "0.34.3", + "next": "^12.2.5 || ^13 || ^14 || ^15 || ^16", + "nodemailer": "^7.0.7", "react": "^17.0.2 || ^18 || ^19", "react-dom": "^17.0.2 || ^18 || ^19" }, @@ -8507,10 +8895,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "license": "MIT" }, "node_modules/node-walker": { @@ -8522,12 +8909,10 @@ } }, "node_modules/nodemailer": { - "version": "6.9.16", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz", - "integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==", + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.13.tgz", + "integrity": "sha512-PNDFSJdP+KFgdsG3ZzMXCgquO7I6McjY2vlqILjtJd0hy8wEvtugS9xKRF2NWlPNGxvLCXlTNIae4serI7dinw==", "license": "MIT-0", - "optional": true, - "peer": true, "engines": { "node": ">=6.0.0" } @@ -9026,13 +9411,13 @@ } }, "node_modules/playwright": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz", - "integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==", + "version": "1.58.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.1.tgz", + "integrity": "sha512-+2uTZHxSCcxjvGc5C891LrS1/NlxglGxzrC4seZiVjcYVQfUa87wBL6rTDqzGjuoWNjnBzRqKmF6zRYGMvQUaQ==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.52.0" + "playwright-core": "1.58.1" }, "bin": { "playwright": "cli.js" @@ -9045,9 +9430,9 @@ } }, "node_modules/playwright-core": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz", - "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==", + "version": "1.58.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.1.tgz", + "integrity": "sha512-bcWzOaTxcW+VOOGBCQgnaKToLJ65d6AqfLVKEWvexyS3AS6rbXl+xdpYRMGSRBClPvyj44njOWoxjNdL/H9UNg==", "devOptional": true, "license": "Apache-2.0", "bin": { @@ -9061,7 +9446,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -9780,19 +10164,6 @@ "resolved": "https://registry.npmjs.org/request-ip/-/request-ip-3.3.0.tgz", "integrity": "sha512-cA6Xh6e0fDBBBwH77SLJaJPBmD3nWVAcF9/XAcsrIHdjhFzFiB5aNQFytdjCGPezU3ROwrR11IddKAM08vohxA==" }, - "node_modules/request/node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, "node_modules/request/node_modules/uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", @@ -9901,9 +10272,10 @@ "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" }, "node_modules/rollbar": { - "version": "2.26.4", - "resolved": "https://registry.npmjs.org/rollbar/-/rollbar-2.26.4.tgz", - "integrity": "sha512-JKmrj6riYm9ZPJisgxljgH4uCsvjMHDHXrinDF7aAFaP+eoF51HomVPtLcDTYLsrJ568aKVNLUhedFajONBwSg==", + "version": "2.26.5", + "resolved": "https://registry.npmjs.org/rollbar/-/rollbar-2.26.5.tgz", + "integrity": "sha512-4Of0ALl5+CU2glyDy5dWMRRy9Ty81DrY2r46ucbqjtCikbgHoWJNGXbQUWpDaLxsc8Q71LT/yj1bPb9NHbJIFQ==", + "license": "MIT", "dependencies": { "async": "~3.2.3", "console-polyfill": "0.3.0", @@ -9912,9 +10284,6 @@ "lru-cache": "~2.2.1", "request-ip": "~3.3.0", "source-map": "^0.5.7" - }, - "optionalDependencies": { - "decache": "^3.0.5" } }, "node_modules/rollbar/node_modules/lru-cache": { @@ -10036,9 +10405,9 @@ } }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -10048,12 +10417,13 @@ } }, "node_modules/serwist": { - "version": "9.0.13", - "resolved": "https://registry.npmjs.org/serwist/-/serwist-9.0.13.tgz", - "integrity": "sha512-BF3bmzYdOVT2lF3iHV0044NqTO6q6GAiqrYpc7L9EPYQXZHOy22WajKaHLvCdvpm2Jpji4SsxUL8/uC1WSCZ5g==", + "version": "9.5.4", + "resolved": "https://registry.npmjs.org/serwist/-/serwist-9.5.4.tgz", + "integrity": "sha512-uTHBzpIeA6rE3oyRt392MbtNQDs2JVZelKD1KkT18UkhX6HRwCeassoI1Nd1h52DqYqa7ZfBeldJ4awy+PYrnQ==", "license": "MIT", "dependencies": { - "idb": "8.0.2" + "@serwist/utils": "9.5.4", + "idb": "8.0.3" }, "peerDependencies": { "typescript": ">=5.0.0" @@ -10096,16 +10466,16 @@ } }, "node_modules/sharp": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", - "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", "hasInstallScript": true, "license": "Apache-2.0", "optional": true, "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.3", - "semver": "^7.6.3" + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" }, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" @@ -10114,25 +10484,30 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.5", - "@img/sharp-darwin-x64": "0.33.5", - "@img/sharp-libvips-darwin-arm64": "1.0.4", - "@img/sharp-libvips-darwin-x64": "1.0.4", - "@img/sharp-libvips-linux-arm": "1.0.5", - "@img/sharp-libvips-linux-arm64": "1.0.4", - "@img/sharp-libvips-linux-s390x": "1.0.4", - "@img/sharp-libvips-linux-x64": "1.0.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", - "@img/sharp-libvips-linuxmusl-x64": "1.0.4", - "@img/sharp-linux-arm": "0.33.5", - "@img/sharp-linux-arm64": "0.33.5", - "@img/sharp-linux-s390x": "0.33.5", - "@img/sharp-linux-x64": "0.33.5", - "@img/sharp-linuxmusl-arm64": "0.33.5", - "@img/sharp-linuxmusl-x64": "0.33.5", - "@img/sharp-wasm32": "0.33.5", - "@img/sharp-win32-ia32": "0.33.5", - "@img/sharp-win32-x64": "0.33.5" + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" } }, "node_modules/shebang-command": { @@ -10182,23 +10557,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "license": "MIT", - "optional": true, - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "license": "MIT", - "optional": true - }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -10250,15 +10608,16 @@ } }, "node_modules/socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "license": "MIT", "dependencies": { - "ip": "^2.0.0", + "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" }, "engines": { - "node": ">= 10.13.0", + "node": ">= 10.0.0", "npm": ">= 3.0.0" } }, @@ -10349,14 +10708,6 @@ "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -10629,19 +10980,22 @@ } }, "node_modules/sucrase/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/sucrase/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, + "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -10794,15 +11148,6 @@ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -10837,12 +11182,6 @@ "node": ">=12" } }, - "node_modules/traverse-chain": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz", - "integrity": "sha512-up6Yvai4PYKhpNp5PkYtx50m3KbwQrqDwbuZP/ItyL64YEWHAvH6Md83LFLV/GRSk/BoUVwwgUzX6SOQSbsfAg==", - "optional": true - }, "node_modules/ts-api-utils": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", @@ -11190,10 +11529,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "funding": [ { "type": "opencollective", @@ -11657,9 +11995,9 @@ } }, "node_modules/zod": { - "version": "3.24.2", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", - "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 93953def..118cde82 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "next": "^15.2.4", "next-auth": "^4.24.11", "next-seo": "^6.6.0", + "nodemailer": "^7.0.13", "omit-call-signature": "^1.0.15", "react": "18.3.1", "react-beautiful-dnd": "^13.1.1", @@ -79,7 +80,7 @@ "jest": "^29.7.0", "postcss": "^8.5.3", "prettier": "3.5.3", - "serwist": "^9.0.11", + "serwist": "^9.0.13", "tailwindcss": "^3.4.17", "ts-jest": "^29.2.5" } From 70813ec1ab9e812457edfe383cecec1258871c1d Mon Sep 17 00:00:00 2001 From: Cullen Date: Wed, 4 Mar 2026 20:52:39 -0500 Subject: [PATCH 158/168] 3/4 --- lib/Enums.ts | 14 +++ lib/client/StatsMath.ts | 6 +- lib/games.ts | 244 +++++++++------------------------------- 3 files changed, 72 insertions(+), 192 deletions(-) diff --git a/lib/Enums.ts b/lib/Enums.ts index 20669b16..9e4361b3 100644 --- a/lib/Enums.ts +++ b/lib/Enums.ts @@ -143,4 +143,18 @@ export namespace RebuiltEnums { ScoreOneOrMoreFuel = "Score One Or More Fuel", ScoreFuelAndClimb = "Score Fuel And Climb", } + export enum OffenceDriverSkill{ + LevelOne = "One", + LevelTwo = "Two", + LevelThree = "Three", + LevelFour = "Four", + LevelFive = "Five", + } + export enum DefenceDriverSkill { + LevelOne = "One", + LevelTwo = "Two", + LevelThree = "Three", + LevelFour = "Four", + LevelFive = "Five", + } } diff --git a/lib/client/StatsMath.ts b/lib/client/StatsMath.ts index 6fd009f7..036edcfb 100644 --- a/lib/client/StatsMath.ts +++ b/lib/client/StatsMath.ts @@ -153,9 +153,9 @@ export function GetMinimum( stat: string, ) { if (!quantitativeReports) return 0; - let minimum = quantitativeReports[0].data[stat]; + let minimum = Number(quantitativeReports[0].data[stat]); for (let repo of quantitativeReports) { - if (repo.data[stat] < minimum) { + if (Number(repo.data[stat]) < minimum) { minimum = repo.data[stat]; } } @@ -170,7 +170,7 @@ export function GetMaximum( if (!quantitativeReports) return 0; let maximum = 0; for (let repo of quantitativeReports) { - if (repo.data[stat] > maximum) { + if (Number(repo.data[stat]) > maximum) { maximum = repo.data[stat]; } } diff --git a/lib/games.ts b/lib/games.ts index ffcf2ca2..9245a863 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -34,7 +34,7 @@ import { import { report } from "process"; import { GetMaximum } from "./client/StatsMath"; import { getMaxListeners } from "events"; -//import { ClimbingCapabilities, ClimbingAbilities } from './Enums'; +//import { ClimbingCapabilities, ClimbingAbilities, DriverSkillLevel } from './Enums'; function getBaseBadges( pitReport: Pitreport | undefined, @@ -1893,18 +1893,19 @@ namespace Reefscape { export namespace Rebuilt { export class QuantitativeData extends QuantData { - AutoScoredOnePoint: number = 0; - AutoScoredFivePoint: number = 0; - AutoScoredTenPoint: number = 0; + TotalAllianceFuelPoints: number = 0; + PercentagePointsScored: number = 0; + + AutoCycles: number = 0; AutoTotalScored: number = 0; AutoClimbedLevelOne: boolean = false; - TeleopScoredOnePoint: number = 0; - TeleopScoredFivePoint: number = 0; - TeleopScoredTenPoint: number = 0; - TeleopTotalScored: number = 0; EngameDefenseStatus: Defense = Defense.None; LevelClimbed: RebuiltEnums.LevelClimbed = RebuiltEnums.LevelClimbed.No; + OffenceDriverSkill: RebuiltEnums.OffenceDriverSkill = + RebuiltEnums.OffenceDriverSkill.LevelOne; + DefenceDriverSkill: RebuiltEnums.DefenceDriverSkill = + RebuiltEnums.DefenceDriverSkill.LevelOne; } export class PitData extends PitReportData { GroundIntake: boolean = false; @@ -1912,7 +1913,7 @@ export namespace Rebuilt { CanDriveUnderTrench: boolean = false; CanDeClimb: boolean = false; CanScoreFuel: boolean = false; - FuelScoredAuto: number = 0; + HopperVolume: number = 0; AutoAbilities: RebuiltEnums.AutoAbilities = RebuiltEnums.AutoAbilities.NoAuto; ClimbingAbilities: RebuiltEnums.ClimbingAbilities = @@ -1926,181 +1927,83 @@ export namespace Rebuilt { { key: "CanDeClimb", label: "Can De-Climb?" }, { key: "CanScoreFuel", label: "Can Score Fuel?" }, { key: "ClimbingAbilites", label: "Climbing?" }, + { key: "HopperVolume", label: "Hopper Volume?" }, ], "Auto (Describe more in comments)": [ { key: "AutoAbilities", label: "Auto Capabilities?" }, - { key: "FuelScoredAuto", label: "Average Fuel Scored In Auto" }, ], }; const quantitativeReportLayout: FormLayoutProps = { Auto: [ - [ - [ - { key: "AutoScoredOnePoint", label: "1 Point Scored (Auto)" }, - { key: "AutoScoredFivePoint", label: "5 Point Scared (Auto)" }, - { key: "AutoScoredTenPoint", label: "10 Point Scored (Auto)" }, - ], - ], + /*[[{ key: "AutoCycles", label: "Number Of Cycles In Auto" }]],*/ { key: "AutoClimbedLevelOne", label: "Climbed Level One (Auto)" }, ], Teleop: [ [ [ - { key: "TeleopScoredOnePoint", label: "1 Point Scored (Teleop)" }, - { key: "TeleopScoredFivePoint", label: "5 Point Scored (Teleop)" }, - { key: "TeleopScoredTenPoint", label: "10 Point Scored (Teleop)" }, + { + key: "TotalAllianceFuelPoints", + label: "Fuel Scored By Allience(Must Be Just A Whole Number)", + }, + { + key: "PercentagePointsScored", + label: "Estimated Percentage Of Points(Must Be Just A Whole Number)", + }, ], ], ], - "Post Match": ["LevelClimbed", "Defense"], + "Post Match": [ + "LevelClimbed", + "Defense", + "OffenceDriverSkill", + "DefenceDriverSkill", + { + key: "EstimatedHopperVolume", + label: "Estamate Of Fuel The Hopper Can Hold?", + }, + { + key: "EstimatedFuelMissed", + label: "Estamate Of Fuel Missed Per Cycle", + }, + ], }; - function getTotalFuel(reports: Report[] | undefined) { - if (!reports) return 0; - - for (const report of reports.map((r) => r.data)) { - report.TeleopTotalScored += report.TeleopScoredOnePoint; - report.TeleopTotalScored += report.TeleopScoredFivePoint * 5; - report.TeleopTotalScored += report.TeleopScoredTenPoint * 10; - report.AutoTotalScored += report.AutoScoredOnePoint; - report.AutoTotalScored += report.AutoScoredFivePoint * 5; - report.AutoTotalScored += report.AutoScoredFivePoint * 10; - } - } - getTotalFuel const statsLayout: StatsLayout = { sections: { Auto: [ - { - key: "AutoScoredOnePoint", - label: "Avg Amt Of Fuel Scored In Increments Of One Auto", - }, - { - label: "> Min Auto One Point Fuel", - get(pitData, quantitativeReports) { - return GetMinimum(quantitativeReports!, "AutoScoredOnePoint"); - }, - }, - { - label: "> Max Auto One Point Fuel", - get(pitData, quantitativeReports) { - return GetMaximum(quantitativeReports!, "AutoScoredOnePoint"); - }, - }, - { - key: "AutoScoredFivePoint", - label: "Avg Amt Of Fuel Scored In Increments Of Five Auto", - }, - { - label: "> Min Auto Five Point Fuel", - get(pitData, quantitativeReports) { - return GetMinimum(quantitativeReports!, "AutoScoredFivePoint"); - }, - }, - { - label: "> Max Auto five Point Fuel", - get(pitData, quantitativeReports) { - return GetMaximum(quantitativeReports!, "AutoScoredFivePoint"); - }, - }, - { - key: "AutoScoredTenPoint", - label: "Avg Amt Of Fuel Scored In Increments Of Ten Auto", - }, - { - label: "> Min Auto Ten Point Fuel", - get(pitData, quantitativeReports) { - return GetMinimum(quantitativeReports!, "autoScoredTenPoint"); - }, - }, - { - label: "> Max Auto Ten Point Fuel", - get(pitData, quantitativeReports) { - return GetMaximum(quantitativeReports!, "autoScoredTenPoint"); - }, - }, - { - label: "Average Auto Fuel", - get(pitData, quantitativeReports) { - if (!quantitativeReports) return 0; - - return ( - quantitativeReports?.reduce( - (acc, report) => - acc + - report.data.AutoScoredOnePoint + - report.data.AutoScoredFivePoint + - report.data.AutoScoredTenPoint, - 0, - ) / quantitativeReports?.length - ); - }, - }, ], + Teleop: [ { - key: "TeleopScoredOnePoint", - label: "Teleop Scored One Point Fuel", - }, - { - label: "< Min Teleop Scored One Point Fuel", - get(pitData, quantitativeReports) { - return GetMinimum(quantitativeReports!, "TeleopScoredOnePoint"); - }, - }, - { - label: "< Max Teleop Scored One Point Fuel", - get(pitData, quantitativeReports) { - return GetMaximum(quantitativeReports!, "TeleopScoredOnePoint"); - }, - }, - { - key: "TeleopScoredFivePoint", - label: "Teleop Scored Five Point Fuel", + key: "TotalAllianceFuelPoints", + label: "Total Fuel Points Scored By Alliance", }, { - label: "< Min Teleop Scored Five Point Fuel", + label: "< Min Fuel Points Scored By Alliance", get(pitData, quantitativeReports) { - return GetMinimum(quantitativeReports!, "TeleopScoredFivePoint"); + return GetMinimum(quantitativeReports!, "TotalAllianceFuelPoints"); }, }, { - label: "< Maximum Teleop Scored Five Point Fuel", + label: "< Max Fuel Points Scored By Alliance", get(pitData, quantitativeReports) { - return GetMaximum(quantitativeReports!, "TeleopScoredFivePoint"); + return GetMaximum(quantitativeReports!, "TotalAllianceFuelPoints"); }, }, { - key: "TeleopScoredTenPoint", - label: "Teleop Scored Ten Point Fuel", - }, - { - label: "< Min Teleop Scored Ten Point Fuel", - get(pitData, quantitativeReports) { - return GetMinimum(quantitativeReports!, "TeleopScoredTenPoint"); - }, + key: "PercentagePointsScored", + label: "Estamated Percentage Points Scored By Team", }, { - label: "< Maximum Teleop Scored Ten Point Fuel", + label: "< Min Estamated Percentage Points Scored By Team", get(pitData, quantitativeReports) { - return GetMaximum(quantitativeReports!, "TeleopScoredTenPoint"); + return GetMinimum(quantitativeReports!, "PercentagePointsScored"); }, }, { - label: "Average Teleop Fuel", + label: "< Max Estamated Percentage Points Scored By Team", get(pitData, quantitativeReports) { - if (!quantitativeReports) return 0; - - return ( - quantitativeReports?.reduce( - (acc, report) => - acc + - report.data.TeleopScoredOnePoint + - report.data.TeleopScoredFivePoint + - report.data.TeleopScoredTenPoint, - 0, - ) / quantitativeReports?.length - ); + return GetMaximum(quantitativeReports!, "PercentagePointsScored"); }, }, ], @@ -2114,60 +2017,26 @@ export namespace Rebuilt { }; const pitStatsLayout: PitStatsLayout = { overallSlideStats: [ - { - key: "FuelScoredAuto", - label: "Amount of fuel scored in auto", - }, ], individualSlideStats: [ { - label: "Average Auto Points", - get: ( - pitReport: Pitreport | undefined, - quantitativeReports: Report[] | undefined, - ) => { - if (!quantitativeReports) return 0; - - const OneFuelScored = NumericalTotal( - "AutoScoredOnePoint", - quantitativeReports, - ); - const FiveFuelScored = NumericalTotal( - "AutoScoredFivePoint", - quantitativeReports, - ); - const TenFuelScored = NumericalTotal( - "AutoScoredTenPoint", - quantitativeReports, - ); - return ( - (OneFuelScored + FiveFuelScored + TenFuelScored) / - quantitativeReports.length - ); - }, - }, - { - label: "Average Teleop Points", + label: "Average Points", get: ( pitReport: Pitreport | undefined, quantitativeReports: Report[] | undefined, ) => { if (!quantitativeReports) return 0; - const TeleopOneFuelScored = NumericalTotal( - "TeleopScoredOnePoint", + const TotalAllianceFuelPoints = NumericalTotal( + "TotalAllianceFuelPoints", quantitativeReports, ); - const TeleopFiveFuelScored = NumericalTotal( - "TeleopScoredFivePoint", - quantitativeReports, - ); - const TeleopTenFuelScored = NumericalTotal( - "TeleopScoredTenPoint", + const PercentagePointsScored = NumericalTotal( + "PercentagePointsScored", quantitativeReports, ); return ( - (TeleopOneFuelScored + TeleopFiveFuelScored + TeleopTenFuelScored) / + (TotalAllianceFuelPoints) / quantitativeReports.length ); }, @@ -2252,11 +2121,9 @@ export namespace Rebuilt { totalPoints += 30; break; } - totalPoints += report.AutoScoredOnePoint + report.TeleopScoredOnePoint; - totalPoints += - (report.AutoScoredFivePoint + report.TeleopScoredFivePoint) * 5; totalPoints += - (report.AutoScoredTenPoint + report.TeleopScoredTenPoint) * 10; + Number(report.TotalAllianceFuelPoints) * + (Number(report.PercentagePointsScored)/10); } return totalPoints / Math.max(reports.length, 1); } @@ -2266,7 +2133,6 @@ export namespace Rebuilt { League.FRC, QuantitativeData, PitData, - //getTotalFuel, pitReportLayout, quantitativeReportLayout, statsLayout, From d9b35006b6f99f3e72156363850db59da7a84f60 Mon Sep 17 00:00:00 2001 From: Cullen Date: Wed, 4 Mar 2026 21:24:33 -0500 Subject: [PATCH 159/168] new 3/4 --- lib/client/StatsMath.ts | 8 +++++--- lib/games.ts | 44 +++++++++++++++++++---------------------- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/lib/client/StatsMath.ts b/lib/client/StatsMath.ts index 036edcfb..44a027c2 100644 --- a/lib/client/StatsMath.ts +++ b/lib/client/StatsMath.ts @@ -44,7 +44,7 @@ export function NumericalTotal( reports: Report[], ) { let sum = 0; - reports?.forEach((report) => (sum += getSelection(selector, report))); + reports?.forEach((report) => (sum += Number(getSelection(selector, report) || 0))); return Round(sum); } @@ -156,8 +156,10 @@ export function GetMinimum( let minimum = Number(quantitativeReports[0].data[stat]); for (let repo of quantitativeReports) { if (Number(repo.data[stat]) < minimum) { - minimum = repo.data[stat]; + minimum = Number(repo.data[stat]); + } + } return minimum; } @@ -171,7 +173,7 @@ export function GetMaximum( let maximum = 0; for (let repo of quantitativeReports) { if (Number(repo.data[stat]) > maximum) { - maximum = repo.data[stat]; + maximum = Number(repo.data[stat]); } } return maximum; diff --git a/lib/games.ts b/lib/games.ts index 9245a863..a9f6d8d9 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1893,11 +1893,9 @@ namespace Reefscape { export namespace Rebuilt { export class QuantitativeData extends QuantData { - TotalAllianceFuelPoints: number = 0; - PercentagePointsScored: number = 0; + TotalAllianceFuelPoints: Number = 0; + PercentagePointsScored: Number = 0; - AutoCycles: number = 0; - AutoTotalScored: number = 0; AutoClimbedLevelOne: boolean = false; EngameDefenseStatus: Defense = Defense.None; @@ -1947,7 +1945,8 @@ export namespace Rebuilt { }, { key: "PercentagePointsScored", - label: "Estimated Percentage Of Points(Must Be Just A Whole Number)", + label: + "Estimated Percentage Of Points(Must Be Just A Whole Number)", }, ], ], @@ -1957,26 +1956,22 @@ export namespace Rebuilt { "Defense", "OffenceDriverSkill", "DefenceDriverSkill", - { - key: "EstimatedHopperVolume", - label: "Estamate Of Fuel The Hopper Can Hold?", - }, - { - key: "EstimatedFuelMissed", - label: "Estamate Of Fuel Missed Per Cycle", - }, ], }; const statsLayout: StatsLayout = { sections: { - Auto: [ - ], + Auto: [], Teleop: [ { - key: "TotalAllianceFuelPoints", label: "Total Fuel Points Scored By Alliance", + get(pitData, quantitativeReports) { + return NumericalTotal( + "TotalAllianceFuelPoints", + quantitativeReports!, + ); + }, }, { label: "< Min Fuel Points Scored By Alliance", @@ -1991,8 +1986,13 @@ export namespace Rebuilt { }, }, { - key: "PercentagePointsScored", label: "Estamated Percentage Points Scored By Team", + get(pitData, quantitativeReports) { + return NumericalTotal( + "PercentagePointsScored", + quantitativeReports!, + ); + }, }, { label: "< Min Estamated Percentage Points Scored By Team", @@ -2016,8 +2016,7 @@ export namespace Rebuilt { }, }; const pitStatsLayout: PitStatsLayout = { - overallSlideStats: [ - ], + overallSlideStats: [], individualSlideStats: [ { label: "Average Points", @@ -2035,10 +2034,7 @@ export namespace Rebuilt { "PercentagePointsScored", quantitativeReports, ); - return ( - (TotalAllianceFuelPoints) / - quantitativeReports.length - ); + return TotalAllianceFuelPoints / quantitativeReports.length; }, }, { @@ -2123,7 +2119,7 @@ export namespace Rebuilt { } totalPoints += Number(report.TotalAllianceFuelPoints) * - (Number(report.PercentagePointsScored)/10); + (Number(report.PercentagePointsScored) / 10); } return totalPoints / Math.max(reports.length, 1); } From 787adb9f8da3fda75d58811cb9d637a211677e43 Mon Sep 17 00:00:00 2001 From: Cullen Date: Thu, 5 Mar 2026 16:13:01 -0500 Subject: [PATCH 160/168] 3/5 --- lib/Gearbox.code-workspace | 9 +++++++++ lib/games.ts | 7 ++++--- 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 lib/Gearbox.code-workspace diff --git a/lib/Gearbox.code-workspace b/lib/Gearbox.code-workspace new file mode 100644 index 00000000..283f6185 --- /dev/null +++ b/lib/Gearbox.code-workspace @@ -0,0 +1,9 @@ +{ + "folders": [ + { + "name": "Gearbox", + "path": ".." + } + ], + "settings": {} +} \ No newline at end of file diff --git a/lib/games.ts b/lib/games.ts index a9f6d8d9..cd6cd9a7 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1941,12 +1941,13 @@ export namespace Rebuilt { [ { key: "TotalAllianceFuelPoints", - label: "Fuel Scored By Allience(Must Be Just A Whole Number)", + label: "Fuel Scored By Allience", + type: "number", }, { key: "PercentagePointsScored", - label: - "Estimated Percentage Of Points(Must Be Just A Whole Number)", + label: "Estimated Percentage Of Points", + type: "number", }, ], ], From e7dab61490a25ba7f4dba05bcfb1d5a534edd833 Mon Sep 17 00:00:00 2001 From: Tr01ler Date: Thu, 5 Mar 2026 16:51:42 -0500 Subject: [PATCH 161/168] Made branch FRC-Rebuilt --- package-lock.json | 8 ++--- package.json | 2 +- tsconfig.json | 83 ++++++++++++++++++++++++++--------------------- 3 files changed, 51 insertions(+), 42 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9d3d3dc2..96f54ad1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "next": "^15.2.6", "next-auth": "^4.24.11", "next-seo": "^6.6.0", - "nodemailer": "^7.0.12", + "nodemailer": "^7.0.13", "omit-call-signature": "^1.0.15", "react": "18.3.1", "react-beautiful-dnd": "^13.1.1", @@ -8895,9 +8895,9 @@ } }, "node_modules/nodemailer": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.12.tgz", - "integrity": "sha512-H+rnK5bX2Pi/6ms3sN4/jRQvYSMltV6vqup/0SFOrxYYY/qoNvhXPlYq3e+Pm9RFJRwrMGbMIwi81M4dxpomhA==", + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.13.tgz", + "integrity": "sha512-PNDFSJdP+KFgdsG3ZzMXCgquO7I6McjY2vlqILjtJd0hy8wEvtugS9xKRF2NWlPNGxvLCXlTNIae4serI7dinw==", "license": "MIT-0", "peer": true, "engines": { diff --git a/package.json b/package.json index 41c2f309..d53e168e 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "next": "^15.2.6", "next-auth": "^4.24.11", "next-seo": "^6.6.0", - "nodemailer": "^7.0.12", + "nodemailer": "^7.0.13", "omit-call-signature": "^1.0.15", "react": "18.3.1", "react-beautiful-dnd": "^13.1.1", diff --git a/tsconfig.json b/tsconfig.json index 8dff24bc..c9757eed 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,39 +1,48 @@ { - "compilerOptions": { - "target": "es5", - "lib": ["dom", "dom.iterable", "esnext", "webworker"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - "downlevelIteration": true, - "paths": { - "@/*": ["./*"] - } - }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - "pages/createTeam.tsx", - "pages/[teamSlug]/createSeason.ksx", - "pages/index.tsx", - "pages/profile.jsx", - "pages/[teamSlug]/index.jsx", - "pages/[teamSlug]/[seasonSlug]/index.jsx", - "pages/[teamSlug]/[seasonSlug]/[competitonSlug]/index.jsx", - "lib/client/getSession.js", - "index.ts", - "pages/_error.js", - "tests/lib/client/InputVerification.test.ts" - ], - "exclude": ["node_modules"] + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext", + "webworker" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "downlevelIteration": true, + "paths": { + "@/*": [ + "./*" + ] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + "pages/createTeam.tsx", + "pages/[teamSlug]/createSeason.ksx", + "pages/index.tsx", + "pages/profile.jsx", + "pages/[teamSlug]/index.jsx", + "pages/[teamSlug]/[seasonSlug]/index.jsx", + "pages/[teamSlug]/[seasonSlug]/[competitonSlug]/index.jsx", + "lib/client/getSession.js", + "index.ts", + "pages/_error.js", + "tests/lib/client/InputVerification.test.ts" + ], + "exclude": [ + "node_modules" + ] } From 8f5ee206a9fc6e299832ab300a52663391357f13 Mon Sep 17 00:00:00 2001 From: Gearbox Bot Date: Thu, 5 Mar 2026 22:01:13 +0000 Subject: [PATCH 162/168] 1.3.6 --- package-lock.json | 451 +--------------------------------------------- package.json | 2 +- 2 files changed, 3 insertions(+), 450 deletions(-) diff --git a/package-lock.json b/package-lock.json index 46a6fbe4..05332676 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sj3", - "version": "1.3.5", + "version": "1.3.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sj3", - "version": "1.3.5", + "version": "1.3.6", "license": "CC BY-NC-SA 4.0", "dependencies": { "dependencies": "^0.0.1", @@ -1649,453 +1649,6 @@ "@img/sharp-libvips-linux-x64": "1.2.4" } }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", - "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", - "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.13.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", - "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", - "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@img/colour": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", - "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", - "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", - "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", - "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", - "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", - "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", - "cpu": [ - "arm" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", - "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", - "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", - "cpu": [ - "ppc64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", - "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", - "cpu": [ - "riscv64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", - "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", - "cpu": [ - "s390x" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", - "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", - "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", - "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", - "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", - "cpu": [ - "arm" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", - "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-ppc64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", - "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", - "cpu": [ - "ppc64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-riscv64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", - "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", - "cpu": [ - "riscv64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", - "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", - "cpu": [ - "s390x" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", - "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.4" - } - }, "node_modules/@img/sharp-linuxmusl-arm64": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", diff --git a/package.json b/package.json index 26e16061..3b42e51d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sj3", - "version": "1.3.5", + "version": "1.3.6", "private": true, "repository": "https://github.com/Decatur-Robotics/Gearbox", "license": "CC BY-NC-SA 4.0", From 4bf85145fd9ddfb2ea9392f269856f8ad080638c Mon Sep 17 00:00:00 2001 From: Tr01ler Date: Thu, 5 Mar 2026 19:08:09 -0500 Subject: [PATCH 163/168] Uh it almost works --- lib/Enums.ts | 17 +- lib/Layout.ts | 4 +- lib/games.ts | 160 +++++++++---------- pages/[teamSlug]/[seasonSlug]/createComp.tsx | 3 + 4 files changed, 88 insertions(+), 96 deletions(-) diff --git a/lib/Enums.ts b/lib/Enums.ts index 5eef3b60..cfadb311 100644 --- a/lib/Enums.ts +++ b/lib/Enums.ts @@ -144,19 +144,7 @@ export namespace RebuiltEnums { ScoreOneOrMoreFuel = "Score One Or More Fuel", ScoreFuelAndClimb = "Score Fuel And Climb", } - export enum OffenceDriverSkill{ - LevelOne = "One", - LevelTwo = "Two", - LevelThree = "Three", - LevelFour = "Four", - LevelFive = "Five", - } - export enum DefenceDriverSkill { - LevelOne = "One", - LevelTwo = "Two", - LevelThree = "Three", - LevelFour = "Four", - LevelFive = "Five", +} export namespace DecodeEnums { export enum EndgameParkStatus { @@ -166,10 +154,11 @@ export namespace DecodeEnums { TwoBotPark = "Two Bot Park", } - export enum AutoCapabilities { + export enum AutoStatus { NoAuto = "No Auto", MovePastStart = "Move Past Start", ScoreOneArtifact = "Score One Artifact", ScoreMultipleArtifacts = "Score Multiple Artifacts", } } + \ No newline at end of file diff --git a/lib/Layout.ts b/lib/Layout.ts index 03dde83f..e0135a6b 100644 --- a/lib/Layout.ts +++ b/lib/Layout.ts @@ -224,7 +224,7 @@ export function keyToType( RebuiltEnums.DriveOverBump, RebuiltEnums.DriveUnderTrench, RebuiltEnums.LevelClimbed, - DecodeEnums.AutoCapabilities, + DecodeEnums.AutoStatus, DecodeEnums.EndgameParkStatus, ]; @@ -247,7 +247,7 @@ export function keyToType( if (key == "LevelClimbed") return RebuiltEnums.LevelClimbed; if (key == "EndgameParkStatusDecode") return DecodeEnums.EndgameParkStatus; - if (key == "AutoAbilities") return DecodeEnums.AutoCapabilities; + if (key == "AutoStatus") return DecodeEnums.AutoStatus; for (const e of enums) { if (Object.values(e).includes(exampleData[key])) return e; diff --git a/lib/games.ts b/lib/games.ts index 9bb5e742..f23b993a 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1898,17 +1898,14 @@ namespace Reefscape { export namespace Rebuilt { export class QuantitativeData extends QuantData { - TotalAllianceFuelPoints: Number = 0; - PercentagePointsScored: Number = 0; + FuelPointsOne: number = 0; + FuelPointsFive: number = 0; + FuelPointsTen: number = 0; AutoClimbedLevelOne: boolean = false; EngameDefenseStatus: Defense = Defense.None; LevelClimbed: RebuiltEnums.LevelClimbed = RebuiltEnums.LevelClimbed.No; - OffenceDriverSkill: RebuiltEnums.OffenceDriverSkill = - RebuiltEnums.OffenceDriverSkill.LevelOne; - DefenceDriverSkill: RebuiltEnums.DefenceDriverSkill = - RebuiltEnums.DefenceDriverSkill.LevelOne; } export class PitData extends PitReportData { GroundIntake: boolean = false; @@ -1932,85 +1929,93 @@ export namespace Rebuilt { { key: "ClimbingAbilites", label: "Climbing?" }, { key: "HopperVolume", label: "Hopper Volume?" }, ], - "Auto (Describe more in comments)": [ + Auto: [ { key: "AutoAbilities", label: "Auto Capabilities?" }, ], }; const quantitativeReportLayout: FormLayoutProps = { Auto: [ - /*[[{ key: "AutoCycles", label: "Number Of Cycles In Auto" }]],*/ { key: "AutoClimbedLevelOne", label: "Climbed Level One (Auto)" }, - ], - Teleop: [ [ [ { - key: "TotalAllianceFuelPoints", - label: "Fuel Scored By Allience", - type: "number", + key: "FuelPointsOne", + label: "One point", + }, + ], + [ + { + key: "FuelPointsFive", + label: "Five points", }, + ], + [ { - key: "PercentagePointsScored", - label: "Estimated Percentage Of Points", - type: "number", + key: "FuelPointsTen", + label: "Ten points", }, ], ], ], - "Post Match": [ - "LevelClimbed", - "Defense", - "OffenceDriverSkill", - "DefenceDriverSkill", + Teleop: [ + [ + [ + { + key: "FuelPointsOne", + label: "One point", + }, + ], + [ + { + key: "FuelPointsFive", + label: "Five points", + }, + ], + [ + { + key: "FuelPointsTen", + label: "Ten points", + }, + ], + ], ], - }; + "Post Match": ["LevelClimbed", "Defense"], + }; const statsLayout: StatsLayout = { sections: { - Auto: [], + Auto: [], Teleop: [ { label: "Total Fuel Points Scored By Alliance", get(pitData, quantitativeReports) { - return NumericalTotal( - "TotalAllianceFuelPoints", - quantitativeReports!, + return ( + NumericalTotal("FuelPointsOne", quantitativeReports!) + + NumericalTotal("FuelPointsFive", quantitativeReports!) * 5 + + NumericalTotal("FuelPointsTen", quantitativeReports!) * 10 ); }, }, { label: "< Min Fuel Points Scored By Alliance", get(pitData, quantitativeReports) { - return GetMinimum(quantitativeReports!, "TotalAllianceFuelPoints"); + return ( + GetMinimum(quantitativeReports!, "FuelPointsOne") + + GetMinimum(quantitativeReports!, "FuelPointsFive") * 5 + + GetMinimum(quantitativeReports!, "FuelPointsTen") * 10 + ); }, }, { label: "< Max Fuel Points Scored By Alliance", get(pitData, quantitativeReports) { - return GetMaximum(quantitativeReports!, "TotalAllianceFuelPoints"); - }, - }, - { - label: "Estamated Percentage Points Scored By Team", - get(pitData, quantitativeReports) { - return NumericalTotal( - "PercentagePointsScored", - quantitativeReports!, + return ( + GetMaximum(quantitativeReports!, "FuelPointsOne") + + GetMaximum(quantitativeReports!, "FuelPointsFive") * 5 + + GetMaximum(quantitativeReports!, "FuelPointsTen") * 10 ); }, }, - { - label: "< Min Estamated Percentage Points Scored By Team", - get(pitData, quantitativeReports) { - return GetMinimum(quantitativeReports!, "PercentagePointsScored"); - }, - }, - { - label: "< Max Estamated Percentage Points Scored By Team", - get(pitData, quantitativeReports) { - return GetMaximum(quantitativeReports!, "PercentagePointsScored"); - }, - }, ], }, getGraphDots: function ( @@ -2020,44 +2025,40 @@ export namespace Rebuilt { return []; }, }; - - const pitStatsLayout: PitStatsLayout = { + + const pitStatsLayout: PitStatsLayout = { overallSlideStats: [], individualSlideStats: [ { label: "Average Points", - get: ( + get: ( pitReport: Pitreport | undefined, quantitativeReports: Report[] | undefined, ) => { - if (!quantitativeReports) return 0; + if (!quantitativeReports) return 0; - const TotalAllianceFuelPoints = NumericalTotal( - "TotalAllianceFuelPoints", - quantitativeReports, - ); - const PercentagePointsScored = NumericalTotal( - "PercentagePointsScored", - quantitativeReports, - ); + const TotalAllianceFuelPoints = + NumericalTotal("FuelPointsOne", quantitativeReports) + + NumericalTotal("FuelPointsFive", quantitativeReports) * 5 + + NumericalTotal("FuelPointsTen", quantitativeReports) * 10; return TotalAllianceFuelPoints / quantitativeReports.length; }, }, { label: "Ave Endgame Stats", - get: ( + get: ( pitReport: Pitreport | undefined, quantitativeReports: Report[] | undefined, ) => { - if (!quantitativeReports) return 0; + if (!quantitativeReports) return 0; const climb = NumericalTotal("LevelClimbed", quantitativeReports); return Round(climb) / quantitativeReports.length; - }, + }, }, ], robotCapabilities: [ - { key: "GroundIntake", label: "Has Ground Intake?" }, + { key: "GroundIntake", label: "Has Ground Intake?" }, { key: "CanDriveOverBump", label: "Can Drive Over Bump?" }, { key: "CanDriveUnderTrench", label: "Can Drive Under Trench?" }, { key: "CanDeClimb", label: "Can De-Climb?" }, @@ -2070,14 +2071,14 @@ export namespace Rebuilt { }, }; - function getBadges( + function getBadges( pitReport: Pitreport | undefined, quantitativeReports: Report[] | undefined, card: boolean, ) { const badges: Badge[] = getBaseBadges(pitReport, quantitativeReports); - - if (pitReport?.data?.GroundIntake) + + if (pitReport?.data?.GroundIntake) badges.push({ text: "Can Use Ground Intake", color: "primary" }); if (pitReport?.data?.CanDriveOverBump) badges.push({ text: "Can Drive Over Bump", color: "accent" }); @@ -2106,15 +2107,14 @@ export namespace Rebuilt { return badges; } - - function getAvgPoints(reports: Report[] | undefined) { + + function getAvgPoints(reports: Report[] | undefined) { if (!reports) return 0; let totalPoints = 0; for (const report of reports.map((r) => r.data)) { - - switch (report.LevelClimbed) { + switch (report.LevelClimbed) { case RebuiltEnums.LevelClimbed.No: break; case RebuiltEnums.LevelClimbed.First: @@ -2128,8 +2128,9 @@ export namespace Rebuilt { break; } totalPoints += - Number(report.TotalAllianceFuelPoints) * - (Number(report.PercentagePointsScored) / 10); + Number(report.FuelPointsOne) + + Number(report.FuelPointsFive) * 5 + + Number(report.FuelPointsTen) * 10; } return totalPoints / Math.max(reports.length, 1); } @@ -2137,21 +2138,20 @@ export namespace Rebuilt { "Rebuilt", 2026, League.FRC, - QuantitativeData, + QuantitativeData, PitData, pitReportLayout, quantitativeReportLayout, statsLayout, pitStatsLayout, - "Rebuilt", + "Rebuilt", "https://www.firstinspires.org/hs-fs/hubfs/image-library/web/frc_rebuilt_1240x860.webp?width=630", - "invert", //Ask Colin - getBadges, + "invert", + getBadges, getAvgPoints, ); } - export namespace Decode { export class QuantitativeData extends QuantData { AutoMovedPastStartingLine: boolean = false; @@ -2178,8 +2178,8 @@ export namespace Decode { ArtifactsScoredAuto: number = 0; AutoAccountsForMotif: boolean = false; - AutoAbilities: DecodeEnums.AutoCapabilities = - DecodeEnums.AutoCapabilities.NoAuto; + AutoAbilities: DecodeEnums.AutoStatus = + DecodeEnums.AutoStatus.NoAuto; } const pitReportLayout: FormLayoutProps = { diff --git a/pages/[teamSlug]/[seasonSlug]/createComp.tsx b/pages/[teamSlug]/[seasonSlug]/createComp.tsx index b8d52415..6bf87ea0 100644 --- a/pages/[teamSlug]/[seasonSlug]/createComp.tsx +++ b/pages/[teamSlug]/[seasonSlug]/createComp.tsx @@ -191,9 +191,12 @@ export const getServerSideProps: GetServerSideProps = async (context) => { return resolved; } + console.log(resolved); + return { props: { ...resolved, + season: serializeDatabaseObject(resolved.season), }, }; From a863d468bdf5ace52af2459f8e0768d0dba1338b Mon Sep 17 00:00:00 2001 From: Tr01ler Date: Thu, 5 Mar 2026 20:02:15 -0500 Subject: [PATCH 164/168] ctrl + S --- lib/Enums.ts | 1 - lib/Layout.ts | 2 +- lib/games.ts | 7 ++----- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/Enums.ts b/lib/Enums.ts index cfadb311..4086cde2 100644 --- a/lib/Enums.ts +++ b/lib/Enums.ts @@ -161,4 +161,3 @@ export namespace DecodeEnums { ScoreMultipleArtifacts = "Score Multiple Artifacts", } } - \ No newline at end of file diff --git a/lib/Layout.ts b/lib/Layout.ts index e0135a6b..e2c06760 100644 --- a/lib/Layout.ts +++ b/lib/Layout.ts @@ -245,7 +245,7 @@ export function keyToType( if (key == "DriveOverBump") return RebuiltEnums.DriveOverBump; if (key == "DiveUnderTrench") return RebuiltEnums.DriveUnderTrench; if (key == "LevelClimbed") return RebuiltEnums.LevelClimbed; - + if (key == "EndgameParkStatusDecode") return DecodeEnums.EndgameParkStatus; if (key == "AutoStatus") return DecodeEnums.AutoStatus; diff --git a/lib/games.ts b/lib/games.ts index f23b993a..b90efff9 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1929,9 +1929,7 @@ export namespace Rebuilt { { key: "ClimbingAbilites", label: "Climbing?" }, { key: "HopperVolume", label: "Hopper Volume?" }, ], - Auto: [ - { key: "AutoAbilities", label: "Auto Capabilities?" }, - ], + Auto: [{ key: "AutoAbilities", label: "Auto Capabilities?" }], }; const quantitativeReportLayout: FormLayoutProps = { Auto: [ @@ -2178,8 +2176,7 @@ export namespace Decode { ArtifactsScoredAuto: number = 0; AutoAccountsForMotif: boolean = false; - AutoAbilities: DecodeEnums.AutoStatus = - DecodeEnums.AutoStatus.NoAuto; + AutoAbilities: DecodeEnums.AutoStatus = DecodeEnums.AutoStatus.NoAuto; } const pitReportLayout: FormLayoutProps = { From 3a9311d9d23680cf6a3cb246ff17f7ed2afb1558 Mon Sep 17 00:00:00 2001 From: Tr01ler Date: Thu, 5 Mar 2026 20:05:16 -0500 Subject: [PATCH 165/168] Prettier go! --- lib/Gearbox.code-workspace | 8 +- lib/client/StatsMath.ts | 6 +- pages/[teamSlug]/[seasonSlug]/createComp.tsx | 2 +- tsconfig.json | 83 +++++++++----------- 4 files changed, 45 insertions(+), 54 deletions(-) diff --git a/lib/Gearbox.code-workspace b/lib/Gearbox.code-workspace index 283f6185..0268e976 100644 --- a/lib/Gearbox.code-workspace +++ b/lib/Gearbox.code-workspace @@ -2,8 +2,8 @@ "folders": [ { "name": "Gearbox", - "path": ".." - } + "path": "..", + }, ], - "settings": {} -} \ No newline at end of file + "settings": {}, +} diff --git a/lib/client/StatsMath.ts b/lib/client/StatsMath.ts index 53bdf6d2..21470c91 100644 --- a/lib/client/StatsMath.ts +++ b/lib/client/StatsMath.ts @@ -49,7 +49,9 @@ export function NumericalTotal( reports: Report[], ) { let sum = 0; - reports?.forEach((report) => (sum += Number(getSelection(selector, report) || 0))); + reports?.forEach( + (report) => (sum += Number(getSelection(selector, report) || 0)), + ); return Round(sum); } @@ -162,9 +164,7 @@ export function GetMinimum( for (let repo of quantitativeReports) { if (Number(repo.data[stat]) < minimum) { minimum = Number(repo.data[stat]); - } - } return minimum; } diff --git a/pages/[teamSlug]/[seasonSlug]/createComp.tsx b/pages/[teamSlug]/[seasonSlug]/createComp.tsx index 6bf87ea0..06fabf7b 100644 --- a/pages/[teamSlug]/[seasonSlug]/createComp.tsx +++ b/pages/[teamSlug]/[seasonSlug]/createComp.tsx @@ -196,7 +196,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => { return { props: { ...resolved, - + season: serializeDatabaseObject(resolved.season), }, }; diff --git a/tsconfig.json b/tsconfig.json index c9757eed..8dff24bc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,48 +1,39 @@ { - "compilerOptions": { - "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext", - "webworker" - ], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - "downlevelIteration": true, - "paths": { - "@/*": [ - "./*" - ] - } - }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - "pages/createTeam.tsx", - "pages/[teamSlug]/createSeason.ksx", - "pages/index.tsx", - "pages/profile.jsx", - "pages/[teamSlug]/index.jsx", - "pages/[teamSlug]/[seasonSlug]/index.jsx", - "pages/[teamSlug]/[seasonSlug]/[competitonSlug]/index.jsx", - "lib/client/getSession.js", - "index.ts", - "pages/_error.js", - "tests/lib/client/InputVerification.test.ts" - ], - "exclude": [ - "node_modules" - ] + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext", "webworker"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "downlevelIteration": true, + "paths": { + "@/*": ["./*"] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + "pages/createTeam.tsx", + "pages/[teamSlug]/createSeason.ksx", + "pages/index.tsx", + "pages/profile.jsx", + "pages/[teamSlug]/index.jsx", + "pages/[teamSlug]/[seasonSlug]/index.jsx", + "pages/[teamSlug]/[seasonSlug]/[competitonSlug]/index.jsx", + "lib/client/getSession.js", + "index.ts", + "pages/_error.js", + "tests/lib/client/InputVerification.test.ts" + ], + "exclude": ["node_modules"] } From 795a0da988d6fc11b021f95e9c71bc7eabf701b6 Mon Sep 17 00:00:00 2001 From: Cullen Date: Wed, 11 Mar 2026 15:59:15 -0400 Subject: [PATCH 166/168] 3/11 --- lib/games.ts | 123 +++++++++++++++++++++++++++++++++++----------- package-lock.json | 94 +++++++++++++++-------------------- 2 files changed, 133 insertions(+), 84 deletions(-) diff --git a/lib/games.ts b/lib/games.ts index b90efff9..72a8ff41 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1898,6 +1898,10 @@ namespace Reefscape { export namespace Rebuilt { export class QuantitativeData extends QuantData { + AutoFuelPointsOne: number =0; + AutoFuelPointsFive: number = 0; + AutoFuelPointsTen: number = 0; + FuelPointsOne: number = 0; FuelPointsFive: number = 0; FuelPointsTen: number = 0; @@ -1908,12 +1912,13 @@ export namespace Rebuilt { LevelClimbed: RebuiltEnums.LevelClimbed = RebuiltEnums.LevelClimbed.No; } export class PitData extends PitReportData { - GroundIntake: boolean = false; CanDriveOverBump: boolean = false; CanDriveUnderTrench: boolean = false; CanDeClimb: boolean = false; - CanScoreFuel: boolean = false; HopperVolume: number = 0; + RobotWeight: number = 0; + RobotWidth: number = 0; + RobotLength: number = 0; AutoAbilities: RebuiltEnums.AutoAbilities = RebuiltEnums.AutoAbilities.NoAuto; ClimbingAbilities: RebuiltEnums.ClimbingAbilities = @@ -1921,12 +1926,10 @@ export namespace Rebuilt { } const pitReportLayout: FormLayoutProps = { Capabilities: [ - { key: "GroundIntake", label: "Has Ground Intake?" }, { key: "CanDriveOverBump", label: "Can Drive Over Bump?" }, { key: "CanDriveUnderTrench", label: "Can Drive Under Trench?" }, { key: "CanDeClimb", label: "Can De-Climb?" }, - { key: "CanScoreFuel", label: "Can Score Fuel?" }, - { key: "ClimbingAbilites", label: "Climbing?" }, + { key: "ClimbingAbilities", label: "Climbing?" }, { key: "HopperVolume", label: "Hopper Volume?" }, ], Auto: [{ key: "AutoAbilities", label: "Auto Capabilities?" }], @@ -1937,20 +1940,20 @@ export namespace Rebuilt { [ [ { - key: "FuelPointsOne", - label: "One point", + key: "AutoFuelPointsOne", + label: "Auto One point", }, ], [ { - key: "FuelPointsFive", - label: "Five points", + key: "AutoFuelPointsFive", + label: "Auto Five points", }, ], [ { - key: "FuelPointsTen", - label: "Ten points", + key: "AutoFuelPointsTen", + label: "Auto Ten points", }, ], ], @@ -1960,29 +1963,60 @@ export namespace Rebuilt { [ { key: "FuelPointsOne", - label: "One point", + label: "Teleop One point", }, ], [ { key: "FuelPointsFive", - label: "Five points", + label: "Teleop Five points", }, ], [ { key: "FuelPointsTen", - label: "Ten points", + label: "TeleopTen points", }, ], ], ], - "Post Match": ["LevelClimbed", "Defense"], + "Post Match": ["LevelClimbed", "EngameDefenseStatus"], }; const statsLayout: StatsLayout = { sections: { - Auto: [], + Auto: [ + { + label: "Total Auto Fuel Points Scored By Alliance", + get(pitData, quantitativeReports) { + return ( + NumericalTotal("AutoFuelPointsOne", quantitativeReports!) + + NumericalTotal("AutoFuelPointsFive", quantitativeReports!) * 5 + + NumericalTotal("AutoFuelPointsTen", quantitativeReports!) * 10 + ); + }, + }, + { + label: "< Min Auto Fuel Points Scored By Alliance", + get(pitData, quantitativeReports) { + return ( + GetMinimum(quantitativeReports!, "AutoFuelPointsOne") + + GetMinimum(quantitativeReports!, "AutoFuelPointsFive") * 5 + + GetMinimum(quantitativeReports!, "AutoFuelPointsTen") * 10 + ); + }, + }, + { + label: "< Max Auto Fuel Points Scored By Alliance", + get(pitData, quantitativeReports) { + return ( + GetMaximum(quantitativeReports!, "AutoFuelPointsOne") + + GetMaximum(quantitativeReports!, "AutoFuelPointsFive") * 5 + + GetMaximum(quantitativeReports!, "AutoFuelPointsTen") * 10 + ); + }, + }, + ], Teleop: [ { label: "Total Fuel Points Scored By Alliance", @@ -2025,21 +2059,56 @@ export namespace Rebuilt { }; const pitStatsLayout: PitStatsLayout = { - overallSlideStats: [], + overallSlideStats: [ + /*HopperVolume: number = 0; + RobotWeight: number = 0; + RobotWidth: number = 0;*/ + { + label: "Estamate Hopper Volume", + key: "HopperVolume", + }, + { + label: "RobotWeight", + key: "RobotWeight", + }, + { + label: "Robot Width", + key: "RobotWidth", + }, + { + label: "Robot Length", + key: "RobotLength", + }, + ], individualSlideStats: [ { - label: "Average Points", + label: "Average Auto Points", get: ( pitReport: Pitreport | undefined, quantitativeReports: Report[] | undefined, ) => { if (!quantitativeReports) return 0; - const TotalAllianceFuelPoints = + const TotalAutoAllianceFuelPoints = + NumericalTotal("AutoFuelPointsOne", quantitativeReports) + + NumericalTotal("AutoFuelPointsFive", quantitativeReports) * 5 + + NumericalTotal("AutoFuelPointsTen", quantitativeReports) * 10; + return TotalAutoAllianceFuelPoints / quantitativeReports.length; + }, + }, + { + label: "Average Teleop Points", + get: ( + pitReport: Pitreport | undefined, + quantitativeReports: Report[] | undefined, + ) => { + if (!quantitativeReports) return 0; + + const TotalTeleopAllianceFuelPoints = NumericalTotal("FuelPointsOne", quantitativeReports) + NumericalTotal("FuelPointsFive", quantitativeReports) * 5 + NumericalTotal("FuelPointsTen", quantitativeReports) * 10; - return TotalAllianceFuelPoints / quantitativeReports.length; + return TotalTeleopAllianceFuelPoints / quantitativeReports.length; }, }, { @@ -2056,11 +2125,9 @@ export namespace Rebuilt { }, ], robotCapabilities: [ - { key: "GroundIntake", label: "Has Ground Intake?" }, { key: "CanDriveOverBump", label: "Can Drive Over Bump?" }, { key: "CanDriveUnderTrench", label: "Can Drive Under Trench?" }, { key: "CanDeClimb", label: "Can De-Climb?" }, - { key: "CanScoreFuel", label: "Can Score Fuel?" }, { key: "ClimbingAbilities", label: "Climbing?" }, ], graphStat: { @@ -2076,16 +2143,12 @@ export namespace Rebuilt { ) { const badges: Badge[] = getBaseBadges(pitReport, quantitativeReports); - if (pitReport?.data?.GroundIntake) - badges.push({ text: "Can Use Ground Intake", color: "primary" }); if (pitReport?.data?.CanDriveOverBump) badges.push({ text: "Can Drive Over Bump", color: "accent" }); if (pitReport?.data?.CanDriveUnderTrench) badges.push({ text: "Can Drive Under Trench", color: "accent" }); if (pitReport?.data?.CanDeClimb) badges.push({ text: "Can Declimb", color: "accent" }); - if (!pitReport?.data?.CanScoreFuel) - badges.push({ text: "Can't Score Fuel", color: "warning" }); if ( pitReport?.data?.ClimbingAbilities === @@ -2126,9 +2189,9 @@ export namespace Rebuilt { break; } totalPoints += - Number(report.FuelPointsOne) + - Number(report.FuelPointsFive) * 5 + - Number(report.FuelPointsTen) * 10; + Number(report.FuelPointsOne + report.AutoFuelPointsOne) + + Number(report.FuelPointsFive + report.AutoFuelPointsOne) * 5 + + Number(report.FuelPointsTen + report.AutoFuelPointsOne) * 10; } return totalPoints / Math.max(reports.length, 1); } @@ -2195,7 +2258,7 @@ export namespace Decode { const quantitativeReportLayout: FormLayoutProps = { Auto: [ - { key: "AutoMovedPastStart", label: "Moved Past Starting line" }, + { key: "AutoMovedPastStartingLine", label: "Moved Past Starting line" }, [ [ { diff --git a/package-lock.json b/package-lock.json index 05332676..2194d31c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -136,7 +136,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", "dev": true, - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", @@ -2308,7 +2307,8 @@ "node_modules/@kurkle/color": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", - "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==", + "peer": true }, "node_modules/@mongodb-js/saslprep": { "version": "1.1.9", @@ -2575,7 +2575,6 @@ "integrity": "sha512-6LdVIUERWxQMmUSSQi0I53GgCBYgM2RpGngCPY7hSeju+VrKjq3lvs7HpJoPbDiY5QM5EYRtRX5fvrinnMAz3w==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "playwright": "1.58.1" }, @@ -2590,7 +2589,6 @@ "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -2777,12 +2775,12 @@ } }, "node_modules/@serwist/build/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -2896,12 +2894,12 @@ } }, "node_modules/@serwist/next/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -3403,7 +3401,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -3530,7 +3527,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3559,9 +3555,10 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4162,7 +4159,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -4624,8 +4620,7 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "peer": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/culori": { "version": "3.3.0", @@ -5300,7 +5295,6 @@ "version": "9.18.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -5474,7 +5468,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.17.0.tgz", "integrity": "sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.17.0", "@typescript-eslint/types": "8.17.0", @@ -5595,12 +5588,12 @@ } }, "node_modules/eslint-config-next/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -5681,7 +5674,6 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", @@ -6139,10 +6131,11 @@ } }, "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -7318,7 +7311,6 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -8211,7 +8203,6 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "devOptional": true, - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -8581,9 +8572,10 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -8676,7 +8668,6 @@ "version": "5.9.2", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.2.tgz", "integrity": "sha512-H60HecKO4Bc+7dhOv4sJlgvenK4fQNqqUIlXxZYQNbfEWSALGAwGoyJd/0Qwk4TttFXUOHJ2ZJQe/52ScaUwtQ==", - "peer": true, "dependencies": { "bson": "^5.5.0", "mongodb-connection-string-url": "^2.6.0", @@ -8765,7 +8756,6 @@ "resolved": "https://registry.npmjs.org/next/-/next-15.5.12.tgz", "integrity": "sha512-Fi/wQ4Etlrn60rz78bebG1i1SR20QxvV8tVp6iJspjLUSHcZoeUXCt+vmWoEcza85ElZzExK/jJ/F6SvtGktjA==", "license": "MIT", - "peer": true, "dependencies": { "@next/env": "15.5.12", "@swc/helpers": "0.5.15", @@ -8818,7 +8808,6 @@ "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.13.tgz", "integrity": "sha512-sgObCfcfL7BzIK76SS5TnQtc3yo2Oifp/yIpfv6fMfeBOiBJkDWF3A2y9+yqnmJ4JKc2C+nMjSjmgDeTwgN1rQ==", "license": "ISC", - "peer": true, "dependencies": { "@babel/runtime": "^7.20.13", "@panva/hkdf": "^1.0.2", @@ -9507,7 +9496,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -9636,7 +9624,6 @@ "version": "10.24.3", "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz", "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -9763,9 +9750,10 @@ "integrity": "sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==" }, "node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.5.tgz", + "integrity": "sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.6" } @@ -9811,7 +9799,6 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -9920,7 +9907,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -11030,12 +11016,13 @@ } }, "node_modules/sucrase/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -11282,7 +11269,6 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -11484,7 +11470,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11522,9 +11507,10 @@ } }, "node_modules/underscore": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", - "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" + "version": "1.13.8", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", + "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", + "license": "MIT" }, "node_modules/undici-types": { "version": "6.21.0", From 36f420534dac2a4dea36a6f71f49453cdaf2e80b Mon Sep 17 00:00:00 2001 From: Cullen Date: Thu, 19 Mar 2026 17:07:07 -0400 Subject: [PATCH 167/168] 3/19 --- lib/Enums.ts | 10 +--------- lib/Layout.ts | 8 ++++---- lib/games.ts | 29 ++++++++++++----------------- 3 files changed, 17 insertions(+), 30 deletions(-) diff --git a/lib/Enums.ts b/lib/Enums.ts index 4086cde2..0014013b 100644 --- a/lib/Enums.ts +++ b/lib/Enums.ts @@ -111,7 +111,7 @@ export namespace ReefscapeEnums { } export namespace RebuiltEnums { - export enum ClimbingAbilities { + export enum ClimbingCapabilities { No = "No", FirstLevel = "FirstLevel", SecondLevel = "SecondLevel", @@ -136,14 +136,6 @@ export namespace RebuiltEnums { Second = "Second", Third = "Third", } - - export enum AutoAbilities { - NoAuto = "No Auto", - MovePastStart = "Move Past Start", - ClimbLevelOne = "Climb Level One", - ScoreOneOrMoreFuel = "Score One Or More Fuel", - ScoreFuelAndClimb = "Score Fuel And Climb", - } } export namespace DecodeEnums { diff --git a/lib/Layout.ts b/lib/Layout.ts index e2c06760..ccd8d0f9 100644 --- a/lib/Layout.ts +++ b/lib/Layout.ts @@ -219,8 +219,8 @@ export function keyToType( ReefscapeEnums.Climbing, ReefscapeEnums.DriveThroughDeepCage, ReefscapeEnums.EndgameClimbStatus, - RebuiltEnums.AutoAbilities, - RebuiltEnums.ClimbingAbilities, + //RebuiltEnums.AutoAbilities, + RebuiltEnums.ClimbingCapabilities, RebuiltEnums.DriveOverBump, RebuiltEnums.DriveUnderTrench, RebuiltEnums.LevelClimbed, @@ -240,8 +240,8 @@ export function keyToType( if (key == "DriveThroughDeepCage") return ReefscapeEnums.DriveThroughDeepCage; if (key == "EndgameClimbStatus") return ReefscapeEnums.EndgameClimbStatus; - if (key == "AutoAbilities") return RebuiltEnums.AutoAbilities; - if (key == "ClimbingAbilities") return RebuiltEnums.ClimbingAbilities; + //if (key == "AutoAbilities") return RebuiltEnums.AutoAbilities; + if (key == "ClimbingCapabilities") return RebuiltEnums.ClimbingCapabilities; if (key == "DriveOverBump") return RebuiltEnums.DriveOverBump; if (key == "DiveUnderTrench") return RebuiltEnums.DriveUnderTrench; if (key == "LevelClimbed") return RebuiltEnums.LevelClimbed; diff --git a/lib/games.ts b/lib/games.ts index 72a8ff41..c8a95289 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1919,17 +1919,16 @@ export namespace Rebuilt { RobotWeight: number = 0; RobotWidth: number = 0; RobotLength: number = 0; - AutoAbilities: RebuiltEnums.AutoAbilities = - RebuiltEnums.AutoAbilities.NoAuto; - ClimbingAbilities: RebuiltEnums.ClimbingAbilities = - RebuiltEnums.ClimbingAbilities.No; + AutoAbilities: string = ""; + ClimbingCapabilities: RebuiltEnums.ClimbingCapabilities = + RebuiltEnums.ClimbingCapabilities.No; } const pitReportLayout: FormLayoutProps = { Capabilities: [ { key: "CanDriveOverBump", label: "Can Drive Over Bump?" }, { key: "CanDriveUnderTrench", label: "Can Drive Under Trench?" }, { key: "CanDeClimb", label: "Can De-Climb?" }, - { key: "ClimbingAbilities", label: "Climbing?" }, + { key: "ClimbingCapabilities", label: "Climbing?" }, { key: "HopperVolume", label: "Hopper Volume?" }, ], Auto: [{ key: "AutoAbilities", label: "Auto Capabilities?" }], @@ -2059,11 +2058,7 @@ export namespace Rebuilt { }; const pitStatsLayout: PitStatsLayout = { - overallSlideStats: [ - /*HopperVolume: number = 0; - RobotWeight: number = 0; - RobotWidth: number = 0;*/ - { + overallSlideStats: [ { label: "Estamate Hopper Volume", key: "HopperVolume", }, @@ -2128,7 +2123,7 @@ export namespace Rebuilt { { key: "CanDriveOverBump", label: "Can Drive Over Bump?" }, { key: "CanDriveUnderTrench", label: "Can Drive Under Trench?" }, { key: "CanDeClimb", label: "Can De-Climb?" }, - { key: "ClimbingAbilities", label: "Climbing?" }, + { key: "ClimbingCapabilities", label: "Climbing?" }, ], graphStat: { label: "Average Fuel Scored In Hopper", @@ -2151,18 +2146,18 @@ export namespace Rebuilt { badges.push({ text: "Can Declimb", color: "accent" }); if ( - pitReport?.data?.ClimbingAbilities === - RebuiltEnums.ClimbingAbilities.FirstLevel + pitReport?.data?.ClimbingCapabilities === + RebuiltEnums.ClimbingCapabilities.FirstLevel ) badges.push({ text: "Can Climb First Level", color: "accent" }); else if ( - pitReport?.data?.ClimbingAbilities === - RebuiltEnums.ClimbingAbilities.SecondLevel + pitReport?.data?.ClimbingCapabilities === + RebuiltEnums.ClimbingCapabilities.SecondLevel ) badges.push({ text: "Can Climb Second Level", color: "accent" }); else if ( - pitReport?.data?.ClimbingAbilities === - RebuiltEnums.ClimbingAbilities.ThirdLevel + pitReport?.data?.ClimbingCapabilities === + RebuiltEnums.ClimbingCapabilities.ThirdLevel ) badges.push({ text: "Can Climb Third Level", color: "accent" }); From 876070fb50a128b8dfa0a76155b298998bf4b8de Mon Sep 17 00:00:00 2001 From: Cullen Date: Thu, 19 Mar 2026 21:22:13 -0400 Subject: [PATCH 168/168] new 3/19 --- lib/games.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/games.ts b/lib/games.ts index c8a95289..33bfe09e 100644 --- a/lib/games.ts +++ b/lib/games.ts @@ -1898,10 +1898,10 @@ namespace Reefscape { export namespace Rebuilt { export class QuantitativeData extends QuantData { - AutoFuelPointsOne: number =0; + AutoFuelPointsOne: number = 0; AutoFuelPointsFive: number = 0; AutoFuelPointsTen: number = 0; - + FuelPointsOne: number = 0; FuelPointsFive: number = 0; FuelPointsTen: number = 0; @@ -2058,7 +2058,8 @@ export namespace Rebuilt { }; const pitStatsLayout: PitStatsLayout = { - overallSlideStats: [ { + overallSlideStats: [ + { label: "Estamate Hopper Volume", key: "HopperVolume", },