Allow stopwatch and resuming runs, low resolution on saves, to not write to disk too often...

main 1.5.0
isark 1 year ago
parent 54fd9ca62e
commit 32a2424783

@ -47,6 +47,9 @@ pub struct Config {
pub num_visible: u16,
#[serde(default = "Config::default_plan_offset")]
pub offset: i16,
#[serde(default = "Config::default_enable_stopwatch")]
pub enable_stopwatch: bool,
}
impl Default for Config {
@ -66,6 +69,7 @@ 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(),
}
}
}
@ -118,4 +122,7 @@ impl Config {
fn default_plan_offset() -> i16 {
1
}
fn default_enable_stopwatch() -> bool {
false
}
}

@ -104,7 +104,6 @@ fn save_plan_at_path(path: PathBuf, plan: Plan) -> bool {
#[tauri::command]
fn save_plan_at_store(name: String, plan: Plan, allow_overwrite: bool) -> Option<PathBuf> {
log::trace!("plan etag {:?}", plan.metadata.latest_server_etag);
Storage::save_plan_at_store_path(&name, plan, allow_overwrite).ok()
}

@ -7,15 +7,16 @@ pub struct Plan {
plan: Vec<PlanElement>,
current: usize,
#[serde(flatten)]
pub metadata: PlanMetadata,
metadata: PlanMetadata,
}
#[derive(Deserialize, Debug, Clone)]
pub struct PlanMetadata {
stored_path: Option<PathBuf>,
update_url: Option<String>,
pub latest_server_etag: Option<String>,
latest_server_etag: Option<String>,
identifier: Option<String>,
last_stored_time: Option<u64>,
}
impl PlanMetadata {
@ -29,12 +30,13 @@ impl Serialize for PlanMetadata {
where
S: serde::Serializer,
{
let mut state = serializer.serialize_struct("PlanMetadata", 5)?;
let mut state = serializer.serialize_struct("PlanMetadata", 6)?;
state.serialize_field("update_url", &self.update_url)?;
state.serialize_field("stored_path", &self.stored_path)?;
state.serialize_field("latest_server_etag", &self.latest_server_etag)?;
state.serialize_field("identifier", &self.identifier)?;
state.serialize_field("last_stored_time", &self.last_stored_time)?;
if let Some(path) = &self.stored_path {
if let Some(name) = path.file_name() {
@ -133,6 +135,7 @@ pub fn convert_old(path: PathBuf) -> Option<Plan> {
update_url: None,
latest_server_etag: None,
identifier: None,
last_stored_time: None,
},
})
}

@ -8,7 +8,7 @@
},
"package": {
"productName": "Nothing",
"version": "1.4.0"
"version": "1.5.0"
},
"tauri": {
"systemTray": {

@ -9,6 +9,7 @@ export interface PlanInterface {
name?: string;
latest_server_etag?: string;
identifier?: string,
last_stored_time?: number;
}
export interface PlanMetadata {
@ -17,6 +18,7 @@ export interface PlanMetadata {
name: string;
latest_server_etag?: string;
identifier?: string;
last_stored_time?: number;
}
export class Plan {
@ -26,6 +28,8 @@ export class Plan {
name?: string;
latest_server_etag?: string;
identifier?: string;
last_stored_time?: number
private path?: string;
private selfSaveSubject: Subject<void> = new Subject<void>();
@ -41,6 +45,7 @@ export class Plan {
this.name = plan.name;
this.latest_server_etag = plan.latest_server_etag;
this.identifier = plan.identifier;
this.last_stored_time = plan.last_stored_time;
this.selfSaveSubject.pipe(debounceTime(500)).subscribe(() => this.directSelfSave());
}
@ -70,10 +75,11 @@ export class Plan {
update_url: this.update_url,
latest_server_etag: this.latest_server_etag,
identifier: this.identifier,
last_stored_time: this.last_stored_time,
};
}
private requestSelfSave() {
public requestSelfSave() {
if (this.path) {
this.selfSaveSubject.next();
}

@ -46,6 +46,7 @@
zones</span>
</div>
</tooltip>
<span *ngIf="shouldDisplayTimer()" class="timer">{{currentTime()}}</span>
</div>
<ngx-moveable #moveable [target]="targetRef" [draggable]="draggable && overlayService.interactable"

@ -117,6 +117,12 @@ notes {
left: 0;
}
.timer {
position: absolute;
top: 0;
left: 32px;
}
.help-area {
background-color: rgba(50, 50, 50, 1);
border: 1px solid black;
@ -136,7 +142,6 @@ notes {
font-size: 10rem;
& span {
display: flex;
align-items: center;
@ -168,6 +173,8 @@ notes {
display: block;
overflow: hidden;
flex-grow: 1;
background-color: rgba(200, 200, 220, 0.15);
border: 1px solid rgba(255, 255, 255, 0.3);
mat-list {
max-height: 100%;

@ -9,12 +9,20 @@ import { PlanService, UrlError } from '../_services/plan.service';
import { Plan, PlanElement, PlanMetadata } from '../_models/plan';
import { WorldAreaService } from '../_services/world-area.service';
import { WorldArea } from '../_models/world-area';
import { Subscription, from } from 'rxjs';
import { Subscription, from, timer } from 'rxjs';
import { open } from '@tauri-apps/api/dialog';
import { OverlayService, StateEvent } from '../_services/overlay.service';
import { appWindow } from '@tauri-apps/api/window';
import { EventsService } from '../_services/events.service';
import { Event } from '@tauri-apps/api/event';
import { MatDialog } from '@angular/material/dialog';
import { ResumeDialog } from './resume-dialog.component';
enum Resume {
Discard,
Next,
Instant
}
@Component({
selector: 'plan-display',
@ -45,9 +53,15 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit {
previousPlans: PlanMetadata[] = [];
checkingPlanUpdate: boolean = false;
start?: Date;
latest?: Date;
timerSubscription?: Subscription;
debouncedSaveStopwatch?: Subscription;
recentUpdateAttempts: Map<string, Date | UrlError> = new Map<string, Date | UrlError>();
resumeOnNext: boolean = false;
constructor(private events: EventsService, public configService: ConfigService, private cdr: ChangeDetectorRef, private shortcut: ShortcutService, public planService: PlanService, public worldAreaService: WorldAreaService, public overlayService: OverlayService, private zone: NgZone) {
constructor(private events: EventsService, public configService: ConfigService, private cdr: ChangeDetectorRef, private shortcut: ShortcutService, public planService: PlanService, public worldAreaService: WorldAreaService, public overlayService: OverlayService, private zone: NgZone, public dialog: MatDialog) {
window.addEventListener("resize", () => {
this.zone.run(() => {
this.windowInitHandler()
@ -60,15 +74,26 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit {
})
this.planService.getCurrentPlan().subscribe(plan => {
if (this.timerSubscription && !this.timerSubscription.closed) this.timerSubscription.unsubscribe();
if (this.debouncedSaveStopwatch && !this.debouncedSaveStopwatch.closed) this.debouncedSaveStopwatch.unsubscribe();
this.currentPlan = plan;
this.start = undefined;
//Close settings anytime we get a new current plan.
this.settingsOpen = false;
if (this.currentPlan.last_stored_time) {
this.askResume();
}
setTimeout(() => this.setIndex(plan.current), 0);
})
this.registerOnZoneEnter();
}
get disablePlans(): boolean {
return this.checkingPlanUpdate;
}
@ -82,11 +107,43 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit {
if (entered.payload === this.currentPlan.plan[current + 1].area_key) {
this.zone.run(() => this.next());
}
console.log("config enableStopwatch", this.configService.config.enableStopwatch)
if (this.configService.config.enableStopwatch) {
console.log("this.start", this.start);
if (entered.payload === this.currentPlan!.plan[0].area_key && !this.start || this.resumeOnNext) {
this.resumeOnNext = false;
this.startStopwatch();
}
}
}
}
});
}
///Assumes currentPlan is set...
private startStopwatch() {
console.log("Starting stopwatch");
if (this.currentPlan?.last_stored_time) {
this.start = new Date(Date.now() - this.currentPlan.last_stored_time);
} else {
this.start = new Date();
}
if (this.timerSubscription && !this.timerSubscription.closed) this.timerSubscription.unsubscribe();
if (this.debouncedSaveStopwatch && !this.debouncedSaveStopwatch.closed) this.debouncedSaveStopwatch.unsubscribe();
this.timerSubscription = timer(0, 1000).subscribe(() => {
this.zone.run(() => { this.latest = new Date(); });
});
this.debouncedSaveStopwatch = timer(0, 10000).subscribe(() => {
this.currentPlan!.last_stored_time = this.elapsedTimeMillis();
console.log("last stored time at save attempt", this.currentPlan!.last_stored_time);
this.currentPlan!.requestSelfSave();
})
}
windowInitHandler() {
if (window.innerWidth > 0) {
this.ngAfterViewInit();
@ -364,6 +421,48 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit {
if (this.hasUpdate(plan) && this.isErr(plan)) return "Error updating";
return "";
}
currentTime() {
if (!this.latest || !this.start) return "";
const diff = this.latest.valueOf() - this.start.valueOf();
const h = String(Math.floor(diff / 3600000)).padStart(2, '0');
const m = String(Math.floor((diff % 3600000) / 60000)).padStart(2, '0');
const s = String(Math.floor((diff % 60000) / 1000)).padStart(2, '0');
return `${h}:${m}:${s}`;
}
elapsedTimeMillis() {
return this.latest!.valueOf() - this.start!.valueOf();
}
shouldDisplayTimer(): boolean {
if (!this.configService.config.enableStopwatch) return false;
if (!this.start || !this.latest) return false;
return this.overlayService.visible && !this.overlayService.interactable;
}
askResume() {
console.log("Asking resume");
const dialogRef = this.dialog.open(ResumeDialog, {disableClose: true});
dialogRef.afterClosed().subscribe(resume => {
switch(resume) {
case Resume.Instant:
this.startStopwatch();
break;
case Resume.Next:
this.resumeOnNext = true;
break;
case Resume.Discard:
this.currentPlan!.last_stored_time = undefined;
this.currentPlan!.requestSelfSave();
break;
}
})
}
}
function commonUUIDToNewCurrent(oldPlan: Plan, updatedPlan: Plan): number {

@ -16,6 +16,8 @@ import {MatTooltipModule} from '@angular/material/tooltip';
import { TooltipComponent } from '../tooltip/tooltip.component';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { MatDialogModule } from '@angular/material/dialog';
import { ResumeDialog } from './resume-dialog.component';
@NgModule({
declarations: [
PlanDisplayComponent
@ -37,7 +39,10 @@ import { ScrollingModule } from '@angular/cdk/scrolling';
MatTooltipModule,
TooltipComponent,
AngularSvgIconModule,
ScrollingModule
ScrollingModule,
MatDialogModule,
ResumeDialog
],
exports: [
PlanDisplayComponent

@ -0,0 +1,6 @@
Resume instantaneously, on next zone enter or not at all?
<div mat-dialog-actions>
<button mat-button color="color-resume-instant" (click)="instant()">Instant</button>
<button mat-button color="color-resume-next" (click)="next()" cdkFocusInitial>Next zone</button>
<button mat-button color="color-resume-no" (click)="discard()" cdkFocusInitial>Not at all</button>
</div>

@ -0,0 +1,33 @@
import { Component } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { MatButtonModule } from "@angular/material/button";
import { MatDialogModule, MatDialogRef } from "@angular/material/dialog";
import { MatFormFieldModule } from "@angular/material/form-field";
import { MatInputModule } from "@angular/material/input";
export enum Resume {
Discard,
Next,
Instant
}
@Component({
selector: 'resume-dialog',
templateUrl: 'resume-dialog.component.html',
standalone: true,
imports: [MatDialogModule, MatFormFieldModule, MatInputModule, FormsModule, MatButtonModule],
})
export class ResumeDialog {
instant() {
this.dialogRef.close(Resume.Instant);
}
next() {
this.dialogRef.close(Resume.Next);
}
discard() {
this.dialogRef.close(Resume.Discard);
}
constructor(public dialogRef: MatDialogRef<ResumeDialog>) {}
}

@ -59,4 +59,10 @@
[max]="configService.config.numVisible - 1" step="1">
</mat-form-field>
</div>
<div class="d-flex flex-row justify-content-between">
<span class="d-block">Enable stopwatch</span>
<mat-slide-toggle [color]="overlayService.isOverlay ? 'primary-on-dark' : 'primary'"
[(ngModel)]="configService.config.enableStopwatch"></mat-slide-toggle>
</div>
</div>

@ -80,4 +80,16 @@ div.picker_wrapper.popup {
.mdc-notched-outline__notch {
clip-path: none !important;
}
.mat-color-resume-instant {
background-color: rgb(76, 146, 146);
}
.mat-color-resume-next {
background-color: rgb(120, 76, 146);
}
.mat-color-resume-no {
background-color: rgb(180, 41, 41);
}
Loading…
Cancel
Save