mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-21 17:12:43 +00:00
Merge remote-tracking branch 'app/develop'
This commit is contained in:
34
frontend/app/src/app/_helpers/rxjs/mutation-observer.ts
Normal file
34
frontend/app/src/app/_helpers/rxjs/mutation-observer.ts
Normal 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();
|
||||
};
|
||||
});
|
||||
}
|
||||
20
frontend/app/src/app/animation/easings.ts
Normal file
20
frontend/app/src/app/animation/easings.ts
Normal 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;
|
||||
83
frontend/app/src/app/animation/fab-expand.ts
Normal file
83
frontend/app/src/app/animation/fab-expand.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (C) 2023 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {AnimationBuilder, AnimationController} from '@ionic/angular';
|
||||
import {AnimationOptions} from '@ionic/angular/providers/nav-controller';
|
||||
import {iosDuration, iosEasing, mdDuration, mdEasing} from './easings';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export function fabExpand(animationController: AnimationController): AnimationBuilder {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (_baseElement: HTMLElement, options: AnimationOptions | any) => {
|
||||
const rootTransition = animationController
|
||||
.create()
|
||||
.duration(options.duration ?? (options.mode === 'ios' ? iosDuration : mdDuration * 1.4))
|
||||
.easing(options.mode === 'ios' ? iosEasing : mdEasing);
|
||||
const back = options.direction === 'back';
|
||||
const fabView = back ? options.enteringEl! : options.leavingEl!;
|
||||
const otherView = back ? options.leavingEl! : options.enteringEl!;
|
||||
|
||||
const fab = fabView.querySelector('ion-fab-button').shadowRoot.querySelector('.button-native');
|
||||
const fabBounds = fab.getBoundingClientRect();
|
||||
const viewBounds = otherView.getBoundingClientRect();
|
||||
|
||||
const useReducedMotion = viewBounds.width > 500;
|
||||
const reducedMotionTransform = `${Math.min(viewBounds.width * 0.3, 200)}px`;
|
||||
const reducedMotionViewBorderRadius = '128px';
|
||||
const reducedMotionFabGrow = '2';
|
||||
const reducedMotionViewShrink = '0.9';
|
||||
|
||||
const viewCenterX = (viewBounds.width - viewBounds.x) / 2;
|
||||
const viewCenterY = (viewBounds.height - viewBounds.y) / 2;
|
||||
|
||||
const viewOnFab = useReducedMotion
|
||||
? `translate(${reducedMotionTransform}, ${reducedMotionTransform}) scale(${reducedMotionViewShrink})`
|
||||
: `translate(${(fabBounds.x - viewBounds.x) / 2}px, ${(fabBounds.y - viewBounds.y) / 2}px) scale(${
|
||||
fabBounds.width / viewBounds.width
|
||||
}, ${fabBounds.height / viewBounds.height})`;
|
||||
const fabOnView = useReducedMotion
|
||||
? `translate(-${reducedMotionTransform}, -${reducedMotionTransform}) scale(${reducedMotionFabGrow})`
|
||||
: `translate(${viewCenterX - fabBounds.x}px, ${viewCenterY - fabBounds.y}px) scale(${
|
||||
viewBounds.width / fabBounds.width
|
||||
}, ${viewBounds.height / fabBounds.height})`;
|
||||
const transformNormal = `translate(0px, 0px) scale(1, 1)`;
|
||||
|
||||
const viewBorderRadius = useReducedMotion ? reducedMotionViewBorderRadius : '50%';
|
||||
|
||||
const fabViewFade = animationController
|
||||
.create()
|
||||
.beforeStyles({zIndex: -1})
|
||||
.fromTo('opacity', '1', '1')
|
||||
.addElement(fabView);
|
||||
const fabGrow = animationController
|
||||
.create()
|
||||
.beforeStyles({transformOrigin: 'center'})
|
||||
.fromTo('transform', back ? fabOnView : transformNormal, back ? transformNormal : fabOnView)
|
||||
.fromTo('opacity', back ? '0' : '1', back ? '1' : '0')
|
||||
.fromTo('borderRadius', back ? '0' : '50%', back ? '50%' : '0')
|
||||
.addElement(fab);
|
||||
const viewGrow = animationController
|
||||
.create()
|
||||
.beforeStyles({zIndex: 200, overflow: 'hidden', transformOrigin: 'center'})
|
||||
.fromTo('transform', back ? transformNormal : viewOnFab, back ? viewOnFab : transformNormal)
|
||||
.fromTo('opacity', back ? '1' : '0', back ? '0' : '1')
|
||||
.fromTo('borderRadius', back ? '0' : viewBorderRadius, back ? viewBorderRadius : '0')
|
||||
.addElement(otherView);
|
||||
|
||||
return rootTransition.addAnimation(fabGrow).addAnimation(viewGrow).addAnimation(fabViewFade);
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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>
|
||||
@@ -1,3 +0,0 @@
|
||||
:host {
|
||||
--width: 100vw;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
19
frontend/app/src/app/modules/dashboard/fade.animation.ts
Normal file
19
frontend/app/src/app/modules/dashboard/fade.animation.ts
Normal 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'}))]),
|
||||
]);
|
||||
28
frontend/app/src/app/modules/dashboard/mensa-filters.ts
Normal file
28
frontend/app/src/app/modules/dashboard/mensa-filters.ts
Normal 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
|
||||
);
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
/**
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
<ion-button
|
||||
expand="full"
|
||||
fill="clear"
|
||||
color="light"
|
||||
*ngIf="item.description && buttonShown"
|
||||
(click)="toggleDescriptionAccordion()"
|
||||
>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,13 @@ import {searchPageSwitchAnimation} from './search-page-switch-animation';
|
||||
providers: [ContextMenuService],
|
||||
})
|
||||
export class SearchPageComponent implements OnInit, OnDestroy {
|
||||
title = 'search.title';
|
||||
@Input() title = 'search.title';
|
||||
|
||||
@Input() placeholder = 'search.search_bar.placeholder';
|
||||
|
||||
@Input() searchInstruction = 'search.instruction';
|
||||
|
||||
@Input() backUrl?: string;
|
||||
|
||||
isHebisAvailable = false;
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
|
||||
<stapps-context contentId="data-list"></stapps-context>
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary" mode="ios" *ngIf="showDrawer">
|
||||
<ion-toolbar color="primary" mode="ios" *ngIf="showDrawer && showTopToolbar">
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button></ion-back-button>
|
||||
<ion-back-button [defaultHref]="backUrl"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{ title | translate }}</ion-title>
|
||||
</ion-toolbar>
|
||||
@@ -28,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
|
||||
|
||||
@@ -37,7 +37,7 @@ ion-toolbar:first-of-type {
|
||||
}
|
||||
|
||||
ion-content {
|
||||
--background: var(--ion-color-light);
|
||||
--background: var(--ion-background-color);
|
||||
}
|
||||
|
||||
.content > div {
|
||||
|
||||
@@ -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
|
||||
43
frontend/app/src/app/modules/data/list/skeleton-list.html
Normal file
43
frontend/app/src/app/modules/data/list/skeleton-list.html
Normal 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 |
34
frontend/app/src/app/modules/data/list/skeleton-list.scss
Normal file
34
frontend/app/src/app/modules/data/list/skeleton-list.scss
Normal 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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 }}"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
/**
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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%;
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
70
frontend/app/src/app/modules/menu/context/facet-filter.ts
Normal file
70
frontend/app/src/app/modules/menu/context/facet-filter.ts
Normal 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);
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user