refactor: replace timetable event modal with route

This commit is contained in:
2023-02-17 16:51:52 +01:00
committed by Rainer Killinger
parent 22e70ae92b
commit 3e5724d9be
12 changed files with 176 additions and 145 deletions

View File

@@ -1,5 +1,5 @@
/*!
* Copyright (C) 2022 StApps
/*
* 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.
@@ -13,23 +13,8 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
:host {
height: 100%;
display: flex;
flex-direction: column;
flex: 1 1 20%;
}
ion-button {
ion-label {
color: var(--ion-color-light);
}
}
ion-card-content {
height: 100%;
padding: 0;
stapps-data-list {
height: 100%;
}
}
// these are the ionic values
export const iosEasing = 'cubic-bezier(0.32,0.72,0,1)';
export const iosDuration = 540;
export const mdEasing = 'cubic-bezier(0.36,0.66,0.04,1)';
export const mdDuration = 280;

View File

@@ -0,0 +1,83 @@
/*
* 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/>.
*/
import {AnimationBuilder, AnimationController} from '@ionic/angular';
import {AnimationOptions} from '@ionic/angular/providers/nav-controller';
import {iosDuration, iosEasing, mdDuration, mdEasing} from './easings';
/**
*
*/
export function fabExpand(animationController: AnimationController): AnimationBuilder {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (_baseElement: HTMLElement, options: AnimationOptions | any) => {
const rootTransition = animationController
.create()
.duration(options.duration ?? (options.mode === 'ios' ? iosDuration : mdDuration * 1.4))
.easing(options.mode === 'ios' ? iosEasing : mdEasing);
const back = options.direction === 'back';
const fabView = back ? options.enteringEl! : options.leavingEl!;
const otherView = back ? options.leavingEl! : options.enteringEl!;
const fab = fabView.querySelector('ion-fab-button').shadowRoot.querySelector('.button-native');
const fabBounds = fab.getBoundingClientRect();
const viewBounds = otherView.getBoundingClientRect();
const useReducedMotion = viewBounds.width > 500;
const reducedMotionTransform = `${Math.min(viewBounds.width * 0.3, 200)}px`;
const reducedMotionViewBorderRadius = '128px';
const reducedMotionFabGrow = '2';
const reducedMotionViewShrink = '0.9';
const viewCenterX = (viewBounds.width - viewBounds.x) / 2;
const viewCenterY = (viewBounds.height - viewBounds.y) / 2;
const viewOnFab = useReducedMotion
? `translate(${reducedMotionTransform}, ${reducedMotionTransform}) scale(${reducedMotionViewShrink})`
: `translate(${(fabBounds.x - viewBounds.x) / 2}px, ${(fabBounds.y - viewBounds.y) / 2}px) scale(${
fabBounds.width / viewBounds.width
}, ${fabBounds.height / viewBounds.height})`;
const fabOnView = useReducedMotion
? `translate(-${reducedMotionTransform}, -${reducedMotionTransform}) scale(${reducedMotionFabGrow})`
: `translate(${viewCenterX - fabBounds.x}px, ${viewCenterY - fabBounds.y}px) scale(${
viewBounds.width / fabBounds.width
}, ${viewBounds.height / fabBounds.height})`;
const transformNormal = `translate(0px, 0px) scale(1, 1)`;
const viewBorderRadius = useReducedMotion ? reducedMotionViewBorderRadius : '50%';
const fabViewFade = animationController
.create()
.beforeStyles({zIndex: -1})
.fromTo('opacity', '1', '1')
.addElement(fabView);
const fabGrow = animationController
.create()
.beforeStyles({transformOrigin: 'center'})
.fromTo('transform', back ? fabOnView : transformNormal, back ? transformNormal : fabOnView)
.fromTo('opacity', back ? '0' : '1', back ? '1' : '0')
.fromTo('borderRadius', back ? '0' : '50%', back ? '50%' : '0')
.addElement(fab);
const viewGrow = animationController
.create()
.beforeStyles({zIndex: 200, overflow: 'hidden', transformOrigin: 'center'})
.fromTo('transform', back ? transformNormal : viewOnFab, back ? viewOnFab : transformNormal)
.fromTo('opacity', back ? '1' : '0', back ? '0' : '1')
.fromTo('borderRadius', back ? '0' : viewBorderRadius, back ? viewBorderRadius : '0')
.addElement(otherView);
return rootTransition.addAnimation(fabGrow).addAnimation(viewGrow).addAnimation(fabViewFade);
};
}

View File

@@ -46,7 +46,13 @@ import {searchPageSwitchAnimation} from './search-page-switch-animation';
providers: [ContextMenuService],
})
export class SearchPageComponent implements OnInit, OnDestroy {
title = 'search.title';
@Input() title = 'search.title';
@Input() placeholder = 'search.search_bar.placeholder';
@Input() searchInstruction = 'search.instruction';
@Input() backUrl?: string;
isHebisAvailable = false;

View File

@@ -15,9 +15,9 @@
<stapps-context contentId="data-list"></stapps-context>
<ion-header>
<ion-toolbar color="primary" mode="ios" *ngIf="showDrawer">
<ion-toolbar color="primary" mode="ios" *ngIf="showDrawer && showTopToolbar">
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-back-button [defaultHref]="backUrl"></ion-back-button>
</ion-buttons>
<ion-title>{{ title | translate }}</ion-title>
</ion-toolbar>
@@ -28,7 +28,7 @@
(search)="hideKeyboard()"
[(ngModel)]="queryText"
showClearButton="always"
placeholder="{{ 'search.search_bar.placeholder' | translate }}"
placeholder="{{ placeholder | translate }}"
mode="md"
type="search"
enterkeyhint="search"
@@ -61,7 +61,7 @@
[style.display]="!showDefaultData && !items && !loading ? 'block' : 'none'"
>
<ion-label class="centeredMessageContainer">
{{ 'search.instruction' | translate }}
{{ searchInstruction | translate }}
</ion-label>
</div>
<stapps-data-list

View File

@@ -0,0 +1,30 @@
/*
* 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/>.
*/
import {Component} from '@angular/core';
import {SCSearchFilter, SCThingType} from '@openstapps/core';
@Component({
selector: 'stapps-choose-events-page',
templateUrl: 'choose-events-page.html',
})
export class ChooseEventsPageComponent {
forcedFilter: SCSearchFilter = {
arguments: {
field: 'type',
value: SCThingType.AcademicEvent,
},
type: 'value',
};
}

View File

@@ -1,5 +1,5 @@
<!--
~ Copyright (C) 2022 StApps
~ 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.
@@ -12,22 +12,12 @@
~ You should have received a copy of the GNU General Public License along with
~ this program. If not, see <https://www.gnu.org/licenses/>.
-->
<ion-toolbar color="primary" mode="ios">
<ion-title>{{ 'schedule.addEventModal.addEvent' | translate | titlecase }}</ion-title>
<ion-buttons slot="end">
<ion-button fill="clear" (click)="modalController.dismiss()">
<ion-label>{{ 'modal.DISMISS' | translate }}</ion-label>
</ion-button>
</ion-buttons>
</ion-toolbar>
<ion-card-content>
<stapps-search-page
[showDrawer]="false"
[forcedFilter]="filter"
[itemRouting]="false"
[showTopToolbar]="false"
[showNavigation]="false"
></stapps-search-page>
</ion-card-content>
<stapps-search-page
[showNavigation]="false"
[showDefaultData]="false"
[forcedFilter]="forcedFilter"
[backUrl]="'..'"
[title]="'schedule.addEventPage.TITLE'"
[placeholder]="'schedule.addEventPage.PLACEHOLDER'"
[searchInstruction]="'schedule.addEventPage.SEARCH_INSTRUCTION'"
></stapps-search-page>

View File

@@ -1,65 +0,0 @@
/*
* 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, OnDestroy, OnInit} from '@angular/core';
import {SCSearchFilter, SCThingType} from '@openstapps/core';
import {ModalController} from '@ionic/angular';
import {DataRoutingService} from '../../../data/data-routing.service';
import {DataDetailComponent} from '../../../data/detail/data-detail.component';
import {Subscription} from 'rxjs';
/**
* TODO
*/
@Component({
selector: 'modal-event-creator',
templateUrl: 'modal-event-creator.html',
styleUrls: ['modal-event-creator.scss'],
})
export class ModalEventCreatorComponent implements OnInit, OnDestroy {
subscriptions: Subscription[] = [];
constructor(readonly modalController: ModalController, readonly dataRoutingService: DataRoutingService) {}
ngOnInit() {
this.subscriptions.push(
this.dataRoutingService.itemSelectListener().subscribe(async item => {
const modal = await this.modalController.create({
component: DataDetailComponent,
componentProps: {
isModal: true,
inputItem: item,
},
canDismiss: true,
});
return modal.present();
}),
);
}
ngOnDestroy() {
for (const subscription of this.subscriptions) subscription.unsubscribe();
}
/**
* Forced filter
*/
filter: SCSearchFilter = {
arguments: {
field: 'type',
value: SCThingType.AcademicEvent,
},
type: 'value',
};
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2022 StApps
* 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.
@@ -15,12 +15,13 @@
import {AfterViewInit, Component, HostListener, Input, OnInit, ViewChild} from '@angular/core';
import {Location} from '@angular/common';
import {ActivatedRoute, Router} from '@angular/router';
import {IonRouterOutlet} from '@ionic/angular';
import {AnimationController, IonRouterOutlet} from '@ionic/angular';
import {SharedAxisChoreographer} from '../../../animation/animation-choreographer';
import {materialSharedAxisX} from '../../../animation/material-motion';
import {ScheduleResponsiveBreakpoint} from './schema/schema';
import {CalendarService} from '../../calendar/calendar.service';
import moment from 'moment';
import {fabExpand} from '../../../animation/fab-expand';
/**
* This needs to be sorted by break point low -> high
@@ -93,6 +94,8 @@ export class SchedulePageComponent implements OnInit, AfterViewInit {
isModalOpen = false;
fabAnimation = fabExpand(this.animationController);
/**
* Amount of days that should be shown according to current display width
*/
@@ -111,6 +114,7 @@ export class SchedulePageComponent implements OnInit, AfterViewInit {
private calendarService: CalendarService,
readonly routerOutlet: IonRouterOutlet,
private router: Router,
private animationController: AnimationController,
private location: Location,
) {}
@@ -168,12 +172,4 @@ export class SchedulePageComponent implements OnInit, AfterViewInit {
onTodayClick() {
this.calendarService.emitGoToDate(moment().startOf('day'));
}
onFABClick() {
this.isModalOpen = true;
}
onModalDismiss() {
this.isModalOpen = false;
}
}

View File

@@ -63,15 +63,15 @@
<stapps-single-events *ngSwitchCase="'single'"></stapps-single-events>
</div>
<ion-fab vertical="bottom" horizontal="end" slot="fixed" (click)="onFABClick()">
<ion-fab
vertical="bottom"
horizontal="end"
slot="fixed"
[routerLink]="['./event-picker']"
[routerAnimation]="fabAnimation"
>
<ion-fab-button>
<ion-icon name="add"></ion-icon>
</ion-fab-button>
</ion-fab>
<ion-modal canDismiss="true" [isOpen]="isModalOpen" (ionModalWillDismiss)="onModalDismiss()">
<ng-template>
<modal-event-creator></modal-event-creator>
</ng-template>
</ion-modal>
</ion-content>

View File

@@ -1,16 +1,16 @@
/*
* 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.
* 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.
* 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/>.
* 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 {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
@@ -26,7 +26,6 @@ import {DataModule} from '../data/data.module';
import {DataProvider} from '../data/data.provider';
import {CalendarViewComponent} from './page/calendar-view.component';
import {ScheduleCursorComponent} from './page/grid/schedule-cursor.component';
import {ModalEventCreatorComponent} from './page/modal/modal-event-creator.component';
import {SchedulePageComponent} from './page/schedule-page.component';
import {ScheduleSingleEventsComponent} from './page/schedule-single-events.component';
import {ScheduleViewComponent} from './page/schedule-view.component';
@@ -37,6 +36,7 @@ import {ThingTranslateModule} from '../../translation/thing-translate.module';
import {InfiniteSwiperComponent} from './page/grid/infinite-swiper.component';
import {CalendarComponent} from './page/components/calendar.component';
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
import {ChooseEventsPageComponent} from './page/choose-events-page.component';
const settingsRoutes: Routes = [
{path: 'schedule', redirectTo: 'schedule/calendar/now'},
@@ -45,6 +45,8 @@ const settingsRoutes: Routes = [
{path: 'schedule/single', redirectTo: 'schedule/single/now'},
// calendar | recurring | single
{path: 'schedule/:mode/:date', component: SchedulePageComponent},
// TODO: this is temporary until the new generalized search page is finished
{path: 'schedule/:mode/:date/event-picker', component: ChooseEventsPageComponent},
];
/**
@@ -54,7 +56,7 @@ const settingsRoutes: Routes = [
declarations: [
CalendarComponent,
CalendarViewComponent,
ModalEventCreatorComponent,
ChooseEventsPageComponent,
ScheduleCardComponent,
ScheduleCursorComponent,
SchedulePageComponent,

View File

@@ -454,8 +454,10 @@
"recurring": "Stundenplan",
"calendar": "Kalender",
"single": "Einzeltermine",
"addEventModal": {
"addEvent": "Events Hinzufügen"
"addEventPage": {
"TITLE": "Termine Hinzufügen",
"PLACEHOLDER": "Termine",
"SEARCH_INSTRUCTION": "Termine finden"
},
"card": {
"forEach": "Alle",

View File

@@ -454,8 +454,10 @@
"recurring": "Recurring",
"calendar": "Calendar",
"single": "Single Events",
"addEventModal": {
"addEvent": "Add Events"
"addEventPage": {
"TITLE": "Add Events",
"PLACEHOLDER": "Events",
"SEARCH_INSTRUCTION": "Find events"
},
"card": {
"forEach": "Every",