Merge branch 'feat/repeated-countdown-reminder'

* feat/repeated-countdown-reminder:
  feat(simpleCounter): add translations
  feat(simpleCounter): make config for new counter type work
  feat(simpleCounter): make new simple counter type work
  feat(simpleCounter): outline new type
This commit is contained in:
Johannes Millan 2024-11-12 20:20:17 +01:00
commit 28d0771463
12 changed files with 161 additions and 34 deletions

View file

@ -59,6 +59,10 @@ export const SIMPLE_COUNTER_FORM: ConfigFormSection<SimpleCounterConfig> = {
label: T.F.SIMPLE_COUNTER.FORM.TYPE_CLICK_COUNTER,
value: SimpleCounterType.ClickCounter,
},
{
label: T.F.SIMPLE_COUNTER.FORM.TYPE_REPEATED_COUNTDOWN,
value: SimpleCounterType.RepeatedCountdownReminder,
},
],
},
},
@ -79,6 +83,27 @@ export const SIMPLE_COUNTER_FORM: ConfigFormSection<SimpleCounterConfig> = {
label: T.F.SIMPLE_COUNTER.FORM.L_ICON_ON,
},
},
{
key: 'countdownDuration',
type: 'duration',
hideExpression: (model: any) => {
return model.type !== SimpleCounterType.RepeatedCountdownReminder;
},
hooks: {
onInit: (field) => {
console.log(field?.formControl?.value);
if (!field?.formControl?.value && field?.formControl?.value !== null) {
field?.formControl?.setValue(30 * 60000);
}
},
},
templateOptions: {
required: false,
isAllowSeconds: false,
label: T.F.SIMPLE_COUNTER.FORM.L_COUNTDOWN_DURATION,
description: T.G.DURATION_DESCRIPTION,
},
},
{
key: 'triggerOnActions',
type: 'select',

View file

@ -3,7 +3,9 @@
<mat-dialog-content>
<div class="form-wrapper">
<mat-form-field *ngIf="data.simpleCounter.type === SimpleCounterType.ClickCounter">
<mat-form-field
*ngIf="data.simpleCounter.type === SimpleCounterType.ClickCounter || data.simpleCounter.type === SimpleCounterType.RepeatedCountdownReminder"
>
<input
(ngModelChange)="onModelChange($event)"
[ngModel]="val"
@ -37,7 +39,7 @@
mat-button
type="button"
>
{{T.G.CANCEL|translate}}
{{ T.G.CANCEL|translate }}
</button>
<button
class="btn btn-primary submit-button"
@ -46,7 +48,7 @@
type="submit"
>
<mat-icon>save</mat-icon>
{{T.G.SAVE|translate}}
{{ T.G.SAVE|translate }}
</button>
</div>
</mat-dialog-actions>

View file

@ -1,30 +1,28 @@
<div
*ngIf="simpleCounter"
*ngIf="simpleCounter() as sc"
class="wrapper"
>
<ng-container *ngIf="simpleCounter.type===SimpleCounterType.StopWatch">
<ng-container *ngIf="sc.type===SimpleCounterType.StopWatch">
<button
(click)="toggleStopwatch()"
(contextmenu)="edit($event)"
(longPress)="edit()"
[color]="simpleCounter.isOn? 'accent': ''"
[color]="sc.isOn? 'accent': ''"
class="main-btn stopwatch"
mat-mini-fab
>
<mat-icon *ngIf="!simpleCounter.isOn">{{simpleCounter.icon}}</mat-icon>
<mat-icon *ngIf="simpleCounter.isOn"
>{{simpleCounter.iconOn||simpleCounter.icon}}</mat-icon
>
<mat-icon *ngIf="!sc.isOn">{{sc.icon}}</mat-icon>
<mat-icon *ngIf="sc.isOn">{{sc.iconOn||sc.icon}}</mat-icon>
</button>
<div
*ngIf="simpleCounter.countOnDay[todayStr]"
*ngIf="sc.countOnDay[todayStr]"
class="label"
>
{{simpleCounter.countOnDay[todayStr]|msToMinuteClockString}}
{{sc.countOnDay[todayStr]|msToMinuteClockString}}
</div>
</ng-container>
<ng-container *ngIf="simpleCounter.type===SimpleCounterType.ClickCounter">
<ng-container *ngIf="sc.type===SimpleCounterType.ClickCounter">
<button
(click)="toggleCounter()"
(contextmenu)="edit($event)"
@ -33,13 +31,35 @@
color=""
mat-mini-fab
>
<mat-icon>{{simpleCounter.icon}}</mat-icon>
<mat-icon>{{sc.icon}}</mat-icon>
</button>
<div
*ngIf="simpleCounter.countOnDay[todayStr]"
*ngIf="sc.countOnDay[todayStr]"
class="label"
>
{{simpleCounter.countOnDay[todayStr]}}
{{sc.countOnDay[todayStr]}}
</div>
</ng-container>
<ng-container *ngIf="sc.type===SimpleCounterType.RepeatedCountdownReminder">
<button
(click)="toggleStopwatch()"
(contextmenu)="edit($event)"
(longPress)="edit()"
[color]="sc.isOn? 'accent': ''"
class="main-btn repeated-countdown"
mat-mini-fab
>
<mat-icon *ngIf="!sc.isOn">{{sc.icon}}</mat-icon>
<mat-icon *ngIf="sc.isOn">{{sc.iconOn||sc.icon}}</mat-icon>
</button>
<div class="extra-label">{{countdownTime$|async|msToMinuteClockString}}</div>
<div
*ngIf="sc.countOnDay[todayStr]"
class="label"
>
{{sc.countOnDay[todayStr]}}
</div>
</ng-container>

View file

@ -25,6 +25,7 @@
}
}
.extra-label,
.label {
margin-left: 0;
position: absolute;
@ -52,6 +53,11 @@
}
}
.extra-label {
bottom: auto;
top: -4px;
}
.controls {
transition: $transition-standard;
position: absolute;

View file

@ -2,7 +2,7 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Input,
input,
OnDestroy,
OnInit,
} from '@angular/core';
@ -12,8 +12,10 @@ import { MatDialog } from '@angular/material/dialog';
import { DialogSimpleCounterEditComponent } from '../dialog-simple-counter-edit/dialog-simple-counter-edit.component';
import { T } from 'src/app/t.const';
import { GlobalTrackingIntervalService } from '../../../core/global-tracking-interval/global-tracking-interval.service';
import { Subscription } from 'rxjs';
import { merge, of, Subject, Subscription } from 'rxjs';
import { DateService } from 'src/app/core/date/date.service';
import { toObservable } from '@angular/core/rxjs-interop';
import { distinctUntilChanged, filter, map, scan, switchMap } from 'rxjs/operators';
@Component({
selector: 'simple-counter-button',
@ -26,10 +28,35 @@ export class SimpleCounterButtonComponent implements OnDestroy, OnInit {
SimpleCounterType: typeof SimpleCounterType = SimpleCounterType;
todayStr: string = this._dateService.todayStr();
@Input() simpleCounter?: SimpleCounter;
simpleCounter = input<SimpleCounter>();
private _todayStr$ = this._globalTrackingIntervalService.todayDateStr$;
private _subs = new Subscription();
private _resetCountdown$ = new Subject();
private _countdownDuration$ = toObservable(this.simpleCounter).pipe(
map((c) => c?.countdownDuration),
filter((v): v is number => typeof v === 'number' && v > 0),
distinctUntilChanged(),
);
countdownTime$ = this._countdownDuration$.pipe(
switchMap((countdownDuration) =>
merge(of(true), this._resetCountdown$).pipe(
switchMap(() =>
this._globalTrackingIntervalService.tick$.pipe(
scan((acc, tick) => {
if (!this.simpleCounter()?.isOn) {
return acc;
}
const newVal = acc - tick.duration;
return newVal < 0 ? 0 : newVal;
}, countdownDuration),
),
),
),
),
);
constructor(
private _simpleCounterService: SimpleCounterService,
@ -46,6 +73,21 @@ export class SimpleCounterButtonComponent implements OnDestroy, OnInit {
this._cd.detectChanges();
}),
);
if (this.simpleCounter()?.type === SimpleCounterType.RepeatedCountdownReminder) {
this._subs.add(
this.countdownTime$.subscribe((countdownTime) => {
// console.log(countdownTime);
if (countdownTime === 0) {
alert(
`${this.simpleCounter()?.title || 'Simple Counter Countdown'} is finished!`,
);
this.toggleCounter();
this._resetCountdown$.next();
}
}),
);
}
}
ngOnDestroy(): void {
@ -53,35 +95,42 @@ export class SimpleCounterButtonComponent implements OnDestroy, OnInit {
}
toggleStopwatch(): void {
if (!this.simpleCounter) {
const c = this.simpleCounter();
if (!c) {
throw new Error('No simple counter model');
}
this._simpleCounterService.toggleCounter(this.simpleCounter.id);
this._simpleCounterService.toggleCounter(c.id);
}
toggleCounter(): void {
if (!this.simpleCounter) {
const c = this.simpleCounter();
if (!c) {
throw new Error('No simple counter model');
}
this._simpleCounterService.increaseCounterToday(this.simpleCounter.id, 1);
this._simpleCounterService.increaseCounterToday(c.id, 1);
}
reset(): void {
if (!this.simpleCounter) {
const c = this.simpleCounter();
if (!c) {
throw new Error('No simple counter model');
}
this._simpleCounterService.setCounterToday(this.simpleCounter.id, 0);
this._simpleCounterService.setCounterToday(c.id, 0);
}
edit(ev?: Event): void {
if (ev) {
ev.preventDefault();
}
const c = this.simpleCounter();
if (!c) {
throw new Error('No simple counter model');
}
this._matDialog.open(DialogSimpleCounterEditComponent, {
restoreFocus: true,
data: {
simpleCounter: this.simpleCounter,
simpleCounter: c,
},
});
}

View file

@ -3,7 +3,7 @@
[formGroup]="form"
>
<formly-form
[fields]="section?.items"
[fields]="items"
[form]="form"
[(model)]="editModel"
[options]="options"

View file

@ -12,7 +12,7 @@ import {
} from '../../config/global-config.model';
import { ProjectCfgFormKey } from '../../project/project.model';
import { SimpleCounterConfig } from '../simple-counter.model';
import { FormlyFormOptions } from '@ngx-formly/core';
import { FormlyFieldConfig, FormlyFormOptions } from '@ngx-formly/core';
import { UntypedFormGroup } from '@angular/forms';
import { T } from 'src/app/t.const';
import { SimpleCounterService } from '../simple-counter.service';
@ -20,6 +20,7 @@ import { map } from 'rxjs/operators';
import { Observable, Subscription } from 'rxjs';
import { MatDialog } from '@angular/material/dialog';
import { DialogConfirmComponent } from '../../../ui/dialog-confirm/dialog-confirm.component';
import { adjustToLiveFormlyForm } from '../../../util/adjust-to-live-formly-form';
@Component({
selector: 'simple-counter-cfg',
@ -28,8 +29,14 @@ import { DialogConfirmComponent } from '../../../ui/dialog-confirm/dialog-confir
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SimpleCounterCfgComponent implements OnDestroy {
@Input() section?: ConfigFormSection<SimpleCounterConfig>;
@Input() cfg?: SimpleCounterConfig;
@Input() set section(section: ConfigFormSection<SimpleCounterConfig>) {
if (section.items) {
this.items = adjustToLiveFormlyForm(section.items);
}
}
@Output() save: EventEmitter<{
sectionKey: GlobalConfigSectionKey | ProjectCfgFormKey;
config: any;
@ -46,6 +53,7 @@ export class SimpleCounterCfgComponent implements OnDestroy {
})),
);
items: FormlyFieldConfig[] = [];
editModel?: SimpleCounterConfig;
private _inModelCopy?: SimpleCounterConfig;

View file

@ -40,11 +40,19 @@ export const DEFAULT_SIMPLE_COUNTERS: SimpleCounter[] = [
},
{
...EMPTY_SIMPLE_COUNTER,
id: 'COFFEE COUNTER',
id: 'COFFEE_COUNTER',
title: 'Coffee Counter',
type: SimpleCounterType.ClickCounter,
icon: 'free_breakfast',
},
{
...EMPTY_SIMPLE_COUNTER,
id: 'STRETCHING_COUNTER',
title: 'Stretching Counter',
type: SimpleCounterType.RepeatedCountdownReminder,
icon: 'fitness_center',
countdownDuration: 30 * 60 * 1000,
},
];
export const SIMPLE_COUNTER_TRIGGER_ACTIONS: string[] = [

View file

@ -4,6 +4,7 @@ import { MODEL_VERSION_KEY } from '../../app.constants';
export enum SimpleCounterType {
StopWatch = 'StopWatch',
ClickCounter = 'ClickCounter',
RepeatedCountdownReminder = 'RepeatedCountdownReminder',
}
export interface SimpleCounterCfgFields {
@ -19,6 +20,9 @@ export interface SimpleCounterCfgFields {
// adv cfg
triggerOnActions: string[];
triggerOffActions?: string[];
// repeated countdown reminder
countdownDuration?: number;
}
export interface SimpleCounterCopy extends SimpleCounterCfgFields {

View file

@ -66,11 +66,12 @@ export class SimpleCounterEffects {
checkTimedCounters$: Observable<unknown> = createEffect(() =>
this._simpleCounterService.enabledAndToggledSimpleCounters$.pipe(
switchMap((items) =>
items && items.length
switchMap((itemsI) => {
const items = itemsI.filter((item) => item.type === SimpleCounterType.StopWatch);
return items && items.length
? this._timeTrackingService.tick$.pipe(map((tick) => ({ tick, items })))
: EMPTY,
),
: EMPTY;
}),
mergeMap(({ items, tick }) => {
const today = this._dateService.todayStr();
return items.map((item) =>

View file

@ -831,6 +831,7 @@ const T = {
L_AUTO_COUNT_UP: 'F.SIMPLE_COUNTER.FORM.L_AUTO_COUNT_UP',
L_AUTO_SWITCH_OFF: 'F.SIMPLE_COUNTER.FORM.L_AUTO_SWITCH_OFF',
L_AUTO_SWITCH_ON: 'F.SIMPLE_COUNTER.FORM.L_AUTO_SWITCH_ON',
L_COUNTDOWN_DURATION: 'F.SIMPLE_COUNTER.FORM.L_COUNTDOWN_DURATION',
L_ICON: 'F.SIMPLE_COUNTER.FORM.L_ICON',
L_ICON_ON: 'F.SIMPLE_COUNTER.FORM.L_ICON_ON',
L_IS_ENABLED: 'F.SIMPLE_COUNTER.FORM.L_IS_ENABLED',
@ -838,6 +839,7 @@ const T = {
L_TYPE: 'F.SIMPLE_COUNTER.FORM.L_TYPE',
TITLE: 'F.SIMPLE_COUNTER.FORM.TITLE',
TYPE_CLICK_COUNTER: 'F.SIMPLE_COUNTER.FORM.TYPE_CLICK_COUNTER',
TYPE_REPEATED_COUNTDOWN: 'F.SIMPLE_COUNTER.FORM.TYPE_REPEATED_COUNTDOWN',
TYPE_STOPWATCH: 'F.SIMPLE_COUNTER.FORM.TYPE_STOPWATCH',
},
},

View file

@ -818,6 +818,7 @@
"L_AUTO_COUNT_UP": "Auto trigger count up for",
"L_AUTO_SWITCH_OFF": "Auto trigger switch off for",
"L_AUTO_SWITCH_ON": "Auto trigger switch on for",
"L_COUNTDOWN_DURATION": "Countdown duration",
"L_ICON": "Icon",
"L_ICON_ON": "Icon when toggled",
"L_IS_ENABLED": "Enabled",
@ -825,6 +826,7 @@
"L_TYPE": "Type",
"TITLE": "Simple Counters",
"TYPE_CLICK_COUNTER": "Click Counter",
"TYPE_REPEATED_COUNTDOWN": "Repeated Countdown",
"TYPE_STOPWATCH": "Stopwatch"
}
},