mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-10 19:52:53 +00:00
refactor: replace timetable event modal with route
This commit is contained in:
@@ -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;
|
||||
83
src/app/animation/fab-expand.ts
Normal file
83
src/app/animation/fab-expand.ts
Normal 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);
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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',
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user