diff --git a/angular/src/app/observer/observer.component.html b/angular/src/app/observer/observer.component.html index 6121882..7e46c55 100644 --- a/angular/src/app/observer/observer.component.html +++ b/angular/src/app/observer/observer.component.html @@ -237,6 +237,17 @@ (validationChanged)="onPanelValidationChanged($event, 'tournamentinfoPanelId')" > + + + + + + ("roundWinBox") || this.roundWinBox; const loadedTimeoutInfo = this.localStorageService.getItem("timeouts"); if (loadedTimeoutInfo) { @@ -403,6 +416,22 @@ export class ObserverComponent implements OnInit { this.electron.confirmCloseDecision(decided === true); } + protected roundWinBoxCheckSpectraPlus() { + if (this.isSupporter) return this.roundWinBox; + if (this.roundWinBox.type != "sponsors") + return { + type: this.roundWinBox.type, + sponsors: [], + }; + const roundWinBoxFirstSponsor = this.roundWinBox.sponsors[0]; + roundWinBoxFirstSponsor.roundCeremonie = ["all"]; + roundWinBoxFirstSponsor.wonTeam = "all"; + return { + type: this.roundWinBox.type, + sponsors: [roundWinBoxFirstSponsor], + }; + } + protected onConnectClick() { if (this.tryingToConnect) return; @@ -465,6 +494,7 @@ export class ObserverComponent implements OnInit { this.watermarkInfo, this.playercamsInfo, this.timeoutInfo, + this.roundWinBoxCheckSpectraPlus(), ); this.saveAllValues(); @@ -484,6 +514,7 @@ export class ObserverComponent implements OnInit { this.localStorageService.setItem("watermark", this.watermarkInfo); this.localStorageService.setItem("playercams", this.playercamsInfo); this.localStorageService.setItem("timeouts", this.timeoutInfo); + this.localStorageService.setItem("roundWinBox", this.roundWinBox); } protected onExtrasClick() { @@ -579,6 +610,7 @@ export class ObserverComponent implements OnInit { }, tournamentInfo: this.tournamentInfo, timeoutDuration: this.tournamentInfo.timeoutDuration, + roundWinBox: this.roundWinBoxCheckSpectraPlus(), sponsorInfo: this.sponsorInfo, }, }) @@ -776,14 +808,35 @@ export type TeamInfo = { }; export type TournamentInfo = { - // name just activates the round win box at this time - name: boolean; logoUrl: string; backdropUrl: string; timeoutDuration: number; showMappool: boolean; }; +export type RoundWinBox = { + type: "disabled" | "tournamentInfo" | "sponsors"; + sponsors: RoundWinBoxSponsors[]; +}; + +export type RoundWinBoxWonTeam = "all" | "left" | "right"; +export type RoundWinBoxRoundCeremonie = ( + | "all" + | "normal" + | "ace" + | "clutch" + | "teamAce" + | "flawless" + | "thrifty" +)[]; + +export type RoundWinBoxSponsors = { + wonTeam: RoundWinBoxWonTeam; + roundCeremonie: RoundWinBoxRoundCeremonie; + iconUrl: string; + backdropUrl: string; +}; + export type SeriesInfo = { enable: boolean; needed: number | null; diff --git a/angular/src/app/roundwinbox/roundwinbox.component.css b/angular/src/app/roundwinbox/roundwinbox.component.css new file mode 100644 index 0000000..e69de29 diff --git a/angular/src/app/roundwinbox/roundwinbox.component.html b/angular/src/app/roundwinbox/roundwinbox.component.html new file mode 100644 index 0000000..e13ec82 --- /dev/null +++ b/angular/src/app/roundwinbox/roundwinbox.component.html @@ -0,0 +1,175 @@ +
+
+

Round Win Box

+ + +

Recommended image size:

+

Logo: Any size with a 1:1 aspect ratio

+

Backdrop: 875x200 / 35:8 - 830x250 / 83:25 on Nobii's Style

+
+
+ + + + + + + + + + + + +

+ Configure Tournament Info in the Box above. +

+

+ Subscribe to Spectra+ to be able to add more than one sponsor. +

+ + + +
+ @for (i of numSequence(data.sponsors.length + 1); track $index) { + @if ($index == 0 || $index > 0 && isSupporter) { +
+
+
+ + + + + + + + +
+
+ + + + + + + + +
+
+
+ @if ($index < data.sponsors.length) { + + } +
+
+ @if ($index < data.sponsors.length) { +
+
+ @if (sponsorIconError[$index] || !data.sponsors[$index].iconUrl) { + + +

Make sure the link points directly to the image.

+

Valid links usually end in .png or .jpg.

+
+ } + +
+
+ @if (sponsorBackdropError[$index] || !data.sponsors[$index].backdropUrl) { + + +

Make sure the link points directly to the image.

+

Valid links usually end in .png or .jpg.

+
+ } + +
+
+ } + @if ($index < data.sponsors.length) { +
+ } + } + } +
+
+
diff --git a/angular/src/app/roundwinbox/roundwinbox.component.ts b/angular/src/app/roundwinbox/roundwinbox.component.ts new file mode 100644 index 0000000..627a755 --- /dev/null +++ b/angular/src/app/roundwinbox/roundwinbox.component.ts @@ -0,0 +1,153 @@ +import { AfterContentInit, Component, EventEmitter, Input, Output } from "@angular/core"; +import { FormsModule } from "@angular/forms"; +import { BlockUIModule } from "primeng/blockui"; +import { FloatLabelModule } from "primeng/floatlabel"; +import { InputNumberModule } from "primeng/inputnumber"; +import { InputTextModule } from "primeng/inputtext"; +import { ToggleSwitchModule } from "primeng/toggleswitch"; +import { RoundWinBox } from "../observer/observer.component"; +import { Validatable, ValidationState } from "../services/validation.service"; +import { PopoverModule } from "primeng/popover"; +import { BlockableDiv } from "../blockablediv/blockablediv.component"; +import { Button } from "primeng/button"; +import { InputGroup } from "primeng/inputgroup"; +import { InputGroupAddon } from "primeng/inputgroupaddon"; +import { Select } from "primeng/select"; +import { MultiSelectModule } from "primeng/multiselect"; + +@Component({ + selector: "app-roundwinbox", + imports: [ + InputTextModule, + FormsModule, + FloatLabelModule, + ToggleSwitchModule, + InputNumberModule, + BlockUIModule, + PopoverModule, + BlockableDiv, + Button, + InputGroup, + InputGroupAddon, + Select, + MultiSelectModule, + ], + templateUrl: "./roundwinbox.component.html", + styleUrl: "./roundwinbox.component.css", +}) +export class RoundwinboxComponent implements Validatable, AfterContentInit { + @Input({ required: true }) + data!: RoundWinBox; + + @Input({ required: false }) + isSupporter: boolean = false; + + roundWinBoxTeams = ["all", "left", "right"]; + roundWinBoxRoundCeremonie = ["all", "normal", "ace", "clutch", "teamAce", "flawless", "thrifty"]; + + ngAfterContentInit(): void { + this.runValidation(); + } + + @Output() + validationChanged = new EventEmitter(); + runValidation() { + let valid: boolean = true; + if (this.data.type !== "sponsors") { + this.validationChanged.emit(ValidationState.OPTIONAL); + return; + } + + this.sponsorTypeError = []; + + for (let i = 0; i < this.data.sponsors.length; i++) { + const sponsor = this.data.sponsors[i]; + for (let j = 0; j < this.data.sponsors.length; j++) { + if (i != j) { + const spons = this.data.sponsors[j]; + if ( + sponsor.wonTeam == spons.wonTeam || + sponsor.wonTeam == "all" || + spons.wonTeam == "all" + ) { + for (const ceremonie of sponsor.roundCeremonie) { + if ( + spons.roundCeremonie.includes(ceremonie) || + (ceremonie == "all" && spons.roundCeremonie.length > 0) + ) { + this.sponsorTypeError[i] = true; + this.sponsorTypeError[j] = true; + } + } + } + } + } + } + + valid = + valid && + !this.sponsorIconError.includes(true) && + !this.sponsorBackdropError.includes(true) && + !this.sponsorTypeError.includes(true); + + this.validationChanged.emit(valid ? ValidationState.VALID : ValidationState.INVALID); + } + + protected sponsorIconError: boolean[] = []; + protected sponsorBackdropError: boolean[] = []; + + protected sponsorTypeError: boolean[] = []; + + protected updateSponsor( + index: number, + field: "iconUrl" | "backdropUrl" | "wonTeam" | "roundCeremonie", + value: string, + ) { + if (!this.data.sponsors[index]) { + this.data.sponsors[index] = { + wonTeam: "all", + roundCeremonie: ["all"], + iconUrl: "", + backdropUrl: "", + }; + } + + this.data.sponsors[index][field] = value as any; + + this.runValidation(); + } + + protected onSponsorImageLoadError(index: number, type: "icon" | "backdrop") { + if (type == "icon") { + if (this.data.sponsors[index].iconUrl == "" || this.data.sponsors[index] == null) { + this.sponsorIconError[index] = false; + this.runValidation(); + return; + } else { + this.sponsorIconError[index] = true; + } + } + + if (type == "backdrop") { + if (this.data.sponsors[index].backdropUrl == "" || this.data.sponsors[index] == null) { + this.sponsorBackdropError[index] = false; + this.runValidation(); + return; + } else { + this.sponsorBackdropError[index] = true; + } + } + this.runValidation(); + } + + protected onSponsorImageLoadSuccess(index: number, type: "icon" | "backdrop") { + if (type == "icon") this.sponsorIconError[index] = false; + if (type == "backdrop") this.sponsorBackdropError[index] = false; + + this.runValidation(); + } + + numSequence(n: number): number[] { + return Array(n); + } +} diff --git a/angular/src/app/services/electron.service.ts b/angular/src/app/services/electron.service.ts index d2b9c05..e02783c 100644 --- a/angular/src/app/services/electron.service.ts +++ b/angular/src/app/services/electron.service.ts @@ -41,6 +41,7 @@ export class ElectronService { watermarkInfo: any, playercamsInfo: any, timeoutInfo: any, + roundWinBox: any, ) { this.api.processInputs( ingestIp, @@ -57,6 +58,7 @@ export class ElectronService { watermarkInfo, playercamsInfo, timeoutInfo, + roundWinBox, ); } diff --git a/angular/src/app/tournamentinfo/tournamentinfo.component.html b/angular/src/app/tournamentinfo/tournamentinfo.component.html index 13010e3..725e6f1 100644 --- a/angular/src/app/tournamentinfo/tournamentinfo.component.html +++ b/angular/src/app/tournamentinfo/tournamentinfo.component.html @@ -13,10 +13,6 @@

Tournament Info

-
- - -
ipcRenderer.send( "process-inputs", @@ -33,6 +34,7 @@ contextBridge.exposeInMainWorld("electronAPI", { watermarkInfo, playercamsInfo, timeoutInfo, + roundWinBox, ), processAuxInputs: (ingestIp: any, name: any) => ipcRenderer.send("process-aux-inputs", ingestIp, name), diff --git a/src/services/connectorService.ts b/src/services/connectorService.ts index 740452a..aafcf36 100644 --- a/src/services/connectorService.ts +++ b/src/services/connectorService.ts @@ -16,6 +16,7 @@ import { IAuxAuthenticationData, IFormattedAuxScoreboardTeam as IFormattedAuxScoreboardTeammate, IFormattedData, + IRoundWinBox, ISeedingInfo, ISeriesInfo, ITimeoutInfo, @@ -89,6 +90,7 @@ export class ConnectorService { watermarkInfo: WatermarkInfo, playercamsInfo: PlayercamsInfo, timeoutInfo: ITimeoutInfo, + roundWinBox: IRoundWinBox, win: Electron.Main.BrowserWindow, ) { if (RegExp("(http|https)://[^/]+:[0-9]+").test(ingestIp)) { @@ -201,6 +203,7 @@ export class ConnectorService { sponsorInfo: sponsorInfo, watermarkInfo: watermarkInfo, playercamsInfo: playercamsInfo, + roundWinBox: roundWinBox, }, }; this.ws.emit(SocketChannels.OBSERVER_LOGON, JSON.stringify(logonData)); diff --git a/src/services/formattingService.ts b/src/services/formattingService.ts index 6e3d8b3..a116def 100644 --- a/src/services/formattingService.ts +++ b/src/services/formattingService.ts @@ -294,6 +294,7 @@ export interface IToolsData { sponsorInfo: SponsorInfo; watermarkInfo: WatermarkInfo; playercamsInfo: PlayercamsInfo; + roundWinBox: IRoundWinBox; } export type ISeriesInfo = { @@ -309,7 +310,6 @@ export type ISeedingInfo = { }; export type ITournamentInfo = { - name: string; logoUrl: string; backdropUrl: string; timeoutDuration: number; @@ -340,6 +340,18 @@ export type PlayercamsInfo = { endTime: number; }; +export type IRoundWinBox = { + type: "disabled" | "tournamentInfo" | "sponsors"; + sponsors: IRoundWinBoxSponsors[]; +}; + +export type IRoundWinBoxSponsors = { + wonTeam: "all" | "left" | "right"; + roundCeremonie: ("all" | "normal" | "ace" | "clutch" | "teamAce" | "flawless" | "thrifty")[]; + iconUrl: string; + backdropUrl: string; +}; + type BaseMapPoolInfo = { type: "past" | "present" | "future" | "error"; };