mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-10-07 10:11:59 +00:00
266 lines
6.8 KiB
TypeScript
266 lines
6.8 KiB
TypeScript
/*
|
|
* Copyright (C) 2023 StApps
|
|
* This program is free software: you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation, version 3.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
/* eslint-disable unicorn/no-null */
|
|
import {DestroyRef, inject, Injectable, OnDestroy} from '@angular/core';
|
|
import {
|
|
Bounds,
|
|
SCDateSeries,
|
|
SCISO8601Date,
|
|
SCISO8601Duration,
|
|
SCSearchFilter,
|
|
SCThingType,
|
|
SCUuid,
|
|
} from '@openstapps/core';
|
|
import {BehaviorSubject, Observable} from 'rxjs';
|
|
import {DataProvider} from '../data/data.provider';
|
|
import {map} from 'rxjs/operators';
|
|
import {DateFormatPipe, DurationPipe} from 'ngx-moment';
|
|
import {pick} from '@openstapps/collection-utils';
|
|
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
|
|
|
/**
|
|
*
|
|
*/
|
|
export function toDateSeriesRelevantData(dateSeries: SCDateSeries): DateSeriesRelevantData {
|
|
return pick(dateSeries, dateSeriesRelevantKeys);
|
|
}
|
|
|
|
export type DateSeriesRelevantKeys = 'uid' | 'dates' | 'exceptions' | 'repeatFrequency' | 'duration';
|
|
|
|
export const dateSeriesRelevantKeys: Array<DateSeriesRelevantKeys> = [
|
|
'uid',
|
|
'dates',
|
|
'exceptions',
|
|
'repeatFrequency',
|
|
'duration',
|
|
];
|
|
|
|
export const formatRelevantKeys: {
|
|
[key in DateSeriesRelevantKeys]: (
|
|
value: SCDateSeries[key],
|
|
dateFormatter: DateFormatPipe,
|
|
durationFormatter: DurationPipe,
|
|
) => string;
|
|
} = {
|
|
uid: value => value,
|
|
dates: (value, dateFormatter) => `[${value.map(it => dateFormatter.transform(it)).join(', ')}]`,
|
|
exceptions: (value, dateFormatter) => `[${value?.map(it => dateFormatter.transform(it)).join(', ') ?? ''}]`,
|
|
repeatFrequency: (value, _, durationFormatter) => durationFormatter.transform(value),
|
|
duration: (value, _, durationFormatter) => durationFormatter.transform(value),
|
|
};
|
|
|
|
export type DateSeriesRelevantData = Pick<SCDateSeries, DateSeriesRelevantKeys>;
|
|
|
|
/**
|
|
* Provider for app settings
|
|
*/
|
|
@Injectable()
|
|
export class ScheduleProvider implements OnDestroy {
|
|
private readonly dataProvider = inject(DataProvider);
|
|
|
|
// tslint:disable:prefer-function-over-method
|
|
|
|
private static partialEventsStorageKey = 'schedule::partial_events';
|
|
|
|
private _partialEvents$?: BehaviorSubject<DateSeriesRelevantData[]>;
|
|
|
|
destroy$ = inject(DestroyRef);
|
|
|
|
constructor() {
|
|
window.addEventListener('storage', this.storageListener);
|
|
}
|
|
|
|
/**
|
|
* Push one or more values to local storage
|
|
*/
|
|
private static get<T>(key: string): T[] {
|
|
const item = localStorage.getItem(key);
|
|
if (item == undefined) {
|
|
return [];
|
|
}
|
|
|
|
return JSON.parse(item) as T[];
|
|
}
|
|
|
|
/**
|
|
* Push one or more values to local storage
|
|
*/
|
|
private static set<T>(key: string, item: T[]) {
|
|
const newValue = JSON.stringify(item);
|
|
// prevent feedback loop from storageEvent -> _uuids$.next() -> set -> storageEvent
|
|
if (newValue !== localStorage.getItem(key)) {
|
|
localStorage.setItem(key, newValue);
|
|
}
|
|
}
|
|
|
|
public async restore(uuids: SCUuid[]): Promise<SCDateSeries[] | undefined> {
|
|
if (uuids.length === 0) {
|
|
return undefined;
|
|
}
|
|
const dateSeries = (await this.getDateSeries(uuids)).dates;
|
|
|
|
this._partialEvents$?.next(dateSeries.map(toDateSeriesRelevantData));
|
|
|
|
return dateSeries;
|
|
}
|
|
|
|
/**
|
|
* TODO
|
|
*/
|
|
public get uuids$(): Observable<SCUuid[]> {
|
|
return this.partialEvents$.pipe(map(events => events.map(it => it.uid)));
|
|
}
|
|
|
|
public get partialEvents$(): BehaviorSubject<DateSeriesRelevantData[]> {
|
|
if (!this._partialEvents$) {
|
|
const data = ScheduleProvider.get<DateSeriesRelevantData>(ScheduleProvider.partialEventsStorageKey);
|
|
|
|
this._partialEvents$ = new BehaviorSubject(data ?? []);
|
|
this._partialEvents$.pipe(takeUntilDestroyed(this.destroy$)).subscribe(result => {
|
|
ScheduleProvider.set(ScheduleProvider.partialEventsStorageKey, result);
|
|
});
|
|
}
|
|
|
|
return this._partialEvents$;
|
|
}
|
|
|
|
/**
|
|
* What to do when local storage updates
|
|
*/
|
|
private storageEventHandler(event: StorageEvent) {
|
|
if (
|
|
event.newValue &&
|
|
event.storageArea === localStorage &&
|
|
event.key === ScheduleProvider.partialEventsStorageKey
|
|
) {
|
|
this._partialEvents$?.next(JSON.parse(event.newValue));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Listen to updates in local storage
|
|
*/
|
|
private storageListener = this.storageEventHandler.bind(this);
|
|
|
|
/**
|
|
* Load Date Series
|
|
*/
|
|
async getDateSeries(
|
|
uuids: SCUuid[],
|
|
frequencies?: Array<SCISO8601Duration>,
|
|
from?: SCISO8601Date | 'now',
|
|
to?: SCISO8601Date | 'now',
|
|
): Promise<{
|
|
dates: SCDateSeries[];
|
|
min: SCISO8601Date;
|
|
max: SCISO8601Date;
|
|
}> {
|
|
if (uuids.length === 0) {
|
|
return {
|
|
dates: [],
|
|
min: '',
|
|
max: '',
|
|
};
|
|
}
|
|
|
|
const filters: SCSearchFilter[] = [
|
|
{
|
|
arguments: {
|
|
field: 'type',
|
|
value: SCThingType.DateSeries,
|
|
},
|
|
type: 'value',
|
|
},
|
|
{
|
|
arguments: {
|
|
filters: uuids.map(uid => ({
|
|
arguments: {
|
|
field: 'uid',
|
|
value: uid,
|
|
},
|
|
type: 'value',
|
|
})),
|
|
operation: 'or',
|
|
},
|
|
type: 'boolean',
|
|
},
|
|
];
|
|
|
|
if (frequencies) {
|
|
filters.push({
|
|
arguments: {
|
|
filters: frequencies.map(frequency => ({
|
|
arguments: {
|
|
field: 'repeatFrequency',
|
|
value: frequency,
|
|
},
|
|
type: 'value',
|
|
})),
|
|
operation: 'or',
|
|
},
|
|
type: 'boolean',
|
|
});
|
|
}
|
|
|
|
if (from || to) {
|
|
const bounds: Bounds<string> = {};
|
|
if (from) {
|
|
bounds.lowerBound = {
|
|
limit: from,
|
|
mode: 'inclusive',
|
|
};
|
|
}
|
|
if (to) {
|
|
bounds.upperBound = {
|
|
limit: to,
|
|
mode: 'inclusive',
|
|
};
|
|
}
|
|
filters.push({
|
|
arguments: {
|
|
field: 'dates',
|
|
bounds: bounds,
|
|
},
|
|
type: 'date range',
|
|
});
|
|
}
|
|
|
|
const result = await this.dataProvider.search({
|
|
filter: {
|
|
arguments: {
|
|
filters: filters,
|
|
operation: 'and',
|
|
},
|
|
type: 'boolean',
|
|
},
|
|
size: 50,
|
|
});
|
|
|
|
return {
|
|
dates: result.data as SCDateSeries[],
|
|
// TODO: https://gitlab.com/openstapps/backend/-/issues/100
|
|
min: new Date(2021, 11, 1).toISOString(),
|
|
max: new Date(2022, 1, 24).toISOString(),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* TODO
|
|
*/
|
|
ngOnDestroy(): void {
|
|
window.removeEventListener('storage', this.storageListener);
|
|
}
|
|
}
|