Merge remote-tracking branch 'app/develop'

This commit is contained in:
2023-05-24 14:31:00 +02:00
152 changed files with 2765 additions and 1866 deletions

View File

@@ -0,0 +1,34 @@
/*
* 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 {Observable} from 'rxjs';
/**
*
*/
export function fromMutationObserver(
target: Node,
options?: MutationObserverInit,
): Observable<MutationRecord[]> {
return new Observable(subscriber => {
const observer = new MutationObserver(mutations => {
subscriber.next(mutations);
});
observer.observe(target, options);
return () => {
observer.disconnect();
};
});
}

View File

@@ -0,0 +1,20 @@
/*
* 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/>.
*/
// 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

@@ -1,3 +1,22 @@
<!--
~ 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/>.
-->
<<<<<<<< HEAD:frontend/app/src/app/util/simple-swiper.html
<ng-content></ng-content>
========
<ion-app style="margin-top: env(safe-area-inset-top)">
<stapps-navigation></stapps-navigation>
</ion-app>
>>>>>>>> app/develop:frontend/app/src/app/app.component.html

View File

@@ -22,10 +22,8 @@
<!-- TODO: translation -->
</ion-toolbar>
</ion-header>
<ion-content class="ion-content-parallax">
<div>
<div class="about-changelog">
<markdown src="assets/about/CHANGELOG.md"></markdown>
</div>
<ion-content parallax>
<div class="about-changelog">
<markdown src="assets/about/CHANGELOG.md"></markdown>
</div>
</ion-content>

View File

@@ -22,32 +22,30 @@
<!-- TODO: translation -->
</ion-toolbar>
</ion-header>
<ion-content class="ion-content-parallax">
<div>
<div class="licenses-content">
<ion-card
*ngFor="let license of licenses"
[href]="license.url || license.repository"
rel="external"
target="_blank"
>
<ion-card-header>
<ion-card-title>
{{ license.name }}
<ion-icon size="16" weight="300" class="supertext-icon" name="open_in_browser"></ion-icon>
</ion-card-title>
<ion-content parallax>
<div class="licenses-content">
<ion-card
*ngFor="let license of licenses"
[href]="license.url || license.repository"
rel="external"
target="_blank"
>
<ion-card-header>
<ion-card-title>
{{ license.name }}
<ion-icon size="16" weight="300" class="supertext-icon" name="open_in_browser"></ion-icon>
</ion-card-title>
<ion-card-subtitle *ngIf="license.authors || license.publisher">
{{ license.authors || license.publisher }}
</ion-card-subtitle>
</ion-card-header>
<ion-card-content>
<ion-chip (click)="$event.preventDefault(); viewLicense(license)">
<ion-icon name="copyright"></ion-icon>
<ion-label>{{ license.licenses }} License</ion-label>
</ion-chip>
</ion-card-content>
</ion-card>
</div>
<ion-card-subtitle *ngIf="license.authors || license.publisher">
{{ license.authors || license.publisher }}
</ion-card-subtitle>
</ion-card-header>
<ion-card-content>
<ion-chip (click)="$event.preventDefault(); viewLicense(license)">
<ion-icon name="copyright"></ion-icon>
<ion-label>{{ license.licenses }} License</ion-label>
</ion-chip>
</ion-card-content>
</ion-card>
</div>
</ion-content>

View File

@@ -1,5 +1,10 @@
<<<<<<<< HEAD:packages/es-mapping-generator/test/mapping-model/aggregations/src/types.ts
/*
* Copyright (C) 2020 StApps
========
/*!
* Copyright (C) 2023 StApps
>>>>>>>> app/develop:frontend/app/src/app/modules/about/about-licenses.scss
* 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,6 +18,17 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
<<<<<<<< HEAD:packages/es-mapping-generator/test/mapping-model/aggregations/src/types.ts
export enum ThingType {
AggArray = 'agg array',
AggGlobal = 'agg global',
AggGlobalNested = 'agg global nested',
AggNested = 'agg nested',
AggInherited = 'agg inherited',
AggInheritedGlobal = 'agg inherited global',
AggInheritedOverwritten = 'agg inherited overwritten',
}
========
ion-content > div {
height: 100%;
}
@@ -22,7 +38,7 @@ cdk-virtual-scroll-viewport {
width: 100%;
}
::ng-deep {
:host ::ng-deep {
.cdk-virtual-scroll-content-wrapper {
width: 100%;
}
@@ -36,3 +52,4 @@ cdk-virtual-scroll-viewport {
vertical-align: text-top;
height: 14px;
}
>>>>>>>> app/develop:frontend/app/src/app/modules/about/about-licenses.scss

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.
@@ -36,7 +36,7 @@
</ion-col>
</ion-row>
</ion-grid>
<ion-item *ngSwitchCase="'router link'" [routerLink]="content.link">
<ion-item *ngSwitchCase="'router link'" [routerLink]="content.link" fill="clear">
<ion-icon *ngIf="content.icon" [name]="content.icon" slot="start"></ion-icon>
<ion-label>{{ 'title' | translateSimple: content }}</ion-label>
</ion-item>

View File

@@ -24,11 +24,9 @@
</ng-template>
</ion-toolbar>
</ion-header>
<ion-content *ngIf="content" class="ion-content-parallax">
<div>
<ion-text color="light">{{ appName }} v{{ version }}</ion-text>
<div class="page-content">
<about-page-content *ngFor="let element of content.content" [content]="element"></about-page-content>
</div>
<ion-content parallax *ngIf="content">
<ion-text>{{ appName }} v{{ version }}</ion-text>
<div class="page-content">
<about-page-content *ngFor="let element of content.content" [content]="element"></about-page-content>
</div>
</ion-content>

View File

@@ -62,10 +62,7 @@ ion-text {
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
background: var(--ion-color-primary-contrast);
&.licenses-content {
background: var(--ion-color-light);
}
background: var(--ion-item-background);
padding-block-end: var(--spacing-md);
@include border-radius-in-parallax(var(--border-radius-default));
@@ -80,7 +77,6 @@ ion-text {
@include border-radius-in-parallax(var(--border-radius-default));
overflow: hidden;
position: relative;
background-color: var(--ion-color-primary-contrast);
margin: 0;
& > ion-thumbnail {
@@ -89,3 +85,7 @@ ion-text {
}
}
}
ion-text {
color: var(--ion-color-primary-contrast);
}

View File

@@ -1,19 +1,20 @@
/*!
* 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/>.
*/
.content {
height: 100%;
padding-inline: 8px;
--ion-item-background: var(--ion-background-color);
}

View File

@@ -29,6 +29,7 @@ import {StorageProvider} from '../storage/storage.provider';
import {DefaultAuthService} from './default-auth.service';
import {PAIAAuthService} from './paia/paia-auth.service';
import {SimpleBrowser} from '../../util/browser.factory';
import {AlertController} from '@ionic/angular';
const AUTH_ORIGIN_PATH = 'stapps.auth.origin_path';
@@ -45,6 +46,7 @@ export class AuthHelperService {
private defaultAuth: DefaultAuthService,
private paiaAuth: PAIAAuthService,
private browser: SimpleBrowser,
private alertController: AlertController,
) {
this.userConfigurationMap = (
this.configProvider.getAnyValue('auth') as {
@@ -118,11 +120,33 @@ export class AuthHelperService {
/**
* Ends browser session by opening endSessionEndpoint URL of the provider
*
* @param providerType Type of the provider (e.g. 'default' or 'paia')
*/
async endBrowserSession(providerType: SCAuthorizationProviderType) {
const endSessionEndpoint = (await this.getProvider(providerType).configuration).endSessionEndpoint ?? '';
if (endSessionEndpoint.length > 0) {
this.browser.open(new URL(endSessionEndpoint).href);
const endSessionEndpoint = (await this.getProvider(providerType).configuration).endSessionEndpoint;
if (endSessionEndpoint) {
const alert: HTMLIonAlertElement = await this.alertController.create({
header: this.translateService.instant(`auth.messages.${providerType}.log_out_alert.header`),
message: this.translateService.instant(`auth.messages.${providerType}.log_out_alert.message`),
buttons: [
{
text: this.translateService.instant('no'),
cssClass: 'default',
},
{
text: this.translateService.instant('yes'),
role: 'confirm',
cssClass: 'preferred',
handler: () => {
this.browser.open(new URL(endSessionEndpoint).href);
},
},
],
});
await alert.present();
}
}
}

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.
@@ -27,3 +27,7 @@ ion-toolbar.in-toolbar {
padding: 0 !important;
}
}
:host {
--ion-item-background: var(--ion-backgrund-color);
}

View File

@@ -117,8 +117,9 @@ export class CatalogComponent implements OnInit, OnDestroy {
semester => semester.startDate <= today && semester.endDate > today,
);
const currentSemesterIndex = semesters.findIndex(semester => semester.uid === currentSemester?.uid);
this.availableSemesters = semesters.slice(currentSemesterIndex - 1, currentSemesterIndex + 2).reverse();
this.availableSemesters = semesters
.slice(Math.max(0, currentSemesterIndex - 1), Math.min(currentSemesterIndex + 2, semesters.length))
.reverse();
if (typeof this.activeSemester !== 'undefined') {
return;
}

View File

@@ -121,11 +121,11 @@ export class CatalogProvider {
arguments: {
bounds: {
lowerBound: {
limit: `${new Date().setFullYear(new Date().getFullYear() - 1)}`,
limit: `${new Date(new Date().setFullYear(new Date().getFullYear() - 1)).toISOString()}`,
mode: 'inclusive',
},
upperBound: {
limit: `${new Date().setFullYear(new Date().getFullYear() + 1)}`,
limit: `${new Date(new Date().setFullYear(new Date().getFullYear() + 1)).toISOString()}`,
mode: 'inclusive',
},
},

View File

@@ -48,14 +48,18 @@
}
ion-content {
--background: var(--ion-color-light);
--background: var(--ion-background-color);
--padding-bottom: var(--spacing-xl);
&::part(inner-scroll) {
scrollbar-gutter: stable;
}
}
.schedule {
width: 100%;
z-index: 3;
background: var(--ion-color-primary);
color: var(--ion-color-primary-contrast);
display: flex;
justify-content: space-between;
gap: var(--spacing-md);

View File

@@ -23,7 +23,6 @@ import {MomentModule} from 'ngx-moment';
import {DataModule} from '../data/data.module';
import {SettingsProvider} from '../settings/settings.provider';
import {DashboardComponent} from './dashboard.component';
import {EditModalComponent} from './edit-modal/edit-modal.component';
import {SearchSectionComponent} from './sections/search-section/search-section.component';
import {NewsSectionComponent} from './sections/news-section/news-section.component';
import {MensaSectionComponent} from './sections/mensa-section/mensa-section.component';
@@ -46,7 +45,6 @@ const catalogRoutes: Routes = [
*/
@NgModule({
declarations: [
EditModalComponent,
SearchSectionComponent,
NewsSectionComponent,
MensaSectionComponent,
@@ -69,6 +67,5 @@ const catalogRoutes: Routes = [
NewsModule,
],
providers: [SettingsProvider, TranslatePipe],
exports: [EditModalComponent],
})
export class DashboardModule {}

View File

@@ -1,52 +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/>.
-->
<ion-header>
<ion-toolbar mode="ios">
<ion-title>{{ 'modal.settings' | translate | titlecase }}</ion-title>
<ion-button fill="clear" slot="start" (click)="dismissModal()">
{{ 'modal.DISMISS_CANCEL' | translate }}
</ion-button>
<ion-button fill="clear" slot="end" (click)="onSaveClick()">
<ion-label>{{ 'modal.DISMISS_CONFIRM' | translate }}</ion-label>
</ion-button>
</ion-toolbar>
</ion-header>
<ion-content>
<ng-container [ngSwitch]="true">
<ion-reorder-group
*ngSwitchCase="type === types.CHECKBOXES"
disabled="false"
(ionItemReorder)="doReorder($event)"
>
<!-- Default reorder icon, end aligned items -->
<ion-item *ngFor="let item of items">
<ion-reorder slot="start"></ion-reorder>
<ion-label>{{ item.labelLocalized }}</ion-label>
<ion-toggle slot="end" [checked]="item.active" [(ngModel)]="item.active"></ion-toggle>
</ion-item>
</ion-reorder-group>
<ion-radio-group *ngSwitchCase="type === types.RADIOBOXES" [(ngModel)]="selectedValue">
<ion-list-header>
<ion-label>{{ 'dashboard.canteens.choose_favorite' | translate }}</ion-label>
</ion-list-header>
<ion-item *ngFor="let item of items">
<ion-label>{{ item.labelLocalized }}</ion-label>
<ion-radio slot="end" [value]="item.id"></ion-radio>
</ion-item>
</ion-radio-group>
</ng-container>
</ion-content>

View File

@@ -1,3 +0,0 @@
:host {
--width: 100vw;
}

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, Input, OnInit, ViewChild} from '@angular/core';
import {IonReorderGroup, ModalController} from '@ionic/angular';
import {ItemReorderEventDetail} from '@ionic/core';
import {EditModalItem, EditModalTypeEnum} from './edit-modal-type.enum';
/**
* Shows a modal window to sort and enable/disable menu items
*/
@Component({
selector: 'stapps-dashboard-edit-modal',
templateUrl: 'edit-modal.component.html',
styleUrls: ['edit-modal.component.scss'],
})
export class EditModalComponent implements OnInit {
@ViewChild(IonReorderGroup) reorderGroup: IonReorderGroup;
@Input() type: EditModalTypeEnum = EditModalTypeEnum.CHECKBOXES;
@Input() items: EditModalItem[];
@Input() selectedValue: string;
reorderedItems: EditModalItem[];
types = EditModalTypeEnum;
constructor(public modalController: ModalController) {}
ngOnInit() {
this.reorderedItems = this.items;
}
ionViewWillLeave() {
this.dismissModal();
}
doReorder(event: CustomEvent<ItemReorderEventDetail>) {
this.reorderedItems = event.detail.complete(this.reorderedItems);
}
onSaveClick() {
this.modalController.dismiss({
items: this.reorderedItems,
selectedValue: this.selectedValue,
});
}
dismissModal() {
this.modalController.dismiss();
}
}

View File

@@ -0,0 +1,19 @@
/*
* 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 {animate, style, transition, trigger} from '@angular/animations';
export const fadeAnimation = trigger('fade', [
transition(':enter', [style({opacity: '0'}), animate(250, style({opacity: '1'}))]),
]);

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/>.
*/
import {SCBuildingCategories, SCThings, SCThingWithCategories} from '@openstapps/core';
const mensaCategories = new Set<SCBuildingCategories>(['canteen', 'cafe', 'student canteen', 'restaurant']);
/**
*
*/
export function isMensaThing(item: SCThings): boolean {
return (
(item as SCThingWithCategories<string, never>).categories?.some(category =>
mensaCategories.has(category as never),
) || false
);
}

View File

@@ -17,9 +17,9 @@
<ion-button slot="button-end" fill="clear" color="medium" [routerLink]="['/favorites']">
<ion-icon slot="icon-only" name="search" size="24"></ion-icon>
</ion-button>
<simple-swiper *ngIf="(items | async)?.length; else noItems">
<simple-swiper *ngIf="items | async as items; else noItems" @fade>
<stapps-data-list-item
*ngFor="let item of items | async"
*ngFor="let item of items"
[hideThumbnail]="true"
[favoriteButton]="false"
[item]="item"

View File

@@ -12,22 +12,11 @@
* 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, OnInit} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {AlertController, AnimationController} from '@ionic/angular';
import {combineLatest} from 'rxjs';
import {debounceTime, distinctUntilChanged, startWith, take} from 'rxjs/operators';
import {NGXLogger} from 'ngx-logger';
import {SCThings} from '@openstapps/core';
import {DataProvider} from '../../../data/data.provider';
import {DataRoutingService} from '../../../data/data-routing.service';
import {SearchPageComponent} from '../../../data/list/search-page.component';
import {PositionService} from '../../../map/position.service';
import {SettingsProvider} from '../../../settings/settings.provider';
import {ChangeDetectionStrategy, Component} from '@angular/core';
import {filter, map} from 'rxjs/operators';
import {FavoritesService} from '../../../favorites/favorites.service';
import {ContextMenuService} from '../../../menu/context/context-menu.service';
import {ConfigProvider} from '../../../config/config.provider';
import {fadeAnimation} from '../../fade.animation';
import {isMensaThing} from '../../mensa-filters';
/**
* Shows a section with meals of the chosen mensa
@@ -36,95 +25,14 @@ import {ConfigProvider} from '../../../config/config.provider';
selector: 'stapps-favorites-section',
templateUrl: 'favorites-section.component.html',
styleUrls: ['favorites-section.component.scss'],
animations: [fadeAnimation],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FavoritesSectionComponent extends SearchPageComponent implements OnInit {
constructor(
protected readonly alertController: AlertController,
protected dataProvider: DataProvider,
protected readonly contextMenuService: ContextMenuService,
protected readonly settingsProvider: SettingsProvider,
protected readonly logger: NGXLogger,
protected dataRoutingService: DataRoutingService,
protected router: Router,
route: ActivatedRoute,
positionService: PositionService,
private favoritesService: FavoritesService,
configProvider: ConfigProvider,
animationController: AnimationController,
) {
super(
alertController,
dataProvider,
contextMenuService,
settingsProvider,
logger,
dataRoutingService,
router,
route,
positionService,
configProvider,
animationController,
);
}
export class FavoritesSectionComponent {
items = this.favoritesService.favoriteThings$.pipe(
map(favorites => favorites.filter(it => !isMensaThing(it))),
filter(favorites => favorites.length > 0),
);
async initialize() {
this.subscriptions.push(
combineLatest([
this.queryTextChanged.pipe(
debounceTime(this.searchQueryDueTime),
distinctUntilChanged(),
startWith(this.queryText),
),
this.favoritesService.favoritesChanged$,
]).subscribe(async () => {
await this.fetchAndUpdateItems();
this.queryChanged.next();
}),
);
}
/**
* Fetches/updates the favorites (search page component's method override)
*/
async fetchAndUpdateItems() {
this.favoritesService
.search(this.queryText, this.filterQuery, this.sortQuery)
.pipe(take(1))
.subscribe(result => {
this.items = new Promise(resolve => {
resolve(result.data && result.data.filter(item => !this.isMensaThing(item)));
});
});
}
/**
* Helper function as 'typeof' is not accessible in HTML
*
* @param item TODO
*/
isMensaThing(item: SCThings): boolean {
return (
this.hasCategories(item) &&
((item.categories as string[]).includes('canteen') ||
(item.categories as string[]).includes('cafe') ||
(item.categories as string[]).includes('student canteen') ||
(item.categories as string[]).includes('restaurant'))
);
}
/**
* TODO
*
* @param item TODO
*/
hasCategories(item: SCThings): item is SCThings & {categories: string[]} {
return typeof (item as {categories: string[]}).categories !== 'undefined';
}
/**
* Emit event that an item was selected
*/
notifySelect(item: SCThings) {
this.dataRoutingService.emitChildEvent(item);
}
constructor(private favoritesService: FavoritesService) {}
}

View File

@@ -12,11 +12,11 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component, Input} from '@angular/core';
import {ChangeDetectionStrategy, Component, Input} from '@angular/core';
import {SCDish, SCPlace, SCThings} from '@openstapps/core';
import {PlaceMensaService} from '../../../data/types/place/special/mensa/place-mensa-service';
import {animate, style, transition, trigger} from '@angular/animations';
import moment from 'moment';
import {fadeAnimation} from '../../fade.animation';
/**
* Shows a section with meals of the chosen mensa
@@ -25,11 +25,8 @@ import moment from 'moment';
selector: 'stapps-mensa-section-content',
templateUrl: 'mensa-section-content.component.html',
styleUrls: ['mensa-section-content.component.scss'],
animations: [
trigger('fade', [
transition(':enter', [style({opacity: '0'}), animate('500ms ease', style({opacity: '1'}))]),
]),
],
animations: [fadeAnimation],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MensaSectionContentComponent {
/**

View File

@@ -30,7 +30,7 @@
<ion-item class="nothing-selected" lines="none">
<ion-label class="ion-text-wrap">
{{ 'dashboard.canteens.no_favorite_prefix' | translate }}
<a (click)="onSectionEdit()">{{ 'dashboard.canteens.no_favorite_link' | translate }}</a>
<a [routerLink]="['/canteen']">{{ 'dashboard.canteens.no_favorite_link' | translate }}</a>
{{ 'dashboard.canteens.no_favorite_suffix' | translate }}
</ion-label>
</ion-item>

View File

@@ -12,23 +12,11 @@
* 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 {ActivatedRoute, Router} from '@angular/router';
import {AlertController, AnimationController, ModalController} from '@ionic/angular';
import {combineLatest, Subscription} from 'rxjs';
import {debounceTime, distinctUntilChanged, startWith, take} from 'rxjs/operators';
import {NGXLogger} from 'ngx-logger';
import {SCThings} from '@openstapps/core';
import {DataProvider} from '../../../data/data.provider';
import {DataRoutingService} from '../../../data/data-routing.service';
import {FoodDataListComponent} from '../../../data/list/food-data-list.component';
import {PositionService} from '../../../map/position.service';
import {SettingsProvider} from '../../../settings/settings.provider';
import {ChangeDetectionStrategy, Component} from '@angular/core';
import {map} from 'rxjs/operators';
import {FavoritesService} from '../../../favorites/favorites.service';
import {ContextMenuService} from '../../../menu/context/context-menu.service';
import {ConfigProvider} from '../../../config/config.provider';
import {animate, style, transition, trigger} from '@angular/animations';
import {fadeAnimation} from '../../fade.animation';
import {isMensaThing} from '../../mensa-filters';
/**
* Shows a section with meals of the chosen mensa
@@ -37,107 +25,11 @@ import {animate, style, transition, trigger} from '@angular/animations';
selector: 'stapps-mensa-section',
templateUrl: 'mensa-section.component.html',
styleUrls: ['mensa-section.component.scss'],
animations: [
trigger('fade', [transition(':enter', [style({opacity: '0'}), animate(250, style({opacity: '1'}))])]),
],
animations: [fadeAnimation],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MensaSectionComponent extends FoodDataListComponent {
sub: Subscription;
export class MensaSectionComponent {
items = this.favoritesService.favoriteThings$.pipe(map(favorites => favorites.filter(isMensaThing)));
constructor(
protected readonly alertController: AlertController,
protected dataProvider: DataProvider,
protected readonly contextMenuService: ContextMenuService,
protected readonly settingsProvider: SettingsProvider,
protected readonly logger: NGXLogger,
protected dataRoutingService: DataRoutingService,
protected router: Router,
route: ActivatedRoute,
protected positionService: PositionService,
public modalController: ModalController,
protected favoritesService: FavoritesService,
configProvider: ConfigProvider,
animationController: AnimationController,
) {
super(
alertController,
dataProvider,
contextMenuService,
settingsProvider,
logger,
dataRoutingService,
router,
route,
positionService,
configProvider,
animationController,
);
}
async initialize() {
super.initialize();
this.subscriptions.push(
combineLatest([
this.queryTextChanged.pipe(
debounceTime(this.searchQueryDueTime),
distinctUntilChanged(),
startWith(this.queryText),
),
this.favoritesService.favoritesChanged$,
]).subscribe(async query => {
this.queryText = query[0];
this.from = 0;
if (typeof this.filterQuery !== 'undefined' || this.queryText?.length > 0 || this.showDefaultData) {
await this.fetchAndUpdateItems();
this.queryChanged.next();
}
}),
);
}
/**
* Fetches/updates the favorites (search page component's method override)
*/
async fetchAndUpdateItems() {
this.favoritesService
.search(this.queryText, this.filterQuery, this.sortQuery)
.pipe(take(1))
.subscribe(result => {
this.items = new Promise(resolve => {
resolve(result.data && result.data.filter(item => this.isMensaThing(item)));
});
});
}
/**
* Helper function as 'typeof' is not accessible in HTML
*
* @param item TODO
*/
isMensaThing(item: SCThings): boolean {
return (
this.hasCategories(item) &&
((item.categories as string[]).includes('canteen') ||
(item.categories as string[]).includes('cafe') ||
(item.categories as string[]).includes('student canteen') ||
(item.categories as string[]).includes('restaurant'))
);
}
/**
* TODO
*
* @param item TODO
*/
hasCategories(item: SCThings): item is SCThings & {categories: string[]} {
return typeof (item as {categories: string[]}).categories !== 'undefined';
}
/**
* Action when user clicked edit to this section
*/
onSectionEdit() {
void this.router.navigate(['/canteen']);
}
constructor(protected favoritesService: FavoritesService) {}
}

View File

@@ -13,12 +13,36 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
<<<<<<<< HEAD:frontend/app/src/app/modules/data/list/data-list-item.scss
:host {
display: block;
}
ion-item::part(native) {
height: 100%;
}
.ion-text-wrap ::ng-deep ion-label {
white-space: normal !important;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
}
ion-item {
--border-color: transparent;
@include border-radius-in-parallax(var(--border-radius-default));
overflow: hidden;
--inner-padding-end: 0;
--padding-start: var(--spacing-sm);
margin: var(--spacing-sm);
========
simple-swiper {
--swiper-slide-width: 256px;
}
>>>>>>>> app/develop:frontend/app/src/app/modules/dashboard/sections/news-section/news-section.component.scss
.more-news {
width: 128px;
font-size: var(--font-size-xl);
--color: var(--ion-color-medium-tint);
@@ -43,3 +67,43 @@ simple-swiper {
width: 200px;
}
}
: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

@@ -12,11 +12,9 @@
* 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 {ChangeDetectionStrategy, Component} from '@angular/core';
import {NewsProvider} from '../../../news/news.provider';
import {SCMessage} from '@openstapps/core';
import {animate, style, transition, trigger} from '@angular/animations';
import {Router} from '@angular/router';
import {fadeAnimation} from '../../fade.animation';
/**
* Shows a section with news
@@ -25,21 +23,14 @@ import {Router} from '@angular/router';
selector: 'stapps-news-section',
templateUrl: 'news-section.component.html',
styleUrls: ['news-section.component.scss'],
animations: [
trigger('fade', [
transition(':enter', [
style({opacity: '0', transform: 'translateX(100px)'}),
animate('250ms ease', style({opacity: '1', transform: 'translateX(0)'})),
]),
]),
],
animations: [fadeAnimation],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NewsSectionComponent {
news: Promise<SCMessage[]>;
news = this.newsProvider
.getCurrentFilters()
// eslint-disable-next-line unicorn/prefer-top-level-await,unicorn/consistent-function-scoping
.then(filters => this.newsProvider.getList(5, 0, filters));
constructor(readonly newsProvider: NewsProvider, readonly router: Router) {
this.news = this.newsProvider
.getCurrentFilters()
.then(filters => this.newsProvider.getList(5, 0, filters));
}
constructor(readonly newsProvider: NewsProvider) {}
}

View File

@@ -13,12 +13,24 @@
~ this program. If not, see <https://www.gnu.org/licenses/>.
-->
<<<<<<<< HEAD:frontend/app/src/app/modules/data/list/data-list-item-host-default.html
<h2>
{{ 'name' | thingTranslate: item }}
</h2>
<p *ngIf="item.description">
<stapps-long-inline-text
[text]="'description' | thingTranslate: item"
[size]="80"
></stapps-long-inline-text>
</p>
========
<stapps-section title="{{ 'dashboard.navigation.item.search' | translate }}">
<ion-searchbar
[routerLink]="'/search'"
[routerLink]="['/search']"
[routerAnimation]="routeTransition"
class="stapps-searchbar ion-activatable ripple-parent"
>
<ion-ripple-effect></ion-ripple-effect>
</ion-searchbar>
</stapps-section>
>>>>>>>> app/develop:frontend/app/src/app/modules/dashboard/sections/search-section/search-section.component.html

View File

@@ -13,10 +13,7 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component} from '@angular/core';
import {Router} from '@angular/router';
import {Capacitor} from '@capacitor/core';
import {Keyboard} from '@capacitor/keyboard';
import {AnimationBuilder, AnimationController} from '@ionic/angular';
import {AnimationController} from '@ionic/angular';
import {homePageSearchTransition} from './search-route-transition';
/**
@@ -28,29 +25,7 @@ import {homePageSearchTransition} from './search-route-transition';
styleUrls: ['search-section.component.scss'],
})
export class SearchSectionComponent {
searchTerm = '';
routeTransition = homePageSearchTransition(this.animationController);
routeTransition: AnimationBuilder;
constructor(private router: Router, private animationController: AnimationController) {
this.routeTransition = homePageSearchTransition(this.animationController);
}
/**
* User submits search
*/
onSubmitSearch() {
this.router
.navigate(['/search'], {queryParams: {query: this.searchTerm}})
.then(() => this.hideKeyboard());
}
/**
* Hides keyboard in native app environments
*/
hideKeyboard() {
if (Capacitor.isNativePlatform()) {
Keyboard.hide();
}
}
constructor(private animationController: AnimationController) {}
}

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.
@@ -26,14 +26,12 @@
<ion-label>{{ label | translate }}</ion-label>
<stapps-edit-modal #editModal (save)="selection.save()">
<ng-template>
<ion-content class="ion-padding modal-content">
<div>
<stapps-edit-event-selection
#selection
[items]="associatedDateSeries"
(modified)="editModal.pendingChanges = true"
></stapps-edit-event-selection>
</div>
<ion-content parallax [parallaxSize]="160" class="ion-padding modal-content">
<stapps-edit-event-selection
#selection
[items]="associatedDateSeries"
(modified)="editModal.pendingChanges = true"
></stapps-edit-event-selection>
</ion-content>
<ion-footer mode="ios">
<ion-toolbar color="light">

View File

@@ -12,8 +12,6 @@
* 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 'src/theme/common/ion-content-parallax';
:host {
display: block;
padding: var(--spacing-sm);
@@ -35,10 +33,7 @@
}
.modal-content {
--background: var(--ion-color-primary);
--color: var(--ion-color-primary-contrast);
@include ion-content-parallax($content-size: 160px);
}
ion-footer > ion-toolbar {

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.
@@ -38,20 +38,39 @@
*ngFor="let date of frequency.children"
(click)="modified.emit(); date.selected = !date.selected; frequency.notifyChildChanged()"
>
<ion-label *ngIf="date.item.dates.length > 1; else single_event" class="ion-text-wrap">
{{ date.item.duration | amDuration: 'hours' }}
{{ 'data.chips.add_events.popover.AT' | translate }}
{{ date.item.dates[0] | amDateFormat: 'HH:mm ddd' }}
{{ 'data.chips.add_events.popover.UNTIL' | translate }}
{{ date.item.dates[date.item.dates.length - 1] | amDateFormat: 'll' }}
<ion-label>
<ng-container *ngIf="date.item.dates.length > 1; else single_event">
<ion-text>
{{ date.item.dates[0] | amDateFormat: 'dddd, LT' }} -
{{ date.item.dates[0] | amAdd: date.item.duration | amDateFormat: 'LT' }}
</ion-text>
<br />
<ion-text>
{{ date.item.dates[0] | amDateFormat: 'LL' }} -
{{ date.item.dates[date.item.dates.length - 1] | amDateFormat: 'LL' }}
</ion-text>
</ng-container>
<ng-template #single_event>
<ion-text *ngIf="date.item.dates[0] as time; else noDates">
{{ time | amDateFormat: 'LL, LT' }} -
{{ time | amAdd: date.item.duration | amDateFormat: 'LT' }}
</ion-text>
<ng-template #noDates>
<ion-text color="danger">{{ 'data.chips.add_events.popover.DATA_ERROR' | translate }}</ion-text>
<br />
<ion-text *ngFor="let id of date.item.identifiers | keyvalue">
{{ id.key }}: {{ id.value }}
</ion-text>
</ng-template>
</ng-template>
<ng-container class="ion-align-items-center" *ngIf="date.item.inPlace">
<br />
<ion-text color="medium" class="place">
<ion-icon name="pin_drop"></ion-icon>
<span> {{ 'inPlace.name' | thingTranslate: date.item }}</span>
</ion-text>
</ng-container>
</ion-label>
<ng-template #single_event>
<ion-label class="ion-text-wrap">
{{ date.item.duration | amDuration: 'hours' }}
{{ 'data.chips.add_events.popover.AT' | translate }}
{{ date.item.dates[date.item.dates.length - 1] | amDateFormat: 'll, HH:mm' }}
</ion-label>
</ng-template>
<ion-checkbox slot="end" [checked]="date.selected"> </ion-checkbox>
</ion-item>
</ion-list>

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.
@@ -42,3 +42,8 @@ ion-item-divider.ios > ion-checkbox {
ion-list.md {
padding-top: 0;
}
.place {
display: inline-flex;
align-items: center;
}

View File

@@ -95,10 +95,11 @@ import {BookDetailContentComponent} from './types/book/book-detail-content.compo
import {BookListItemComponent} from './types/book/book-list-item.component';
import {PeriodicalListItemComponent} from './types/periodical/periodical-list-item.component';
import {PeriodicalDetailContentComponent} from './types/periodical/periodical-detail-content.component';
import {SCThingListItemVirtualScrollStrategyDirective} from './list/sc-thing-list-item-virtual-scroll-strategy.directive';
import {DataListItemHostDirective} from './list/data-list-item-host.directive';
import {DataListItemHostDefaultComponent} from './list/data-list-item-host-default.component';
import {browserFactory, SimpleBrowser} from '../../util/browser.factory';
import {DishCharacteristicsComponent} from './types/dish/dish-characteristics.component';
import {SkeletonListComponent} from './list/skeleton-list.component';
/**
* Module for handling data
@@ -111,6 +112,7 @@ import {browserFactory, SimpleBrowser} from '../../util/browser.factory';
AddressDetailComponent,
CatalogDetailContentComponent,
CatalogListItemComponent,
DishCharacteristicsComponent,
DataDetailComponent,
DataDetailContentComponent,
DataIconPipe,
@@ -126,6 +128,7 @@ import {browserFactory, SimpleBrowser} from '../../util/browser.factory';
EventListItemComponent,
FavoriteButtonComponent,
FavoriteDetailContentComponent,
SkeletonListComponent,
FavoriteListItemComponent,
FoodDataListComponent,
LocateActionChipComponent,
@@ -145,7 +148,6 @@ import {browserFactory, SimpleBrowser} from '../../util/browser.factory';
PlaceListItemComponent,
PlaceMensaDetailComponent,
SearchPageComponent,
SCThingListItemVirtualScrollStrategyDirective,
SemesterDetailContentComponent,
SemesterListItemComponent,
DataListItemHostDirective,
@@ -207,7 +209,6 @@ import {browserFactory, SimpleBrowser} from '../../util/browser.factory';
},
],
exports: [
SCThingListItemVirtualScrollStrategyDirective,
DataDetailComponent,
DataDetailContentComponent,
DataIconPipe,

View File

@@ -38,7 +38,7 @@ stapps-origin-detail {
position: relative;
margin-block-start: calc((var(--header-spacing-bottom) - var(--spacing-xl)) * -1);
margin-block-end: var(--spacing-xl);
background-color: var(--ion-color-primary-contrast);
background-color: var(--ion-card-background);
@include content-padding();
& > ion-thumbnail {
@@ -73,7 +73,6 @@ stapps-origin-detail {
padding-bottom: 8px;
}
ion-card-header {
color: var(--ion-color-dark);
padding-top: 8px;
padding-bottom: 4px;
font-weight: bold;

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.
@@ -30,8 +30,8 @@
</ion-toolbar>
</ion-header>
<ng-content select="[header]"></ng-content>
<ion-content class="ion-no-padding ion-content-parallax">
<div [ngSwitch]="true">
<ion-content parallax class="ion-no-padding">
<ng-container [ngSwitch]="true">
<ng-container *ngSwitchCase="!item && (isDisconnected | async)">
<div class="centeredMessageContainer">
<ion-icon name="signal_disconnected"></ion-icon>
@@ -59,5 +59,5 @@
[contentTemplateRef]="contentTemplateRef"
></stapps-data-detail-content>
</ng-container>
</div>
</ng-container>
</ion-content>

View File

@@ -12,10 +12,21 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
.crumb-label {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
cursor: pointer;
}
ion-breadcrumb {
color: var(--ion-color-primary-contrast);
&::part(separator) {
color: var(--ion-color-primary-contrast);
}
&::part(collapsed-indicator) {
background: var(--ion-color-primary-contrast);
}
}

View File

@@ -1,18 +1,29 @@
<!--
~ 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/>.
-->
<<<<<<<< HEAD:frontend/app/src/app/modules/dashboard/sections/search-section/search-section.component.html
<stapps-section title="{{ 'dashboard.navigation.item.search' | translate }}">
<ion-searchbar
[routerLink]="'/search'"
[routerAnimation]="routeTransition"
class="stapps-searchbar ion-activatable ripple-parent"
>
<ion-ripple-effect></ion-ripple-effect>
</ion-searchbar>
</stapps-section>
========
<ion-button (click)="toggle($event)" color="medium" size="small" fill="clear">
<ion-icon
slot="icon-only"
@@ -21,3 +32,4 @@
name="grade"
></ion-icon>
</ion-button>
>>>>>>>> app/develop:frontend/app/src/app/modules/data/elements/favorite-button.component.html

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.
@@ -27,6 +27,7 @@
}
.selected {
// TODO
color: #fbc02d;
}
}

View File

@@ -14,17 +14,17 @@
-->
<div>
<ion-text *ngIf="price && !soldOut">
<ion-text *ngIf="price && !soldOut" style="white-space: nowrap">
<h2>
{{ price | currency: 'EUR':'symbol':undefined:'de' }}
</h2>
</ion-text>
<ion-text *ngIf="soldOut" color="danger">
<ion-text *ngIf="soldOut" color="danger" class="sold-out" style="white-space: nowrap">
<h2>
{{ 'data.detail.offers.sold_out' | translate }}
</h2>
</ion-text>
<p *ngIf="_offers[0].inPlace && !soldOut" class="place">
<p *ngIf="_offers[0].inPlace && !soldOut" class="place" style="white-space: nowrap">
<ion-icon name="pin_drop"></ion-icon>{{ _offers[0].inPlace.name
}}<span *ngIf="_offers.length > 1">...</span>
</p>

View File

@@ -38,7 +38,6 @@
<ion-button
expand="full"
fill="clear"
color="light"
*ngIf="item.description && buttonShown"
(click)="toggleDescriptionAccordion()"
>

View File

@@ -39,10 +39,14 @@ ion-card {
padding: 0 0 var(--header-spacing-bottom);
.description * {
color: var(--ion-color-light);
color: var(--ion-color-primary-contrast);
}
.openingHours {
color: var(--ion-color-light);
color: var(--ion-color-primary-contrast);
}
}
ion-button {
--color: var(--ion-color-primary-contrast);
}
}

View File

@@ -13,6 +13,19 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
<<<<<<<< HEAD:frontend/app/src/app/util/simple-swiper.component.ts
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>;
========
import {Component, Input} from '@angular/core';
import {SCThings} from '@openstapps/core';
@@ -22,4 +35,5 @@ import {SCThings} from '@openstapps/core';
})
export class DataListItemHostDefaultComponent {
@Input() item: SCThings;
>>>>>>>> app/develop:frontend/app/src/app/modules/data/list/data-list-item-host-default.component.ts
}

View File

@@ -13,7 +13,6 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
@import 'src/theme/util/_mixins.scss';
@import 'src/theme/common/_helper.scss';
:host {
display: block;
@@ -44,17 +43,13 @@ ion-item {
ion-label {
width: 100%;
margin-right: 0;
div {
display: flex;
flex-direction: column;
}
}
::ng-deep {
ion-note {
@extend %horizontal-list;
}
}
}
:host.square ::ng-deep {

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,7 +12,6 @@
* 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 {CdkVirtualScrollViewport} from '@angular/cdk/scrolling';
import {
Component,
ContentChild,
@@ -29,6 +28,7 @@ import {
} from '@angular/core';
import {SCThings} from '@openstapps/core';
import {BehaviorSubject, Observable, Subscription} from 'rxjs';
import {IonInfiniteScroll} from '@ionic/angular';
export interface DataListContext<T> {
$implicit: T;
@@ -43,11 +43,6 @@ export interface DataListContext<T> {
styleUrls: ['data-list.scss'],
})
export class DataListComponent implements OnChanges, OnInit, OnDestroy {
/**
* Amount of list items left to show (in percent) that should trigger a data reload
*/
private readonly reloadThreshold = 0.2;
/**
* All SCThings to display
*/
@@ -86,7 +81,7 @@ export class DataListComponent implements OnChanges, OnInit, OnDestroy {
*/
subscriptions: Subscription[] = [];
@ViewChild(CdkVirtualScrollViewport) viewPort: CdkVirtualScrollViewport;
@ViewChild(IonInfiniteScroll) infiniteScroll: IonInfiniteScroll;
/**
* Signalizes that the data is being loaded
@@ -113,6 +108,7 @@ export class DataListComponent implements OnChanges, OnInit, OnDestroy {
ngOnChanges(changes: SimpleChanges): void {
if (Array.isArray(this.items) && typeof changes.items !== 'undefined') {
this.itemStream.next(this.items);
this.infiniteScroll.complete();
}
}
@@ -127,7 +123,7 @@ export class DataListComponent implements OnChanges, OnInit, OnDestroy {
if (typeof this.resetToTop !== 'undefined') {
this.subscriptions.push(
this.resetToTop.subscribe(() => {
this.viewPort.scrollToIndex(0);
// this.viewPort.scrollToIndex(0);
}),
);
}
@@ -139,18 +135,4 @@ export class DataListComponent implements OnChanges, OnInit, OnDestroy {
notifyLoadMore() {
this.loadMore.emit();
}
/**
* Function to call whenever scroll view visible range changed
*/
scrolled(index: number) {
if (
// first condition prevents "load more" to be executed even before scrolling
index > 0 &&
(this.items?.length ?? 0) - this.viewPort.getRenderedRange().end <=
(this.items?.length ?? 0) * this.reloadThreshold
) {
this.notifyLoadMore();
}
}
}

View File

@@ -15,29 +15,23 @@
<ng-container *ngIf="itemStream | async as items">
<ng-content select="[header]"></ng-content>
<cdk-virtual-scroll-viewport
scThingListItemVirtualScrollStrategy
[style.display]="items && items.length ? 'block' : 'none'"
(loadMore)="notifyLoadMore()"
>
<ng-container *cdkVirtualFor="let item of items; trackBy: identifyItem">
<ion-list [style.display]="items && items.length ? 'block' : 'none'">
<ng-container *ngFor="let item of items">
<ng-container
*ngTemplateOutlet="listItemTemplateRef || defaultListItem; context: {$implicit: item}"
></ng-container>
</ng-container>
</cdk-virtual-scroll-viewport>
<ion-infinite-scroll (ionInfinite)="notifyLoadMore()">
<ion-infinite-scroll-content></ion-infinite-scroll-content>
</ion-infinite-scroll>
</ion-list>
</ng-container>
<div [style.display]="!loading && items && items.length === 0 ? 'block' : 'none'">
<ion-label class="centeredMessageContainer">
{{ 'search.nothing_found' | translate | titlecase }}
</ion-label>
</div>
<ion-list [style.display]="loading ? 'block' : 'none'">
<stapps-skeleton-list-item
[hideThumbnail]="singleType"
*ngFor="let skeleton of [].constructor(skeletonItems)"
></stapps-skeleton-list-item>
</ion-list>
<skeleton-list [style.display]="loading ? 'block' : 'none'"></skeleton-list>
<ng-template let-item #defaultListItem>
<stapps-data-list-item [item]="item" [hideThumbnail]="singleType"></stapps-data-list-item>

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,18 +12,14 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
<<<<<<<< HEAD:frontend/app/src/app/modules/settings/item/settings-item.scss
========
cdk-virtual-scroll-viewport {
ion-list {
background: none;
}
skeleton-list {
height: 100%;
width: 100%;
}
::ng-deep {
.cdk-virtual-scroll-content-wrapper {
width: 100%;
}
}
.virtual-scroll-expander {
clear: both;
}
>>>>>>>> app/develop:frontend/app/src/app/modules/data/list/data-list.scss

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,10 +12,11 @@
* 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 {Component, OnDestroy, OnInit} from '@angular/core';
import {MapPosition} from '../../map/position.service';
import {SearchPageComponent} from './search-page.component';
import {Geolocation} from '@capacitor/geolocation';
import {Subscription} from 'rxjs';
/**
* Presents a list of places for eating/drinking
@@ -24,15 +25,19 @@ import {Geolocation} from '@capacitor/geolocation';
templateUrl: 'search-page.html',
styleUrls: ['../../data/list/search-page.scss'],
})
export class FoodDataListComponent extends SearchPageComponent {
export class FoodDataListComponent extends SearchPageComponent implements OnInit, OnDestroy {
title = 'canteens.title';
showNavigation = false;
locationWatch?: Subscription;
/**
* Sets the forced filter to present only places for eating/drinking
*/
initialize() {
ngOnInit() {
this.locationWatch?.unsubscribe();
this.locationWatch = this.createLocationWatch();
this.showDefaultData = true;
this.sortQuery = [
@@ -92,29 +97,36 @@ export class FoodDataListComponent extends SearchPageComponent {
},
];
}
super.ngOnInit();
}
private createLocationWatch(): Subscription {
return this.positionService
.watchCurrentLocation(this.constructor.name, {enableHighAccuracy: false, maximumAge: 1000})
.subscribe({
next: (position: MapPosition) => {
this.positionService.position = position;
},
error: async _error => {
this.positionService.position = undefined;
await Geolocation.checkPermissions();
},
});
}
async ionViewWillEnter() {
await super.ionViewWillEnter();
this.subscriptions.push(
this.positionService
.watchCurrentLocation(this.constructor.name, {enableHighAccuracy: false, maximumAge: 1000})
.subscribe({
next: (position: MapPosition) => {
this.positionService.position = position;
},
error: async _error => {
this.positionService.position = undefined;
await Geolocation.checkPermissions();
},
}),
);
this.locationWatch?.unsubscribe();
this.locationWatch = this.createLocationWatch();
}
ionViewWillLeave() {
void this.positionService.clearWatcher(this.constructor.name);
for (const sub of this.subscriptions) {
sub.unsubscribe();
}
this.locationWatch?.unsubscribe();
}
ngOnDestroy() {
super.ngOnDestroy();
this.locationWatch?.unsubscribe();
}
}

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,12 +28,12 @@
(search)="hideKeyboard()"
[(ngModel)]="queryText"
showClearButton="always"
placeholder="{{ 'search.search_bar.placeholder' | translate }}"
placeholder="{{ placeholder | translate }}"
mode="md"
type="search"
enterkeyhint="search"
class="filterable"
autofocus
[autofocus]="!showDefaultData"
>
<ion-menu-button menu="context" auto-hide="false">
<ion-icon name="tune"></ion-icon>
@@ -56,9 +56,12 @@
</ion-header>
<ion-content class="content">
<div [style.display]="!showDefaultData && !items && !loading ? 'block' : 'none'">
<div
[class.no-results]="!showDefaultData && !items && !loading"
[style.display]="!showDefaultData && !items && !loading ? 'block' : 'none'"
>
<ion-label class="centeredMessageContainer">
{{ 'search.instruction' | translate }}
{{ searchInstruction | translate }}
</ion-label>
</div>
<stapps-data-list

View File

@@ -37,7 +37,7 @@ ion-toolbar:first-of-type {
}
ion-content {
--background: var(--ion-color-light);
--background: var(--ion-background-color);
}
.content > div {

View File

@@ -0,0 +1,33 @@
/*
* 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, Input} from '@angular/core';
import {SCThings} from '@openstapps/core';
@Component({
<<<<<<<< HEAD:frontend/app/src/app/modules/data/list/data-list-item-host-default.component.ts
selector: 'data-list-item-host-default',
templateUrl: 'data-list-item-host-default.html',
})
export class DataListItemHostDefaultComponent {
@Input() item: SCThings;
}
========
selector: 'skeleton-list',
templateUrl: 'skeleton-list.html',
styleUrls: ['skeleton-list.scss'],
})
export class SkeletonListComponent {}
>>>>>>>> app/develop:frontend/app/src/app/modules/data/list/skeleton-list.component.ts

View File

@@ -0,0 +1,43 @@
<!--
~ 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/>.
-->
<svg xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="list-item" width="100%" height="75" patternUnits="userSpaceOnUse">
<rect width="100%" height="67" class="item"></rect>
<mask id="label-mask">
<rect rx="8" x="24" y="24" width="36" height="36" fill="white"></rect>
<rect rx="6" x="72" y="24" width="180" height="12" fill="white"></rect>
<rect rx="6" x="72" y="48" width="92" height="12" fill="white"></rect>
</mask>
<g mask="url(#label-mask)">
<rect class="label" width="100%" height="100%"></rect>
<rect fill="white" width="16" height="100%" style="filter: blur(8px)">
<animateTransform
attributeName="transform"
attributeType="XML"
type="translate"
from="0 0"
to="1000 0"
dur="2s"
repeatCount="indefinite"
></animateTransform>
</rect>
</g>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#list-item)"></rect>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,34 @@
/*!
* 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/>.
*/
svg {
width: 100%;
height: 100%;
}
$item-height: 92;
$gap: 4;
.label {
fill: var(--ion-color-medium);
opacity: 0.1;
}
.item {
rx: var(--border-radius-default);
fill: var(--ion-item-background, var(--ion-background-color, #fff));
x: var(--spacing-sm);
y: var(--spacing-sm);
width: calc(100% - var(--spacing-sm) * 2);
}

View File

@@ -0,0 +1,25 @@
/*
* 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, Input} from '@angular/core';
import {SCDish} from '@openstapps/core';
@Component({
selector: 'stapps-dish-characteristics',
templateUrl: 'dish-characteristics.html',
styleUrls: ['dish-characteristics.scss'],
})
export class DishCharacteristicsComponent {
@Input() item: SCDish;
}

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,17 @@
~ 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>
<ion-note>
<ng-container *ngIf="item.characteristics">
<ng-container *ngFor="let characteristic of 'characteristics' | thingTranslate: item">
<!-- Abbr tag shows the actual name on hover -->
<abbr
[style.--background-url]="'url(' + characteristic.image + ')'"
[title]="characteristic.name | titlecase"
></abbr>
</ng-container>
</ng-container>
<ion-label>
{{ 'categories' | thingTranslate: item | join: ', ' | titlecase }}
</ion-label>
</ion-note>

View File

@@ -0,0 +1,38 @@
/*!
* 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/>.
*/
ion-note {
list-style: none;
display: flex;
flex-direction: row-reverse;
justify-content: start;
}
abbr {
width: 16px;
aspect-ratio: 1;
background: var(--ion-color-medium);
mask-image: var(--background-url);
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
mask-mode: alpha;
+ ion-label::after {
content: '';
margin-inline: var(--spacing-xs);
}
}

View File

@@ -13,31 +13,7 @@
~ this program. If not, see <https://www.gnu.org/licenses/>.
-->
<ion-grid>
<ion-row>
<ion-col>
<ion-card *ngIf="item.categories">
<ion-card-header>
{{ 'categories' | thingTranslate: item | join: ', ' | titlecase }}
</ion-card-header>
</ion-card>
</ion-col>
<ion-col>
<ion-card *ngIf="item.characteristics">
<ion-card-header class="no-padding-inline-start vertical-list">
<ul>
<li>
<ng-container *ngFor="let characteristic of 'characteristics' | thingTranslate: item">
<img [src]="characteristic.image" [alt]="characteristic.name | titlecase" />
</ng-container>
</li>
</ul>
</ion-card-header>
</ion-card>
</ion-col>
</ion-row>
</ion-grid>
<stapps-dish-characteristics *ngIf="item.characteristics" [item]="item"></stapps-dish-characteristics>
<stapps-offers-detail *ngIf="item.offers" [offers]="item.offers"></stapps-offers-detail>
<!-- unwanted by swffm
<ion-card *ngIf="item.nutrition">
@@ -92,7 +68,7 @@
-->
<stapps-simple-card
*ngIf="item.additives"
[title]="'additives' | propertyNameTranslate: item"
[title]="$any('additives' | propertyNameTranslate: item) | titlecase"
[content]="'additives' | thingTranslate: item | join: ', '"
>
</stapps-simple-card>

View File

@@ -12,10 +12,7 @@
* 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 'src/theme/common/_helper.scss';
.vertical-list {
ul li img {
filter: unset;
}
stapps-dish-characteristics {
margin: var(--spacing-lg);
margin-block-end: var(--spacing-sm);
}

View File

@@ -14,28 +14,17 @@
-->
<ion-grid>
<ion-row>
<ion-col>
<ion-row class="ion-justify-content-between">
<ion-col size="11" size-sm="10">
<div class="ion-text-wrap">
<ion-label class="title">{{ 'name' | thingTranslate: item }}</ion-label>
<p class="title-sub ion-hide-sm-down">
{{ 'description' | thingTranslate: item }}
</p>
<ion-note>
<ul>
<li>
{{ 'categories' | thingTranslate: item | join: ', ' | titlecase }}
</li>
<li *ngIf="item.characteristics">
<ng-container *ngFor="let characteristic of 'characteristics' | thingTranslate: item">
<img [src]="characteristic.image" [alt]="characteristic.name | titlecase" />
</ng-container>
</li>
</ul>
</ion-note>
<stapps-dish-characteristics [item]="item"></stapps-dish-characteristics>
</div>
</ion-col>
<ion-col width-10 text-right>
<ion-col>
<div class="ion-text-end">
<stapps-offers-in-list *ngIf="item.offers" [offers]="item.offers"></stapps-offers-in-list>
</div>

View File

@@ -24,9 +24,9 @@
<p *ngIf="item.academicTerms" class="title-sub">
{{ 'name' | thingTranslate: item.academicTerms[0] }}
</p>
<ion-note *ngIf="!item.categories">{{ 'type' | thingTranslate: item }}</ion-note>
<ion-note *ngIf="!item.categories">{{ 'type' | thingTranslate: item | titlecase }}</ion-note>
<ion-note *ngIf="item.categories">
{{ 'categories' | thingTranslate: item | join: ', ' }}
{{ 'categories' | thingTranslate: item | join: ', ' | titlecase }}
</ion-note>
</div>
</ion-col>

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.
@@ -40,9 +40,8 @@
</div>
<ng-template #news>
<ion-thumbnail>
<ion-thumbnail *ngIf="item.image" style="background-image: url('{{ item.image }}')">
<ion-img
*ngIf="item.image"
src="{{ item.image }}"
(ionError)="$event.target.nextSibling.style.display = 'none'"
alt="{{ item.name }}"

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 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,17 +15,24 @@
:host {
ion-thumbnail {
position: relative;
width: 100%;
height: auto;
img {
display: block;
}
}
// Show smaller image on a desktop
@media (min-width: 992px) {
ion-thumbnail {
width: 60%;
margin: 0 auto;
margin-inline: auto;
overflow: hidden;
background-size: contain;
background-position: center;
transform: scaleX(-1);
ion-img {
backdrop-filter: blur(32px);
&::part(image) {
transform: scaleX(-1);
max-width: 16cm;
object-fit: contain;
margin-inline: auto;
}
}
}

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,10 +12,13 @@
* 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 {Component, Input, OnDestroy, OnInit} from '@angular/core';
import {SCBuilding, SCFloor, SCPointOfInterest, SCRoom, SCThings} from '@openstapps/core';
import {DataProvider} from '../../data.provider';
import {hasValidLocation, isSCFloor} from './place-types';
import {DataRoutingService} from '../../data-routing.service';
import {Router} from '@angular/router';
import {Subscription} from 'rxjs';
/**
* TODO
@@ -26,7 +29,7 @@ import {hasValidLocation, isSCFloor} from './place-types';
selector: 'stapps-place-detail-content',
templateUrl: 'place-detail-content.html',
})
export class PlaceDetailContentComponent implements OnInit {
export class PlaceDetailContentComponent implements OnInit, OnDestroy {
/**
* TODO
*/
@@ -39,6 +42,8 @@ export class PlaceDetailContentComponent implements OnInit {
*/
hasValidLocation = false;
itemRouting: Subscription;
/**
* TODO
*
@@ -63,7 +68,17 @@ export class PlaceDetailContentComponent implements OnInit {
);
}
constructor(dataRoutingService: DataRoutingService, router: Router) {
this.itemRouting = dataRoutingService.itemSelectListener().subscribe(item => {
void router.navigate(['/data-detail', item.uid]);
});
}
ngOnInit() {
this.hasValidLocation = !isSCFloor(this.item) && hasValidLocation(this.item);
}
ngOnDestroy() {
this.itemRouting.unsubscribe();
}
}

View File

@@ -35,8 +35,7 @@
{{ 'inPlace' | propertyNameTranslate: item | titlecase }}
</ion-card-header>
<ion-card-content>
<ion-icon name="pin_drop"></ion-icon>
<a [routerLink]="['/data-detail', item.inPlace.uid]">{{ 'name' | thingTranslate: item.inPlace }}</a>
<stapps-data-list-item [item]="item.inPlace"></stapps-data-list-item>
</ion-card-content>
</ion-card>
</ng-container>

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.
@@ -14,7 +14,7 @@
*/
import {Component, Input} from '@angular/core';
import {PositionService} from '../../../map/position.service';
import {Subscription, interval} from 'rxjs';
import {interval, Subscription} from 'rxjs';
import {hasValidLocation, isSCFloor, PlaceTypes, PlaceTypesWithDistance} from './place-types';
/**
@@ -23,6 +23,7 @@ import {hasValidLocation, isSCFloor, PlaceTypes, PlaceTypesWithDistance} from '.
@Component({
selector: 'stapps-place-list-item',
templateUrl: 'place-list-item.html',
styleUrls: ['place-list-item.scss'],
})
export class PlaceListItemComponent {
/**

View File

@@ -26,28 +26,24 @@
</p>
<p>
<ion-note *ngIf="item.categories && item.type !== 'building'; else onlyType">
<ul>
<li>
{{ 'categories' | thingTranslate: item | join: ', ' | titlecase }}
</li>
<li *ngIf="distance">
<ion-icon name="directions_walk"></ion-icon>
{{ distance | metersLocalized }}
</li>
</ul>
<ion-label>
{{ 'categories' | thingTranslate: item | join: ', ' | titlecase }}
</ion-label>
<ion-label *ngIf="distance" class="distance">
<ion-icon name="directions_walk"></ion-icon>
{{ distance | metersLocalized }}
</ion-label>
</ion-note>
</p>
<ng-template #onlyType>
<ion-note>
<ul>
<li>
{{ 'type' | thingTranslate: item }}
</li>
<li *ngIf="distance">
<ion-icon name="directions_walk"></ion-icon>
{{ distance | metersLocalized }}
</li>
</ul>
<ion-label>
{{ 'type' | thingTranslate: item | titlecase }}
</ion-label>
<ion-label *ngIf="distance" class="distance">
<ion-icon name="directions_walk"></ion-icon>
{{ distance | metersLocalized }}
</ion-label>
</ion-note>
</ng-template>
</ng-container>
@@ -56,10 +52,11 @@
</p>
</div>
</ion-col>
<div *ngIf="item.type !== 'building'">
<ion-col width-20 text-right *ngIf="item.inPlace">
<ion-icon name="pin_drop"></ion-icon>{{ 'name' | thingTranslate: item.inPlace }}
<ng-container *ngIf="item.type !== 'building'">
<ion-col size="auto" class="in-place" *ngIf="item.inPlace">
<ion-icon name="pin_drop"></ion-icon
><ion-label>{{ 'name' | thingTranslate: item.inPlace }}</ion-label>
</ion-col>
</div>
</ng-container>
</ion-row>
</ion-grid>

View File

@@ -0,0 +1,36 @@
/*!
* 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/>.
*/
ion-note {
display: flex;
> ion-label {
display: inline-flex;
justify-content: center;
align-items: center;
}
}
ion-label + ion-label.distance::before {
content: '';
margin-inline: var(--spacing-xs);
}
.in-place {
display: flex;
justify-content: end;
align-items: center;
}

View File

@@ -51,6 +51,8 @@ export class FavoritesService {
// using debounce time 0 allows change detection to run through async suspension
favoritesChanged$ = this.favorites.pipe(debounceTime(0));
favoriteThings$ = this.favoritesChanged$.pipe(map(favorite => [...favorite.values()].map(it => it.data)));
static getDataFromFavorites(items: SCFavorite[]) {
return items.map(item => item.data);
}

View File

@@ -21,107 +21,105 @@
<ion-title>{{ 'feedback.page.TITLE' | translate }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-content-parallax">
<div>
<div class="feedback-content">
<ion-card>
<form #feedbackForm="ngForm" (ngSubmit)="onSubmit()">
<ion-item>
<ion-label position="stacked">{{ 'feedback.form.name.label' | translate }}</ion-label>
<ion-input
placeholder="{{ 'feedback.form.name.placeholder' | translate }}"
[(ngModel)]="author.name"
name="name"
></ion-input>
</ion-item>
<ion-item>
<ion-label position="stacked">{{ 'feedback.form.type.label' | translate }}</ion-label>
<ion-select
[(ngModel)]="message.name"
value="comment"
name="title"
interface="popover"
required="true"
>
<ion-select-option value="Comment">{{
'feedback.form.type.values.comment' | translate
}}</ion-select-option>
<ion-select-option value="Bug">{{
'feedback.form.type.values.bug' | translate
}}</ion-select-option>
</ion-select>
</ion-item>
<ion-item>
<ion-label position="stacked">{{ 'feedback.form.email.label' | translate }}</ion-label>
<ion-input
placeholder="{{ 'feedback.form.email.placeholder' | translate }}"
[(ngModel)]="author.email"
type="email"
name="email"
ngModel
email
></ion-input>
</ion-item>
<ion-item>
<ion-label position="stacked">{{ 'feedback.form.message.label' | translate }}</ion-label>
<ion-textarea
[(ngModel)]="message.messageBody"
placeholder="{{
'feedback.form.message.placeholder' | translate: {number: MINIMUM_MESSAGE_SIZE}
}}"
name="message"
required="true"
minlength="{{ MINIMUM_MESSAGE_SIZE }}"
autoGrow="true"
></ion-textarea>
</ion-item>
<ion-item>
<ion-label class="ion-text-wrap">{{ 'feedback.form.termsAgree.0' | translate }} </ion-label>
<ion-checkbox
color="primary"
slot="start"
[(ngModel)]="termsAgree"
name="termsAgree"
></ion-checkbox>
</ion-item>
<ion-item lines="none">
<ion-label
><a style="display: contents" [routerLink]="['/about/privacy']">{{
'feedback.form.termsAgree.1' | translate
}}</a></ion-label
>
</ion-item>
<ion-item>
<ion-label class="ion-text-wrap">{{ 'feedback.form.protocolDataAgree' | translate }}</ion-label>
<ion-checkbox
color="primary"
slot="start"
[(ngModel)]="protocolDataAgree"
name="protocolDataAgree"
></ion-checkbox>
</ion-item>
<ion-card>
<ion-card-title>
<ion-button expand="block" fill="clear" (click)="toggleShowMetaData()">
<ng-container *ngIf="!showMetaData; else hide">{{
'feedback.form.protocolData.show' | translate
}}</ng-container>
<ng-template #hide>{{ 'feedback.form.protocolData.hide' | translate }}</ng-template>
</ion-button>
</ion-card-title>
<ion-card-content *ngIf="metaData && showMetaData">
<pre>{{ metaData | json }}</pre>
</ion-card-content>
</ion-card>
<ion-button
type="submit"
color="primary"
expand="block"
[disabled]="!feedbackForm.valid || !termsAgree || submitSuccess"
>{{ 'feedback.form.submit' | translate }}</ion-button
<ion-content parallax>
<div class="feedback-content">
<ion-card>
<form #feedbackForm="ngForm" (ngSubmit)="onSubmit()">
<ion-item>
<ion-label position="stacked">{{ 'feedback.form.name.label' | translate }}</ion-label>
<ion-input
placeholder="{{ 'feedback.form.name.placeholder' | translate }}"
[(ngModel)]="author.name"
name="name"
></ion-input>
</ion-item>
<ion-item>
<ion-label position="stacked">{{ 'feedback.form.type.label' | translate }}</ion-label>
<ion-select
[(ngModel)]="message.name"
value="comment"
name="title"
interface="popover"
required="true"
>
</form>
</ion-card>
</div>
<ion-select-option value="Comment">{{
'feedback.form.type.values.comment' | translate
}}</ion-select-option>
<ion-select-option value="Bug">{{
'feedback.form.type.values.bug' | translate
}}</ion-select-option>
</ion-select>
</ion-item>
<ion-item>
<ion-label position="stacked">{{ 'feedback.form.email.label' | translate }}</ion-label>
<ion-input
placeholder="{{ 'feedback.form.email.placeholder' | translate }}"
[(ngModel)]="author.email"
type="email"
name="email"
ngModel
email
></ion-input>
</ion-item>
<ion-item>
<ion-label position="stacked">{{ 'feedback.form.message.label' | translate }}</ion-label>
<ion-textarea
[(ngModel)]="message.messageBody"
placeholder="{{
'feedback.form.message.placeholder' | translate: {number: MINIMUM_MESSAGE_SIZE}
}}"
name="message"
required="true"
minlength="{{ MINIMUM_MESSAGE_SIZE }}"
autoGrow="true"
></ion-textarea>
</ion-item>
<ion-item>
<ion-label class="ion-text-wrap">{{ 'feedback.form.termsAgree.0' | translate }} </ion-label>
<ion-checkbox
color="primary"
slot="start"
[(ngModel)]="termsAgree"
name="termsAgree"
></ion-checkbox>
</ion-item>
<ion-item lines="none">
<ion-label
><a style="display: contents" [routerLink]="['/about/privacy']">{{
'feedback.form.termsAgree.1' | translate
}}</a></ion-label
>
</ion-item>
<ion-item>
<ion-label class="ion-text-wrap">{{ 'feedback.form.protocolDataAgree' | translate }}</ion-label>
<ion-checkbox
color="primary"
slot="start"
[(ngModel)]="protocolDataAgree"
name="protocolDataAgree"
></ion-checkbox>
</ion-item>
<ion-card>
<ion-card-title>
<ion-button expand="block" fill="clear" (click)="toggleShowMetaData()">
<ng-container *ngIf="!showMetaData; else hide">{{
'feedback.form.protocolData.show' | translate
}}</ng-container>
<ng-template #hide>{{ 'feedback.form.protocolData.hide' | translate }}</ng-template>
</ion-button>
</ion-card-title>
<ion-card-content *ngIf="metaData && showMetaData">
<pre>{{ metaData | json }}</pre>
</ion-card-content>
</ion-card>
<ion-button
type="submit"
color="primary"
expand="block"
[disabled]="!feedbackForm.valid || !termsAgree || submitSuccess"
>{{ 'feedback.form.submit' | translate }}</ion-button
>
</form>
</ion-card>
</div>
</ion-content>

View File

@@ -57,7 +57,7 @@ ion-button {
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
background: var(--ion-color-light);
background: var(--ion-item-background);
@include border-radius-in-parallax(var(--border-radius-default));
& > * {
@@ -70,7 +70,6 @@ ion-button {
@include border-radius-in-parallax(var(--border-radius-default));
overflow: hidden;
position: relative;
background-color: var(--ion-color-primary-contrast);
margin: 0;
& > ion-thumbnail {

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.
@@ -87,18 +87,10 @@ div.map-buttons {
ion-button {
margin: 4px;
// important for iOS
// TODO: find an option that is better suited for the iOS theme
--box-shadow: var(--map-box-shadow);
align-self: flex-end;
}
ion-button::part(native) {
background: white;
}
ion-button::part(native):hover,
ion-button::part(native):focus {
background: whitesmoke;
}
}
div.map-buttons.above {

View File

@@ -1,3 +1,18 @@
/*!
* 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/>.
*/
div.map-container {
height: 100%;
width: 100%;

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.
@@ -159,6 +159,11 @@ describe('ContextMenuComponent', async () => {
{
field: 'type',
buckets: [{count: 10, key: 'date series', checked: true}],
info: {
onlyOnType: SCThingType.AcademicEvent,
field: 'date series',
sortOrder: 0,
},
},
];
@@ -210,7 +215,7 @@ function getFilterContextType(): FilterContext {
compact: false,
options: facetsMock
.filter(facet => facet.buckets.length > 0)
.map(facet => {
.map((facet, i) => {
return {
buckets: facet.buckets.map(bucket => {
return {
@@ -222,6 +227,11 @@ function getFilterContextType(): FilterContext {
compact: false,
field: facet.field,
onlyOnType: facet.onlyOnType,
info: {
onlyOnType: facet.onlyOnType,
field: facet.field,
sortOrder: i,
},
};
}),
};

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.
@@ -17,7 +17,7 @@ import {LangChangeEvent, TranslateService} from '@ngx-translate/core';
import {SCLanguage, SCThingTranslator, SCThingType, SCTranslations} from '@openstapps/core';
import {Subscription} from 'rxjs';
import {ContextMenuService} from './context-menu.service';
import {FilterContext, SortContext, SortContextOption} from './context-type';
import {FilterContext, FilterFacet, SortContext, SortContextOption} from './context-type';
/**
* The context menu
@@ -49,6 +49,19 @@ export class ContextMenuComponent implements OnDestroy {
*/
filterOption: FilterContext;
/**
* Picks facets based on the compact filter option and sorts
* them based on
*
* No specific type => Type name alphabetically => Bucket count
*/
get facets(): FilterFacet[] {
const options = this.filterOption.compact
? this.filterOption.options.slice(0, this.compactFilterOptionCount)
: this.filterOption.options;
return options.filter(it => it.buckets.length > 0);
}
/**
* Possible languages to be used for translation
*/
@@ -102,18 +115,6 @@ export class ContextMenuComponent implements OnDestroy {
this.contextMenuService.contextFilterChanged(this.filterOption);
};
/**
* Returns translated property name
*/
getTranslatedPropertyName(property: string, onlyForType?: SCThingType): string {
return (
this.translator.translatedPropertyNames(
onlyForType ?? SCThingType.AcademicEvent,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) as any
)[property];
}
/**
* Returns translated property 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.
@@ -41,7 +41,7 @@
<ion-icon *ngIf="!sortOption.reversed" name="arrow_upward"></ion-icon>
</span>
</ion-label>
<ion-radio slot="end" [value]="i"> </ion-radio>
<ion-radio slot="end" [value]="i"></ion-radio>
</ion-item>
</ion-radio-group>
</ion-list>
@@ -55,31 +55,17 @@
</ion-button>
</ion-list-header>
<ion-list
class="filter-group"
*ngFor="
let facet of !filterOption.compact
? filterOption.options.slice(0, compactFilterOptionCount)
: filterOption.options
"
>
<div *ngIf="!facet.field.includes('.')">
<ion-list class="filter-group" *ngFor="let facet of facets">
<div>
<ion-list-header class="h3">
<ion-label>
{{
(facet.onlyOnType
? getTranslatedPropertyName(facet.field, facet.onlyOnType)
: getTranslatedPropertyName(facet.field)
) | titlecase
}}
{{
facet.onlyOnType
? ' | ' + (getTranslatedPropertyValue(facet.onlyOnType, 'type') | titlecase)
: ''
}}
<span *ngIf="facet.info.onlyOnType"
><b>{{ facet.info.onlyOnType | titlecase }}</b> /
</span>
{{ facet.info.field | titlecase }}
</ion-label>
</ion-list-header>
<div *ngIf="facet.buckets.length > 0">
<div>
<ion-item
*ngFor="
let bucket of !facet.compact

View File

@@ -1,14 +1,32 @@
/*
* 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 {TestBed} from '@angular/core/testing';
import {ContextMenuService} from './context-menu.service';
import {SCFacet} from '@openstapps/core';
import {FilterContext, SortContext} from './context-type';
import {ThingTranslateModule} from '../../../translation/thing-translate.module';
import {TranslateModule} from '@ngx-translate/core';
describe('ContextMenuService', () => {
let service: ContextMenuService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ThingTranslateModule.forRoot(), TranslateModule.forRoot()],
providers: [ContextMenuService],
});
service = TestBed.inject(ContextMenuService);
@@ -123,6 +141,10 @@ const filterContext: FilterContext = {
},
],
field: 'type',
info: {
field: 'type',
sortOrder: 0,
},
},
],
};

View File

@@ -13,9 +13,19 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Injectable} from '@angular/core';
import {SCFacet, SCSearchFilter, SCSearchSort, SCThingType} from '@openstapps/core';
import {
SCFacet,
SCSearchFilter,
SCSearchSort,
SCThingTranslator,
SCThingType,
SCTranslations,
} from '@openstapps/core';
import {Subject} from 'rxjs';
import {FilterBucket, FilterContext, FilterFacet, SortContext} from './context-type';
import {FilterBucket, FilterContext, FilterFacet, SortContext, TransformedFacet} from './context-type';
import {TranslateService} from '@ngx-translate/core';
import {ThingTranslateService} from '../../../translation/thing-translate.service';
import {transformFacets} from './facet-filter';
/**
* ContextMenuService provides bidirectional communication of context menu options and search queries
@@ -72,6 +82,11 @@ export class ContextMenuService {
*/
sortQueryChanged$ = this.sortQuery.asObservable();
constructor(
private readonly translate: TranslateService,
private readonly thingTranslate: ThingTranslateService,
) {}
/**
* Returns SCSearchFilter if filterContext value is set, undefined otherwise
*
@@ -178,18 +193,9 @@ export class ContextMenuService {
* Updates the filter context options from given facets
*/
updateContextFilter(facets: SCFacet[]) {
// arrange facet field "type" to first position
facets.sort((a: SCFacet, b: SCFacet) => {
if (a.field === 'type') {
return -1;
}
if (b.field === 'type') {
return 1;
}
return 0;
});
const language = this.translate.currentLang as keyof SCTranslations<unknown>;
const translator = new SCThingTranslator(language);
const transformedFacets = transformFacets(facets, language, this.thingTranslate, translator);
if (!this.contextFilter) {
this.contextFilter = {
@@ -198,23 +204,24 @@ export class ContextMenuService {
};
}
this.updateContextFilterOptions(this.contextFilter, facets);
this.updateContextFilterOptions(this.contextFilter, transformedFacets);
}
/**
* Updates context filter with new facets.
* It preserves the checked status of existing filter options
*/
updateContextFilterOptions = (contextFilter: FilterContext, facets: SCFacet[]) => {
updateContextFilterOptions = (contextFilter: FilterContext, facets: TransformedFacet[]) => {
const newFilterOptions: FilterFacet[] = [];
// iterate new facets
for (const facet of facets) {
for (const {facet, info} of facets) {
if (facet.buckets.length > 0) {
const newFilterFacet: FilterFacet = {
buckets: [],
field: facet.field,
onlyOnType: facet.onlyOnType,
info,
};
newFilterOptions.push(newFilterFacet);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020 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,7 +12,7 @@
* 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 {SCFacet, SCFacetBucket} from '@openstapps/core';
import {SCFacet, SCFacetBucket, SCThingType} from '@openstapps/core';
export type ContextType = FilterContext | SortContext;
@@ -84,6 +84,21 @@ export interface FilterFacet extends SCFacet {
* Compact view of the option buckets
*/
compact?: boolean;
/**
* Translated information about the facet
*/
info: FacetInfo;
}
export interface FacetInfo {
onlyOnType?: SCThingType;
field: string;
sortOrder: number;
}
export interface TransformedFacet {
facet: SCFacet;
info: FacetInfo;
}
export interface FilterBucket extends SCFacetBucket {

View File

@@ -0,0 +1,70 @@
/*
* 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 {TransformedFacet} from './context-type';
import {SCFacet, SCThingTranslator, SCThingType, SCTranslations} from '@openstapps/core';
import {searchFilters} from '../../../../config/search-filter';
import {ThingTranslateService} from '../../../translation/thing-translate.service';
const filterConfig = Object.entries(searchFilters).map(([pattern, entries]) => {
return {
typePattern: new RegExp(`^${pattern}$`),
facetFilter: Object.entries(entries).map(([pattern, facet]) => ({
pattern: new RegExp(`^${pattern}$`),
...facet,
})),
};
});
/**
* Transforms facets to
*
* 1. only include facets that are allowed in the options
* 2. translates all fields
* 3. sorts the facets according to the config
*/
export function transformFacets(
facets: SCFacet[],
language: keyof SCTranslations<unknown>,
thingTranslate: ThingTranslateService,
translator: SCThingTranslator,
): TransformedFacet[] {
return facets
.map(facet => ({
facet,
info: filterConfig
.filter(({typePattern}) => typePattern.test((facet.onlyOnType as string) || ''))
.flatMap(({facetFilter}) =>
facetFilter
.filter(({pattern}) => pattern.test(facet.field))
.map(it => ({
onlyOnType: facet.onlyOnType
? (translator.translatedPropertyValue(facet.onlyOnType, 'type') as SCThingType)
: undefined,
field:
it.translations && it.name
? it.translations[language]?.name || it.name
: thingTranslate.getPropertyName(
facet.onlyOnType || SCThingType.AcademicEvent,
facet.field,
),
sortOrder: it.sortOrder,
})),
)
.sort(({sortOrder: a}, {sortOrder: b}) => a - b)[0],
}))
.filter(({info}) => !!info)
.sort(({info: {sortOrder: a}}, {info: {sortOrder: b}}) => a - b);
}

View File

@@ -14,7 +14,7 @@
-->
<stapps-offline-notice></stapps-offline-notice>
<ion-split-pane contentId="main" when="lg">
<ion-split-pane contentId="main" when="xl">
<ion-menu menuId="main" contentId="main" type="overlay" side="start" swipe-gesture="false">
<ion-header>
<ion-toolbar color="primary" mode="ios">

View File

@@ -16,7 +16,7 @@
@import '../../../../theme/util/mixins';
stapps-navigation-tabs {
@include ion-lg-up {
@include ion-xl-up {
display: none;
}
}
@@ -41,7 +41,7 @@ stapps-offline-notice.is-offline ~ ion-split-pane {
margin-left: var(--navigation-rail-width);
}
@include ion-lg-up {
@include ion-xl-up {
margin-left: 0;
}
}
@@ -66,7 +66,7 @@ stapps-offline-notice.is-offline ~ ion-split-pane {
}
ion-router-outlet {
background: white;
background: var(--ion-background-color);
}
.menu-category {

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,7 +12,6 @@
* 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 {Directive, ElementRef, Input, OnDestroy, OnInit, Renderer2} from '@angular/core';
import {AnimationController, NavController} from '@ionic/angular';
import {Router, RouterEvent} from '@angular/router';
@@ -46,6 +45,12 @@ export class RootLinkDirective implements OnInit, OnDestroy {
ngOnInit() {
const animation = tabsTransition(this.animationController);
this.renderer.setAttribute(this.element.nativeElement, 'button', '');
if (document.querySelector('#main')?.childNodes.length === 1) {
if (this.router.url === this.rootLink) {
this.setActive();
}
this.needsInit = false;
}
this.subscriptions.push(
this.router.events.subscribe(event => {

View File

@@ -23,7 +23,7 @@ ion-card::part(native) {
display: flex;
flex-direction: column-reverse;
height: 100%;
background: linear-gradient(to top, var(--ion-color-dark), transparent);
background: linear-gradient(to top, #0e0e0e, transparent);
}
.card {
@@ -38,10 +38,10 @@ ion-card-title {
-webkit-box-orient: vertical;
overflow: hidden;
font-size: var(--font-size-xl);
--color: var(--ion-color-dark-contrast);
--color: var(--ion-text-color-dark, white);
max-lines: 3;
}
ion-card-subtitle {
--color: var(--ion-color-dark-contrast);
--color: var(--ion-text-color-dark, white);
}

View File

@@ -22,41 +22,39 @@
</ion-toolbar>
</ion-header>
<ion-content class="ion-content-parallax" (elementSizeChange)="calcPageSize($event)">
<div>
<ion-refresher slot="fixed" (ionRefresh)="refresh($event.target)">
<ion-refresher-content
pullingIcon="chevron-down-outline"
pullingText="{{ 'data.REFRESH_ACTION' | translate }}"
refreshingText="{{ 'data.REFRESHING' | translate }}"
refreshingSpinner="crescent"
>
</ion-refresher-content>
</ion-refresher>
<ion-grid>
<ion-row>
<ion-col size="12">
<stapps-news-settings-filter
*ngIf="settings"
[settings]="settings"
(filtersChanged)="toggleFilter($event)"
></stapps-news-settings-filter>
</ion-col>
</ion-row>
</ion-grid>
<div class="news-grid">
<ng-container *ngIf="!news">
<stapps-skeleton-news-item *ngFor="let skeleton of [1, 2, 3, 4, 5]"></stapps-skeleton-news-item>
</ng-container>
<ng-container *ngIf="news.length > 0">
<stapps-news-item *ngFor="let item of news" [item]="item"></stapps-news-item>
</ng-container>
</div>
<ion-label *ngIf="news.length === 0" class="centeredMessageContainer">
{{ 'search.nothing_found' | translate | titlecase }}
</ion-label>
<ion-infinite-scroll id="infinite-scroll" threshold="20%" (ionInfinite)="loadMore($event.target)">
<ion-infinite-scroll-content loading-spinner="crescent"> </ion-infinite-scroll-content>
</ion-infinite-scroll>
<ion-content parallax (elementSizeChange)="calcPageSize($event)">
<ion-refresher slot="fixed" (ionRefresh)="refresh($event.target)">
<ion-refresher-content
pullingIcon="chevron-down-outline"
pullingText="{{ 'data.REFRESH_ACTION' | translate }}"
refreshingText="{{ 'data.REFRESHING' | translate }}"
refreshingSpinner="crescent"
>
</ion-refresher-content>
</ion-refresher>
<ion-grid>
<ion-row>
<ion-col size="12">
<stapps-news-settings-filter
*ngIf="settings"
[settings]="settings"
(filtersChanged)="toggleFilter($event)"
></stapps-news-settings-filter>
</ion-col>
</ion-row>
</ion-grid>
<div class="news-grid">
<ng-container *ngIf="!news">
<stapps-skeleton-news-item *ngFor="let skeleton of [1, 2, 3, 4, 5]"></stapps-skeleton-news-item>
</ng-container>
<ng-container *ngIf="news.length > 0">
<stapps-news-item *ngFor="let item of news" [item]="item"></stapps-news-item>
</ng-container>
</div>
<ion-label *ngIf="news.length === 0" class="centeredMessageContainer">
{{ 'search.nothing_found' | translate | titlecase }}
</ion-label>
<ion-infinite-scroll id="infinite-scroll" threshold="20%" (ionInfinite)="loadMore($event.target)">
<ion-infinite-scroll-content loading-spinner="crescent"> </ion-infinite-scroll-content>
</ion-infinite-scroll>
</ion-content>

View File

@@ -14,13 +14,11 @@
*/
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
import {SCSection} from './sections';
import {SCSection} from '../../../../config/profile-page-sections';
import {AuthHelperService} from '../../auth/auth-helper.service';
import {Observable, Subscription} from 'rxjs';
import {SCAuthorizationProviderType} from '@openstapps/core';
import Swiper from 'swiper';
import {AlertController} from '@ionic/angular';
import {TranslateService} from '@ngx-translate/core';
@Component({
selector: 'stapps-profile-page-section',
@@ -55,11 +53,7 @@ export class ProfilePageSectionComponent implements OnInit, OnDestroy {
},
};
constructor(
private authHelper: AuthHelperService,
private alertController: AlertController,
private translateService: TranslateService,
) {}
constructor(private authHelper: AuthHelperService) {}
ngOnInit() {
if (this.item.authProvider) {
@@ -100,27 +94,7 @@ export class ProfilePageSectionComponent implements OnInit, OnDestroy {
async signOut(providerType: SCAuthorizationProviderType) {
await this.authHelper.getProvider(providerType).signOut();
const alert: HTMLIonAlertElement = await this.alertController.create({
header: this.translateService.instant(`auth.messages.${providerType}.log_out_alert.header`),
message: this.translateService.instant(`auth.messages.${providerType}.log_out_alert.message`),
buttons: [
{
text: this.translateService.instant('no'),
cssClass: 'default',
},
{
text: this.translateService.instant('yes'),
role: 'confirm',
cssClass: 'preferred',
handler: () => {
this.authHelper.endBrowserSession(providerType);
},
},
],
});
await alert.present();
await this.authHelper.endBrowserSession(providerType);
}
ngOnDestroy() {

View File

@@ -28,6 +28,7 @@
*ngFor="let link of item.links"
[routerLink]="link.link"
[disabled]="link.needsAuth && !isLoggedIn"
[detail]="false"
>
<div>
<ion-icon [name]="link.icon" size="36" color="dark"></ion-icon>

View File

@@ -12,6 +12,8 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
@use 'sass:math';
$width: 108px;
simple-swiper {
@@ -22,7 +24,7 @@ simple-swiper {
@each $i in 7, 6, 5, 4, 3, 2, 1 {
$max: #{($width + 8px) * $i};
@container (inline-size < #{$max}) {
--swiper-slide-width: #{100cqi / $i};
--swiper-slide-width: #{math.div(100cqi, $i)};
}
}
}
@@ -50,6 +52,7 @@ ion-item {
justify-content: center;
align-items: center;
font-size: var(--font-size-sm);
padding: 0 var(--spacing-xs) 0 var(--spacing-xs);
}
&::part(native) {

View File

@@ -21,7 +21,7 @@ import {ActivatedRoute} from '@angular/router';
import {ScheduleProvider} from '../../calendar/schedule.provider';
import moment from 'moment';
import {SCIcon} from '../../../util/ion-icon/icon';
import {profilePageSections} from './sections';
import {profilePageSections} from '../../../../config/profile-page-sections';
import {filter, map} from 'rxjs/operators';
const CourseCard = {

View File

@@ -22,101 +22,97 @@
</ion-toolbar>
</ion-header>
<ion-content>
<div>
<section class="user-card-wrapper">
<ion-card class="user-card">
<ion-card-header>
<ion-img src="assets/imgs/header.svg"></ion-img>
<span *ngIf="user$ | async as userInfo">
{{
userInfo.role ? (userInfo?.role | titlecase) : ('profile.role_guest' | translate | titlecase)
}}
</span>
</ion-card-header>
<ion-card-content>
<ion-img class="profile-card-img" src="assets/imgs/profile-card-head.svg"></ion-img>
<ion-grid>
<ion-row>
<ion-col size="3"></ion-col>
<ion-col
*ngIf="data.default.loggedIn$ | async as loggedIn; else logInPrompt"
size="9"
class="main-info"
>
<ng-container *ngIf="user$ | async as userInfo">
<ion-text class="full-name">
{{ userInfo?.name }}
<ion-content color="light" parallax [parallaxSize]="130">
<section class="user-card-wrapper">
<ion-card class="user-card">
<ion-card-header>
<ion-img src="assets/imgs/header.svg"></ion-img>
<span *ngIf="user$ | async as userInfo">
{{ userInfo.role ? (userInfo?.role | titlecase) : ('profile.role_guest' | translate | titlecase) }}
</span>
</ion-card-header>
<ion-card-content>
<ion-img class="profile-card-img" src="assets/imgs/profile-card-head.svg"></ion-img>
<ion-grid>
<ion-row>
<ion-col size="3"></ion-col>
<ion-col
*ngIf="data.default.loggedIn$ | async as loggedIn; else logInPrompt"
size="9"
class="main-info"
>
<ng-container *ngIf="user$ | async as userInfo">
<ion-text class="full-name">
{{ userInfo?.name }}
</ion-text>
<div class="matriculation-number">
<ion-label>
{{ 'profile.userInfo.studentId' | translate | uppercase }}
</ion-label>
<ion-text>
{{ userInfo?.studentId }}
</ion-text>
<div class="matriculation-number">
<ion-label>
{{ 'profile.userInfo.studentId' | translate | uppercase }}
</ion-label>
<ion-text>
{{ userInfo?.studentId }}
</ion-text>
</div>
<div class="user-name">
<ion-label>
{{ 'profile.userInfo.username' | translate | uppercase }}
</ion-label>
<ion-text>{{ userInfo?.id }}</ion-text>
</div>
<div class="email">
<ion-label>
{{ 'profile.userInfo.email' | translate | uppercase }}
</ion-label>
<ion-text>
{{ userInfo?.email }}
</ion-text>
</div>
</ng-container>
</div>
<div class="user-name">
<ion-label>
{{ 'profile.userInfo.username' | translate | uppercase }}
</ion-label>
<ion-text>{{ userInfo?.id }}</ion-text>
</div>
<div class="email">
<ion-label>
{{ 'profile.userInfo.email' | translate | uppercase }}
</ion-label>
<ion-text>
{{ userInfo?.email }}
</ion-text>
</div>
</ng-container>
</ion-col>
<ng-template #logInPrompt>
<ion-col size="9">
<ion-text class="log-in-prompt">
{{ 'profile.userInfo.logInPrompt' | translate }}
</ion-text>
</ion-col>
<ng-template #logInPrompt>
<ion-col size="9">
<ion-text class="log-in-prompt">
{{ 'profile.userInfo.logInPrompt' | translate }}
</ion-text>
</ion-col>
</ng-template>
</ion-row>
</ion-grid>
</ion-card-content>
</ion-card>
</section>
<stapps-profile-page-section
*ngFor="let section of sections"
[item]="section"
></stapps-profile-page-section>
<section class="courses">
<ion-label class="section-headline">
{{ 'profile.titleCourses' | translate | uppercase }}
</ion-label>
<ion-card class="courses-card">
<ion-card-header (click)="toggleCourseCardState()">
<span>{{ 'profile.courses.today' | translate | uppercase }}</span>
<ion-icon [name]="courseCardState" fill="red" size="20"></ion-icon>
</ion-card-header>
<ion-card-content class="course-card" [class.show-card]="courseCardState === courseCardEnum.expanded">
<ng-container *ngIf="myCoursesToday.length === 0">
<div class="no-course">
{{ 'profile.courses.no_courses' | translate }}
</ng-template>
</ion-row>
</ion-grid>
</ion-card-content>
</ion-card>
</section>
<stapps-profile-page-section
*ngFor="let section of sections"
[item]="section"
></stapps-profile-page-section>
<section class="courses">
<ion-label class="section-headline">
{{ 'profile.titleCourses' | translate | uppercase }}
</ion-label>
<ion-card class="courses-card">
<ion-card-header (click)="toggleCourseCardState()">
<span>{{ 'profile.courses.today' | translate | uppercase }}</span>
<ion-icon [name]="courseCardState" color="dark" size="20"></ion-icon>
</ion-card-header>
<ion-card-content class="course-card" [class.show-card]="courseCardState === courseCardEnum.expanded">
<ng-container *ngIf="myCoursesToday.length === 0">
<div class="no-course">
{{ 'profile.courses.no_courses' | translate }}
</div>
</ng-container>
<ng-container *ngFor="let myCourse of myCoursesToday">
<div class="clickable" [routerLink]="['/data-detail', myCourse.course.event.uid]">
<div>{{ myCourse?.startTime }} - {{ myCourse?.endTime }}</div>
<div>{{ myCourse?.course.event?.originalCategory }}</div>
<div [class.last]="!myCourse?.course.inPlace?.name">
{{ myCourse.course?.event?.name }}
</div>
</ng-container>
<ng-container *ngFor="let myCourse of myCoursesToday">
<div class="clickable" [routerLink]="['/data-detail', myCourse.course.event.uid]">
<div>{{ myCourse?.startTime }} - {{ myCourse?.endTime }}</div>
<div>{{ myCourse?.course.event?.originalCategory }}</div>
<div [class.last]="!myCourse?.course.inPlace?.name">
{{ myCourse.course?.event?.name }}
</div>
<div *ngIf="myCourse.course?.inPlace?.name" [class.last]="myCourse.course?.inPlace?.name">
{{ myCourse.course?.inPlace.name }}
</div>
<div *ngIf="myCourse.course?.inPlace?.name" [class.last]="myCourse.course?.inPlace?.name">
{{ myCourse.course?.inPlace.name }}
</div>
</ng-container>
</ion-card-content>
</ion-card>
</section>
</div>
</div>
</ng-container>
</ion-card-content>
</ion-card>
</section>
</ion-content>

View File

@@ -12,15 +12,7 @@
* 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 'src/theme/common/ion-content-parallax';
:host {
ion-content {
--background: var(--ion-color-light);
@include ion-content-parallax($content-size: 130px);
}
section {
margin-bottom: calc(2 * var(--spacing-lg) - var(--spacing-md));
padding: var(--spacing-md);
@@ -159,14 +151,14 @@
max-width: 800px;
ion-card-header {
background-color: var(--ion-color-light-contrast);
background-color: var(--ion-item-background);
border-radius: var(--border-radius-default) var(--border-radius-default) 0 0;
display: flex;
justify-content: space-between;
align-items: center;
span {
color: var(--ion-color-light);
color: var(--ion-item-background-color-contrast);
font-size: var(--font-size-lg);
font-weight: var(--font-weight-bold);
}
@@ -179,7 +171,7 @@
ion-card-content {
margin: 0;
padding: 0;
background-color: var(--ion-color-primary-contrast);
background-color: var(--ion-item-background);
border-radius: var(--border-radius-default);
max-height: 0;
overflow: hidden;
@@ -196,7 +188,7 @@
div {
font-size: var(--font-size-md);
font-weight: var(--font-weight-black);
color: var(--ion-color-light-contrast);
color: var(--ion-item-background-color-contrast);
text-align: center;
&.no-course {

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

@@ -0,0 +1,23 @@
<!--
~ 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-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,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.
@@ -86,17 +86,17 @@ ion-content {
.hours-wrapper {
z-index: 100;
background-color: var(--ion-color-primary-contrast);
background-color: var(--ion-background-color);
.hour-lines {
width: 40px;
height: 70px;
border-right: 1px solid var(--ion-color-light);
border-right: 1px solid var(--ion-item-border-color);
text-align: center;
top: 0;
position: absolute;
font-weight: var(--font-weight-bold);
background-color: var(--calender-background-color);
background-color: var(--ion-background-color);
}
}
@@ -106,7 +106,7 @@ ion-content {
}
.date-header {
border-bottom: 1px solid var(--calender-date-line-gray);
border-bottom: 1px solid var(--ion-item-border-color);
padding: var(--spacing-sm) auto;
height: fit-content;
font-weight: var(--font-weight-bold);

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.
@@ -27,8 +27,8 @@ import {ScheduleEvent} from '../schema/schema';
})
export class ScheduleCardComponent implements OnInit {
cardColor = {
isBlack: false,
isBlue: false,
isExercise: false,
isLecture: false,
isDefault: false,
};
@@ -91,8 +91,8 @@ export class ScheduleCardComponent implements OnInit {
this.title = this.scheduleEvent.dateSeries.event.name;
this.cardColor = {
isBlack: false,
isBlue: false,
isLecture: false,
isExercise: false,
isDefault: false,
};
@@ -102,10 +102,10 @@ export class ScheduleCardComponent implements OnInit {
: ''
) {
case 'lecture':
this.cardColor.isBlue = true;
this.cardColor.isLecture = true;
break;
case 'exercise':
this.cardColor.isBlack = true;
this.cardColor.isExercise = true;
break;
default:
this.cardColor.isDefault = true;

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.
@@ -19,8 +19,8 @@
[style.marginTop.px]="noOffset ? 0 : (fromY - fromHour) * scale - 5 + 45"
[routerLink]="['/data-detail', scheduleEvent.dateSeries.uid]"
[class.defaultCard]="cardColor.isDefault"
[class.blueCard]="cardColor.isBlue"
[class.blackCard]="cardColor.isBlack"
[class.lectureCard]="cardColor.isLecture"
[class.exerciseCard]="cardColor.isExercise"
class="clickable"
>
<ion-card-header mode="md">

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.
@@ -20,55 +20,23 @@ ion-card {
margin-right: 0;
border-radius: 0;
&.blueCard {
--background: var(--calender-blue-card);
@each $name in lecture, exercise, default {
&.#{$name}Card {
--background: var(--calender-#{$name}-card);
ion-card-title,
ion-card-subtitle span,
ion-card-content ion-note,
ion-card-content ion-text {
color: var(--ion-color-primary-contrast);
}
ion-card-title,
ion-card-subtitle span,
ion-card-content ion-note,
ion-card-content ion-text {
color: var(--calender-#{$name}-card-contrast);
}
&:after {
background: linear-gradient(
rgba(var(--calender-blue-card-rgb), 0%),
rgba(var(--calender-blue-card-rgb), 100%)
);
}
}
&.blackCard {
--background: var(--calender-black-card);
ion-card-title,
ion-card-subtitle span,
ion-card-content ion-note,
ion-card-content ion-text {
color: var(--ion-color-primary-contrast);
}
&:after {
background: linear-gradient(
rgba(var(--calender-black-card-rgb), 0%),
rgba(var(--calender-black-card-rgb), 100%)
);
}
}
&.defaultCard {
--background: var(--calender-default-card);
ion-card-title,
ion-card-subtitle span,
ion-card-content ion-note,
ion-card-content ion-text {
color: var(--ion-color-light-contrast);
}
&:after {
background: linear-gradient(
rgba(var(--calender-light-card-rgb), 0%),
rgba(var(--calender-light-card-rgb), 100%)
);
&:after {
background: linear-gradient(
rgba(var(--calender-#{$name}-card-rgb), 0%),
rgba(var(--calender-#{$name}-card-rgb), 100%)
);
}
}
}
@@ -78,10 +46,6 @@ ion-card {
bottom: 0;
height: 33%;
width: 100%;
background: linear-gradient(
rgba(var(--calender-blue-card-rgb), 0%),
rgba(var(--calender-blue-card-rgb), 100%)
);
}
ion-card-header {

View File

@@ -1,5 +1,5 @@
/*!
* Copyright (C) 2021 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,8 +15,8 @@
:host {
.day-wrapper {
border-left: 1px solid var(--calender-date-line-gray);
border-right: 1px solid var(--calender-date-line-gray);
border-left: 1px solid var(--ion-item-border-color);
border-right: 1px solid var(--ion-item-border-color);
&.leftmost {
border-left: unset;
@@ -28,15 +28,15 @@
left: 0;
height: fit-content;
padding: var(--spacing-md);
border-bottom: 2px solid var(--ion-color-light);
border-bottom: 2px solid var(--ion-item-border-color);
font-size: var(--font-size-md);
font-weight: var(--font-weight-bold);
text-align: center;
background-color: var(--ion-color-primary-contrast);
background-color: var(--ion-background-color);
z-index: 3;
&.leftmost {
border-left: 1px solid var(--ion-color-light);
border-left: 1px solid var(--ion-item-border-color);
}
}
}

Some files were not shown because too many files have changed in this diff Show More