mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-21 00:52:55 +00:00
feat: timetable module - schedule and calendar
This commit is contained in:
107
src/app/modules/schedule/page/grid/infinite-slides.component.ts
Normal file
107
src/app/modules/schedule/page/grid/infinite-slides.component.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
9
src/app/modules/schedule/page/grid/infinite-slides.html
Normal file
9
src/app/modules/schedule/page/grid/infinite-slides.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<ion-slides
|
||||
#slides
|
||||
pager="false"
|
||||
[options]="slideOpts"
|
||||
(ionSlideNextEnd)="onPageChange(1)"
|
||||
(ionSlidePrevEnd)="onPageChange(-1)"
|
||||
>
|
||||
<ng-content></ng-content>
|
||||
</ion-slides>
|
||||
4
src/app/modules/schedule/page/grid/infinite-slides.scss
Normal file
4
src/app/modules/schedule/page/grid/infinite-slides.scss
Normal file
@@ -0,0 +1,4 @@
|
||||
ion-slides {
|
||||
width: 100%;
|
||||
height: 1100px; // BIG TODO: This is completely bypasses the scale parameter
|
||||
}
|
||||
100
src/app/modules/schedule/page/grid/schedule-card.component.ts
Normal file
100
src/app/modules/schedule/page/grid/schedule-card.component.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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, OnInit} from '@angular/core';
|
||||
import moment from 'moment';
|
||||
import {ScheduleProvider} from '../../schedule.provider';
|
||||
import {ScheduleEvent} from '../schema/schema';
|
||||
|
||||
/**
|
||||
* Component that can display a schedule event
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-schedule-card',
|
||||
templateUrl: 'schedule-card.html',
|
||||
styleUrls: ['../../../data/list/data-list-item.scss', 'schedule-card.scss'],
|
||||
})
|
||||
export class ScheduleCardComponent implements OnInit {
|
||||
/**
|
||||
* The hour from which on the schedule is displayed
|
||||
*/
|
||||
@Input() fromHour = 0;
|
||||
|
||||
/**
|
||||
* Card Y start position
|
||||
*/
|
||||
fromY = 0;
|
||||
|
||||
/**
|
||||
* Card Y end position
|
||||
*/
|
||||
height = 0;
|
||||
|
||||
/**
|
||||
* Show the card without a top offset
|
||||
*/
|
||||
@Input() noOffset = false;
|
||||
|
||||
/**
|
||||
* The scale of the schedule
|
||||
*/
|
||||
@Input() scale = 1;
|
||||
|
||||
/**
|
||||
* The event
|
||||
*/
|
||||
@Input() scheduleEvent: ScheduleEvent;
|
||||
|
||||
/**
|
||||
* The title of the event
|
||||
*/
|
||||
title: string;
|
||||
|
||||
constructor(private readonly scheduleProvider: ScheduleProvider) {}
|
||||
|
||||
/**
|
||||
* Get the note text
|
||||
*/
|
||||
getNote(): string | undefined {
|
||||
return 'categories' in this.scheduleEvent.dateSeries.event
|
||||
? this.scheduleEvent.dateSeries.event.categories?.join(', ')
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializer
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.fromY = this.noOffset ? 0 : this.scheduleEvent.time.start;
|
||||
this.height = moment.duration(this.scheduleEvent.time.duration).asHours();
|
||||
|
||||
this.title = this.scheduleEvent.dateSeries.event.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the event
|
||||
*/
|
||||
removeEvent(): false {
|
||||
if (confirm('Remove event?')) {
|
||||
this.scheduleProvider.uuids$.next(
|
||||
this.scheduleProvider.uuids$.value.filter(
|
||||
it => it !== this.scheduleEvent.dateSeries.uid,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// to prevent event propagation
|
||||
return false;
|
||||
}
|
||||
}
|
||||
30
src/app/modules/schedule/page/grid/schedule-card.html
Normal file
30
src/app/modules/schedule/page/grid/schedule-card.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<ion-card
|
||||
[style.height.px]="height * scale"
|
||||
[style.marginTop.px]="(fromY - fromHour) * scale - 5"
|
||||
[routerLink]="['/data-detail', scheduleEvent.dateSeries.event.uid]"
|
||||
>
|
||||
<ion-card-header mode="md">
|
||||
<ion-card-title>
|
||||
{{
|
||||
this.scheduleEvent.dateSeries.event.name
|
||||
| nullishCoalesce: this.scheduleEvent.dateSeries.name
|
||||
}}
|
||||
</ion-card-title>
|
||||
<ion-card-subtitle>
|
||||
<ion-icon name="calendar"></ion-icon>
|
||||
<span class="repetitions">
|
||||
{{ scheduleEvent.dateSeries.frequency }}
|
||||
until
|
||||
{{
|
||||
scheduleEvent.dateSeries.dates | last | amDateFormat: 'DD. MMM YYYY'
|
||||
}}
|
||||
</span>
|
||||
</ion-card-subtitle>
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<ion-note>
|
||||
{{ getNote() }}
|
||||
</ion-note>
|
||||
</ion-card-content>
|
||||
<div></div>
|
||||
</ion-card>
|
||||
37
src/app/modules/schedule/page/grid/schedule-card.scss
Normal file
37
src/app/modules/schedule/page/grid/schedule-card.scss
Normal file
@@ -0,0 +1,37 @@
|
||||
ion-card {
|
||||
z-index: 2;
|
||||
|
||||
ion-grid {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
ion-row {
|
||||
ion-col {
|
||||
height: 5px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ion-card-header {
|
||||
height: available;
|
||||
width: 100%;
|
||||
|
||||
ion-card-title {
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
height: 20px;
|
||||
width: 100%;
|
||||
background: linear-gradient(
|
||||
rgba(255, 255, 255, 0) 0%,
|
||||
rgba(255, 255, 255, 255) 100%
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2018, 2019 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, OnInit} from '@angular/core';
|
||||
import moment from 'moment';
|
||||
import {HoursRange} from '../schema/schema';
|
||||
|
||||
/**
|
||||
* Component that displays the schedule
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-schedule-cursor',
|
||||
templateUrl: 'schedule-cursor.html',
|
||||
styleUrls: ['schedule-cursor.scss'],
|
||||
})
|
||||
export class ScheduleCursorComponent implements OnInit {
|
||||
/**
|
||||
* Cursor update
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore unused
|
||||
private cursorInterval: NodeJS.Timeout;
|
||||
|
||||
/**
|
||||
* Range of hours to display
|
||||
*/
|
||||
@Input() readonly hoursRange: HoursRange;
|
||||
|
||||
/**
|
||||
* Cursor
|
||||
*/
|
||||
now = ScheduleCursorComponent.getCursorTime();
|
||||
|
||||
/**
|
||||
* Vertical scale of the schedule (distance between hour lines)
|
||||
*/
|
||||
@Input() readonly scale: number;
|
||||
|
||||
/**
|
||||
* Get a floating point time 0..24
|
||||
*/
|
||||
static getCursorTime(): number {
|
||||
const mnt = moment(moment.now());
|
||||
|
||||
const hh = mnt.hours();
|
||||
const mm = mnt.minutes();
|
||||
|
||||
// tslint:disable-next-line:no-magic-numbers
|
||||
return hh + mm / 60;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.cursorInterval = setInterval(async () => {
|
||||
this.now = ScheduleCursorComponent.getCursorTime();
|
||||
// tslint:disable-next-line:no-magic-numbers
|
||||
}, 1000 * 60 /*1 Minute*/);
|
||||
}
|
||||
}
|
||||
6
src/app/modules/schedule/page/grid/schedule-cursor.html
Normal file
6
src/app/modules/schedule/page/grid/schedule-cursor.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<div [style.marginTop.px]="(now - hoursRange.from) * scale">
|
||||
<div>
|
||||
<hr />
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
38
src/app/modules/schedule/page/grid/schedule-cursor.scss
Normal file
38
src/app/modules/schedule/page/grid/schedule-cursor.scss
Normal file
@@ -0,0 +1,38 @@
|
||||
div {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
z-index: 0;
|
||||
|
||||
div {
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
|
||||
hr {
|
||||
width: calc(100% - 8px);
|
||||
position: absolute;
|
||||
margin-left: 4px;
|
||||
margin-right: 16px;
|
||||
margin-top: 8px;
|
||||
height: 2px;
|
||||
border-top: 2px solid var(--ion-color-primary);
|
||||
margin-block-start: 0;
|
||||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
div {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
left: -4px;
|
||||
border-radius: 50% 0 50% 50%;
|
||||
transform: rotateZ(45deg);
|
||||
background-color: var(--ion-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user