From 7c3e739f8d3c6911bc48f4124492edd31b680f6c Mon Sep 17 00:00:00 2001 From: isark Date: Tue, 19 Mar 2024 22:15:54 +0100 Subject: [PATCH] Mildly improved support for livesplit inspired functionality. Also initial beginnings of supporting an "aggregated" live stat for your ongoing run compared to another one. --- src-tauri/src/config.rs | 28 +++- src/app/_services/run-stat.service.ts | 5 +- src/app/_services/time-tracker.service.ts | 70 ++++++--- .../aggregate-display.component.html | 4 + .../aggregate-display.component.scss | 0 .../aggregate-display.component.spec.ts | 21 +++ .../aggregate-display.component.ts | 104 +++++++++++++ src/app/carousel/carousel.component.ts | 43 +++--- .../plan-display/plan-display.component.html | 143 +++++++++++------- .../plan-display/plan-display.component.scss | 28 +++- .../plan-display/plan-display.component.ts | 110 ++++++++++++-- src/app/plan-display/plan-display.module.ts | 4 +- src/app/settings/settings.component.html | 21 +++ 13 files changed, 470 insertions(+), 111 deletions(-) create mode 100644 src/app/aggregate-display/aggregate-display.component.html create mode 100644 src/app/aggregate-display/aggregate-display.component.scss create mode 100644 src/app/aggregate-display/aggregate-display.component.spec.ts create mode 100644 src/app/aggregate-display/aggregate-display.component.ts diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index 686c3e0..7b6a233 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -26,6 +26,8 @@ pub struct Rect { pub struct Config { #[serde(default = "Config::default_initial_plan_window_position")] pub initial_plan_window_position: Rect, + #[serde(default = "Config::default_initial_agg_window_position")] + pub initial_agg_window_position: Rect, #[serde(default = "Config::default_hide_on_unfocus")] pub hide_on_unfocus: bool, #[serde(default = "Config::default_toggle_overlay")] @@ -53,12 +55,20 @@ pub struct Config { #[serde(default = "Config::default_run_compare_history")] pub run_compare_history: Option, + + #[serde(default = "Config::default_show_livesplit")] + show_livesplit: bool, + #[serde(default = "Config::default_show_live_aggregate")] + show_live_aggregate: bool, + #[serde(default = "Config::default_shorten_zone_names")] + shorten_zone_names: bool, } impl Default for Config { fn default() -> Self { Self { initial_plan_window_position: Self::default_initial_plan_window_position(), + initial_agg_window_position: Self::default_initial_agg_window_position(), hide_on_unfocus: Self::default_hide_on_unfocus(), toggle_overlay: Self::default_toggle_overlay(), prev: Self::default_prev(), @@ -72,8 +82,11 @@ impl Default for Config { note_default_fg: Self::default_note_default_fg(), num_visible: Self::default_plan_num_visible(), offset: Self::default_plan_offset(), - enable_stopwatch: Self::default_enable_stopwatch(), run_compare_history: Self::default_run_compare_history(), + enable_stopwatch: Self::default_enable_stopwatch(), + show_livesplit: Self::default_show_livesplit(), + show_live_aggregate: Self::default_show_live_aggregate(), + shorten_zone_names: Self::default_shorten_zone_names(), } } } @@ -83,6 +96,10 @@ impl Config { Default::default() } + fn default_initial_agg_window_position() -> Rect { + Default::default() + } + fn default_hide_on_unfocus() -> bool { true } @@ -133,4 +150,13 @@ impl Config { fn default_run_compare_history() -> Option { None } + fn default_show_livesplit() -> bool { + false + } + fn default_show_live_aggregate() -> bool { + false + } + fn default_shorten_zone_names() -> bool { + false + } } diff --git a/src/app/_services/run-stat.service.ts b/src/app/_services/run-stat.service.ts index a14d19d..0d1e391 100644 --- a/src/app/_services/run-stat.service.ts +++ b/src/app/_services/run-stat.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { EntryType, RunHistory } from './time-tracker.service'; +import { EntryType, RunHistory, TrackEntry } from './time-tracker.service'; import { Plan } from '../_models/plan'; @@ -51,6 +51,9 @@ export type RunStatType = RunStat | AggregateRunStat; providedIn: 'root' }) export class RunStatService { + reachedCheckpointTimes() { + + } /// practically which zone can't have a last exit time as last exit is not determinable for the last entry aggregateNAId?: string; diff --git a/src/app/_services/time-tracker.service.ts b/src/app/_services/time-tracker.service.ts index 65ac298..527627e 100644 --- a/src/app/_services/time-tracker.service.ts +++ b/src/app/_services/time-tracker.service.ts @@ -71,6 +71,7 @@ export class RunHistory { providedIn: 'root' }) export class TimeTrackerService { + private currentRunHistory?: RunHistory; private timerSubscription?: Subscription; @@ -84,12 +85,32 @@ export class TimeTrackerService { private storedHistoriesSubject: Subject> = new ReplaySubject>(1); + private pushSubject: Subject = new ReplaySubject(1); + private newCurrentRunHistorySubject: Subject = new ReplaySubject(1); + constructor(private configService: ConfigService, public dialog: MatDialog, private zone: NgZone) { this.loadCache(); appWindow.listen("entered", (entered) => { if (entered.payload && typeof entered.payload === 'string') this.onZoneEnter(entered.payload); }); + + this.pushSubject.subscribe((entry) => { + this.currentRunHistory?.entries.push(entry); + }); + } + + public getCurrentRunHistory(): Observable { + return this.newCurrentRunHistorySubject.asObservable(); + } + + getLatestEntry() { + return this.pushSubject.asObservable(); + } + + private setCurrentRunHistory(history: RunHistory) { + this.currentRunHistory = history; + this.newCurrentRunHistorySubject.next(history); } get elapsedTimeMillis() { @@ -120,24 +141,24 @@ export class TimeTrackerService { this.loadHistory(plan.last_stored_time).subscribe(history => { if (history) { - this.currentRunHistory = history; + this.setCurrentRunHistory(history); } else { //Legacy or missing history, attempt to preserve elapsed time - this.currentRunHistory = this.createNew(plan.name); - plan.last_stored_time = this.currentRunHistory.uuid; + this.setCurrentRunHistory(this.createNew(plan.name)); + plan.last_stored_time = this.currentRunHistory!.uuid; const old_time = parseInt(plan.last_stored_time, 10); if (!isNaN(old_time) && old_time > 0) { - this.currentRunHistory.currentElapsedMillis = old_time; + this.currentRunHistory!.currentElapsedMillis = old_time; } plan.requestSelfSave(); } - this.currentRunHistory.plan = plan; + this.currentRunHistory!.plan = plan; this.askResume(plan); }); } else { - this.currentRunHistory = this.createNew(plan.name); - this.currentRunHistory.plan = plan; + this.setCurrentRunHistory(this.createNew(plan.name)); + this.currentRunHistory!.plan = plan; } } @@ -162,7 +183,6 @@ export class TimeTrackerService { this.timerSubscription = timer(0, 1000).subscribe(() => { this.zone.run(() => { this.latest = new Date(); - this.currentRunHistory!.currentElapsedMillis = this.elapsedTimeMillis; }); }); @@ -174,15 +194,14 @@ export class TimeTrackerService { private underlyingSaveStopwatch() { if (this.currentRunHistory && this.active && this.start && this.latest) { - console.log("Underlying save!"); this.currentRunHistory!.currentElapsedMillis = this.elapsedTimeMillis; this.currentRunHistory!.last_updated = Date.now(); this.saveHistory(this.currentRunHistory!).subscribe(() => { }); this.loadCache(); - - if(this.currentRunHistory.plan) { + + if (this.currentRunHistory.plan) { this.currentRunHistory.plan.last_stored_time = this.currentRunHistory.uuid; this.currentRunHistory.plan.requestSelfSave(); } @@ -205,7 +224,7 @@ export class TimeTrackerService { public onForceNext(forced_area: string) { if (this.configService.config.enableStopwatch) { if (this.isActive) { - this.currentRunHistory?.entries.push({ + this.pushSubject.next({ type: EntryType.PlanForceNext, zone: forced_area, current_elapsed_millis: this.elapsedTimeMillis @@ -217,7 +236,7 @@ export class TimeTrackerService { public onForcePrev(forced_area: string) { if (this.configService.config.enableStopwatch) { if (this.isActive) { - this.currentRunHistory?.entries.push({ + this.pushSubject.next({ type: EntryType.PlanForcePrev, zone: forced_area, current_elapsed_millis: this.elapsedTimeMillis @@ -228,7 +247,7 @@ export class TimeTrackerService { //Not perfect but good enough.. public reportCheckpoint(checkpoint: string) { - this.currentRunHistory?.entries.push({ + this.pushSubject.next({ type: EntryType.CheckpointReached, zone: checkpoint, current_elapsed_millis: this.elapsedTimeMillis @@ -291,7 +310,7 @@ export class TimeTrackerService { this.startStopwatch(); } - this.currentRunHistory?.entries.push({ + this.pushSubject.next({ type: EntryType.ZoneEnter, zone: zone, current_elapsed_millis: this.elapsedTimeMillis @@ -307,19 +326,36 @@ export class TimeTrackerService { switch (resume) { case Resume.Instant: this.startStopwatch(); + this.loadReachedCheckpoints(); break; case Resume.Next: this.resumeOnNext = true; + this.loadReachedCheckpoints(); break; case Resume.Discard: - this.currentRunHistory = this.createNew(plan.name); - plan.last_stored_time = this.currentRunHistory.uuid; + this.setCurrentRunHistory(this.createNew(plan.name)); + plan.last_stored_time = this.currentRunHistory!.uuid; plan.requestSelfSave(); break; } }) } + loadReachedCheckpoints() { + if (!this.currentRunHistory || !this.currentRunHistory.plan || !this.configService.config.runCompareHistory) return; + + this.loadHistory(this.configService.config.runCompareHistory).subscribe(history => { + if (!history) return; + const checkpoints = new Map(history.entries.filter(entry => entry.type === EntryType.CheckpointReached).map(entry => [entry.zone, entry.current_elapsed_millis])); + const ourCheckpointValidZones = new Map(this.currentRunHistory?.plan?.plan.filter(entry => checkpoints.has(entry.uuid!)).map(entry => [entry.uuid!, entry])); + this.currentRunHistory?.entries.filter(entry => entry.type === EntryType.CheckpointReached).forEach(entry => { + if (ourCheckpointValidZones.has(entry.zone)) { + ourCheckpointValidZones.get(entry.zone)!.checkpoint_your_millis = entry.current_elapsed_millis; + } + }) + }); + } + private createNew(associatedName?: string) { const uuid = uuidv4(); diff --git a/src/app/aggregate-display/aggregate-display.component.html b/src/app/aggregate-display/aggregate-display.component.html new file mode 100644 index 0000000..7fc266d --- /dev/null +++ b/src/app/aggregate-display/aggregate-display.component.html @@ -0,0 +1,4 @@ +
+ Spent: {{currentSpent()}} + Compare spent: {{compareSpent()}} +
\ No newline at end of file diff --git a/src/app/aggregate-display/aggregate-display.component.scss b/src/app/aggregate-display/aggregate-display.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/aggregate-display/aggregate-display.component.spec.ts b/src/app/aggregate-display/aggregate-display.component.spec.ts new file mode 100644 index 0000000..3434b99 --- /dev/null +++ b/src/app/aggregate-display/aggregate-display.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AggregateDisplayComponent } from './aggregate-display.component'; + +describe('AggregateDisplayComponent', () => { + let component: AggregateDisplayComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [AggregateDisplayComponent] + }); + fixture = TestBed.createComponent(AggregateDisplayComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/aggregate-display/aggregate-display.component.ts b/src/app/aggregate-display/aggregate-display.component.ts new file mode 100644 index 0000000..266ff4d --- /dev/null +++ b/src/app/aggregate-display/aggregate-display.component.ts @@ -0,0 +1,104 @@ +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { PlanService } from '../_services/plan.service'; +import { EntryType, RunHistory, TimeTrackerService, TrackEntry } from '../_services/time-tracker.service'; +import { Plan } from '../_models/plan'; +import { ConfigService } from '../_services/config.service'; +import { RunStatService, UnformattedAggregateRunStat, UnformattedAggregationData } from '../_services/run-stat.service'; + +@Component({ + selector: 'app-aggregate-display', + standalone: true, + imports: [CommonModule], + templateUrl: './aggregate-display.component.html', + styleUrls: ['./aggregate-display.component.scss'] +}) +export class AggregateDisplayComponent { + private aggregatedRunHistory?: UnformattedAggregationData; + private currentHistory?: RunHistory; + private latestEntry?: TrackEntry; + + private compareAggregate?: Map; + private currentAggregate?: Map; + + constructor( + private planSerive: PlanService, + public timeTrackerService: TimeTrackerService, + private configService: ConfigService, + private runStatService: RunStatService + ) { + this.timeTrackerService.getCurrentRunHistory().subscribe(history => { + this.currentHistory = history; + this.aggregatedRunHistory = this.runStatService.calcAggregated(history); + this.loadComparisonData(); + }); + + this.timeTrackerService.getLatestEntry().subscribe(entry => { + if (entry.type != EntryType.ZoneEnter) return; + + if (this.latestEntry) { + this.expandAggregated(this.latestEntry, entry); + } + this.latestEntry = entry; + }); + } + + private loadComparisonData() { + if (!this.configService.config.runCompareHistory) { + return; + } + + this.timeTrackerService.loadHistory(this.configService.config.runCompareHistory).subscribe(history => { + if (history) { + this.aggregatedRunHistory = this.runStatService.calcAggregated(history); + this.compareAggregate = new Map(this.aggregatedRunHistory.aggregation.map(agg => [agg.zoneId, agg])); + } + }); + } + + expandAggregated(oldEntry: TrackEntry, newEntry: TrackEntry) { + if (!this.currentAggregate) return; + + let aggregate: UnformattedAggregateRunStat = { + zoneId: oldEntry.zone, + aggregateFirstEntry: oldEntry.current_elapsed_millis, + aggregateLastExit: newEntry.current_elapsed_millis, + aggregateTimeSpent: (newEntry.current_elapsed_millis - oldEntry.current_elapsed_millis), + aggregateNumEntries: 1, + } + + const existing = this.currentAggregate.get(oldEntry.zone); + if (existing) { + existing.aggregateLastExit = aggregate.aggregateLastExit; + existing.aggregateTimeSpent += aggregate.aggregateTimeSpent; + existing.aggregateNumEntries++; + } + + this.currentAggregate.set(aggregate.zoneId, existing ?? aggregate); + } + + currentSpent() { + if (!this.latestEntry) return "N/A"; + + const value = this.currentAggregate?.get(this.latestEntry?.zone)?.aggregateTimeSpent ?? 0 + (this.timeTrackerService.elapsedTimeMillis - this.latestEntry?.current_elapsed_millis); + + if(value) { + return this.timeTrackerService.hmsTimestamp(value); + } else { + return "N/A"; + } + } + + compareSpent() { + if (!this.latestEntry) return "N/A"; + const value = this.compareAggregate?.get(this.latestEntry?.zone)?.aggregateTimeSpent; + + if(value) { + return this.timeTrackerService.hmsTimestamp(value); + } else { + return "N/A"; + } + } + + +} diff --git a/src/app/carousel/carousel.component.ts b/src/app/carousel/carousel.component.ts index a26314c..e946925 100644 --- a/src/app/carousel/carousel.component.ts +++ b/src/app/carousel/carousel.component.ts @@ -56,7 +56,7 @@ export class CarouselComponent implements OnInit, AfterViewInit, OnChanges { @Input() offset: number = 0; containerDirectionLength: number = 0; private debouncedOnchange: Subject = new Subject(); - + constructor(private cdr: ChangeDetectorRef) { this.visibleSlides = []; this.debouncedOnchange.pipe(debounceTime(500)).subscribe(() => this.realOnChange()); @@ -65,27 +65,26 @@ export class CarouselComponent implements OnInit, AfterViewInit, OnChanges { } } - ngOnInit(): void { this.afterInitSelf.next(this); this.intersectionObserver = new IntersectionObserver((entries, observer) => { + let changed = false; entries.forEach(entry => { - const runIntersectionHandling = () => { - const entryIndex = parseInt(entry.target.getAttribute('data-slideIndex')!); - if (!entryIndex && entryIndex != 0) { - return; - } - - const entryIntersectingSlide = this.visibleSlides?.find(s => s.index == entryIndex); - if (!entryIntersectingSlide) { - return; - } - - entryIntersectingSlide.currentlyIntersecting = entry.isIntersecting; - }; - runIntersectionHandling(); + const entryIndex = parseInt(entry.target.getAttribute('data-slideIndex')!); + if (!entryIndex && entryIndex != 0) { + return; + } + + const entryIntersectingSlide = this.visibleSlides?.find(s => s.index == entryIndex); + if (!entryIntersectingSlide) { + return; + } + + entryIntersectingSlide.currentlyIntersecting = entry.isIntersecting; + }); + if (changed) { this.onChange(); - }) + } }) } @@ -98,7 +97,7 @@ export class CarouselComponent implements OnInit, AfterViewInit, OnChanges { } ngOnChanges(changes: SimpleChanges): void { - if(changes['numVisible'] || changes['offset']) { + if (changes['numVisible'] || changes['offset']) { this.reinitializeVisibleSlides(); } } @@ -138,10 +137,10 @@ export class CarouselComponent implements OnInit, AfterViewInit, OnChanges { const start = Math.max(0, this.current - this.numExtraPrev()); const end = Math.min(this.current + this.numExtraNext(), this.slides!.length - 1); for (let i = start; i <= end; i++) { - this.visibleSlides?.push({ - index: i, - currentlyIntersecting: false, - }); + this.visibleSlides?.push({ + index: i, + currentlyIntersecting: false, + }); } this.onChange(); diff --git a/src/app/plan-display/plan-display.component.html b/src/app/plan-display/plan-display.component.html index f8d415d..6ac459b 100644 --- a/src/app/plan-display/plan-display.component.html +++ b/src/app/plan-display/plan-display.component.html @@ -2,62 +2,94 @@
-
- - - -
-
-
{{yourDiff(slide)}}
-
- -
{{worldAreaMap!.get(slide.area_key)!.name}}
- -
-
{{cpMillis(slide)}}
-
(W)
-
(T)
+
+
+ + + +
+
+
{{yourDiff(slide)}}
+
+ +
{{displayZoneName(worldAreaMap!.get(slide.area_key)!.name)}} +
+ +
+
+ {{cpMillis(slide)}}
+
(W)
+
(T)
+
-
- - - - - - - - - + + + + + + + + + - - - -
- (W) = Waypoint - (T) = Trial - The plan window's will have a glow in the corresponding color(s) above to help indicate if the - current zone has any of those. - You can scroll in the plan window (while it is in 'interactable' mode) to quickly switch many - zones -
-
- {{timeTrackerService.hmsTimestamp(timeTrackerService.elapsedTimeMillis)}} + + + +
+ (W) = Waypoint + (T) = Trial + The plan window's will have a glow in the corresponding color(s) above to help indicate if + the + current zone has any of those. + You can scroll in the plan window (while it is in 'interactable' mode) to quickly switch + many + zones +
+
+ {{timeTrackerService.hmsTimestamp(timeTrackerService.elapsedTimeMillis)}} +
+ + + + +
- + +
+ +
+ +
+ + + +
+
@@ -117,6 +149,9 @@
- - + +
\ No newline at end of file diff --git a/src/app/plan-display/plan-display.component.scss b/src/app/plan-display/plan-display.component.scss index 5858f8e..51f4f20 100644 --- a/src/app/plan-display/plan-display.component.scss +++ b/src/app/plan-display/plan-display.component.scss @@ -25,12 +25,38 @@ } } +.target-aggregate { + min-width: 50px; + min-height: 50px; + display: flex; + flex-direction: column; + user-select: none; + z-index: -1; + + &>* { + flex: 1 1 200px; + + &:first-child { + flex: 1 1 15px; + min-height: 50px; + max-height: 120px; + } + } +} + .text-marker { position: absolute; right: 10px; gap: 2px; } +.text-marker-left { + position: absolute; + left: 10px; + bottom: 0px; + gap: 2px; +} + .waypoint-text { color: rgba(25, 255, 255, 0.5); } @@ -86,7 +112,7 @@ } .zone-slide { - display: relative; + position: relative; height: 100%; display: flex; align-items: center; diff --git a/src/app/plan-display/plan-display.component.ts b/src/app/plan-display/plan-display.component.ts index 3f371e6..684f58a 100644 --- a/src/app/plan-display/plan-display.component.ts +++ b/src/app/plan-display/plan-display.component.ts @@ -17,7 +17,7 @@ import { EventsService } from '../_services/events.service'; import { Event } from '@tauri-apps/api/event'; import { MatDialog } from '@angular/material/dialog'; import { TimeTrackerService } from '../_services/time-tracker.service'; -import { RunStatService } from '../_services/run-stat.service'; +import { AggregateRunStat, RunStatService, UnformattedAggregateRunStat, UnformattedAggregationData } from '../_services/run-stat.service'; @Component({ selector: 'plan-display', @@ -28,8 +28,11 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit { @Input() backgroundColor?: String; draggable: boolean = true; rect?: Rect; + rectAgg?: Rect; bounds: any = { "left": 0, "top": 0, "right": 0, "bottom": 0, "position": "css" }; + boundsAgg: any = { "left": 0, "top": 0, "right": 0, "bottom": 0, "position": "css" }; @ViewChild("moveable") moveable?: NgxMoveableComponent; + @ViewChild("moveable2") moveable2?: NgxMoveableComponent; slideIndex: number = 0; zoneSlides?: CarouselComponent; @@ -50,6 +53,9 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit { recentUpdateAttempts: Map = new Map(); + aggregatedRunHistory?: UnformattedAggregationData; + latestZoneId?: string; + constructor( public configService: ConfigService, public planService: PlanService, @@ -78,7 +84,6 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit { this.currentPlan = plan; if (this.configService.config.enableStopwatch) { - console.log(configService.config.runCompareHistory); this.loadComparisonData(this.currentPlan); } this.timeTrackerService.onNewRun(plan); @@ -110,8 +115,11 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit { registerOnZoneEnter() { appWindow.listen("entered", (entered) => { - if (this.currentPlan && typeof entered.payload == "string" && this.currentPlan.isNext(entered.payload)) { - this.zone.run(() => this.next()); + if (this.currentPlan && typeof entered.payload == "string") { + this.zone.run(() => this.latestZoneId = (entered.payload as string)); + if (this.currentPlan.isNext(entered.payload)) { + this.zone.run(() => this.next()); + } } }); } @@ -123,7 +131,7 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit { } ngOnInit() { - this.worldAreaService.getWorldAreas().subscribe(a => this.worldAreaMap = a); + this.worldAreaService.getFullWorldAreas().subscribe(a => this.worldAreaMap = a); this.overlayStateChangeHandle = this.events.listen("OverlayStateChange").subscribe(this.onOverlayStateChange.bind(this)); } @@ -159,6 +167,18 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit { return `${this.rect!.height}px`; } + transformAgg() { + return `translate(${this.rectAgg!.x}px, ${this.rectAgg!.y}px)`; + } + + widthAgg() { + return `${this.rectAgg!.width}px`; + } + + heightAgg() { + return `${this.rectAgg!.height}px`; + } + hasWaypoint(key?: string): boolean { if (!key) { key = this.currentPlan!.plan[this.currentPlan!.current].area_key; @@ -178,6 +198,7 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit { ngAfterViewInit(): void { if (window.innerWidth > 0) { const cfgRect = this.configService.config.initialPlanWindowPosition; + const cfgRectAgg = this.configService.config.initialAggWindowPosition; this.rect = { x: cfgRect.x * window.innerWidth, @@ -185,7 +206,16 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit { width: cfgRect.width * window.innerWidth, height: cfgRect.height * window.innerHeight, } + + this.rectAgg = { + x: cfgRectAgg.x * window.innerWidth, + y: cfgRectAgg.y * window.innerHeight, + width: cfgRectAgg.width * window.innerWidth, + height: cfgRectAgg.height * window.innerHeight, + } + this.moveable?.updateRect(); + this.moveable2?.updateRect(); setTimeout(() => this.cdr.detectChanges(), 0); this.init = true; @@ -211,6 +241,25 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit { this.saveRect(); } + onDragAgg(e: OnDrag) { + this.rectAgg!.x = e.translate[0]; + this.rectAgg!.y = e.translate[1]; + } + + onDragEndAgg(e: OnDragEnd) { + this.saveRectAgg(); + } + + onResizeAgg(e: OnResize) { + this.rectAgg!.width = e.width; + this.rectAgg!.height = e.height; + this.onDragAgg(e.drag); + } + + onResizeEndAgg(e: OnResizeEnd) { + this.saveRectAgg(); + } + saveRect() { const toCfgRect = this.rect!; this.configService.config.initialPlanWindowPosition = { @@ -221,6 +270,16 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit { } } + saveRectAgg() { + const toCfgRect = this.rectAgg!; + this.configService.config.initialAggWindowPosition = { + x: toCfgRect.x / window.innerWidth, + y: toCfgRect.y / window.innerHeight, + width: toCfgRect.width / window.innerWidth, + height: toCfgRect.height / window.innerHeight, + } + } + registerZoneSlides(carousel: CarouselComponent) { this.zoneSlides = carousel; this.zoneSlides.setIndex(this.slideIndex); @@ -266,13 +325,13 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit { } checkCheckpoint() { - if(!this.currentPlan || !this.timeTrackerService.isActive) return; + if (!this.currentPlan || !this.timeTrackerService.isActive) return; const currentElem = this.currentPlan.plan[this.currentPlan.current]; - if(currentElem.checkpoint && !currentElem.checkpoint_your_millis) { - currentElem.checkpoint_your_millis = this.timeTrackerService.elapsedTimeMillis; - this.timeTrackerService.reportCheckpoint(currentElem.uuid!); - } + if (currentElem.checkpoint && !currentElem.checkpoint_your_millis) { + currentElem.checkpoint_your_millis = this.timeTrackerService.elapsedTimeMillis; + this.timeTrackerService.reportCheckpoint(currentElem.uuid!); + } } yourDiff(element: PlanElement) { @@ -281,7 +340,7 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit { const diff = element.checkpoint_your_millis - element.checkpoint_millis; const neg = diff <= 0; const abs = Math.abs(diff); - if(diff == 0) { + if (diff == 0) { return `${neg ? "-" : "+"}00:00:00`; } else { return `${neg ? "-" : "+"}${this.timeTrackerService.hmsTimestamp(abs)}`; @@ -299,11 +358,11 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit { showDiff(element: PlanElement) { return element.checkpoint && element.checkpoint_your_millis && element.checkpoint_millis; } - + cpMillis(element: PlanElement) { - if(!element.checkpoint) return ""; - if(!element.checkpoint_millis) return "N/A"; + if (!element.checkpoint) return ""; + if (!element.checkpoint_millis) return "N/A"; return this.timeTrackerService.hmsTimestamp(element.checkpoint_millis); } @@ -469,6 +528,29 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit { startStopwatch() { this.timeTrackerService.startLoaded(); } + + displayZoneName(zoneName: string) { + if (this.configService.config.shortenZoneNames) { + return this.trim(this.trimUnneccesaryWords(zoneName)); + } else { + return zoneName; + } + } + + trimUnneccesaryWords(zoneName: string) { + if (zoneName.toLowerCase().startsWith("the ")) { + return zoneName.substring(4); + } else { + return zoneName; + } + } + + trim(zoneName: string, letters: number = 12) { + if (zoneName.length > letters + 3) { + return zoneName.substring(0, letters) + "..."; + } + return zoneName; + } } function commonUUIDToNewCurrent(oldPlan: Plan, updatedPlan: Plan): number { diff --git a/src/app/plan-display/plan-display.module.ts b/src/app/plan-display/plan-display.module.ts index cb7cae7..1081f51 100644 --- a/src/app/plan-display/plan-display.module.ts +++ b/src/app/plan-display/plan-display.module.ts @@ -18,6 +18,7 @@ import { AngularSvgIconModule } from 'angular-svg-icon'; import { ScrollingModule } from '@angular/cdk/scrolling'; import { MatDialogModule } from '@angular/material/dialog'; import { ResumeDialog } from './resume-dialog.component'; +import { AggregateDisplayComponent } from '../aggregate-display/aggregate-display.component'; @NgModule({ declarations: [ PlanDisplayComponent @@ -41,7 +42,8 @@ import { ResumeDialog } from './resume-dialog.component'; AngularSvgIconModule, ScrollingModule, MatDialogModule, - ResumeDialog + ResumeDialog, + AggregateDisplayComponent ], exports: [ diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index 04aace6..0e9ceed 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -60,9 +60,30 @@
+
+ Shorten zone names + +
+
Enable stopwatch
+ + +
+ Enable displaying scuffed livesplit + +
+ +
+ Enable aggregate-stats live compare + +
+
+ \ No newline at end of file