/* * 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 . */ 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; /** * 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> = {}; /** * UUIDs */ @Input() uuids: SCUuid[]; /** * UUID subscription */ uuidSubscription: Subscription; @Input() useInfiniteSwiper = true; @Input() weekDates: Array; 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 { 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; } }