diff --git a/config/scouting/2026/index.ts b/config/scouting/2026/index.ts index 4405dc72..319dda05 100644 --- a/config/scouting/2026/index.ts +++ b/config/scouting/2026/index.ts @@ -421,13 +421,13 @@ export function layout() { options: [ { name: "Auto", - html: '', + html: '', refers: "auto", active: true }, { name: "Teleop", - html: '', + html: '', refers: "teleop", active: false }, diff --git a/helpers/scouting.ts b/helpers/scouting.ts index 557ec9f0..1688c1fd 100644 --- a/helpers/scouting.ts +++ b/helpers/scouting.ts @@ -1,4 +1,5 @@ import ScoutingEntry from "../models/scoutingEntry"; +import PurchaseEntry from "../models/purchaseEntry"; import ScoutingCategory from "../models/scoutingCategory"; import Team from "../models/team"; import { getTeamByNumber } from "./teams"; @@ -334,13 +335,33 @@ export async function getTotalIncentives(contributingTeam, contributingUser) { }) .select({ xp: 1, nuts: 1, bolts: 1 }) .lean(); + + let purchases = await PurchaseEntry.find({ + "contributor.team": (await getTeamByNumber(contributingTeam))._id, + "contributor.username": contributingUser + }) + .select({ xp: 1, nuts: 1, bolts: 1 }) + .lean(); + + let nuts = + Number( + entries.reduce((total, current: any) => total + current.nuts, 0) + ) - + Number( + purchases.reduce((total, current: any) => total + current.nuts, 0) + ); + let bolts = + Number( + entries.reduce((total, current: any) => total + current.bolts, 0) + ) - + Number( + purchases.reduce((total, current: any) => total + current.bolts, 0) + ); + return { xp: entries.reduce((total, current: any) => total + current.xp, 0), - nuts: entries.reduce((total, current: any) => total + current.nuts, 0), - bolts: entries.reduce( - (total, current: any) => total + current.bolts, - 0 - ), + nuts: nuts, + bolts: bolts, ...getLevelAndProgress( entries.reduce((total, current: any) => total + current.xp, 0) ) diff --git a/helpers/shop.ts b/helpers/shop.ts index af8843f6..5c562b18 100644 --- a/helpers/shop.ts +++ b/helpers/shop.ts @@ -2,6 +2,8 @@ import mongoose from "mongoose"; import ShopItem from "../models/shopItem"; import UserInventory from "../models/userInventory"; import { getTotalIncentives } from "./scouting"; +import PurchaseEntry from "../models/purchaseEntry"; +import { getTeamByNumber } from "./teams"; export interface ShopItem { id: string; @@ -53,9 +55,6 @@ export async function purchaseShopItem( item?: ShopItem; newBalance?: { nuts: number; bolts: number }; }> { - const session = await mongoose.startSession(); - session.startTransaction(); - try { const item = await ShopItem.findById(itemId); if (!item || !item.enabled) { @@ -73,6 +72,15 @@ export async function purchaseShopItem( throw new Error("Insufficient funds"); } + const existingInventory = await UserInventory.findOne({ + "user.team": teamNumber, + "user.username": username, + "items.itemId": item._id + }); + if (existingInventory) { + throw new Error("You already own this item"); + } + await UserInventory.findOneAndUpdate( { "user.team": teamNumber, @@ -86,12 +94,15 @@ export async function purchaseShopItem( } } }, - { upsert: true, session } + { upsert: true } ); - // await deductCurrency(teamNumber, username, item.price); - - await session.commitTransaction(); + await PurchaseEntry.create({ + "contributor.team": (await getTeamByNumber(teamNumber))._id, + "contributor.username": username, + nuts: item.price.nuts, + bolts: item.price.bolts + }); const newBalance = (await getTotalIncentives( teamNumber, @@ -115,13 +126,10 @@ export async function purchaseShopItem( } }; } catch (error) { - await session.abortTransaction(); return { success: false, error: error.message }; - } finally { - session.endSession(); } } diff --git a/models/purchaseEntry.ts b/models/purchaseEntry.ts new file mode 100644 index 00000000..785746e5 --- /dev/null +++ b/models/purchaseEntry.ts @@ -0,0 +1,26 @@ +import mongoose from "../db"; + +let ScoutingContributor = new mongoose.Schema({ + team: { + ref: "Team", + type: mongoose.Schema.Types.ObjectId + }, + username: String +}); + +export default mongoose.model( + "PurchaseEntry", + new mongoose.Schema({ + contributor: ScoutingContributor, + nuts: { + type: Number, + required: false, + default: 0 + }, + bolts: { + type: Number, + required: false, + default: 0 + } + }) +); diff --git a/package-lock.json b/package-lock.json index 7749ad87..eb882dc8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -175,97 +175,6 @@ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "string-width-cjs": { - "version": "npm:string-width@4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, - "strip-ansi-cjs": { - "version": "npm:strip-ansi@6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "wrap-ansi-cjs": { - "version": "npm:wrap-ansi@7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - } } }, "@jridgewell/resolve-uri": { @@ -4793,6 +4702,36 @@ "strip-ansi": "^7.0.1" } }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -4816,6 +4755,21 @@ "ansi-regex": "^6.0.1" } }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + } + } + }, "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -5203,6 +5157,67 @@ } } }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/static/css/scouting.css b/static/css/scouting.css index e252f44d..ee6a347c 100644 --- a/static/css/scouting.css +++ b/static/css/scouting.css @@ -24,7 +24,7 @@ html, body { overflow-x: hidden; - background: #fefefe; + background: var(--backgroundColor); } body { @@ -904,6 +904,7 @@ header .icon-button .send-container svg { .match-window .component-select select { width: 70%; } + .match-window:has([data-page="4"]) .component-pagebutton { width: 100%; } @@ -1926,6 +1927,7 @@ table.teams-table .ag-center-cols-viewport .ag-center-cols-container { word-wrap: break-word; line-height: 1.5rem; } + /* } */ @media (max-width: 420px) { @@ -2835,12 +2837,15 @@ div.component-score-counter div.tally { .component-counter .counter-controls > .counter-bulk[data-amount="5"] { grid-column: 1; } + .component-counter .counter-controls > .counter-minus { grid-column: 2; } + .component-counter .counter-controls > .counter-bulk[data-amount="-5"] { grid-column: 3; } + .component-counter .counter-controls > .counter-bulk[data-amount="-10"] { grid-column: 4; } @@ -3001,6 +3006,12 @@ div.preset { cursor: not-allowed; } +.purchasebtn:disabled { + opacity: 0.5; + cursor: not-allowed; + color: var(--contentColor); +} + .item-image { font-size: 48px; text-align: center; @@ -3075,3 +3086,441 @@ div.preset { padding: 10px; } } + +/* DARK MODE */ + +[data-theme="dark"] { + --backgroundColor: #110814; + --contentColor: #f4f4f4; + --primaryBackgroundColor: #5c2d6e; + --primaryContentColor: #d4c8de; + --primaryDarkerBackgroundColor: #3d1a4d; + --primaryDarkerBackgroundColor2: #4a2259; + --disabledColor: #888888; + --option-bg: #2a1d35; + --select-bg: rgba(255, 255, 255, 0.15); + --arrow-bg: rgba(255, 255, 255, 0.1); + --cgray: rgba(200, 200, 200, 0.25); + --surface-color: #2a1d35; + --surface-dim: #332440; +} + +/* Body & page background */ +html[data-theme="dark"], +html[data-theme="dark"] body { + background: var(--backgroundColor); + color: var(--contentColor); +} + +/* Autofill override */ +[data-theme="dark"] input:-webkit-autofill { + box-shadow: 0 0 0px 1000px #2a1d35 inset !important; + -webkit-text-fill-color: var(--contentColor) !important; +} + +/* Input borders */ +[data-theme="dark"] .login-window input, +[data-theme="dark"] .home-window input, +[data-theme="dark"] .data-window input, +[data-theme="dark"] .popup-modal input { + border-bottom-color: #555; + color: var(--contentColor); +} + +/* Labels */ +[data-theme="dark"] .group label { + color: #aaa; +} + +/* Select dropdowns */ +[data-theme="dark"] .home-window select, +[data-theme="dark"] .component-dropdown select, +[data-theme="dark"] .data-window select { + background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4.95 10'%3E%3Crect fill='%232a1d35' width='4.95' height='10'/%3E%3Cpolygon fill='%23bbb' points='1.41 4.67 2.48 3.18 3.54 4.67'/%3E%3Cpolygon fill='%23bbb' points='3.54 5.33 2.48 6.82 1.41 5.33'/%3E%3C/svg%3E") + no-repeat 100% 50%; + color: var(--contentColor); +} + +[data-theme="dark"] .match-window select, +[data-theme="dark"] select.event-code, +[data-theme="dark"] select.team { + color: var(--contentColor); +} + +/* Select option backgrounds */ +[data-theme="dark"] select.event-code > option, +[data-theme="dark"] select.team > option, +[data-theme="dark"] .component-dropdown > select > option, +[data-theme="dark"] .home-window select > option, +[data-theme="dark"] .data-window select > option { + background-color: var(--surface-color); + color: var(--contentColor); +} + +[data-theme="dark"] .match-window select, +[data-theme="dark"] select.event-code, +[data-theme="dark"] select.team { + box-shadow: 0 0 1em 0 rgba(150, 150, 150, 0.2); +} + +/* Component dropdown select */ +[data-theme="dark"] .component-dropdown > select { + color: var(--contentColor); +} + +/* Line separator */ +[data-theme="dark"] .line { + background-color: #555; +} + +/* Block element (white background) */ +[data-theme="dark"] .block { + background-color: var(--backgroundColor); +} + +/* Login window text */ +[data-theme="dark"] .login-window p { + color: var(--contentColor); +} + +/* Navbar box-shadow */ +[data-theme="dark"] .navbar { + box-shadow: 0px 0px 21px 0px rgba(0, 0, 0, 0.5); +} + +/* Header bar */ +[data-theme="dark"] body > header { + color: #f4f4f4; +} + +[data-theme="dark"] .header-incentives .xp p { + color: #f4f4f4; + -webkit-text-stroke-color: var(--primaryDarkerBackgroundColor); +} + +[data-theme="dark"] .header-incentives .xp > .xp-container { + border-color: #f4f4f4; +} + +[data-theme="dark"] .header-incentives .xp > .xp-container > .xp-filled { + background-color: #f4f4f4; +} + +/* Analysis options nav */ +[data-theme="dark"] .data-window .analysis-options nav { + background-color: var(--surface-color); + border-color: #555; +} + +[data-theme="dark"] .data-window .analysis-options nav a { + color: var(--contentColor); +} + +[data-theme="dark"] .data-window .analysis-options nav a:hover { + background-color: rgba(255, 255, 255, 0.08); +} + +/* Tables */ +[data-theme="dark"] table, +[data-theme="dark"] th, +[data-theme="dark"] td { + border-color: #555; +} + +[data-theme="dark"] .data-window table td, +[data-theme="dark"] .data-window table th { + border-color: #555; +} + +/* Popup modal & location popup */ +[data-theme="dark"] .popup-modal { + background-color: var(--surface-color); +} + +[data-theme="dark"] .location-popup { + background-color: var(--surface-dim); +} + +/* Popup dividers */ +[data-theme="dark"] .options-slider > div::after, +[data-theme="dark"] .popup-element::after { + background: rgba(255, 255, 255, 0.15); +} + +/* Toggle & component cards */ +[data-theme="dark"] .component-toggle { + background: var(--surface-color); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); +} + +[data-theme="dark"] .popup-options > p, +[data-theme="dark"] .component-toggle > p { + color: var(--contentColor); +} + +[data-theme="dark"] .popup-options label[data-checkbox], +[data-theme="dark"] .component-toggle label { + background: #555; +} + +/* Checkbox (popup modal) */ +[data-theme="dark"] .popup-modal input[type="checkbox"] { + border-bottom-color: #555; +} + +/* Clock component */ +[data-theme="dark"] .component-clock { + background: var(--surface-color); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); +} + +[data-theme="dark"] .component-clock > div.controls > div:nth-child(2) { + border-color: var(--contentColor); +} + +/* Dropdown component */ +[data-theme="dark"] .component-dropdown { + background: var(--surface-color); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); +} + +/* Textarea component */ +[data-theme="dark"] .component-textarea { + background: var(--surface-color); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); +} + +[data-theme="dark"] .component-textarea textarea { + background: rgba(255, 255, 255, 0.05); + border-color: #555; + color: var(--contentColor); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); +} + +[data-theme="dark"] .upload-box { + background: rgba(255, 255, 255, 0.08); +} + +[data-theme="dark"] textarea:focus { + background: rgba(255, 255, 255, 0.08); +} + +[data-theme="dark"] .options-expand { + filter: invert(1); +} + +/* Textbox in match window */ +[data-theme="dark"] .match-window .component-textbox > div { + background-color: var(--surface-color); + box-shadow: 0px 0px 15px -3px rgba(0, 0, 0, 0.5); +} + +[data-theme="dark"] .match-window .component-textbox > div > textarea { + color: var(--contentColor); +} + +/* Rating component */ +[data-theme="dark"] .match-window .component-rating { + background: var(--surface-color); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); +} + +[data-theme="dark"] .component-rating .range-slider { + background: #444; +} + +[data-theme="dark"] .component-rating .range-slider::-webkit-slider-thumb { + background: #9b59b6; +} + +[data-theme="dark"] .component-rating .range-slider::-moz-range-thumb { + background: #9b59b6; +} + +[data-theme="dark"] + .component-rating + .range-slider:focus::-webkit-slider-thumb { + box-shadow: 0 0 0 3px var(--surface-color), + 0 0 0 6px var(--primaryBackgroundColor); +} + +[data-theme="dark"] ::-moz-range-track { + background: #444; +} + +/* Score counter component */ +[data-theme="dark"] div.component-score-counter { + background: var(--surface-color); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); +} + +[data-theme="dark"] div.component-score-counter div.disabled { + background: rgba(100, 100, 100, 0.4) !important; + color: var(--contentColor) !important; +} + +/* Counter component */ +[data-theme="dark"] .component-counter { + background: var(--surface-color); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); +} + +[data-theme="dark"] .component-counter .counter-count { + color: var(--primaryContentColor); +} + +[data-theme="dark"] .component-counter .counter-row + .counter-row { + border-top-color: rgba(255, 255, 255, 0.1); +} + +/* Pagebar / controls */ +[data-theme="dark"] aside#controls { + box-shadow: rgba(0, 0, 0, 0.3) 0px 3px 15px, rgba(0, 0, 0, 0.4) 0px 3px 6px; + background: var(--surface-color); +} + +[data-theme="dark"] aside#controls > div { + color: var(--contentColor); +} + +[data-theme="dark"] aside#controls > div.active { + color: white; +} + +/* Leaderboard */ +[data-theme="dark"] .leaderboard-list { + background: var(--surface-color); + box-shadow: 0 2px 15px rgba(0, 0, 0, 0.5); +} + +[data-theme="dark"] .leaderboard-item { + border-bottom-color: rgba(255, 255, 255, 0.08); +} + +[data-theme="dark"] .leaderboard-item:first-child { + background: linear-gradient( + to right, + rgba(255, 215, 0, 0.2), + var(--surface-color) + ); +} + +[data-theme="dark"] .leaderboard-item:nth-child(2) { + background: linear-gradient( + to right, + rgba(192, 192, 192, 0.2), + var(--surface-color) + ); +} + +[data-theme="dark"] .leaderboard-item:nth-child(3) { + background: linear-gradient( + to right, + rgba(129, 37, 37, 0.3), + var(--surface-color) + ); +} + +[data-theme="dark"] .leaderboard-item .team { + color: #999; +} + +/* Shop */ +[data-theme="dark"] .shop-item { + background: var(--surface-color); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); +} + +[data-theme="dark"] .nuts img, +[data-theme="dark"] .bolts img { + filter: invert(1); +} + +/* Checkbox (match window) */ +[data-theme="dark"] .match-window .component-checkbox > .checkmark { + background-color: #444; + border-color: var(--contentColor); +} + +[data-theme="dark"] + .match-window + .component-checkbox:hover + > input + ~ .checkmark { + background-color: #555; +} + +/* Status box */ +[data-theme="dark"] .status-box { + border-color: #555; +} + +/* ag-Grid dark overrides */ +[data-theme="dark"] .ag-theme-alpine { + --ag-background-color: var(--surface-color); + --ag-header-background-color: var(--surface-dim); + --ag-odd-row-background-color: #2a1d35; + --ag-row-hover-color: rgba(92, 45, 110, 0.2); + --ag-foreground-color: var(--contentColor); + --ag-border-color: #444; + --ag-secondary-foreground-color: #bbb; + --ag-header-foreground-color: var(--contentColor); +} + +/* Scrollbar dark */ +[data-theme="dark"] ::-webkit-scrollbar-thumb { + background: rgba(200, 150, 220, 0.3); +} + +[data-theme="dark"] ::-webkit-scrollbar-thumb:hover { + background: rgba(200, 150, 220, 0.5); +} + +/* Dark mode toggle in sidebar */ +.dark-mode-toggle-scouting { + display: none; + align-items: center; + gap: 10px; + padding: 0.75em 1em; + margin: 0 -1em; + font-size: 1.125em; + color: #ffffff; + cursor: pointer; + border: none; + background: none; + width: calc(100% + 2em); + transition: background-color ease-in-out 250ms; +} + +.dark-mode-toggle-scouting:hover { + background-color: var(--primaryDarkerBackgroundColor2); +} + +.dark-mode-toggle-scouting .toggle-track { + width: 40px; + height: 22px; + background: rgba(255, 255, 255, 0.25); + border-radius: 11px; + position: relative; + transition: background 0.2s; + flex-shrink: 0; +} + +[data-theme="dark"] .dark-mode-toggle-scouting .toggle-track { + background: rgba(255, 255, 255, 0.45); +} + +.dark-mode-toggle-scouting .toggle-thumb { + width: 18px; + height: 18px; + background: white; + border-radius: 50%; + position: absolute; + top: 2px; + left: 2px; + transition: transform 0.2s; +} + +[data-theme="dark"] .dark-mode-toggle-scouting .toggle-thumb { + transform: translateX(18px); +} diff --git a/static/js/scoutingsdk.js b/static/js/scoutingsdk.js index 3e8cd84a..8148ce64 100644 --- a/static/js/scoutingsdk.js +++ b/static/js/scoutingsdk.js @@ -295,9 +295,19 @@ const ScoutingAppSDK = function (element, config) { let nutsElement = document.querySelector( ".header-incentives .nuts > p" ); + + let nutsElementShop = document.querySelector( + ".shop-balance .nuts span" + ); + let boltsElement = document.querySelector( ".header-incentives .bolts > p" ); + + let boltsElementShop = document.querySelector( + ".shop-balance .bolts span" + ); + let levelsElement = document.querySelector(".header-incentives .xp p"); let progressElement = document.querySelector( ".header-incentives .xp .xp-filled" @@ -306,6 +316,12 @@ const ScoutingAppSDK = function (element, config) { incentives.totals.nuts - parseInt(nutsElement.innerHTML); let differenceBolts = incentives.totals.bolts - parseInt(boltsElement.innerHTML); + + let differenceNutsShop = + incentives.totals.nuts - parseInt(nutsElementShop.innerHTML); + let differenceBoltsShop = + incentives.totals.bolts - parseInt(nutsElementShop.innerHTML); + let differenceLevels = incentives.totals.level - parseInt(levelsElement.innerHTML); let differenceProgress = @@ -317,6 +333,10 @@ const ScoutingAppSDK = function (element, config) { nutsElement.innerHTML = parseInt(nutsElement.innerHTML) + Math.abs(differenceNuts) / differenceNuts; + + nutsElementShop.innerHTML = + parseInt(nutsElementShop.innerHTML) + + Math.abs(differenceNutsShop) / differenceNutsShop; }, Math.abs(differenceNuts), 500 @@ -326,6 +346,10 @@ const ScoutingAppSDK = function (element, config) { boltsElement.innerHTML = parseInt(boltsElement.innerHTML) + Math.abs(differenceBolts) / differenceBolts; + + boltsElementShop.innerHTML = + parseInt(boltsElementShop.innerHTML) + + Math.abs(differenceBoltsShop) / differenceBoltsShop; }, Math.abs(differenceBolts), 500 @@ -1556,7 +1580,14 @@ ${_this.escape(teamNumber)} (Blue ${i + 1}) ], rowData, domLayout: "autoHeight", - theme: agGrid.themeQuartz + theme: + document.documentElement.getAttribute( + "data-theme" + ) === "dark" + ? agGrid.themeQuartz.withPart( + agGrid.colorSchemeDark + ) + : agGrid.themeQuartz }); }; @@ -1791,7 +1822,14 @@ ${_this.escape(teamNumber)} (Blue ${i + 1}) enableClickSelection: false }, // domLayout: "autoHeight", - theme: agGrid.themeQuartz, + theme: + document.documentElement.getAttribute( + "data-theme" + ) === "dark" + ? agGrid.themeQuartz.withPart( + agGrid.colorSchemeDark + ) + : agGrid.themeQuartz, suppressCellFocus: true, onRowClicked: function (event) { const api = gridOptions.api; @@ -1937,6 +1975,29 @@ ${_this.escape(teamNumber)} (Blue ${i + 1}) )}; const resizetypes = ["line", "boxplot"]; chart_config${ind} = (resizetypes.includes(chart_config${ind}.type)) ? parse(chart_config${ind}) : chart_config${ind}; + if (chart_config${ind}.type === "line" || chart_config${ind}.type === "bar" && document.documentElement.getAttribute("data-theme") === "dark") { + chart_config${ind}.options.scales.x.grid = chart_config${ind}.options.scales.x.grid || {}; + chart_config${ind}.options.scales.x.angleLines = chart_config${ind}.options.scales.x.angleLines || {}; + chart_config${ind}.options.scales.x.pointLabels = chart_config${ind}.options.scales.x.pointLabels || {}; + chart_config${ind}.options.scales.x.ticks = chart_config${ind}.options.scales.x.ticks || {}; + chart_config${ind}.options.scales.x.grid.color = "rgba(255, 255, 255, 0.2)"; + chart_config${ind}.options.scales.x.angleLines.color = "rgba(255, 255, 255, 0.3)"; + chart_config${ind}.options.scales.x.pointLabels.color = "rgba(255, 255, 255, 0.85)"; + chart_config${ind}.options.scales.x.pointLabels.backdropColor = "transparent"; + chart_config${ind}.options.scales.x.ticks.backdropColor = "transparent"; + chart_config${ind}.options.scales.x.ticks.color = "rgba(255, 255, 255, 0.6)"; + + chart_config${ind}.options.scales.y.grid = chart_config${ind}.options.scales.y.grid || {}; + chart_config${ind}.options.scales.y.angleLines = chart_config${ind}.options.scales.y.angleLines || {}; + chart_config${ind}.options.scales.y.pointLabels = chart_config${ind}.options.scales.y.pointLabels || {}; + chart_config${ind}.options.scales.y.ticks = chart_config${ind}.options.scales.y.ticks || {}; + chart_config${ind}.options.scales.y.grid.color = "rgba(255, 255, 255, 0.2)"; + chart_config${ind}.options.scales.y.angleLines.color = "rgba(255, 255, 255, 0.3)"; + chart_config${ind}.options.scales.y.pointLabels.color = "rgba(255, 255, 255, 0.85)"; + chart_config${ind}.options.scales.y.pointLabels.backdropColor = "transparent"; + chart_config${ind}.options.scales.y.ticks.backdropColor = "transparent"; + chart_config${ind}.options.scales.y.ticks.color = "rgba(255, 255, 255, 0.6)"; + } let chart${ind} = new Chart(document.getElementById("${id}").getContext("2d"), chart_config${ind}); if (resizetypes.includes(chart_config${ind}.type)) { window.addEventListener("resize", () => { @@ -1985,6 +2046,18 @@ ${_this.escape(teamNumber)} (Blue ${i + 1}) )}; const resizetypes = ["line", "boxplot"]; chart_config${ind} = (resizetypes.includes(chart_config${ind}.type)) ? parse(chart_config${ind}) : chart_config${ind}; + if (chart_config${ind}.type === "radar" && document.documentElement.getAttribute("data-theme") === "dark") { + chart_config${ind}.options.scales.r.grid = chart_config${ind}.options.scales.r.grid || {}; + chart_config${ind}.options.scales.r.angleLines = chart_config${ind}.options.scales.r.angleLines || {}; + chart_config${ind}.options.scales.r.pointLabels = chart_config${ind}.options.scales.r.pointLabels || {}; + chart_config${ind}.options.scales.r.ticks = chart_config${ind}.options.scales.r.ticks || {}; + chart_config${ind}.options.scales.r.grid.color = "rgba(255, 255, 255, 0.2)"; + chart_config${ind}.options.scales.r.angleLines.color = "rgba(255, 255, 255, 0.3)"; + chart_config${ind}.options.scales.r.pointLabels.color = "rgba(255, 255, 255, 0.85)"; + chart_config${ind}.options.scales.r.pointLabels.backdropColor = "transparent"; + chart_config${ind}.options.scales.r.ticks.backdropColor = "transparent"; + chart_config${ind}.options.scales.r.ticks.color = "rgba(255, 255, 255, 0.6)"; + } let chart${ind} = new Chart(document.getElementById("${id}").getContext("2d"), chart_config${ind}); if (resizetypes.includes(chart_config${ind}.type)) { window.addEventListener("resize", () => { @@ -2267,7 +2340,14 @@ ${_this.escape(teamNumber)} (Blue ${i + 1}) rowData: rowData, pagination: true, paginationPageSize: 20, - theme: agGrid.themeQuartz + theme: + document.documentElement.getAttribute( + "data-theme" + ) === "dark" + ? agGrid.themeQuartz.withPart( + agGrid.colorSchemeDark + ) + : agGrid.themeQuartz }); element.querySelector(".notes").innerHTML = @@ -2373,7 +2453,14 @@ ${_this.escape(teamNumber)} (Blue ${i + 1}) rowData: rowData, pagination: true, paginationPageSize: 20, - theme: agGrid.themeQuartz + theme: + document.documentElement.getAttribute( + "data-theme" + ) === "dark" + ? agGrid.themeQuartz.withPart( + agGrid.colorSchemeDark + ) + : agGrid.themeQuartz }); element.querySelector(".notes").innerHTML = @@ -6026,30 +6113,54 @@ ${_this.escape(teamNumber)} (Blue ${i + 1}) if (configuration.latest.autofill == null) { configuration.latest.autofill = true; } - document.documentElement.style.setProperty( - "--backgroundColor", - configuration.theme.backgroundColor - ); - document.documentElement.style.setProperty( - "--contentColor", - configuration.theme.contentColor - ); - document.documentElement.style.setProperty( - "--primaryBackgroundColor", - configuration.theme.primaryBackgroundColor - ); - document.documentElement.style.setProperty( - "--primaryContentColor", - configuration.theme.primaryContentColor - ); - document.documentElement.style.setProperty( - "--primaryDarkerBackgroundColor", - configuration.theme.primaryDarkerBackgroundColor - ); - document.documentElement.style.setProperty( - "--disabledColor", - configuration.theme.disabledColor - ); + // Store both light and dark theme configs for toggling + configuration.theme.dark = { + backgroundColor: "#110814", + contentColor: "#f4f4f4", + primaryBackgroundColor: "#5c2d6e", + primaryContentColor: "#d4c8de", + primaryDarkerBackgroundColor: "#3d1a4d", + disabledColor: "#888888" + }; + + function applyThemeVars() { + let t = + document.documentElement.getAttribute("data-theme") === "dark" + ? configuration.theme.dark + : configuration.theme; + document.documentElement.style.setProperty( + "--backgroundColor", + t.backgroundColor + ); + document.documentElement.style.setProperty( + "--contentColor", + t.contentColor + ); + document.documentElement.style.setProperty( + "--primaryBackgroundColor", + t.primaryBackgroundColor + ); + document.documentElement.style.setProperty( + "--primaryContentColor", + t.primaryContentColor + ); + document.documentElement.style.setProperty( + "--primaryDarkerBackgroundColor", + t.primaryDarkerBackgroundColor + ); + document.documentElement.style.setProperty( + "--disabledColor", + t.disabledColor + ); + } + applyThemeVars(); + + new MutationObserver(function () { + applyThemeVars(); + }).observe(document.documentElement, { + attributes: true, + attributeFilter: ["data-theme"] + }); if (configuration.pages == null) { configuration.pages = []; } @@ -6210,6 +6321,21 @@ ${_this.escape(teamNumber)} (Blue ${i + 1}) _this.showShopPage = async () => { return new Promise(async (resolve, reject) => { try { + const [shop, inventory] = await Promise.all([ + fetch("/api/v1/scouting/shop/items"), + fetch("/api/v1/scouting/shop/inventory") + ]); + const data = await shop.json(); + const inventoryData = await inventory.json(); + const items = data.body.items; + const ownedIds = new Set( + (inventoryData.body?.inventory || []).map( + (entry) => + entry.itemId?._id?.toString() || + entry.itemId?.toString() + ) + ); + let nutsElement = document.querySelector( ".header-incentives .nuts > p" ); @@ -6232,73 +6358,75 @@ ${_this.escape(teamNumber)} (Blue ${i + 1})
${_this.escape(item.description)}
-
-
- ${_this.escape(item.description)}
+
+
Failed to load shop data. Please try again later.