Files
openstapps/src/app/modules/schedule/page/components/calendar.component.ts
2022-10-24 17:21:12 +00:00

275 lines
6.8 KiB
TypeScript

/*
* Copyright (C) 2022 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/>.
*/
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {SCISO8601Date, SCUuid} from '@openstapps/core';
import moment, {Moment} from 'moment';
import {
materialFade,
materialManualFade,
materialSharedAxisX,
} from '../../../../animation/material-motion';
import {ScheduleProvider} from '../../../calendar/schedule.provider';
import {ScheduleEvent, ScheduleResponsiveBreakpoint} from '../schema/schema';
import {SwiperComponent} from 'swiper/angular';
import {InfiniteSwiperComponent} from '../grid/infinite-swiper.component';
import {IonContent, IonDatetime} from '@ionic/angular';
import {Subscription} from 'rxjs';
import {CalendarService} from '../../../calendar/calendar.service';
import {getScheduleCursorOffset} from '../grid/schedule-cursor-offset';
/**
* Component that displays the schedule
*/
@Component({
selector: 'stapps-calendar-component',
templateUrl: 'calendar-component.html',
styleUrls: ['calendar-component.scss'],
animations: [materialFade, materialSharedAxisX, materialManualFade],
})
export class CalendarComponent implements OnInit, OnDestroy {
/**
* The day that the schedule started out on
*/
@Input() baselineDate: Moment;
/**
* Range of date of the slides shown on screen.
*/
dateRange: {
startDate: string;
endDate: string;
} = {
startDate: '',
endDate: '',
};
calendarServiceSubscription: Subscription;
prevHeaderIndex = 0;
/**
* Hours for grid
*/
@Input() hours: number[];
/**
* Range of hours to display
*/
@Input() readonly hoursRange = {
from: 5,
to: 22,
};
todaySlideIndex: number;
initialSlideIndex?: Promise<number>;
/**
* Layout of the schedule
*/
@Input() layout: ScheduleResponsiveBreakpoint;
/**
* Route fragment
*/
routeFragment = 'schedule/calendar';
/**
* Vertical scale of the schedule (distance between hour lines)
*/
@Input() scale = 70;
/**
* unix -> (uid -> event)
*/
@Input() testSchedule: Record<SCISO8601Date, Record<SCUuid, ScheduleEvent>> =
{};
/**
* UUIDs
*/
@Input() uuids: SCUuid[];
/**
* UUID subscription
*/
uuidSubscription: Subscription;
@Input() useInfiniteSwiper = true;
@Input() weekDates: Array<Moment>;
constructor(
protected readonly activatedRoute: ActivatedRoute,
protected readonly calendarService: CalendarService,
protected readonly scheduleProvider: ScheduleProvider,
) {}
ngOnInit() {
this.onInit();
}
ngOnDestroy() {
this.onDestroy();
}
onInit() {
let dayString: string | number | null =
this.activatedRoute.snapshot.paramMap.get('date');
if (dayString == undefined || dayString === 'now') {
const fragments = window.location.href.split('/');
const urlFragment: string = fragments[fragments.length - 1] ?? '';
dayString = /^\d{4}-\d{2}-\d{2}$/.test(urlFragment)
? urlFragment
: moment.now();
}
this.baselineDate = moment(dayString).startOf('day');
this.initialSlideIndex = new Promise(resolve => {
this.uuidSubscription = this.scheduleProvider.uuids$.subscribe(
async result => {
this.uuids = result;
resolve(await this.loadEvents());
},
);
});
this.dateRange.startDate = this.calculateDateFromIndex(0, 0, 'DD.MM.YY');
this.dateRange.endDate = this.calculateDateFromIndex(
0,
this.layout.days - 1,
'DD.MM.YY',
);
}
async scrollCursorIntoView(content: IonContent) {
const scrollElement = await content.getScrollElement();
scrollElement.scrollTo({
top:
getScheduleCursorOffset(this.hoursRange.from, this.scale) -
scrollElement.clientHeight / 3,
});
}
onDestroy() {
this.uuidSubscription.unsubscribe();
if (this.calendarServiceSubscription) {
this.calendarServiceSubscription.unsubscribe();
}
}
/**
* Get date from baseline date and index of current slide.
*
* @param index number
* @param delta number - is added to index
* @param dateFormat string
*/
calculateDateFromIndex(index: number, delta = 0, dateFormat = 'YYYY-MM-DD') {
return moment(this.baselineDate)
.add(index + delta, 'days')
.format(dateFormat);
}
/**
* Change page
*/
onPageChange(index: number) {
this.setDateRange(index);
window.history.replaceState(
{},
'',
`${this.routeFragment}/${this.calculateDateFromIndex(index)}`,
);
}
setDateRange(index: number) {
this.dateRange.startDate = this.calculateDateFromIndex(
index,
0,
'DD.MM.YY',
);
this.dateRange.endDate = this.calculateDateFromIndex(
index,
this.layout.days - 1,
'DD.MM.YY',
);
}
onHeaderSwipe(index: number, infiniteController: InfiniteSwiperComponent) {
if (index < this.prevHeaderIndex) {
infiniteController?.pageBackwards();
}
if (index > this.prevHeaderIndex) {
infiniteController?.pageForward();
}
this.prevHeaderIndex = index;
}
syncSwiper(self: SwiperComponent, other: SwiperComponent) {
other.swiperRef.slideTo(self.swiperRef.activeIndex);
}
presentDatePopover(
mainSwiper: InfiniteSwiperComponent,
headerSwiper: InfiniteSwiperComponent,
index: number,
popoverDateTime: IonDatetime,
) {
const nextIndex =
moment(popoverDateTime.value).diff(this.baselineDate, 'days') -
headerSwiper.virtualIndex -
index;
mainSwiper.goToIndex(nextIndex).then(() => {
this.setDateRange(nextIndex);
});
popoverDateTime.confirm(true);
}
/**
* Load events
*/
async loadEvents(): Promise<number> {
const dateSeries = await this.scheduleProvider.getDateSeries(this.uuids);
this.testSchedule = {};
for (const series of dateSeries.dates) {
for (const date of series.dates) {
const index = moment(date)
.startOf('day')
.diff(this.baselineDate, 'days');
// fall back to default
(this.testSchedule[index] ?? (this.testSchedule[index] = {}))[
series.uid
] = {
dateSeries: series,
time: {
start: moment(date).hours(),
duration: series.duration,
},
};
}
}
return this.todaySlideIndex;
}
}