mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-21 09:03:02 +00:00
refactor: use SwiperJS in schedule module
This commit is contained in:
22
package-lock.json
generated
22
package-lock.json
generated
@@ -8789,6 +8789,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dom7": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom7/-/dom7-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-xOJ0LAHFwktyj8Xljz4R2wzRI+Y9mR0plkMP0WlqtwqAkqn/vbdAyRifiW/w8mXe17LGktntcAwsQ5fKVDBNYg==",
|
||||||
|
"requires": {
|
||||||
|
"ssr-window": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"domelementtype": {
|
"domelementtype": {
|
||||||
"version": "1.3.1",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
|
||||||
@@ -19717,6 +19725,11 @@
|
|||||||
"tweetnacl": "~0.14.0"
|
"tweetnacl": "~0.14.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ssr-window": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ssr-window/-/ssr-window-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-qCg6wJNeGNTVcPK2KFNfwtHU1gA3UZDZdxogu+Ys5+Ue5PMOENxUb7sscpAWWo4mWOBkJRCwQ50IlyA7qZ0hxw=="
|
||||||
|
},
|
||||||
"ssri": {
|
"ssri": {
|
||||||
"version": "8.0.1",
|
"version": "8.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
|
||||||
@@ -20306,6 +20319,15 @@
|
|||||||
"stable": "^0.1.8"
|
"stable": "^0.1.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"swiper": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/swiper/-/swiper-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-uDsORU5ZS8q8Q0Mf4ml1FuOabjm3EWJOoFAUaENcIlgHhdr0sTPeX6BZZUaQ8qXWuBNEvz4XE2wJeVR7M03wIw==",
|
||||||
|
"requires": {
|
||||||
|
"dom7": "^4.0.0",
|
||||||
|
"ssr-window": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"symbol-observable": {
|
"symbol-observable": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
|
||||||
|
|||||||
@@ -82,6 +82,7 @@
|
|||||||
"core-js": "2.6.5",
|
"core-js": "2.6.5",
|
||||||
"deepmerge": "3.3.0",
|
"deepmerge": "3.3.0",
|
||||||
"form-data": "2.5.0",
|
"form-data": "2.5.0",
|
||||||
|
"swiper": "7.1.0",
|
||||||
"geojson": "0.5.0",
|
"geojson": "0.5.0",
|
||||||
"leaflet": "1.7.1",
|
"leaflet": "1.7.1",
|
||||||
"leaflet.markercluster": "1.5.1",
|
"leaflet.markercluster": "1.5.1",
|
||||||
|
|||||||
@@ -12,23 +12,14 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with
|
* You should have received a copy of the GNU General Public License along with
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import {
|
import {Component, Input, OnDestroy, OnInit, ViewChild} from '@angular/core';
|
||||||
Component,
|
|
||||||
Input,
|
|
||||||
OnChanges,
|
|
||||||
OnDestroy,
|
|
||||||
OnInit,
|
|
||||||
SimpleChanges,
|
|
||||||
ViewChild,
|
|
||||||
} from '@angular/core';
|
|
||||||
import {ActivatedRoute} from '@angular/router';
|
import {ActivatedRoute} from '@angular/router';
|
||||||
import {IonDatetime, Platform} from '@ionic/angular';
|
import {Platform} from '@ionic/angular';
|
||||||
import {SCUuid} from '@openstapps/core';
|
import {SCISO8601Date, SCUuid} from '@openstapps/core';
|
||||||
import {last} from 'lodash-es';
|
import {last} from 'lodash-es';
|
||||||
import moment, {Moment} from 'moment';
|
import moment, {Moment} from 'moment';
|
||||||
import {DateFormatPipe} from 'ngx-moment';
|
import {DateFormatPipe} from 'ngx-moment';
|
||||||
import {Subscription} from 'rxjs';
|
import {Subscription} from 'rxjs';
|
||||||
import {SharedAxisChoreographer} from '../../../animation/animation-choreographer';
|
|
||||||
import {
|
import {
|
||||||
materialFade,
|
materialFade,
|
||||||
materialManualFade,
|
materialManualFade,
|
||||||
@@ -36,6 +27,7 @@ import {
|
|||||||
} from '../../../animation/material-motion';
|
} from '../../../animation/material-motion';
|
||||||
import {ScheduleProvider} from '../schedule.provider';
|
import {ScheduleProvider} from '../schedule.provider';
|
||||||
import {ScheduleEvent, ScheduleResponsiveBreakpoint} from './schema/schema';
|
import {ScheduleEvent, ScheduleResponsiveBreakpoint} from './schema/schema';
|
||||||
|
import {SwiperComponent} from 'swiper/angular';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that displays the schedule
|
* Component that displays the schedule
|
||||||
@@ -46,42 +38,21 @@ import {ScheduleEvent, ScheduleResponsiveBreakpoint} from './schema/schema';
|
|||||||
styleUrls: ['calendar-view.scss'],
|
styleUrls: ['calendar-view.scss'],
|
||||||
animations: [materialFade, materialSharedAxisX, materialManualFade],
|
animations: [materialFade, materialSharedAxisX, materialManualFade],
|
||||||
})
|
})
|
||||||
export class CalendarViewComponent implements OnDestroy, OnInit, OnChanges {
|
export class CalendarViewComponent implements OnDestroy, OnInit {
|
||||||
/**
|
/**
|
||||||
* UUID subscription
|
* UUID subscription
|
||||||
*/
|
*/
|
||||||
private _uuidSubscription: Subscription;
|
private _uuidSubscription: Subscription;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The day that is routed to
|
* The day that the schedule started out on
|
||||||
*/
|
*/
|
||||||
protected routeDate: Moment;
|
baselineDate: Moment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see {blockDateTimeChange}
|
* Oldest event to newest event
|
||||||
*/
|
*/
|
||||||
anticipateDatetimeChangeBlocked = false;
|
dateRange: [Date, Date];
|
||||||
|
|
||||||
/**
|
|
||||||
* @see {blockDateTimeChange}
|
|
||||||
*/
|
|
||||||
// tslint:disable-next-line:no-magic-numbers
|
|
||||||
readonly anticipateDatetimeChangeTimeoutMs: 100;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Animation state for cards
|
|
||||||
*/
|
|
||||||
cardsAnimationState: 'in' | 'out' = 'out';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The cursor
|
|
||||||
*/
|
|
||||||
@ViewChild('cursor', {read: HTMLElement}) cursor?: HTMLElement;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Choreographer
|
|
||||||
*/
|
|
||||||
dateLabelsChoreographer: SharedAxisChoreographer<Moment[]>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The date range to initially display
|
* The date range to initially display
|
||||||
@@ -106,11 +77,17 @@ export class CalendarViewComponent implements OnDestroy, OnInit, OnChanges {
|
|||||||
to: 22,
|
to: 22,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
todaySlideIndex: number;
|
||||||
|
|
||||||
|
initialSlideIndex?: Promise<number>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Layout of the schedule
|
* Layout of the schedule
|
||||||
*/
|
*/
|
||||||
@Input() layout: ScheduleResponsiveBreakpoint;
|
@Input() layout: ScheduleResponsiveBreakpoint;
|
||||||
|
|
||||||
|
@ViewChild('mainSwiper', {static: false}) swiperRef: SwiperComponent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the date format for the date field
|
* Get the date format for the date field
|
||||||
*/
|
*/
|
||||||
@@ -128,15 +105,20 @@ export class CalendarViewComponent implements OnDestroy, OnInit, OnChanges {
|
|||||||
scale = 60;
|
scale = 60;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* date -> (uid -> event)
|
* unix -> (uid -> event)
|
||||||
*/
|
*/
|
||||||
testSchedule: Record<string, Record<SCUuid, ScheduleEvent>> = {};
|
testSchedule: Record<SCISO8601Date, Record<SCUuid, ScheduleEvent>> = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UUIDs
|
* UUIDs
|
||||||
*/
|
*/
|
||||||
uuids: SCUuid[];
|
uuids: SCUuid[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For use in templates
|
||||||
|
*/
|
||||||
|
moment = moment;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly scheduleProvider: ScheduleProvider,
|
protected readonly scheduleProvider: ScheduleProvider,
|
||||||
protected readonly activatedRoute: ActivatedRoute,
|
protected readonly activatedRoute: ActivatedRoute,
|
||||||
@@ -152,88 +134,22 @@ export class CalendarViewComponent implements OnDestroy, OnInit, OnChanges {
|
|||||||
this.hours = [...Array.from({length: this.hoursAmount}).keys()];
|
this.hours = [...Array.from({length: this.hoursAmount}).keys()];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Because of some stupid Ionic implementation, there is no
|
|
||||||
* way to wait for the datetime picker to be dismissed without
|
|
||||||
* listening for (ionChange). Unfortunately that also includes
|
|
||||||
* changes caused by a page change, so whenever we do that,
|
|
||||||
* we have to block the event for a few milliseconds.
|
|
||||||
*/
|
|
||||||
blockDateTimeChange() {
|
|
||||||
this.anticipateDatetimeChangeBlocked = true;
|
|
||||||
setTimeout(() => {
|
|
||||||
this.anticipateDatetimeChangeBlocked = false;
|
|
||||||
}, this.anticipateDatetimeChangeTimeoutMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine displayed dates according to display size
|
|
||||||
*/
|
|
||||||
determineDisplayDates() {
|
|
||||||
// let's boldly assume that we at least display one day
|
|
||||||
|
|
||||||
const out = [moment(this.routeDate).startOf(this.layout.startOf)];
|
|
||||||
for (let i = 1; i < this.layout.days; i++) {
|
|
||||||
out.push(out[0].clone().add(i, 'day'));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.displayDates = [
|
|
||||||
out.map(it => it.clone().subtract(this.layout.days, 'days')),
|
|
||||||
out,
|
|
||||||
out.map(it => it.clone().add(this.layout.days, 'days')),
|
|
||||||
];
|
|
||||||
|
|
||||||
this.dateLabelsChoreographer?.changeViewForState(this.getDateLabels(), 0);
|
|
||||||
// void this.mainSlides.slideTo(this.mode === 'schedule' ? 0 : 1, 0, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get date labels
|
|
||||||
*/
|
|
||||||
getDateLabels(): Moment[] {
|
|
||||||
return (this.displayDates[1] ?? this.displayDates[0]).map(it => it.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Jump to a date
|
|
||||||
*/
|
|
||||||
jumpToDate(alt: IonDatetime, offset = 0, date?: Moment) {
|
|
||||||
if (this.anticipateDatetimeChangeBlocked) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newDate = (date ?? moment(alt.value)).subtract(offset, 'days');
|
|
||||||
const direction = this.routeDate.isBefore(newDate)
|
|
||||||
? 1
|
|
||||||
: this.routeDate.isAfter(newDate)
|
|
||||||
? -1
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
this.blockDateTimeChange();
|
|
||||||
this.routeDate = newDate;
|
|
||||||
this.determineDisplayDates();
|
|
||||||
|
|
||||||
this.dateLabelsChoreographer.changeViewForState(
|
|
||||||
this.getDateLabels(),
|
|
||||||
direction,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load events
|
* Load events
|
||||||
*/
|
*/
|
||||||
async loadEvents(): Promise<void> {
|
async loadEvents(): Promise<number> {
|
||||||
this.cardsAnimationState = 'out';
|
|
||||||
const dateSeries = await this.scheduleProvider.getDateSeries(this.uuids);
|
const dateSeries = await this.scheduleProvider.getDateSeries(this.uuids);
|
||||||
|
|
||||||
this.testSchedule = {};
|
this.testSchedule = {};
|
||||||
|
|
||||||
for (const series of dateSeries) {
|
for (const series of dateSeries.dates) {
|
||||||
for (const date of series.dates) {
|
for (const date of series.dates) {
|
||||||
const parsedDate = moment(date).startOf('day').unix();
|
const index = moment(date)
|
||||||
|
.startOf('day')
|
||||||
|
.diff(this.baselineDate, 'days');
|
||||||
|
|
||||||
// fall back to default
|
// fall back to default
|
||||||
(this.testSchedule[parsedDate] ?? (this.testSchedule[parsedDate] = {}))[
|
(this.testSchedule[index] ?? (this.testSchedule[index] = {}))[
|
||||||
series.uid
|
series.uid
|
||||||
] = {
|
] = {
|
||||||
dateSeries: series,
|
dateSeries: series,
|
||||||
@@ -245,20 +161,7 @@ export class CalendarViewComponent implements OnDestroy, OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.cursor?.scrollIntoView();
|
return this.todaySlideIndex;
|
||||||
this.cardsAnimationState = 'in';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On Changes
|
|
||||||
*/
|
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
|
||||||
const layout = changes.layout?.currentValue as
|
|
||||||
| ScheduleResponsiveBreakpoint
|
|
||||||
| undefined;
|
|
||||||
if (layout) {
|
|
||||||
this.determineDisplayDates();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -272,13 +175,6 @@ export class CalendarViewComponent implements OnDestroy, OnInit, OnChanges {
|
|||||||
* Initialize
|
* Initialize
|
||||||
*/
|
*/
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this._uuidSubscription = this.scheduleProvider.uuids$.subscribe(
|
|
||||||
async result => {
|
|
||||||
this.uuids = result;
|
|
||||||
await this.loadEvents();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let dayString: string | number | null =
|
let dayString: string | number | null =
|
||||||
this.activatedRoute.snapshot.paramMap.get('date');
|
this.activatedRoute.snapshot.paramMap.get('date');
|
||||||
if (dayString == undefined || dayString === 'now') {
|
if (dayString == undefined || dayString === 'now') {
|
||||||
@@ -288,37 +184,30 @@ export class CalendarViewComponent implements OnDestroy, OnInit, OnChanges {
|
|||||||
? urlFragment
|
? urlFragment
|
||||||
: moment.now();
|
: moment.now();
|
||||||
}
|
}
|
||||||
this.routeDate = moment(dayString).startOf('day');
|
|
||||||
this.dateLabelsChoreographer = new SharedAxisChoreographer(
|
|
||||||
this.getDateLabels(),
|
|
||||||
);
|
|
||||||
|
|
||||||
this.determineDisplayDates();
|
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());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change page
|
* Change page
|
||||||
*/
|
*/
|
||||||
async onPageChange(direction: number) {
|
onPageChange(index: number) {
|
||||||
this.blockDateTimeChange();
|
|
||||||
const amount = direction * this.displayDates[0].length;
|
|
||||||
|
|
||||||
this.routeDate.add(amount, 'days');
|
|
||||||
window.history.replaceState(
|
window.history.replaceState(
|
||||||
{},
|
{},
|
||||||
'',
|
'',
|
||||||
`#/${this.routeFragment}/${this.routeDate.format('YYYY-MM-DD')}`,
|
`${this.routeFragment}/${this.baselineDate
|
||||||
);
|
.clone()
|
||||||
|
.add(index, 'days')
|
||||||
for (const slide of this.displayDates) {
|
.format('YYYY-MM-DD')}`,
|
||||||
for (const date of slide) {
|
|
||||||
date.add(amount, 'days');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dateLabelsChoreographer.changeViewForState(
|
|
||||||
this.getDateLabels(),
|
|
||||||
direction > 0 ? 1 : direction < 0 ? -1 : 0,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,80 +1,89 @@
|
|||||||
<div>
|
<!--
|
||||||
<ion-button fill="clear" class="left-button" (click)="mainSlides.prevPage()">
|
~ Copyright (C) 2021 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/>.
|
||||||
|
-->
|
||||||
|
<div class="header">
|
||||||
|
<ion-button
|
||||||
|
fill="clear"
|
||||||
|
class="left-button"
|
||||||
|
(click)="mainSwiper.pageBackwards()"
|
||||||
|
>
|
||||||
<ion-icon slot="icon-only" name="chevron-back-outline"></ion-icon>
|
<ion-icon slot="icon-only" name="chevron-back-outline"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-button fill="clear" class="right-button" (click)="mainSlides.nextPage()">
|
<ion-button
|
||||||
|
fill="clear"
|
||||||
|
class="right-button"
|
||||||
|
(click)="mainSwiper.pageForward()"
|
||||||
|
>
|
||||||
<ion-icon slot="icon-only" name="chevron-forward-outline"></ion-icon>
|
<ion-icon slot="icon-only" name="chevron-forward-outline"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-grid
|
<infinite-swiper
|
||||||
class="day-labels"
|
class="header-swiper"
|
||||||
[@materialSharedAxisX]="dateLabelsChoreographer.animationState"
|
#headerSwiper
|
||||||
(@materialSharedAxisX.done)="dateLabelsChoreographer.animationDone()"
|
[slidesPerView]="layout.days"
|
||||||
|
[controller]="mainSwiper"
|
||||||
>
|
>
|
||||||
<ion-row>
|
<ng-template let-index>
|
||||||
<ion-col
|
<div
|
||||||
*ngFor="
|
*ngIf="index | dateFromIndex: baselineDate as date"
|
||||||
let item of dateLabelsChoreographer.currentValue;
|
class="day-labels"
|
||||||
let idx = index
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<ion-button expand="block" fill="clear" (click)="datetime.open()">
|
<ion-button expand="block" fill="clear" (click)="datetime.open()">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
{{
|
{{
|
||||||
item
|
date
|
||||||
| amDateFormat: ((item | dateIsThis: 'week') ? 'dddd' : 'll')
|
| amDateFormat: ((date | dateIsThis: 'week') ? 'dddd' : 'll')
|
||||||
}}
|
}}
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
||||||
<!-- This poor datetime element is a phantom element to provide us with a date picker -->
|
<!-- This poor datetime element is a phantom element to provide us with a date picker -->
|
||||||
<ion-datetime
|
<ion-datetime
|
||||||
|
class="phantom"
|
||||||
#datetime
|
#datetime
|
||||||
[displayFormat]="localDateFormat"
|
[displayFormat]="localDateFormat"
|
||||||
[value]="item.toISOString()"
|
[value]="date.toISOString()"
|
||||||
(ionChange)="jumpToDate(datetime, idx)"
|
(ionChange)="
|
||||||
|
mainSwiper.goToIndex(
|
||||||
|
moment($event.detail.value).diff(baselineDate, 'days')
|
||||||
|
)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
</ion-datetime>
|
</ion-datetime>
|
||||||
</ion-col>
|
</div>
|
||||||
</ion-row>
|
</ng-template>
|
||||||
</ion-grid>
|
</infinite-swiper>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</div>
|
</div>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<stapps-infinite-slides
|
<infinite-swiper
|
||||||
#mainSlides
|
#mainSwiper
|
||||||
(pageChangeCallback)="onPageChange($event.direction)"
|
[controller]="headerSwiper"
|
||||||
|
[slidesPerView]="layout.days"
|
||||||
|
(indexChange)="onPageChange($event)"
|
||||||
>
|
>
|
||||||
<ion-slide class="slide" *ngFor="let slide of displayDates">
|
<ng-template let-index>
|
||||||
<ion-grid>
|
<schedule-day
|
||||||
<ion-row>
|
[day]="index | dateFromIndex: baselineDate"
|
||||||
<ion-col *ngFor="let item of slide">
|
[hoursRange]="hoursRange"
|
||||||
<div
|
[scale]="scale"
|
||||||
class="vertical-line"
|
[uuids]="uuids"
|
||||||
[style.height.px]="hoursAmount * scale"
|
[dateSeries]="testSchedule[index]"
|
||||||
></div>
|
>
|
||||||
<stapps-schedule-cursor
|
</schedule-day>
|
||||||
*ngIf="item | dateIsThis: 'date'"
|
</ng-template>
|
||||||
[hoursRange]="hoursRange"
|
</infinite-swiper>
|
||||||
[scale]="scale"
|
|
||||||
#cursor
|
|
||||||
>
|
|
||||||
</stapps-schedule-cursor>
|
|
||||||
<div [@materialManualFade]="cardsAnimationState">
|
|
||||||
<stapps-schedule-card
|
|
||||||
*ngFor="let entry of testSchedule[item.unix()] | keyvalue"
|
|
||||||
[scheduleEvent]="entry.value"
|
|
||||||
[fromHour]="hoursRange.from"
|
|
||||||
[scale]="scale"
|
|
||||||
>
|
|
||||||
</stapps-schedule-card>
|
|
||||||
</div>
|
|
||||||
</ion-col>
|
|
||||||
</ion-row>
|
|
||||||
</ion-grid>
|
|
||||||
</ion-slide>
|
|
||||||
</stapps-infinite-slides>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="hour-lines"
|
class="hour-lines"
|
||||||
*ngFor="let i of hours"
|
*ngFor="let i of hours"
|
||||||
|
|||||||
@@ -1,4 +1,24 @@
|
|||||||
div {
|
/*!
|
||||||
|
* Copyright (C) 2021 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.header-swiper {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.left-button, .right-button {
|
.left-button, .right-button {
|
||||||
@@ -18,64 +38,19 @@ div {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.day-labels {
|
// phantom element
|
||||||
|
.phantom {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
visibility: hidden;
|
||||||
left: 0;
|
height: 0 !important;
|
||||||
padding: 8px 20px 8px 30px;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
ion-row {
|
|
||||||
padding-right: 20px;
|
|
||||||
|
|
||||||
ion-col {
|
|
||||||
ion-button {
|
|
||||||
position: absolute;
|
|
||||||
top: -8px;
|
|
||||||
font-size: large;
|
|
||||||
text-align: center;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
// phantom element
|
|
||||||
ion-datetime {
|
|
||||||
position: absolute;
|
|
||||||
visibility: hidden;
|
|
||||||
height: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.slide {
|
.day-labels {
|
||||||
ion-grid {
|
width: 100%;
|
||||||
position: absolute;
|
height: 100%;
|
||||||
top: 0;
|
display: flex;
|
||||||
height: fit-content;
|
justify-content: center;
|
||||||
width: 100%;
|
align-items: center;
|
||||||
padding-top: 8px;
|
|
||||||
|
|
||||||
ion-row {
|
|
||||||
ion-col {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.vertical-line {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
border-left: 1px solid #dbdbdb;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
stapps-schedule-card {
|
|
||||||
z-index: 4;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hour-lines {
|
.hour-lines {
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2021 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, EventEmitter, Input, Output, ViewChild} from '@angular/core';
|
|
||||||
import {IonSlides} from '@ionic/angular';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Component that can display infinite slides
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'stapps-infinite-slides',
|
|
||||||
templateUrl: 'infinite-slides.html',
|
|
||||||
styleUrls: ['infinite-slides.scss'],
|
|
||||||
})
|
|
||||||
export class InfiniteSlidesComponent {
|
|
||||||
/**
|
|
||||||
* If the view was initialized
|
|
||||||
*/
|
|
||||||
initialized = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback for when the page has changed
|
|
||||||
*
|
|
||||||
* The caller needs to replace the component here
|
|
||||||
*/
|
|
||||||
@Output() pageChangeCallback: EventEmitter<{
|
|
||||||
/**
|
|
||||||
* The current page
|
|
||||||
*/
|
|
||||||
currentPage: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The direction that was scrolled
|
|
||||||
*/
|
|
||||||
direction: number;
|
|
||||||
}> = new EventEmitter();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The virtual page we are currently on
|
|
||||||
*/
|
|
||||||
page = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Options for IonSlides
|
|
||||||
*/
|
|
||||||
@Input() slideOpts = {
|
|
||||||
initialSlide: 1,
|
|
||||||
speed: 200,
|
|
||||||
loop: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Slider element
|
|
||||||
*/
|
|
||||||
@ViewChild('slides') slides: IonSlides;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Slide to next page
|
|
||||||
*/
|
|
||||||
async nextPage() {
|
|
||||||
await this.slides.slideNext(this.slideOpts.speed);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Change page
|
|
||||||
*/
|
|
||||||
async onPageChange(direction: number) {
|
|
||||||
if (!this.initialized) {
|
|
||||||
// setting the initial page to 1 causes a page change to
|
|
||||||
// be emitted initially, which intern would cause the
|
|
||||||
// page to actually change one to far, so we listen for
|
|
||||||
// that first page change and skip it
|
|
||||||
this.initialized = true;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.page += direction;
|
|
||||||
|
|
||||||
this.pageChangeCallback.emit({
|
|
||||||
currentPage: this.page,
|
|
||||||
direction: direction,
|
|
||||||
});
|
|
||||||
|
|
||||||
// tslint:disable-next-line:no-magic-numbers
|
|
||||||
this.slides.slideTo(1, 0, false).then();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Slide to previous page
|
|
||||||
*/
|
|
||||||
async prevPage() {
|
|
||||||
await this.slides.slidePrev(this.slideOpts.speed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<ion-slides
|
|
||||||
#slides
|
|
||||||
pager="false"
|
|
||||||
[options]="slideOpts"
|
|
||||||
(ionSlideNextEnd)="onPageChange(1)"
|
|
||||||
(ionSlidePrevEnd)="onPageChange(-1)"
|
|
||||||
>
|
|
||||||
<ng-content></ng-content>
|
|
||||||
</ion-slides>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
ion-slides {
|
|
||||||
width: 100%;
|
|
||||||
height: 1100px; // BIG TODO: This is completely bypasses the scale parameter
|
|
||||||
}
|
|
||||||
256
src/app/modules/schedule/page/grid/infinite-swiper.component.ts
Normal file
256
src/app/modules/schedule/page/grid/infinite-swiper.component.ts
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 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 @typescript-eslint/no-non-null-assertion */
|
||||||
|
import {
|
||||||
|
AfterViewInit,
|
||||||
|
Component,
|
||||||
|
ContentChild,
|
||||||
|
ElementRef,
|
||||||
|
EventEmitter,
|
||||||
|
Input,
|
||||||
|
OnChanges,
|
||||||
|
OnDestroy,
|
||||||
|
OnInit,
|
||||||
|
Output,
|
||||||
|
QueryList,
|
||||||
|
SimpleChanges,
|
||||||
|
TemplateRef,
|
||||||
|
ViewChild,
|
||||||
|
ViewChildren,
|
||||||
|
ViewContainerRef,
|
||||||
|
} from '@angular/core';
|
||||||
|
import Swiper from 'swiper';
|
||||||
|
import {drop, dropRight, forEach, range, take, takeRight, zip} from 'lodash-es';
|
||||||
|
import {materialManualFade} from '../../../../animation/material-motion';
|
||||||
|
|
||||||
|
export interface SlideContext {
|
||||||
|
$implicit: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for specified amount of time
|
||||||
|
*/
|
||||||
|
async function wait(ms?: number) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is an infinite version of the swiper
|
||||||
|
*
|
||||||
|
* The basic principle it works on is
|
||||||
|
* 1. The user can never swiper further than the amount of visible slides
|
||||||
|
* 2. Only out of view slides are re-initialized
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'infinite-swiper',
|
||||||
|
templateUrl: 'infinite-swiper.html',
|
||||||
|
styleUrls: ['infinite-swiper.scss'],
|
||||||
|
animations: [materialManualFade],
|
||||||
|
})
|
||||||
|
export class InfiniteSwiperComponent
|
||||||
|
implements OnInit, AfterViewInit, OnDestroy, OnChanges
|
||||||
|
{
|
||||||
|
@Input() controller?: InfiniteSwiperComponent;
|
||||||
|
|
||||||
|
@Input() slidesPerView = 5;
|
||||||
|
|
||||||
|
virtualIndex = 0;
|
||||||
|
|
||||||
|
@ContentChild(TemplateRef) userSlideTemplateRef: TemplateRef<SlideContext>;
|
||||||
|
|
||||||
|
@Output() indexChange = new EventEmitter<number>();
|
||||||
|
|
||||||
|
slidesArray: number[];
|
||||||
|
|
||||||
|
@ViewChild('swiper', {static: true})
|
||||||
|
swiperElement: ElementRef<HTMLDivElement>;
|
||||||
|
|
||||||
|
@ViewChildren('slideContainers', {read: ViewContainerRef})
|
||||||
|
slideContainers: QueryList<ViewContainerRef>;
|
||||||
|
|
||||||
|
swiper: Swiper;
|
||||||
|
|
||||||
|
visibilityState: 'in' | 'out' = 'in';
|
||||||
|
|
||||||
|
private preventControllerCallback = false;
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.createSwiper();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
this.initSwiper();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.swiper.destroy();
|
||||||
|
this.clearSlides();
|
||||||
|
}
|
||||||
|
|
||||||
|
async ngOnChanges(changes: SimpleChanges) {
|
||||||
|
if ('slidesPerView' in changes) {
|
||||||
|
const change = changes.slidesPerView;
|
||||||
|
if (change.isFirstChange()) return;
|
||||||
|
|
||||||
|
// little bit of a cheesy trick just to reinitialize
|
||||||
|
// everything... But you know, it works just fine.
|
||||||
|
// And how often are you realistically going to
|
||||||
|
// resize your window.
|
||||||
|
this.visibilityState = 'out';
|
||||||
|
await wait(250);
|
||||||
|
|
||||||
|
this.ngOnDestroy();
|
||||||
|
this.createSwiper();
|
||||||
|
await wait();
|
||||||
|
this.initSwiper();
|
||||||
|
|
||||||
|
this.visibilityState = 'in';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createSwiper() {
|
||||||
|
this.resetSlides();
|
||||||
|
|
||||||
|
// I have absolutely no clue why two results are returned here.
|
||||||
|
// Probably a bug, so be on the lookout if you get odd errors
|
||||||
|
const [swiper] = new Swiper('.swiper', {
|
||||||
|
// TODO: evaluate if the controller has decent performance, some time in the future
|
||||||
|
// modules: [Controller],
|
||||||
|
slidesPerView: this.slidesPerView,
|
||||||
|
initialSlide: this.slidesPerView,
|
||||||
|
init: false,
|
||||||
|
}) as unknown as [Swiper, Swiper];
|
||||||
|
this.swiper = swiper;
|
||||||
|
}
|
||||||
|
|
||||||
|
initSwiper() {
|
||||||
|
this.swiper.init(this.swiperElement.nativeElement);
|
||||||
|
// SwiperJS controller still has some performance issues unfortunately...
|
||||||
|
// So unfortunately we are kind of forced to use a workaround :/
|
||||||
|
// TODO: evaluate if the controller has decent performance, some time in the future
|
||||||
|
/*setTimeout(() => {
|
||||||
|
this.swiper.controller.control = this.controller?.swiper;
|
||||||
|
});*/
|
||||||
|
|
||||||
|
this.shiftSlides();
|
||||||
|
|
||||||
|
this.swiper.on('activeIndexChange', () => {
|
||||||
|
if (!this.preventControllerCallback) {
|
||||||
|
this.controller?.controllerSlideTo(this.swiper.activeIndex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.swiper.on('slideChangeTransitionEnd', () => {
|
||||||
|
this.shiftSlides(this.swiper.activeIndex);
|
||||||
|
this.indexChange.emit(this.virtualIndex);
|
||||||
|
this.preventControllerCallback = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clearSlides() {
|
||||||
|
for (const container of this.slideContainers) {
|
||||||
|
while (container.length > 0) {
|
||||||
|
container.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pageForward() {
|
||||||
|
this.swiper.slideTo(this.slidesPerView * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
pageBackwards() {
|
||||||
|
this.swiper.slideTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is require to not cause a callback loop
|
||||||
|
* when the controller slides
|
||||||
|
*/
|
||||||
|
private async controllerSlideTo(index: number) {
|
||||||
|
// TODO: prevent virtual index falling out of sync
|
||||||
|
this.preventControllerCallback = true;
|
||||||
|
this.swiper.slideTo(index);
|
||||||
|
await wait(400);
|
||||||
|
if (this.controller && this.virtualIndex !== this.controller.virtualIndex) {
|
||||||
|
console.warn(
|
||||||
|
`Virtual indices fell out of sync ${this.virtualIndex} : ${this.controller.virtualIndex}, correcting...`,
|
||||||
|
);
|
||||||
|
await this.controller.goToIndex(this.virtualIndex, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async goToIndex(index: number, runCallbacks = true) {
|
||||||
|
if (runCallbacks) {
|
||||||
|
this.controller?.goToIndex(index, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.visibilityState = 'out';
|
||||||
|
|
||||||
|
await wait(250);
|
||||||
|
|
||||||
|
this.virtualIndex = index;
|
||||||
|
this.clearSlides();
|
||||||
|
this.shiftSlides();
|
||||||
|
|
||||||
|
this.visibilityState = 'in';
|
||||||
|
}
|
||||||
|
|
||||||
|
shiftSlides(activeIndex = this.slidesPerView) {
|
||||||
|
const delta = this.slidesPerView - activeIndex;
|
||||||
|
const deltaAmount = Math.abs(delta);
|
||||||
|
const direction = delta > 0;
|
||||||
|
this.virtualIndex -= delta;
|
||||||
|
const containers = this.slideContainers.toArray();
|
||||||
|
|
||||||
|
const slides = containers.map(it =>
|
||||||
|
it.length > 0 ? it.detach(0) : undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
// delete slides that are going to be dropped
|
||||||
|
for (const slide of (direction ? takeRight : take)(slides, deltaAmount)) {
|
||||||
|
slide?.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// reuse existing slides
|
||||||
|
const newElements: undefined[] = Array.from({length: deltaAmount});
|
||||||
|
const shiftedSlides = direction
|
||||||
|
? [...newElements, ...dropRight(slides, deltaAmount)]
|
||||||
|
: [...drop(slides, deltaAmount), ...newElements];
|
||||||
|
|
||||||
|
forEach(zip(containers, shiftedSlides), ([container, element], i) => {
|
||||||
|
// TODO: we should be able to skip this... In theory.
|
||||||
|
while (container!.length > 0) {
|
||||||
|
console.warn('Slide container is not empty after detach!');
|
||||||
|
container!.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
container!.insert(element);
|
||||||
|
} else {
|
||||||
|
container!.createEmbeddedView(this.userSlideTemplateRef, {
|
||||||
|
$implicit: this.virtualIndex + (i - this.slidesPerView),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.swiper.slideTo(this.slidesPerView, 0, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetSlides() {
|
||||||
|
this.slidesArray = range(0, this.slidesPerView * 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/app/modules/schedule/page/grid/infinite-swiper.html
Normal file
26
src/app/modules/schedule/page/grid/infinite-swiper.html
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2021 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<div #swiper class="swiper">
|
||||||
|
<div class="swiper-wrapper">
|
||||||
|
<div
|
||||||
|
[@materialManualFade]="visibilityState"
|
||||||
|
class="swiper-slide"
|
||||||
|
*ngFor="let index of slidesArray"
|
||||||
|
>
|
||||||
|
<ng-container #slideContainers></ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
19
src/app/modules/schedule/page/grid/infinite-swiper.scss
Normal file
19
src/app/modules/schedule/page/grid/infinite-swiper.scss
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/*!
|
||||||
|
* Copyright (C) 2021 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.swiper {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2018, 2019 StApps
|
* Copyright (C) 2021 StApps
|
||||||
* This program is free software: you can redistribute it and/or modify it
|
* 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
|
* under the terms of the GNU General Public License as published by the Free
|
||||||
* Software Foundation, version 3.
|
* Software Foundation, version 3.
|
||||||
@@ -13,8 +13,8 @@
|
|||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import {Component, Input, OnInit} from '@angular/core';
|
import {Component, Input, OnInit} from '@angular/core';
|
||||||
import moment from 'moment';
|
|
||||||
import {HoursRange} from '../schema/schema';
|
import {HoursRange} from '../schema/schema';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that displays the schedule
|
* Component that displays the schedule
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
|
/*!
|
||||||
|
* Copyright (C) 2021 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
div {
|
div {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -5,7 +20,7 @@ div {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
top: 0;
|
top: 4px;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
|
|
||||||
div {
|
div {
|
||||||
@@ -13,9 +28,8 @@ div {
|
|||||||
height: fit-content;
|
height: fit-content;
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
width: calc(100% - 8px);
|
width: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-left: 4px;
|
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
@@ -29,7 +43,6 @@ div {
|
|||||||
height: 8px;
|
height: 8px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -3px;
|
top: -3px;
|
||||||
left: -4px;
|
|
||||||
border-radius: 50% 0 50% 50%;
|
border-radius: 50% 0 50% 50%;
|
||||||
transform: rotateZ(45deg);
|
transform: rotateZ(45deg);
|
||||||
background-color: var(--ion-color-primary);
|
background-color: var(--ion-color-primary);
|
||||||
|
|||||||
68
src/app/modules/schedule/page/grid/schedule-day.component.ts
Normal file
68
src/app/modules/schedule/page/grid/schedule-day.component.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 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} from '@angular/core';
|
||||||
|
import moment from 'moment';
|
||||||
|
import {Range, ScheduleEvent} from '../schema/schema';
|
||||||
|
import {ScheduleProvider} from '../../schedule.provider';
|
||||||
|
import {SCISO8601Duration, SCUuid} from '@openstapps/core';
|
||||||
|
import {materialFade} from '../../../../animation/material-motion';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'schedule-day',
|
||||||
|
templateUrl: 'schedule-day.html',
|
||||||
|
styleUrls: ['schedule-day.scss'],
|
||||||
|
animations: [materialFade],
|
||||||
|
})
|
||||||
|
export class ScheduleDayComponent {
|
||||||
|
@Input() day: moment.Moment;
|
||||||
|
|
||||||
|
@Input() hoursRange: Range<number>;
|
||||||
|
|
||||||
|
@Input() uuids: SCUuid[];
|
||||||
|
|
||||||
|
@Input() scale: number;
|
||||||
|
|
||||||
|
@Input() frequencies?: SCISO8601Duration[];
|
||||||
|
|
||||||
|
@Input() dateSeries?: Record<string, ScheduleEvent>;
|
||||||
|
|
||||||
|
constructor(protected readonly scheduleProvider: ScheduleProvider) {}
|
||||||
|
|
||||||
|
// ngOnInit() {
|
||||||
|
// this.dateSeries = this.fetchDateSeries();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// TODO: backend bug results in the wrong date series being returned
|
||||||
|
/* async fetchDateSeries(): Promise<ScheduleEvent[]> {
|
||||||
|
const dateSeries = await this.scheduleProvider.getDateSeries(
|
||||||
|
this.uuids,
|
||||||
|
this.frequencies,
|
||||||
|
this.momentDay.clone().startOf('day').toISOString(),
|
||||||
|
this.momentDay.clone().endOf('day').toISOString(),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const series of dateSeries.dates) {
|
||||||
|
console.log(JSON.stringify(series.dates));
|
||||||
|
}
|
||||||
|
|
||||||
|
return dateSeries.dates.map(it => ({
|
||||||
|
dateSeries: it,
|
||||||
|
time: {
|
||||||
|
start: moment(it.dates.find(date => date === this.day)).hours(),
|
||||||
|
duration: it.duration,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
} */
|
||||||
|
}
|
||||||
35
src/app/modules/schedule/page/grid/schedule-day.html
Normal file
35
src/app/modules/schedule/page/grid/schedule-day.html
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2021 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/>.
|
||||||
|
-->
|
||||||
|
<div>
|
||||||
|
<div class="vertical-line"></div>
|
||||||
|
<stapps-schedule-cursor
|
||||||
|
class="cursor"
|
||||||
|
*ngIf="day | dateIsThis: 'date'"
|
||||||
|
[hoursRange]="hoursRange"
|
||||||
|
[scale]="scale"
|
||||||
|
>
|
||||||
|
</stapps-schedule-cursor>
|
||||||
|
<div *ngIf="dateSeries as dateSeries">
|
||||||
|
<!-- TODO: entry/exit animation -->
|
||||||
|
<stapps-schedule-card
|
||||||
|
class="schedule-card"
|
||||||
|
*ngFor="let entry of dateSeries | entries"
|
||||||
|
[scheduleEvent]="entry"
|
||||||
|
[fromHour]="hoursRange.from"
|
||||||
|
[scale]="scale"
|
||||||
|
>
|
||||||
|
</stapps-schedule-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
37
src/app/modules/schedule/page/grid/schedule-day.scss
Normal file
37
src/app/modules/schedule/page/grid/schedule-day.scss
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/*!
|
||||||
|
* Copyright (C) 2021 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.schedule-card {
|
||||||
|
position: absolute;
|
||||||
|
top: 13px;
|
||||||
|
left: 0;
|
||||||
|
z-index: 4;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical-line {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 1px;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #dbdbdb;
|
||||||
|
}
|
||||||
@@ -115,7 +115,7 @@ export class ScheduleSingleEventsComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
// TODO: replace with filter
|
// TODO: replace with filter
|
||||||
return ScheduleSingleEventsComponent.groupDateSeriesToDays(
|
return ScheduleSingleEventsComponent.groupDateSeriesToDays(
|
||||||
dateSeries.filter(it => isNil(it.repeatFrequency)),
|
dateSeries.dates.filter(it => isNil(it.repeatFrequency)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,8 +71,8 @@ export class ScheduleViewComponent extends CalendarViewComponent {
|
|||||||
/**
|
/**
|
||||||
* Load events
|
* Load events
|
||||||
*/
|
*/
|
||||||
// @Override
|
// TODO: @Override
|
||||||
async loadEvents(): Promise<void> {
|
/*async loadEvents(): Promise<void> {
|
||||||
this.cardsAnimationState = 'out';
|
this.cardsAnimationState = 'out';
|
||||||
const dateSeries = await this.scheduleProvider.getDateSeries(
|
const dateSeries = await this.scheduleProvider.getDateSeries(
|
||||||
this.uuids,
|
this.uuids,
|
||||||
@@ -82,7 +82,7 @@ export class ScheduleViewComponent extends CalendarViewComponent {
|
|||||||
|
|
||||||
this.testSchedule = {};
|
this.testSchedule = {};
|
||||||
|
|
||||||
for (const series of dateSeries) {
|
for (const series of dateSeries.dates) {
|
||||||
if (series.dates.length > 0) {
|
if (series.dates.length > 0) {
|
||||||
const date = moment(moment.now())
|
const date = moment(moment.now())
|
||||||
.startOf('week')
|
.startOf('week')
|
||||||
@@ -104,5 +104,5 @@ export class ScheduleViewComponent extends CalendarViewComponent {
|
|||||||
|
|
||||||
this.cursor?.scrollIntoView();
|
this.cursor?.scrollIntoView();
|
||||||
this.cardsAnimationState = 'in';
|
this.cardsAnimationState = 'in';
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2020 StApps
|
* Copyright (C) 2021 StApps
|
||||||
* This program is free software: you can redistribute it and/or modify it
|
* 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
|
* under the terms of the GNU General Public License as published by the Free
|
||||||
* Software Foundation, version 3.
|
* Software Foundation, version 3.
|
||||||
@@ -21,6 +21,11 @@ interface DateRange {
|
|||||||
start: number;
|
start: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Range<T> {
|
||||||
|
from: T;
|
||||||
|
to: T;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimal interface to provide information about a custom event
|
* Minimal interface to provide information about a custom event
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2018, 2019 StApps
|
* Copyright (C) 2021 StApps
|
||||||
* This program is free software: you can redistribute it and/or modify it
|
* 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
|
* under the terms of the GNU General Public License as published by the Free
|
||||||
* Software Foundation, version 3.
|
* Software Foundation, version 3.
|
||||||
@@ -25,13 +25,16 @@ import {UtilModule} from '../../util/util.module';
|
|||||||
import {DataModule} from '../data/data.module';
|
import {DataModule} from '../data/data.module';
|
||||||
import {DataProvider} from '../data/data.provider';
|
import {DataProvider} from '../data/data.provider';
|
||||||
import {CalendarViewComponent} from './page/calendar-view.component';
|
import {CalendarViewComponent} from './page/calendar-view.component';
|
||||||
import {InfiniteSlidesComponent} from './page/grid/infinite-slides.component';
|
|
||||||
import {ScheduleCursorComponent} from './page/grid/schedule-cursor.component';
|
import {ScheduleCursorComponent} from './page/grid/schedule-cursor.component';
|
||||||
import {ModalEventCreatorComponent} from './page/modal/modal-event-creator.component';
|
import {ModalEventCreatorComponent} from './page/modal/modal-event-creator.component';
|
||||||
import {SchedulePageComponent} from './page/schedule-page.component';
|
import {SchedulePageComponent} from './page/schedule-page.component';
|
||||||
import {ScheduleSingleEventsComponent} from './page/schedule-single-events.component';
|
import {ScheduleSingleEventsComponent} from './page/schedule-single-events.component';
|
||||||
import {ScheduleViewComponent} from './page/schedule-view.component';
|
import {ScheduleViewComponent} from './page/schedule-view.component';
|
||||||
import {ScheduleProvider} from './schedule.provider';
|
import {ScheduleProvider} from './schedule.provider';
|
||||||
|
import {SwiperModule} from 'swiper/angular';
|
||||||
|
import {ScheduleDayComponent} from './page/grid/schedule-day.component';
|
||||||
|
import {ThingTranslateModule} from '../../translation/thing-translate.module';
|
||||||
|
import {InfiniteSwiperComponent} from './page/grid/infinite-swiper.component';
|
||||||
|
|
||||||
const settingsRoutes: Routes = [
|
const settingsRoutes: Routes = [
|
||||||
{path: 'schedule', redirectTo: 'schedule/calendar/now'},
|
{path: 'schedule', redirectTo: 'schedule/calendar/now'},
|
||||||
@@ -48,23 +51,26 @@ const settingsRoutes: Routes = [
|
|||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
CalendarViewComponent,
|
CalendarViewComponent,
|
||||||
InfiniteSlidesComponent,
|
|
||||||
ModalEventCreatorComponent,
|
ModalEventCreatorComponent,
|
||||||
ScheduleCardComponent,
|
ScheduleCardComponent,
|
||||||
ScheduleCursorComponent,
|
ScheduleCursorComponent,
|
||||||
SchedulePageComponent,
|
SchedulePageComponent,
|
||||||
ScheduleSingleEventsComponent,
|
ScheduleSingleEventsComponent,
|
||||||
|
ScheduleDayComponent,
|
||||||
ScheduleViewComponent,
|
ScheduleViewComponent,
|
||||||
|
InfiniteSwiperComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
FormsModule,
|
|
||||||
UtilModule,
|
|
||||||
IonicModule.forRoot(),
|
|
||||||
TranslateModule.forChild(),
|
|
||||||
RouterModule.forChild(settingsRoutes),
|
|
||||||
DataModule,
|
DataModule,
|
||||||
|
FormsModule,
|
||||||
|
IonicModule.forRoot(),
|
||||||
MomentModule,
|
MomentModule,
|
||||||
|
RouterModule.forChild(settingsRoutes),
|
||||||
|
SwiperModule,
|
||||||
|
TranslateModule.forChild(),
|
||||||
|
UtilModule,
|
||||||
|
ThingTranslateModule,
|
||||||
],
|
],
|
||||||
providers: [ScheduleProvider, DataProvider, DateFormatPipe],
|
providers: [ScheduleProvider, DataProvider, DateFormatPipe],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -112,9 +112,17 @@ export class ScheduleProvider implements OnDestroy {
|
|||||||
frequencies?: Array<SCISO8601Duration>,
|
frequencies?: Array<SCISO8601Duration>,
|
||||||
from?: SCISO8601Date | 'now',
|
from?: SCISO8601Date | 'now',
|
||||||
to?: SCISO8601Date | 'now',
|
to?: SCISO8601Date | 'now',
|
||||||
): Promise<SCDateSeries[]> {
|
): Promise<{
|
||||||
|
dates: SCDateSeries[];
|
||||||
|
min: SCISO8601Date;
|
||||||
|
max: SCISO8601Date;
|
||||||
|
}> {
|
||||||
if (uuids.length === 0) {
|
if (uuids.length === 0) {
|
||||||
return [];
|
return {
|
||||||
|
dates: [],
|
||||||
|
min: '',
|
||||||
|
max: '',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters: SCSearchFilter[] = [
|
const filters: SCSearchFilter[] = [
|
||||||
@@ -159,12 +167,14 @@ export class ScheduleProvider implements OnDestroy {
|
|||||||
if (from || to) {
|
if (from || to) {
|
||||||
const bounds: Bounds<string> = {};
|
const bounds: Bounds<string> = {};
|
||||||
if (from) {
|
if (from) {
|
||||||
|
console.log(from);
|
||||||
bounds.lowerBound = {
|
bounds.lowerBound = {
|
||||||
limit: from,
|
limit: from,
|
||||||
mode: 'inclusive',
|
mode: 'inclusive',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (to) {
|
if (to) {
|
||||||
|
console.log(to);
|
||||||
bounds.upperBound = {
|
bounds.upperBound = {
|
||||||
limit: to,
|
limit: to,
|
||||||
mode: 'inclusive',
|
mode: 'inclusive',
|
||||||
@@ -179,17 +189,22 @@ export class ScheduleProvider implements OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const result = await this.dataProvider.search({
|
||||||
await this.dataProvider.search({
|
filter: {
|
||||||
filter: {
|
arguments: {
|
||||||
arguments: {
|
filters: filters,
|
||||||
filters: filters,
|
operation: 'and',
|
||||||
operation: 'and',
|
|
||||||
},
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
},
|
||||||
})
|
type: 'boolean',
|
||||||
).data as SCDateSeries[];
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
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(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2020-2021 StApps
|
* Copyright (C) 2021 StApps
|
||||||
* This program is free software: you can redistribute it and/or modify it
|
* 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
|
* under the terms of the GNU General Public License as published by the Free
|
||||||
* Software Foundation, version 3.
|
* Software Foundation, version 3.
|
||||||
@@ -48,6 +48,28 @@ export class ArrayJoinPipe implements PipeTransform {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
@Pipe({
|
||||||
|
name: 'entries',
|
||||||
|
pure: true,
|
||||||
|
})
|
||||||
|
export class EntriesPipe implements PipeTransform {
|
||||||
|
transform<T>(value: Record<string | number | symbol, T>): T[] {
|
||||||
|
return Object.values(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
@Pipe({
|
||||||
|
name: 'toUnix',
|
||||||
|
pure: true,
|
||||||
|
})
|
||||||
|
export class ToUnixPipe implements PipeTransform {
|
||||||
|
transform(value: string | number | Date | null | undefined): number {
|
||||||
|
return (value instanceof Date ? value : new Date(value ?? 0)).valueOf();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'sentencecase',
|
name: 'sentencecase',
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import {
|
|||||||
StringSplitPipe,
|
StringSplitPipe,
|
||||||
OpeningHoursPipe,
|
OpeningHoursPipe,
|
||||||
DurationLocalizedPipe,
|
DurationLocalizedPipe,
|
||||||
|
ToUnixPipe,
|
||||||
|
EntriesPipe,
|
||||||
} from './common-string-pipes';
|
} from './common-string-pipes';
|
||||||
import {
|
import {
|
||||||
ThingTranslateDefaultParser,
|
ThingTranslateDefaultParser,
|
||||||
@@ -52,6 +54,8 @@ export interface ThingTranslateModuleConfig {
|
|||||||
DateLocalizedFormatPipe,
|
DateLocalizedFormatPipe,
|
||||||
OpeningHoursPipe,
|
OpeningHoursPipe,
|
||||||
SentenceCasePipe,
|
SentenceCasePipe,
|
||||||
|
ToUnixPipe,
|
||||||
|
EntriesPipe,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
ArrayJoinPipe,
|
ArrayJoinPipe,
|
||||||
@@ -65,6 +69,8 @@ export interface ThingTranslateModuleConfig {
|
|||||||
DateLocalizedFormatPipe,
|
DateLocalizedFormatPipe,
|
||||||
OpeningHoursPipe,
|
OpeningHoursPipe,
|
||||||
SentenceCasePipe,
|
SentenceCasePipe,
|
||||||
|
ToUnixPipe,
|
||||||
|
EntriesPipe,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class ThingTranslateModule {
|
export class ThingTranslateModule {
|
||||||
|
|||||||
27
src/app/util/date-from-index.pipe.ts
Normal file
27
src/app/util/date-from-index.pipe.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 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 {Pipe, PipeTransform} from '@angular/core';
|
||||||
|
import {Moment} from 'moment';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'dateFromIndex',
|
||||||
|
pure: true,
|
||||||
|
})
|
||||||
|
export class DateFromIndexPipe implements PipeTransform {
|
||||||
|
transform(index: number, baseline: Moment): Moment {
|
||||||
|
return baseline.clone().add(index, 'days');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,7 +30,16 @@ export class DateIsThisPipe implements PipeTransform {
|
|||||||
* Transform
|
* Transform
|
||||||
*/
|
*/
|
||||||
// tslint:disable-next-line:prefer-function-over-method
|
// tslint:disable-next-line:prefer-function-over-method
|
||||||
transform(value: Moment, granularity: unitOfTime.StartOf): boolean {
|
transform(
|
||||||
return value.isSame(moment(moment.now()), granularity);
|
value: Moment | string | number,
|
||||||
|
granularity: unitOfTime.StartOf,
|
||||||
|
): boolean {
|
||||||
|
return (
|
||||||
|
typeof value === 'string'
|
||||||
|
? moment(value)
|
||||||
|
: typeof value === 'number'
|
||||||
|
? moment.unix(value)
|
||||||
|
: value
|
||||||
|
).isSame(moment(moment.now()), granularity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,20 @@ import {NgModule} from '@angular/core';
|
|||||||
import {ArrayLastPipe} from './array-last.pipe';
|
import {ArrayLastPipe} from './array-last.pipe';
|
||||||
import {DateIsThisPipe} from './date-is-today.pipe';
|
import {DateIsThisPipe} from './date-is-today.pipe';
|
||||||
import {NullishCoalescingPipe} from './nullish-coalecing.pipe';
|
import {NullishCoalescingPipe} from './nullish-coalecing.pipe';
|
||||||
|
import {DateFromIndexPipe} from './date-from-index.pipe';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [ArrayLastPipe, DateIsThisPipe, NullishCoalescingPipe],
|
declarations: [
|
||||||
exports: [ArrayLastPipe, DateIsThisPipe, NullishCoalescingPipe],
|
ArrayLastPipe,
|
||||||
|
DateIsThisPipe,
|
||||||
|
NullishCoalescingPipe,
|
||||||
|
DateFromIndexPipe,
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
ArrayLastPipe,
|
||||||
|
DateIsThisPipe,
|
||||||
|
NullishCoalescingPipe,
|
||||||
|
DateFromIndexPipe,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class UtilModule {}
|
export class UtilModule {}
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
|
/*!
|
||||||
|
* Copyright (C) 2021 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
// http://ionicframework.com/docs/theming/
|
// http://ionicframework.com/docs/theming/
|
||||||
@import "~@ionic/angular/css/normalize.css";
|
@import "~@ionic/angular/css/normalize.css";
|
||||||
@import "~@ionic/angular/css/structure.css";
|
@import "~@ionic/angular/css/structure.css";
|
||||||
@@ -11,6 +26,10 @@
|
|||||||
@import "~@ionic/angular/css/flex-utils.css";
|
@import "~@ionic/angular/css/flex-utils.css";
|
||||||
@import "~@ionic/angular/css/display.css";
|
@import "~@ionic/angular/css/display.css";
|
||||||
|
|
||||||
|
// https://swiperjs.com/angular#styles
|
||||||
|
@import "swiper/scss";
|
||||||
|
@import "swiper/scss/controller";
|
||||||
|
|
||||||
/* StApps */
|
/* StApps */
|
||||||
|
|
||||||
ion-item {
|
ion-item {
|
||||||
|
|||||||
Reference in New Issue
Block a user