feat: revamp dashboard mensa section

This commit is contained in:
Thea Schöbl
2023-02-27 14:44:20 +00:00
parent ec2ac250bc
commit 298f48a914
27 changed files with 578 additions and 385 deletions

View File

@@ -58,14 +58,15 @@ describe('dashboard', async function () {
cy.visit('/overview'); cy.visit('/overview');
cy.get('stapps-mensa-section').within(() => { cy.get('stapps-mensa-section').within(() => {
cy.get('.card').should('have.length', 1); cy.get('swiper').should('not.exist');
cy.get('.card > ion-label > a').should('have.text', 'Übersicht der Mensen'); cy.get('.nothing-selected > ion-label > a').should('have.text', 'Übersicht der Mensen');
}); });
}); });
it('should add a mensa', function () { it('should add a mensa', function () {
cy.clock(new Date('2022-06-08'), ['Date']);
cy.visit('/overview'); cy.visit('/overview');
cy.get('stapps-mensa-section').find('.card > ion-label > a').click(); cy.get('stapps-mensa-section').find('.nothing-selected > ion-label > a').click();
cy.intercept('POST', 'https://mobile.server.uni-frankfurt.de/search', { cy.intercept('POST', 'https://mobile.server.uni-frankfurt.de/search', {
fixture: 'search/types/canteen/canteen-search-result.json', fixture: 'search/types/canteen/canteen-search-result.json',
}); });
@@ -74,7 +75,7 @@ describe('dashboard', async function () {
fixture: 'search/types/dish/dish-2.json', fixture: 'search/types/dish/dish-2.json',
}); });
cy.get('ion-back-button').click(); cy.get('ion-back-button').click();
cy.get('stapps-mensa-section').find('.card').should('have.length.greaterThan', 1); cy.get('stapps-mensa-section').find('simple-swiper > *').should('have.length.greaterThan', 1);
}); });
}); });
@@ -85,24 +86,35 @@ describe('dashboard', async function () {
}).as('search'); }).as('search');
}); });
it('should have desktop navigation buttons', function () { // TODO: Cypress has no real way of setting the presence of a pointing device,
cy.visit('/overview'); // which means the behavior is undefined and depends on the testing device
// it('should have desktop navigation buttons', function () {
// cy.visit('/overview');
//
// cy.get('stapps-news-section').within(function () {
// cy.get('.swiper-button').should('not.have.css', 'display: none');
// });
// });
cy.get('stapps-news-section').within(function () { // it('should not have desktop navigation buttons on mobile', function () {
cy.get('.swiper-button-prev').should('exist'); // cy.visit('/overview');
cy.get('.swiper-button-next').should('exist'); //
}); // cy.get('stapps-news-section').within(function () {
}); // cy.get('.swiper-button').should('have.css', 'display: none');
// });
// });
it('should have working desktop navigation', function () { it('should have working desktop navigation', function () {
cy.visit('/overview'); cy.visit('/overview');
cy.get('stapps-news-section').within(function () { cy.get('stapps-news-section').within(function () {
cy.get('.swiper-slide-active').should('have.text', 'DE for Students and Employees'); cy.get('simple-swiper > *').eq(0).should('be.visible');
cy.get('.swiper-button-next').click({scrollBehavior: false}); // TODO: see tests above, button will be visible or invisible
// depending on the testing device
cy.get('.swiper-button > ion-button').eq(1).click({scrollBehavior: false, force: true});
cy.get('.swiper-slide-active').should('have.text', 'DE for Students'); cy.get('simple-swiper > *').eq(0).should('not.be.visible');
}); });
}); });
@@ -113,7 +125,7 @@ describe('dashboard', async function () {
fixture: 'search/types/message/single-message.json', fixture: 'search/types/message/single-message.json',
}).as('search'); }).as('search');
cy.get('stapps-news-section').contains('a', 'Mehr Nachrichten').click(); cy.get('stapps-news-section').contains('ion-item', 'Mehr Nachrichten').click();
cy.url().should('include', '/news'); cy.url().should('include', '/news');
}); });
}); });

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 * 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.
@@ -49,6 +49,7 @@
ion-content { ion-content {
--background: var(--ion-color-light); --background: var(--ion-color-light);
--padding-bottom: var(--spacing-xl);
} }
.schedule { .schedule {
@@ -140,75 +141,3 @@ ion-content {
} }
} }
} }
.section {
padding: var(--spacing-md);
&.section-extended {
padding-right: 0;
ion-icon[name='edit'] {
margin-right: var(--spacing-md);
}
}
&:first-of-type {
padding-top: var(--spacing-lg);
}
& > ion-label:first-child {
font-family: var(--headline-font-family);
font-size: var(--font-size-lg);
font-weight: var(--font-weight-semi-bold);
text-transform: uppercase;
margin-bottom: var(--spacing-md);
width: 100%;
display: flex;
flex-direction: revert;
justify-content: space-between;
ion-icon {
color: var(--ion-color-medium-shade);
width: 25px;
height: 25px;
}
}
}
.swiper {
background-color: var(--ion-color-primary-contrast);
border-radius: var(--border-radius-default);
padding: var(--spacing-lg);
width: 28%;
display: flex;
flex-direction: column;
font-size: var(--font-size-xs);
font-weight: var(--font-weight-bold);
ion-icon {
width: 40px;
height: 40px;
margin-bottom: var(--spacing-xs);
}
}
ion-searchbar {
padding: 0;
--background: var(--ion-color-primary-contrast);
::ng-deep .searchbar-input-container {
height: 100%;
input {
padding: var(--spacing-lg);
}
ion-icon {
left: auto;
right: var(--spacing-lg);
width: 30px;
}
}
}

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 * 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.
@@ -34,6 +34,7 @@ import {FavoritesSectionComponent} from './sections/favorites-section/favorites-
import {ThingTranslateModule} from '../../translation/thing-translate.module'; import {ThingTranslateModule} from '../../translation/thing-translate.module';
import {UtilModule} from '../../util/util.module'; import {UtilModule} from '../../util/util.module';
import {IonIconModule} from '../../util/ion-icon/ion-icon.module'; import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
import {NewsModule} from '../news/news.module';
const catalogRoutes: Routes = [ const catalogRoutes: Routes = [
{ {
@@ -69,6 +70,7 @@ const catalogRoutes: Routes = [
SwiperModule, SwiperModule,
ThingTranslateModule.forChild(), ThingTranslateModule.forChild(),
UtilModule, UtilModule,
NewsModule,
], ],
providers: [SettingsProvider, TranslatePipe], providers: [SettingsProvider, TranslatePipe],
exports: [EditModalComponent], exports: [EditModalComponent],

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 ~ 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.
@@ -12,16 +12,46 @@
~ 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/>.
--> -->
<ion-grid>
<ion-row>
<ion-col>
<a *ngIf="item; else titleTemplate" [routerLink]="['/data-detail', item.uid]">
<ng-container *ngTemplateOutlet="titleTemplate"></ng-container>
</a>
<ion-label class="section-headline" <ng-template #titleTemplate>
>{{ title }} <ion-label class="section-headline">{{ title }} </ion-label>
<ion-icon <ng-content select="[slot=subtitle]"></ng-content>
size="25" </ng-template>
class="icon-margin-right" </ion-col>
*ngIf="isEditable"
(click)="onEditClick()" <ng-container *ngIf="swiper">
name="edit_square" <ion-col size="auto" class="swiper-button">
></ion-icon> <ion-button fill="clear" color="medium" (click)="slidePrev()" [disabled]="false">
<ion-icon size="25" *ngIf="customIcon" (click)="onEditClick()" [name]="customIcon"></ion-icon> <ion-icon size="24" slot="icon-only" name="chevron_left"></ion-icon>
</ion-label> </ion-button>
<ng-content></ng-content> </ion-col>
<ion-col size="auto" class="swiper-button">
<ion-button fill="clear" color="medium" (click)="slideNext()" [disabled]="false">
<ion-icon size="24" slot="icon-only" name="chevron_right"></ion-icon>
</ion-button>
</ion-col>
</ng-container>
<ion-col size="auto" *ngIf="isEditable">
<ion-button fill="clear" color="medium" (click)="onEditClick()">
<ion-icon size="24" slot="icon-only" name="edit_square"></ion-icon>
</ion-button>
</ion-col>
<ion-col size="auto" *ngIf="customIcon">
<ion-button fill="clear" color="medium" (click)="onEditClick()">
<ion-icon slot="icon-only" size="24" [name]="customIcon"></ion-icon>
</ion-button>
</ion-col>
</ion-row>
<ion-row>
<ion-col #content>
<ng-content></ng-content>
</ion-col>
</ion-row>
</ion-grid>

View File

@@ -1,58 +1,67 @@
/*! /*!
* Copyright (C) 2022 StApps * Copyright (C) 2023 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.
* *
* This program is distributed in the hope that it will be useful, but WITHOUT * This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details. * more details.
* *
* 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 '../../../../theme/util/mixins'; @import '../../../../theme/util/mixins';
a {
display: contents;
color: unset;
text-decoration: unset;
}
ion-grid {
width: 100%;
}
ion-label {
font-family: var(--headline-font-family);
font-weight: var(--font-weight-bold);
&:only-child {
height: 100%;
display: flex;
align-items: center;
}
}
ion-grid {
padding: 0;
}
ion-col {
padding: 0;
}
ion-button::part(native) {
padding-inline: var(--spacing-sm);
}
@media (hover: none) {
.swiper-button {
display: none;
}
}
:host { :host {
display: block; display: block;
padding: var(--spacing-sm) var(--spacing-md) var(--spacing-sm); padding: var(--spacing-sm) var(--spacing-md) var(--spacing-sm);
--swiper-scroll-padding: var(--spacing-md);
--swiper-gap: var(--spacing-sm);
@include ion-md-up { @include ion-md-up {
padding: var(--spacing-lg) var(--spacing-xxl) var(--spacing-sm); padding: var(--spacing-lg) var(--spacing-xxl) var(--spacing-sm);
} --swiper-scroll-padding: var(--spacing-xxl);
&.is-editable ::ng-deep {
.swiper-button-prev {
right: 65px;
}
.swiper-button-next {
right: 35px;
}
}
&.is-extended {
padding-right: 0;
.icon-margin-right {
margin-right: var(--spacing-md);
}
}
& > ion-label:first-child {
font-family: var(--headline-font-family);
font-weight: var(--font-weight-bold);
ion-icon {
color: var(--ion-color-medium-shade);
position: relative;
margin-block: auto;
cursor: pointer;
&:hover ::ng-deep stapps-icon {
--fill: 1;
}
}
} }
} }

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 * 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.
@@ -12,7 +12,18 @@
* 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 {Component, EventEmitter, HostBinding, Input, OnInit, Output} from '@angular/core'; import {
AfterContentInit,
Component,
EventEmitter,
HostBinding,
Input,
OnDestroy,
OnInit,
Output,
ViewContainerRef,
} from '@angular/core';
import {SCThings} from '@openstapps/core';
/** /**
* Shows a horizontal list of action chips * Shows a horizontal list of action chips
@@ -22,7 +33,7 @@ import {Component, EventEmitter, HostBinding, Input, OnInit, Output} from '@angu
templateUrl: 'section.component.html', templateUrl: 'section.component.html',
styleUrls: ['section.component.scss'], styleUrls: ['section.component.scss'],
}) })
export class SectionComponent implements OnInit { export class SectionComponent implements OnInit, AfterContentInit, OnDestroy {
@HostBinding('class.is-extended') isExtendedClass = false; @HostBinding('class.is-extended') isExtendedClass = false;
@HostBinding('class.is-editable') isEditableClass = false; @HostBinding('class.is-editable') isEditableClass = false;
@@ -35,18 +46,61 @@ export class SectionComponent implements OnInit {
@Input() customIcon?: string = undefined; @Input() customIcon?: string = undefined;
@Input() item?: SCThings;
// eslint-disable-next-line @angular-eslint/no-output-on-prefix // eslint-disable-next-line @angular-eslint/no-output-on-prefix
@Output() onEdit = new EventEmitter<void>(); @Output() onEdit = new EventEmitter<void>();
mutationObserver: MutationObserver;
swiper?: HTMLElement;
constructor(readonly viewContainerRef: ViewContainerRef) {}
ngOnInit() { ngOnInit() {
this.isExtendedClass = this.isSectionExtended; this.isExtendedClass = this.isSectionExtended;
this.isEditableClass = this.isEditable; this.isEditableClass = this.isEditable;
} }
ngAfterContentInit() {
this.mutationObserver = new MutationObserver(() => {
const simpleSwiper = this.viewContainerRef.element.nativeElement.querySelector('simple-swiper');
if (!simpleSwiper) return;
this.swiper = simpleSwiper;
});
this.mutationObserver.observe(this.viewContainerRef.element.nativeElement, {
childList: true,
subtree: true,
});
}
slideNext() {
if (this.swiper) {
this.swiper.scrollBy({
left: this.swiper.offsetWidth,
behavior: 'smooth',
});
}
}
slidePrev() {
if (this.swiper) {
this.swiper.scrollBy({
left: -this.swiper.offsetWidth,
behavior: 'smooth',
});
}
}
/** /**
* Action when edit is clicked * Action when edit is clicked
*/ */
onEditClick() { onEditClick() {
this.onEdit.emit(); this.onEdit.emit();
} }
ngOnDestroy() {
this.mutationObserver.disconnect();
}
} }

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 ~ 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.
@@ -18,23 +18,22 @@
[isEditable]="true" [isEditable]="true"
(onEdit)="onSectionEdit()" (onEdit)="onSectionEdit()"
> >
<div *ngIf="(items | async)?.length" class="container"> <simple-swiper *ngIf="(items | async)?.length; else noItems">
<div *ngFor="let item of items | async" class="card clickable" (click)="notifySelect(item)"> <stapps-data-list-item
<ion-thumbnail class="ion-margin-end"> *ngFor="let item of items | async"
<ion-icon color="dark" [attr.name]="item.type | dataIcon"></ion-icon> [hideThumbnail]="true"
</ion-thumbnail> [favoriteButton]="false"
<ion-label> [item]="item"
{{ 'name' | thingTranslate: item }} appearance="square"
</ion-label> ></stapps-data-list-item>
</div> </simple-swiper>
</div> <ng-template #noItems>
<ng-container *ngIf="!(items | async)?.length"> <ion-item class="nothing-selected" lines="none">
<div class="card"> <ion-label class="ion-text-wrap">
<ion-label>
{{ 'dashboard.favorites.no_favorite_prefix' | translate }} {{ 'dashboard.favorites.no_favorite_prefix' | translate }}
<a (click)="onSectionEdit()">{{ 'dashboard.favorites.no_favorite_link' | translate }}</a> <a (click)="onSectionEdit()">{{ 'dashboard.favorites.no_favorite_link' | translate }}</a>
{{ 'dashboard.favorites.no_favorite_suffix' | translate }} {{ 'dashboard.favorites.no_favorite_suffix' | translate }}
</ion-label> </ion-label>
</div> </ion-item>
</ng-container> </ng-template>
</stapps-section> </stapps-section>

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 * 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,39 +13,11 @@
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
.container { .nothing-selected::part(native) {
display: grid; background: none;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); color: var(--ion-color-medium-shade);
gap: var(--spacing-sm); }
--size: 60px;
simple-swiper {
& > * { --swiper-slide-width: 180px;
display: inline-block;
position: relative;
overflow: hidden;
width: 100%;
min-height: var(--size);
margin-bottom: var(--spacing-sm);
ion-thumbnail {
position: absolute;
bottom: 0;
right: 0;
z-index: 1;
transform: translate(10%, 20%);
margin: 0 auto var(--spacing-xs);
--size: 60px;
ion-icon {
width: var(--size);
height: var(--size);
margin: auto;
--ion-color-base: var(--ion-color-light-icon) !important;
}
}
ion-label {
position: relative;
z-index: 2;
}
}
} }

View File

@@ -1,17 +1,34 @@
<swiper <!--
[config]="sliderOptions" ~ Copyright (C) 2023 StApps
[navigation]="true" ~ This program is free software: you can redistribute it and/or modify it
class="mensa-swiper card-swiper" ~ under the terms of the GNU General Public License as published by the Free
*ngIf="dishes && dishes.length > 0" ~ Software Foundation, version 3.
> ~
<ng-template swiperSlide *ngFor="let dish of dishes"> ~ This program is distributed in the hope that it will be useful, but WITHOUT
<a [routerLink]="'/data-detail/' + dish.uid" class="card"> ~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
<ion-label>{{ 'name' | thingTranslate: dish }}</ion-label> ~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
</a> ~ more details.
</ng-template> ~
</swiper> ~ You should have received a copy of the GNU General Public License along with
<div class="card" *ngIf="!dishes || dishes.length === 0"> ~ this program. If not, see <https://www.gnu.org/licenses/>.
<ion-label> -->
{{ 'dashboard.canteens.no_dishes_available' | translate }}
</ion-label> <ng-container *ngIf="dishes | async as dishes; else loading">
</div> <simple-swiper *ngIf="dishes.length > 0" @fade>
<stapps-data-list-item
*ngFor="let dish of dishes"
[hideThumbnail]="true"
[favoriteButton]="false"
[item]="dish"
appearance="square"
></stapps-data-list-item>
</simple-swiper>
<ion-item class="no-dishes" *ngIf="!dishes || dishes.length === 0" lines="none">
<ion-label>
{{ 'dashboard.canteens.no_dishes_available' | translate }}
</ion-label>
</ion-item>
</ng-container>
<ng-template #loading>
<div class="placeholder"></div>
</ng-template>

View File

@@ -0,0 +1,28 @@
/*!
* 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/>.
*/
.no-dishes::part(native) {
background: none;
color: var(--ion-color-medium-shade);
}
simple-swiper {
--swiper-slide-width: 180px;
}
.placeholder,
stapps-data-list-item {
height: 140px;
}

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 * 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.
@@ -12,9 +12,11 @@
* 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 {Component, Input, OnInit, OnChanges, SimpleChanges} from '@angular/core'; import {Component, Input} from '@angular/core';
import {SCDish, SCPlace, SCThings} from '@openstapps/core'; import {SCDish, SCPlace, SCThings} from '@openstapps/core';
import {PlaceMensaService} from '../../../data/types/place/special/mensa/place-mensa-service'; import {PlaceMensaService} from '../../../data/types/place/special/mensa/place-mensa-service';
import {animate, style, transition, trigger} from '@angular/animations';
import moment from 'moment';
/** /**
* Shows a section with meals of the chosen mensa * Shows a section with meals of the chosen mensa
@@ -22,50 +24,29 @@ import {PlaceMensaService} from '../../../data/types/place/special/mensa/place-m
@Component({ @Component({
selector: 'stapps-mensa-section-content', selector: 'stapps-mensa-section-content',
templateUrl: 'mensa-section-content.component.html', templateUrl: 'mensa-section-content.component.html',
styleUrls: ['mensa-section.component.scss'], styleUrls: ['mensa-section-content.component.scss'],
animations: [
trigger('fade', [
transition(':enter', [style({opacity: '0'}), animate('500ms ease', style({opacity: '1'}))]),
]),
],
}) })
export class MensaSectionContentComponent implements OnInit, OnChanges { export class MensaSectionContentComponent {
/**
* Slider options
*/
sliderOptions = {
spaceBetween: 12,
freeMode: {
enabled: true,
sticky: true,
},
width: 120,
};
/** /**
* Map of dishes for each day * Map of dishes for each day
*/ */
// eslint-disable-next-line unicorn/no-null // eslint-disable-next-line unicorn/no-null
dishes: SCDish[] | null = []; dishes: Promise<SCDish[]>;
@Input() items: SCThings[]; @Input() set item(value: SCThings) {
if (!value) return;
this.dishes = this.mensaService.getAllDishes(value as SCPlace, 1).then(it => {
const closestDayWithDishes = Object.keys(it)
.filter(key => it[key].length > 0)
.find(key => moment(key).isSame(moment(), 'day'));
return closestDayWithDishes ? it[closestDayWithDishes] : [];
});
}
constructor(private readonly mensaService: PlaceMensaService) {} constructor(private readonly mensaService: PlaceMensaService) {}
async ngOnInit() {
await this.getDishes();
}
async ngOnChanges(changes: SimpleChanges) {
if (typeof changes.items !== 'undefined') {
await this.getDishes();
}
}
/**
* Request dishes
*/
async getDishes() {
if (this.items) {
for (const item of this.items) {
const dishes = await this.mensaService.getAllDishes(item as SCPlace, 1);
this.dishes?.push(...dishes[Object.keys(dishes)[0]]);
}
}
}
} }

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 ~ 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,21 +13,34 @@
~ this program. If not, see <https://www.gnu.org/licenses/>. ~ this program. If not, see <https://www.gnu.org/licenses/>.
--> -->
<stapps-section <ng-container *ngIf="items | async as items">
[title]="'dashboard.canteens.title' | translate" <ng-container *ngIf="items.length !== 0; else nothingSelected">
[isEditable]="true" <ng-container *ngFor="let item of items">
(onEdit)="onSectionEdit()" <stapps-section
> @fade
<ng-container *ngIf="(items | async)?.length"> [item]="item"
<stapps-mensa-section-content [items]="items | async"></stapps-mensa-section-content> [title]="'name' | thingTranslate: item"
[isEditable]="true"
(onEdit)="onSectionEdit()"
>
<stapps-opening-hours slot="subtitle" [openingHours]="item.openingHours"></stapps-opening-hours>
<stapps-mensa-section-content [item]="item"></stapps-mensa-section-content>
</stapps-section>
</ng-container>
</ng-container> </ng-container>
<ng-container *ngIf="!(items | async)?.length"> <ng-template #nothingSelected>
<div class="card"> <stapps-section
<ion-label> [title]="'dashboard.canteens.title' | translate"
{{ 'dashboard.canteens.no_favorite_prefix' | translate }} [isEditable]="true"
<a (click)="onSectionEdit()">{{ 'dashboard.canteens.no_favorite_link' | translate }}</a> (onEdit)="onSectionEdit()"
{{ 'dashboard.canteens.no_favorite_suffix' | translate }} >
</ion-label> <ion-item class="nothing-selected" lines="none">
</div> <ion-label class="ion-text-wrap">
</ng-container> {{ 'dashboard.canteens.no_favorite_prefix' | translate }}
</stapps-section> <a (click)="onSectionEdit()">{{ 'dashboard.canteens.no_favorite_link' | translate }}</a>
{{ 'dashboard.canteens.no_favorite_suffix' | translate }}
</ion-label>
</ion-item>
</stapps-section>
</ng-template>
</ng-container>

View File

@@ -0,0 +1,28 @@
/*!
* 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/>.
*/
stapps-mensa-section-content {
display: block;
margin-block-start: var(--spacing-md);
}
.nothing-selected::part(native) {
background: none;
color: var(--ion-color-medium-shade);
}
:host {
transition: height 150ms ease;
}

View File

@@ -28,6 +28,7 @@ import {SettingsProvider} from '../../../settings/settings.provider';
import {FavoritesService} from '../../../favorites/favorites.service'; import {FavoritesService} from '../../../favorites/favorites.service';
import {ContextMenuService} from '../../../menu/context/context-menu.service'; import {ContextMenuService} from '../../../menu/context/context-menu.service';
import {ConfigProvider} from '../../../config/config.provider'; import {ConfigProvider} from '../../../config/config.provider';
import {animate, style, transition, trigger} from '@angular/animations';
/** /**
* Shows a section with meals of the chosen mensa * Shows a section with meals of the chosen mensa
@@ -36,6 +37,9 @@ import {ConfigProvider} from '../../../config/config.provider';
selector: 'stapps-mensa-section', selector: 'stapps-mensa-section',
templateUrl: 'mensa-section.component.html', templateUrl: 'mensa-section.component.html',
styleUrls: ['mensa-section.component.scss'], styleUrls: ['mensa-section.component.scss'],
animations: [
trigger('fade', [transition(':enter', [style({opacity: '0'}), animate(250, style({opacity: '1'}))])]),
],
}) })
export class MensaSectionComponent extends FoodDataListComponent { export class MensaSectionComponent extends FoodDataListComponent {
sub: Subscription; sub: Subscription;

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 ~ 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.
@@ -20,26 +20,13 @@
class="is-editable" class="is-editable"
(onEdit)="onMoreNewsClicked()" (onEdit)="onMoreNewsClicked()"
> >
<swiper <simple-swiper class="news-swiper card-swiper" *ngIf="news" @fade>
[config]="sliderOptions" <stapps-news-item *ngFor="let newsItem of news" [item]="newsItem"> </stapps-news-item>
slidesPerView="auto" <ion-item [routerLink]="['/news']" class="more-news" lines="none">
[navigation]="true" <ion-label>{{ 'dashboard.news.moreNews' | translate | titlecase }}</ion-label>
*ngIf="news.length > 0" <ion-thumbnail class="ion-margin-end">
class="news-swiper card-swiper" <ion-icon color="medium" name="read_more" size="150"></ion-icon>
> </ion-thumbnail>
<ng-template swiperSlide *ngFor="let newsItem of news"> </ion-item>
<a [routerLink]="['/data-detail', newsItem.uid]" class="card"> </simple-swiper>
<ion-img [src]="newsItem.image"></ion-img>
<ion-label>{{ newsItem.name }}</ion-label>
</a>
</ng-template>
<ng-template swiperSlide>
<a [routerLink]="['/news']" class="card more-news">
<ion-label>{{ 'dashboard.news.moreNews' | translate | titlecase }}</ion-label>
<ion-thumbnail class="ion-margin-end">
<ion-icon color="dark" name="read_more" size="128"></ion-icon>
</ion-thumbnail>
</a>
</ng-template>
</swiper>
</stapps-section> </stapps-section>

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 * 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,50 +13,33 @@
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
.news-swiper.swiper { simple-swiper {
.swiper-slide { --swiper-slide-width: 256px;
padding: 0; }
.card { .more-news {
padding: 0; width: 128px;
font-size: var(--font-size-xl);
--color: var(--ion-color-medium-tint);
ion-img { &::part(native) {
border-radius: var(--border-radius-default) var(--border-radius-default) 0 0; height: 100%;
overflow: hidden; background: none;
} border-radius: var(--border-radius-default);
}
ion-label { ion-label {
margin: var(--spacing-lg); position: absolute;
text-align: left; top: 0;
font-size: var(--font-size-sm); left: 0;
font-weight: var(--font-weight-bold); }
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
&.more-news { ion-thumbnail {
ion-label { position: absolute;
font-size: var(--font-size-lg); bottom: 0;
} left: 0;
z-index: 100;
ion-thumbnail { height: 200px;
position: absolute; width: 200px;
bottom: 0;
right: 0;
z-index: 1;
margin: 0 auto var(--spacing-xs);
--size: 160px;
ion-icon {
width: var(--size);
height: var(--size);
margin: auto;
--ion-color-base: var(--ion-color-light-icon) !important;
}
}
}
}
} }
} }

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 * 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.
@@ -12,7 +12,7 @@
* 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 {Component, OnInit, ViewEncapsulation} from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {Router} from '@angular/router'; import {Router} from '@angular/router';
import {NewsPageComponent} from '../../../news/page/news-page.component'; import {NewsPageComponent} from '../../../news/page/news-page.component';
import {NewsProvider} from '../../../news/news.provider'; import {NewsProvider} from '../../../news/news.provider';
@@ -20,6 +20,7 @@ import {SettingsProvider} from '../../../settings/settings.provider';
import {newsFilterSettingsFieldsMapping, NewsFilterSettingsNames} from '../../../news/news-filter-settings'; import {newsFilterSettingsFieldsMapping, NewsFilterSettingsNames} from '../../../news/news-filter-settings';
import {DataProvider} from '../../../data/data.provider'; import {DataProvider} from '../../../data/data.provider';
import {SCSearchValueFilter} from '@openstapps/core'; import {SCSearchValueFilter} from '@openstapps/core';
import {animate, style, transition, trigger} from '@angular/animations';
/** /**
* Shows a section with news * Shows a section with news
@@ -28,26 +29,16 @@ import {SCSearchValueFilter} from '@openstapps/core';
selector: 'stapps-news-section', selector: 'stapps-news-section',
templateUrl: 'news-section.component.html', templateUrl: 'news-section.component.html',
styleUrls: ['news-section.component.scss'], styleUrls: ['news-section.component.scss'],
encapsulation: ViewEncapsulation.None, animations: [
trigger('fade', [
transition(':enter', [
style({opacity: '0', transform: 'translateX(100px)'}),
animate('250ms ease', style({opacity: '1', transform: 'translateX(0)'})),
]),
]),
],
}) })
export class NewsSectionComponent extends NewsPageComponent implements OnInit { export class NewsSectionComponent extends NewsPageComponent implements OnInit {
/**
* Slider options
*/
sliderOptions = {
spaceBetween: 12,
freeMode: {
enabled: true,
sticky: true,
},
width: 300,
breakpoints: {
768: {
width: 380,
},
},
};
pageSize = 5; pageSize = 5;
/** /**

View File

@@ -1,16 +1,16 @@
<!-- <!--
~ Copyright (C) 2022 StApps ~ Copyright (C) 2023 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.
~ ~
~ This program is distributed in the hope that it will be useful, but WITHOUT ~ This program is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for ~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
~ more details. ~ more details.
~ ~
~ 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/>.
--> -->
<div> <div>
@@ -24,7 +24,7 @@
{{ 'data.detail.offers.sold_out' | translate }} {{ 'data.detail.offers.sold_out' | translate }}
</h2> </h2>
</ion-text> </ion-text>
<p *ngIf="_offers[0].inPlace && !soldOut"> <p *ngIf="_offers[0].inPlace && !soldOut" class="place">
<ion-icon name="pin_drop"></ion-icon>{{ _offers[0].inPlace.name <ion-icon name="pin_drop"></ion-icon>{{ _offers[0].inPlace.name
}}<span *ngIf="_offers.length > 1">...</span> }}<span *ngIf="_offers.length > 1">...</span>
</p> </p>

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 * 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.
@@ -12,7 +12,7 @@
* 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 {Component, ContentChild, Input, TemplateRef} from '@angular/core'; import {Component, ContentChild, HostBinding, Input, TemplateRef} from '@angular/core';
import {SCThings} from '@openstapps/core'; import {SCThings} from '@openstapps/core';
import {DataRoutingService} from '../data-routing.service'; import {DataRoutingService} from '../data-routing.service';
import {DataListContext} from './data-list.component'; import {DataListContext} from './data-list.component';
@@ -40,8 +40,14 @@ export class DataListItemComponent {
@Input() lines = 'inset'; @Input() lines = 'inset';
@Input() appearance: 'normal' | 'square' = 'normal';
@ContentChild(TemplateRef) contentTemplateRef: TemplateRef<DataListContext<SCThings>>; @ContentChild(TemplateRef) contentTemplateRef: TemplateRef<DataListContext<SCThings>>;
@HostBinding('class.square') get square() {
return this.appearance === 'square';
}
constructor(private readonly dataRoutingService: DataRoutingService) {} constructor(private readonly dataRoutingService: DataRoutingService) {}
/** /**

View File

@@ -112,7 +112,11 @@
></stapps-long-inline-text> ></stapps-long-inline-text>
</p> </p>
</div> </div>
<stapps-action-chip-list slot="end" [item]="item"></stapps-action-chip-list> <stapps-action-chip-list
*ngIf="appearance !== 'square'"
slot="end"
[item]="item"
></stapps-action-chip-list>
</div> </div>
</ion-label> </ion-label>
</ng-template> </ng-template>

View File

@@ -41,3 +41,43 @@ ion-item {
} }
} }
} }
:host.square ::ng-deep {
ion-item {
margin: 0;
}
ion-row {
flex-direction: column;
justify-content: space-between;
height: 120px;
}
ion-col {
flex-grow: 0;
flex-basis: min-content;
}
.title {
display: -webkit-box;
white-space: break-spaces;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
.title-sub {
display: none;
}
// fix for Safari
stapps-offers-in-list {
position: absolute;
bottom: 0;
right: 0;
}
stapps-offers-in-list .place {
display: none;
}
}

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 * 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.
@@ -33,6 +33,10 @@ ion-card::part(native) {
} }
ion-card-title { ion-card-title {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
font-size: var(--font-size-xl); font-size: var(--font-size-xl);
--color: var(--ion-color-dark-contrast); --color: var(--ion-color-dark-contrast);
} }

View File

@@ -1,16 +1,16 @@
/* /*
* Copyright (C) 2022 StApps * Copyright (C) 2023 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.
* *
* This program is distributed in the hope that it will be useful, but WITHOUT * This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details. * more details.
* *
* 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 {CommonModule} from '@angular/common'; import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core'; import {NgModule} from '@angular/core';
@@ -57,5 +57,6 @@ const newsRoutes: Routes = [{path: 'news', component: NewsPageComponent}];
UtilModule, UtilModule,
], ],
providers: [SettingsProvider], providers: [SettingsProvider],
exports: [NewsItemComponent],
}) })
export class NewsModule {} export class NewsModule {}

View File

@@ -0,0 +1,27 @@
/*
* 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, ContentChildren, ElementRef, ViewContainerRef} from '@angular/core';
@Component({
selector: 'simple-swiper',
templateUrl: 'simple-swiper.html',
styleUrls: ['simple-swiper.scss'],
})
export class SimpleSwiperComponent {
constructor(readonly viewContainerRef: ViewContainerRef) {}
@ContentChildren('*') children: ElementRef<unknown>;
}

View File

@@ -0,0 +1,15 @@
<!--
~ 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/>.
-->
<ng-content></ng-content>

View File

@@ -0,0 +1,54 @@
/*!
* 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/>.
*/
:host {
position: relative;
display: grid;
grid-auto-flow: column;
justify-content: start;
scroll-snap-type: x mandatory;
overflow-x: auto;
overflow-y: hidden;
contain: layout;
margin-inline: calc(-1 * var(--swiper-scroll-padding));
gap: var(--swiper-gap, 0);
&::ng-deep > *:not(ion-button) {
contain: layout;
scroll-snap-align: start;
scroll-margin-inline: var(--swiper-scroll-padding, 0);
width: var(--swiper-slide-width);
&:first-child {
padding-inline-start: var(--swiper-scroll-padding);
width: calc(var(--swiper-slide-width) + var(--swiper-scroll-padding));
}
&:last-child {
scroll-snap-align: end;
padding-inline-end: var(--swiper-scroll-padding);
width: calc(var(--swiper-slide-width) + var(--swiper-scroll-padding));
}
}
&::-webkit-scrollbar {
display: none;
}
scrollbar-width: none;
}

View File

@@ -27,6 +27,7 @@ import {TranslateModule} from '@ngx-translate/core';
import {ElementSizeChangeDirective} from './element-size-change.directive'; import {ElementSizeChangeDirective} from './element-size-change.directive';
import {OpeningHoursComponent} from './opening-hours.component'; import {OpeningHoursComponent} from './opening-hours.component';
import {ThingTranslateModule} from '../translation/thing-translate.module'; import {ThingTranslateModule} from '../translation/thing-translate.module';
import {SimpleSwiperComponent} from './simple-swiper.component';
@NgModule({ @NgModule({
imports: [BrowserModule, IonicModule, TranslateModule, ThingTranslateModule.forChild()], imports: [BrowserModule, IonicModule, TranslateModule, ThingTranslateModule.forChild()],
@@ -41,6 +42,7 @@ import {ThingTranslateModule} from '../translation/thing-translate.module';
NextDateInListPipe, NextDateInListPipe,
EditModalComponent, EditModalComponent,
OpeningHoursComponent, OpeningHoursComponent,
SimpleSwiperComponent,
], ],
exports: [ exports: [
ElementSizeChangeDirective, ElementSizeChangeDirective,
@@ -53,6 +55,7 @@ import {ThingTranslateModule} from '../translation/thing-translate.module';
NextDateInListPipe, NextDateInListPipe,
EditModalComponent, EditModalComponent,
OpeningHoursComponent, OpeningHoursComponent,
SimpleSwiperComponent,
], ],
}) })
export class UtilModule {} export class UtilModule {}