mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-22 17:42:57 +00:00
feat: apply new layout overhaul
This commit is contained in:
committed by
Rainer Killinger
parent
f16e5394cc
commit
7bbdba5c0b
20
angular.json
20
angular.json
@@ -39,6 +39,16 @@
|
||||
"glob": "**/*",
|
||||
"input": "./node_modules/leaflet/dist/images",
|
||||
"output": "assets/"
|
||||
},
|
||||
{
|
||||
"glob": "**/*.svg",
|
||||
"input": "src/assets/custom-ion-icons",
|
||||
"output": "./svg"
|
||||
},
|
||||
{
|
||||
"glob": "**/*.svg",
|
||||
"input": "src/assets/tabler-icons",
|
||||
"output": "./svg"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
@@ -112,6 +122,9 @@
|
||||
"ci": {
|
||||
"progress": false,
|
||||
"browserTarget": "app:build"
|
||||
},
|
||||
"fake": {
|
||||
"browserTarget": "app:build:fake"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
@@ -149,12 +162,7 @@
|
||||
},
|
||||
{
|
||||
"glob": "**/*.svg",
|
||||
"input": "node_modules/ionicons/dist/ionicons/svg",
|
||||
"output": "./svg"
|
||||
},
|
||||
{
|
||||
"glob": "**/*.svg",
|
||||
"input": "src/assets/custom-ionicons",
|
||||
"input": "src/assets/tabler-icons",
|
||||
"output": "./svg"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
*/
|
||||
describe('Settings Page', () => {
|
||||
it('should have a proper title', () => {
|
||||
cy.visit('/settings');
|
||||
cy.visit('/app/settings');
|
||||
|
||||
cy.get('ion-title').contains('Einstellungen');
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<dict>
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>com.transistorsoft.fetch</string>
|
||||
@@ -38,16 +37,16 @@
|
||||
<string>de.anyschool.app</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict/>
|
||||
<dict />
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<true />
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
<true />
|
||||
</dict>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
@@ -71,6 +70,6 @@
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<true />
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -26,5 +26,8 @@
|
||||
"useDialog": false
|
||||
},
|
||||
"LocalNotifications": {}
|
||||
},
|
||||
"server": {
|
||||
"url": "http://localhost:8100"
|
||||
}
|
||||
}
|
||||
|
||||
23063
package-lock.json
generated
23063
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,9 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {PreloadAllModules, RouterModule, Routes} from '@angular/router';
|
||||
|
||||
const routes: Routes = [{path: '', redirectTo: '/news', pathMatch: 'full'}];
|
||||
const routes: Routes = [
|
||||
{path: '', redirectTo: '/dashboard', pathMatch: 'full'},
|
||||
];
|
||||
|
||||
/**
|
||||
* TODO
|
||||
@@ -25,6 +27,12 @@ const routes: Routes = [{path: '', redirectTo: '/news', pathMatch: 'full'}];
|
||||
imports: [
|
||||
RouterModule.forRoot(routes, {
|
||||
preloadingStrategy: PreloadAllModules,
|
||||
errorHandler: error => {
|
||||
// Handle unknown routes, at the moment this can only be done via window.location
|
||||
if (error.message.includes('Cannot match any routes')) {
|
||||
window.location.href = '/dashboard';
|
||||
}
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<ion-app>
|
||||
<ion-app style="margin-top: env(safe-area-inset-top)">
|
||||
<stapps-navigation></stapps-navigation>
|
||||
</ion-app>
|
||||
|
||||
@@ -37,12 +37,15 @@ import {TranslateHttpLoader} from '@ngx-translate/http-loader';
|
||||
import moment from 'moment';
|
||||
import 'moment/min/locales';
|
||||
import {LoggerModule, NGXLogger, NgxLoggerLevel} from 'ngx-logger';
|
||||
import SwiperCore, {FreeMode, Navigation} from 'swiper';
|
||||
|
||||
import {environment} from '../environments/environment';
|
||||
import {AppRoutingModule} from './app-routing.module';
|
||||
import {AppComponent} from './app.component';
|
||||
import {CatalogModule} from './modules/catalog/catalog.module';
|
||||
import {ConfigModule} from './modules/config/config.module';
|
||||
import {ConfigProvider} from './modules/config/config.provider';
|
||||
import {DashboardModule} from './modules/dashboard/dashboard.module';
|
||||
import {DataModule} from './modules/data/data.module';
|
||||
import {HebisModule} from './modules/hebis/hebis.module';
|
||||
import {MapModule} from './modules/map/map.module';
|
||||
@@ -76,6 +79,8 @@ import {PAIAAuthService} from './modules/auth/paia/paia-auth.service';
|
||||
|
||||
registerLocaleData(localeDe);
|
||||
|
||||
SwiperCore.use([FreeMode, Navigation]);
|
||||
|
||||
/**
|
||||
* Initializes data needed on startup
|
||||
*
|
||||
@@ -84,6 +89,7 @@ registerLocaleData(localeDe);
|
||||
* @param settingsProvider provider of settings (e.g. language that has been set)
|
||||
* @param configProvider TODO
|
||||
* @param translateService TODO
|
||||
* @param _routingStackService Just for init and to track the stack from the get go
|
||||
*/
|
||||
export function initializerFactory(
|
||||
storageProvider: StorageProvider,
|
||||
@@ -151,6 +157,7 @@ export function createTranslateLoader(http: HttpClient) {
|
||||
CatalogModule,
|
||||
CommonModule,
|
||||
ConfigModule,
|
||||
DashboardModule,
|
||||
DataModule,
|
||||
HebisModule,
|
||||
IonicModule.forRoot(),
|
||||
|
||||
@@ -14,10 +14,12 @@
|
||||
-->
|
||||
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary">
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
<ion-buttons slot="start">
|
||||
<ion-menu-button></ion-menu-button>
|
||||
<ion-back-button></ion-back-button>
|
||||
<ion-back-button
|
||||
[defaultHref]="'..'"
|
||||
[text]="'back' | translate | titlecase"
|
||||
></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Changelog</ion-title>
|
||||
<!-- TODO: translation -->
|
||||
|
||||
@@ -14,10 +14,12 @@
|
||||
-->
|
||||
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary">
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
<ion-buttons slot="start">
|
||||
<ion-menu-button></ion-menu-button>
|
||||
<ion-back-button></ion-back-button>
|
||||
<ion-back-button
|
||||
[defaultHref]="'..'"
|
||||
[text]="'back' | translate | titlecase"
|
||||
></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Licenses</ion-title>
|
||||
<!-- TODO: translation -->
|
||||
@@ -37,7 +39,7 @@
|
||||
<ion-card-header>
|
||||
<ion-card-title>
|
||||
{{ license.name }}
|
||||
<ion-icon class="supertext-icon" name="open-outline"></ion-icon>
|
||||
<ion-icon class="supertext-icon" name="external-link"></ion-icon>
|
||||
</ion-card-title>
|
||||
|
||||
<ion-card-subtitle *ngIf="license.authors || license.publisher">
|
||||
@@ -46,7 +48,7 @@
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<ion-chip (click)="$event.preventDefault(); viewLicense(license)">
|
||||
<ion-icon name="document"></ion-icon>
|
||||
<ion-icon name="file"></ion-icon>
|
||||
<ion-label>{{ license.licenses }} License</ion-label>
|
||||
</ion-chip>
|
||||
</ion-card-content>
|
||||
|
||||
@@ -14,10 +14,12 @@
|
||||
-->
|
||||
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary">
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
<ion-buttons slot="start">
|
||||
<ion-menu-button></ion-menu-button>
|
||||
<ion-back-button></ion-back-button>
|
||||
<ion-back-button
|
||||
[defaultHref]="'..'"
|
||||
[text]="'back' | translate | titlecase"
|
||||
></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title *ngIf="content; else titleLoading">{{
|
||||
'title' | translateSimple: content
|
||||
|
||||
@@ -28,6 +28,7 @@ import {DataModule} from '../data/data.module';
|
||||
import {ScrollingModule} from '@angular/cdk/scrolling';
|
||||
import {AboutLicenseModalComponent} from './about-license-modal.component';
|
||||
import {AboutChangelogComponent} from './about-changelog.component';
|
||||
import {UtilModule} from '../../util/util.module';
|
||||
|
||||
const settingsRoutes: Routes = [
|
||||
{path: 'about', component: AboutPageComponent},
|
||||
@@ -59,6 +60,7 @@ const settingsRoutes: Routes = [
|
||||
MarkdownModule,
|
||||
DataModule,
|
||||
ScrollingModule,
|
||||
UtilModule,
|
||||
],
|
||||
providers: [ConfigProvider],
|
||||
})
|
||||
|
||||
@@ -13,10 +13,12 @@
|
||||
~ this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary">
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button></ion-back-button>
|
||||
<ion-menu-button></ion-menu-button>
|
||||
<ion-back-button
|
||||
[defaultHref]="'..'"
|
||||
[text]="'back' | translate | titlecase"
|
||||
></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{ 'data.detail.TITLE' | translate }}</ion-title>
|
||||
</ion-toolbar>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary">
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button></ion-back-button>
|
||||
<ion-menu-button></ion-menu-button>
|
||||
<ion-back-button
|
||||
[defaultHref]="'..'"
|
||||
[text]="'back' | translate | titlecase"
|
||||
></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{ 'assessments.TITLE' | translate }}</ion-title>
|
||||
</ion-toolbar>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<ion-item-divider>
|
||||
<ion-label>{{ event.title }}</ion-label>
|
||||
<ion-note slot="start" *ngIf="event.events.length > 1">
|
||||
<ion-icon name="warning"></ion-icon>
|
||||
<ion-icon name="alert-triangle"></ion-icon>
|
||||
</ion-note>
|
||||
</ion-item-divider>
|
||||
<ion-item *ngFor="let iCalEvent of event.events">
|
||||
|
||||
@@ -24,6 +24,7 @@ import {ThingTranslateModule} from '../../translation/thing-translate.module';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {MomentModule} from 'ngx-moment';
|
||||
import {UtilModule} from '../../util/util.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AddEventReviewModalComponent],
|
||||
@@ -34,6 +35,7 @@ import {MomentModule} from 'ngx-moment';
|
||||
FormsModule,
|
||||
CommonModule,
|
||||
MomentModule,
|
||||
UtilModule,
|
||||
],
|
||||
exports: [],
|
||||
providers: [Calendar, CalendarService, ScheduleProvider],
|
||||
|
||||
@@ -16,9 +16,10 @@
|
||||
import {Calendar} from '@awesome-cordova-plugins/calendar/ngx';
|
||||
import {Injectable} from '@angular/core';
|
||||
import {ICalEvent} from './ical/ical';
|
||||
import moment, {duration, unitOfTime} from 'moment';
|
||||
import moment, {duration, Moment, unitOfTime} from 'moment';
|
||||
import {Dialog} from '@capacitor/dialog';
|
||||
import {CalendarInfo} from './calendar-info';
|
||||
import {Subject} from 'rxjs';
|
||||
|
||||
const CALENDAR_NAME = 'StApps';
|
||||
|
||||
@@ -33,6 +34,10 @@ const RECURRENCE_PATTERNS: Partial<
|
||||
|
||||
@Injectable()
|
||||
export class CalendarService {
|
||||
goToDate = new Subject<number>();
|
||||
|
||||
goToDateClicked = this.goToDate.asObservable();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
constructor(readonly calendar: Calendar) {}
|
||||
|
||||
@@ -98,4 +103,14 @@ export class CalendarService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit the calendar index corresponding to the input date.
|
||||
*
|
||||
* @param date Moment - date the calendar should go to
|
||||
*/
|
||||
emitGoToDate(date: Moment) {
|
||||
const index = date.diff(moment().startOf('day'), 'days');
|
||||
this.goToDate.next(index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary">
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button></ion-back-button>
|
||||
<ion-menu-button></ion-menu-button>
|
||||
<ion-back-button
|
||||
[defaultHref]="'..'"
|
||||
[text]="'back' | translate | titlecase"
|
||||
></ion-back-button>
|
||||
</ion-buttons>
|
||||
<!--TODO: read this from the config (menu item title)-->
|
||||
<ion-title>{{ 'catalog.title' | translate | titlecase }}</ion-title>
|
||||
|
||||
@@ -129,13 +129,18 @@ export class CatalogComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
if (this.activeSemester && this.selectedSemesterUID === '') {
|
||||
this.selectedSemesterUID = this.activeSemester.uid;
|
||||
this.updateLocation(this.selectedSemesterUID);
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
segmentChanged(event: any) {
|
||||
if (
|
||||
this.activeSemester &&
|
||||
this.activeSemester.uid !== (event.detail.value as string)
|
||||
) {
|
||||
this.updateLocation(event.detail.value as string);
|
||||
}
|
||||
|
||||
this.activeSemester = this.availableSemesters.find(
|
||||
semester => semester.uid === (event.detail.value as string),
|
||||
);
|
||||
|
||||
@@ -22,6 +22,7 @@ import {MomentModule} from 'ngx-moment';
|
||||
import {DataModule} from '../data/data.module';
|
||||
import {SettingsProvider} from '../settings/settings.provider';
|
||||
import {CatalogComponent} from './catalog.component';
|
||||
import {UtilModule} from '../../util/util.module';
|
||||
|
||||
const catalogRoutes: Routes = [
|
||||
{path: 'catalog', component: CatalogComponent},
|
||||
@@ -41,6 +42,7 @@ const catalogRoutes: Routes = [
|
||||
CommonModule,
|
||||
MomentModule,
|
||||
DataModule,
|
||||
UtilModule,
|
||||
],
|
||||
providers: [SettingsProvider],
|
||||
})
|
||||
|
||||
46
src/app/modules/dashboard/dashboard.component.html
Normal file
46
src/app/modules/dashboard/dashboard.component.html
Normal file
@@ -0,0 +1,46 @@
|
||||
<ion-header>
|
||||
<ion-toolbar class="ion-hide-md-up">
|
||||
<ion-label slot="start">{{
|
||||
'dashboard.header.title' | daytimeKey | translate
|
||||
}}</ion-label>
|
||||
<ion-img src="assets/imgs/logo.png" class="logo"></ion-img>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<div class="schedule">
|
||||
<a [routerLink]="['/schedule/recurring']">
|
||||
<ion-icon name="layout-grid"></ion-icon>
|
||||
<ion-label>{{ 'schedule.recurring' | translate }}</ion-label>
|
||||
</a>
|
||||
<a
|
||||
*ngIf="nextEvent && nextEvent.event"
|
||||
[routerLink]="['/data-detail', nextEvent.event.uid]"
|
||||
class="schedule-item-button"
|
||||
>
|
||||
<ion-label>{{ 'dashboard.schedule.title' | translate }}</ion-label>
|
||||
<ion-label>
|
||||
{{ nextEvent.dates | nextDateInList | amDateFormat: 'll, HH:mm' }}
|
||||
{{ 'timeSuffix' | translate }}
|
||||
</ion-label>
|
||||
<ion-label>{{ nextEvent.event.name }}</ion-label>
|
||||
</a>
|
||||
<a
|
||||
*ngIf="!nextEvent || !nextEvent.event"
|
||||
[routerLink]="['/schedule/recurring']"
|
||||
class="schedule-item-button"
|
||||
>
|
||||
<ion-label>{{ 'dashboard.schedule.title' | translate }}</ion-label>
|
||||
<ion-label>{{ 'dashboard.schedule.noEvent' | translate }}</ion-label>
|
||||
<ion-label>{{ 'dashboard.schedule.noEventLink' | translate }}</ion-label>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="scrollable-container">
|
||||
<stapps-navigation-section></stapps-navigation-section>
|
||||
<stapps-search-section></stapps-search-section>
|
||||
<stapps-news-section></stapps-news-section>
|
||||
<stapps-mensa-section></stapps-mensa-section>
|
||||
<stapps-favorites-section></stapps-favorites-section>
|
||||
</div>
|
||||
</ion-content>
|
||||
224
src/app/modules/dashboard/dashboard.component.scss
Normal file
224
src/app/modules/dashboard/dashboard.component.scss
Normal file
@@ -0,0 +1,224 @@
|
||||
@import '../../../theme/util/mixins';
|
||||
|
||||
:host ion-toolbar:last-of-type {
|
||||
--padding-top: var(--spacing-md);
|
||||
--padding-bottom: 0;
|
||||
|
||||
ion-icon {
|
||||
margin-right: var(--spacing-sm);
|
||||
width: var(--font-size-xl);
|
||||
height: var(--font-size-xl);
|
||||
}
|
||||
|
||||
ion-label {
|
||||
font-family: var(--headline-font-family);
|
||||
font-size: var(--font-size-md);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 27vw;
|
||||
max-width: 150px;
|
||||
max-height: 80px;
|
||||
aspect-ratio: 1/1;
|
||||
object-position: right;
|
||||
margin-left: auto;
|
||||
margin-right: var(--spacing-sm);
|
||||
|
||||
@include phoneLandscape {
|
||||
width: auto;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
@include phonePortraitSmall {
|
||||
width: auto;
|
||||
height: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ion-content {
|
||||
--background: var(--ion-color-light);
|
||||
--overflow: hidden;
|
||||
|
||||
.scrollable-container {
|
||||
overflow: hidden auto;
|
||||
height: 100%;
|
||||
padding-top: 160px;
|
||||
|
||||
@media (max-width: 440px) {
|
||||
padding-top: 140px;
|
||||
}
|
||||
|
||||
@include ion-md-up {
|
||||
padding-top: 0;
|
||||
padding-bottom: 160px;
|
||||
}
|
||||
}
|
||||
|
||||
@include phoneLandscape {
|
||||
--overflow: hidden auto;
|
||||
|
||||
.scrollable-container {
|
||||
overflow: initial;
|
||||
height: initial;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.schedule {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 3;
|
||||
background: var(--ion-color-primary);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: var(--spacing-md);
|
||||
padding: var(--spacing-sm) var(--spacing-sm) var(--spacing-xl);
|
||||
|
||||
@include ion-md-up {
|
||||
position: unset;
|
||||
width: unset;
|
||||
z-index: unset;
|
||||
height: calc(var(--tablet-top-bar-height) + (2 * var(--spacing-xl)));
|
||||
margin: 0;
|
||||
padding: var(--spacing-xl);
|
||||
}
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--ion-color-primary-contrast);
|
||||
text-decoration: none;
|
||||
height: auto;
|
||||
padding: var(--spacing-lg);
|
||||
border-radius: var(--border-radius-default);
|
||||
}
|
||||
|
||||
a:first-child {
|
||||
border: 2px solid var(--ion-color-primary-tint);
|
||||
text-align: center;
|
||||
flex: 1 1 auto;
|
||||
aspect-ratio: 1;
|
||||
box-sizing: content-box;
|
||||
max-height: 100px;
|
||||
|
||||
@include phoneLandscape {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
ion-icon {
|
||||
margin: auto auto var(--spacing-xs);
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
ion-label {
|
||||
margin: 0 auto auto;
|
||||
font-size: var(--font-size-xxs);
|
||||
font-weight: var(--font-weight-semi-bold);
|
||||
}
|
||||
}
|
||||
|
||||
a:last-child {
|
||||
flex: 1 1 65%;
|
||||
background: var(--linear-gradient);
|
||||
justify-content: center;
|
||||
|
||||
@include ion-md-up {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
|
||||
@include phoneLandscape {
|
||||
flex: 1 1 85%;
|
||||
}
|
||||
|
||||
ion-label {
|
||||
font-size: var(--font-size-xxs);
|
||||
font-weight: var(--font-weight-bold);
|
||||
line-height: 1.4;
|
||||
}
|
||||
ion-label:first-child {
|
||||
text-transform: uppercase;
|
||||
color: var(--ion-color-secondary);
|
||||
}
|
||||
ion-label:nth-child(2n) {
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: var(--font-weight-semi-bold);
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: var(--spacing-md);
|
||||
|
||||
&.section-extended {
|
||||
padding-right: 0;
|
||||
ion-icon[name='edit'] {
|
||||
margin-right: var(--spacing-md);
|
||||
}
|
||||
}
|
||||
|
||||
&:first-of-type {
|
||||
padding-top: var(--spacing-lg);
|
||||
}
|
||||
|
||||
& > ion-label:first-child {
|
||||
font-family: var(--headline-font-family);
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: var(--font-weight-semi-bold);
|
||||
text-transform: uppercase;
|
||||
margin-bottom: var(--spacing-md);
|
||||
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: revert;
|
||||
justify-content: space-between;
|
||||
|
||||
ion-icon {
|
||||
color: var(--ion-color-medium-shade);
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.swiper {
|
||||
background-color: var(--ion-color-primary-contrast);
|
||||
border-radius: var(--border-radius-default);
|
||||
padding: var(--spacing-lg);
|
||||
width: 28%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: var(--font-weight-bold);
|
||||
|
||||
ion-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ion-searchbar {
|
||||
padding: 0;
|
||||
--background: var(--ion-color-primary-contrast);
|
||||
|
||||
::ng-deep .searchbar-input-container {
|
||||
height: 100%;
|
||||
|
||||
input {
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
ion-icon {
|
||||
left: auto;
|
||||
right: var(--spacing-lg);
|
||||
width: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
107
src/app/modules/dashboard/dashboard.component.ts
Normal file
107
src/app/modules/dashboard/dashboard.component.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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, OnInit, OnDestroy} from '@angular/core';
|
||||
import {Router} from '@angular/router';
|
||||
import {Location} from '@angular/common';
|
||||
import {Subscription} from 'rxjs';
|
||||
import moment from 'moment';
|
||||
import {SCDateSeries, SCUuid} from '@openstapps/core';
|
||||
import {SplashScreen} from '@capacitor/splash-screen';
|
||||
|
||||
import {DataRoutingService} from '../data/data-routing.service';
|
||||
import {ScheduleProvider} from '../calendar/schedule.provider';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dashboard',
|
||||
templateUrl: './dashboard.component.html',
|
||||
styleUrls: ['./dashboard.component.scss'],
|
||||
})
|
||||
export class DashboardComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* Array of all subscriptions to Observables
|
||||
*/
|
||||
subscriptions: Subscription[] = [];
|
||||
|
||||
/**
|
||||
* UUID subscription
|
||||
*/
|
||||
private _uuidSubscription: Subscription;
|
||||
|
||||
/**
|
||||
* The events to display
|
||||
*/
|
||||
private uuids: SCUuid[];
|
||||
|
||||
/**
|
||||
* Next event in calendar
|
||||
*/
|
||||
nextEvent: SCDateSeries | undefined;
|
||||
|
||||
/**
|
||||
* Slider options
|
||||
*/
|
||||
quickNavigationOptions = {
|
||||
slidesPerView: 'auto',
|
||||
spaceBetween: 12,
|
||||
freeMode: {
|
||||
enabled: true,
|
||||
sticky: true,
|
||||
},
|
||||
};
|
||||
|
||||
constructor(
|
||||
private readonly dataRoutingService: DataRoutingService,
|
||||
private scheduleProvider: ScheduleProvider,
|
||||
protected router: Router,
|
||||
public location: Location,
|
||||
) {
|
||||
this.subscriptions.push(
|
||||
this.dataRoutingService.itemSelectListener().subscribe(item => {
|
||||
void this.router.navigate(['data-detail', item.uid]);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this._uuidSubscription = this.scheduleProvider.uuids$.subscribe(
|
||||
async result => {
|
||||
this.uuids = result;
|
||||
await this.loadNextEvent();
|
||||
},
|
||||
);
|
||||
await SplashScreen.hide();
|
||||
}
|
||||
|
||||
async loadNextEvent() {
|
||||
const dataSeries = await this.scheduleProvider.getDateSeries(
|
||||
this.uuids,
|
||||
['P1W', 'P2W', 'P3W', 'P4W'],
|
||||
moment(moment.now()).startOf('week').toISOString(),
|
||||
);
|
||||
|
||||
this.nextEvent =
|
||||
(dataSeries && dataSeries.dates && dataSeries.dates[0]) || undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove subscriptions when the component is removed
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
for (const sub of this.subscriptions) {
|
||||
sub.unsubscribe();
|
||||
}
|
||||
this._uuidSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
73
src/app/modules/dashboard/dashboard.module.ts
Normal file
73
src/app/modules/dashboard/dashboard.module.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 {CommonModule} from '@angular/common';
|
||||
import {NgModule} from '@angular/core';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {IonicModule} from '@ionic/angular';
|
||||
import {SwiperModule} from 'swiper/angular';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
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 {SectionComponent} from './section/section.component';
|
||||
import {NavigationSectionComponent} from './sections/navigation-section/navigation-section.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';
|
||||
import {MensaSectionContentComponent} from './sections/mensa-section/mensa-section-content.component';
|
||||
import {FavoritesSectionComponent} from './sections/favorites-section/favorites-section.component';
|
||||
import {ThingTranslateModule} from '../../translation/thing-translate.module';
|
||||
import {UtilModule} from '../../util/util.module';
|
||||
|
||||
const catalogRoutes: Routes = [
|
||||
{
|
||||
path: 'dashboard',
|
||||
component: DashboardComponent,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Catalog Module
|
||||
*/
|
||||
@NgModule({
|
||||
declarations: [
|
||||
SectionComponent,
|
||||
EditModalComponent,
|
||||
NavigationSectionComponent,
|
||||
SearchSectionComponent,
|
||||
NewsSectionComponent,
|
||||
MensaSectionComponent,
|
||||
MensaSectionContentComponent,
|
||||
FavoritesSectionComponent,
|
||||
DashboardComponent,
|
||||
],
|
||||
imports: [
|
||||
IonicModule.forRoot(),
|
||||
FormsModule,
|
||||
TranslateModule.forChild(),
|
||||
RouterModule.forChild(catalogRoutes),
|
||||
CommonModule,
|
||||
MomentModule,
|
||||
DataModule,
|
||||
SwiperModule,
|
||||
ThingTranslateModule.forChild(),
|
||||
UtilModule,
|
||||
],
|
||||
providers: [SettingsProvider],
|
||||
})
|
||||
export class DashboardModule {}
|
||||
87
src/app/modules/dashboard/dashboard.provider.ts
Normal file
87
src/app/modules/dashboard/dashboard.provider.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 {Injectable} from '@angular/core';
|
||||
import {
|
||||
SCBooleanFilterArguments,
|
||||
SCMessage,
|
||||
SCSearchBooleanFilter,
|
||||
SCSearchFilter,
|
||||
SCSearchQuery,
|
||||
} from '@openstapps/core';
|
||||
import {DataProvider} from '../data/data.provider';
|
||||
/**
|
||||
* Service for providing catalog and semester data
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class DashboardProvider {
|
||||
constructor(private readonly dataProvider: DataProvider) {}
|
||||
|
||||
/**
|
||||
* Get news messages
|
||||
*
|
||||
* @param size How many messages/news to fetch
|
||||
* @param from From which (results) page to start
|
||||
* @param filters Additional filters to apply
|
||||
*/
|
||||
async getNews(
|
||||
size: number,
|
||||
from: number,
|
||||
filters?: SCSearchFilter[],
|
||||
): Promise<SCMessage[]> {
|
||||
const query: SCSearchQuery = {
|
||||
filter: {
|
||||
type: 'boolean',
|
||||
arguments: {
|
||||
filters: [
|
||||
{
|
||||
type: 'value',
|
||||
arguments: {
|
||||
field: 'type',
|
||||
value: 'message',
|
||||
},
|
||||
},
|
||||
],
|
||||
operation: 'and',
|
||||
},
|
||||
},
|
||||
sort: [
|
||||
{
|
||||
type: 'generic',
|
||||
arguments: {
|
||||
field: 'datePublished',
|
||||
},
|
||||
order: 'desc',
|
||||
},
|
||||
],
|
||||
size: size,
|
||||
from: from,
|
||||
};
|
||||
|
||||
if (typeof filters !== 'undefined') {
|
||||
for (const filter of filters) {
|
||||
(
|
||||
(query.filter as SCSearchBooleanFilter)
|
||||
.arguments as SCBooleanFilterArguments
|
||||
).filters.push(filter);
|
||||
}
|
||||
}
|
||||
|
||||
const result = await this.dataProvider.search(query);
|
||||
|
||||
return result.data as SCMessage[];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum EditModalTypeEnum {
|
||||
CHECKBOXES,
|
||||
RADIOBOXES,
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<ion-header translucent>
|
||||
<ion-toolbar mode="ios">
|
||||
<ion-title>{{ 'modal.settings' | translate }}</ion-title>
|
||||
<ion-button fill="clear" slot="end" (click)="dismissModal()">
|
||||
<ion-icon name="x"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content fullscreen>
|
||||
<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 reorderedItems">
|
||||
<ion-reorder slot="start"></ion-reorder>
|
||||
<ion-label>{{ item.label | translate }}</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.name }}</ion-label>
|
||||
<ion-radio slot="end" [value]="item.uid"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
</ng-container>
|
||||
</ion-content>
|
||||
<ion-footer class="ion-text-end">
|
||||
<ion-button fill="clear" class="ion-margin-end" (click)="onSaveClick()">{{
|
||||
'save' | translate
|
||||
}}</ion-button>
|
||||
<ion-button fill="clear" (click)="dismissModal()">{{
|
||||
'abort' | translate
|
||||
}}</ion-button>
|
||||
</ion-footer>
|
||||
@@ -0,0 +1,3 @@
|
||||
:host {
|
||||
--width: 100vw;
|
||||
}
|
||||
67
src/app/modules/dashboard/edit-modal/edit-modal.component.ts
Normal file
67
src/app/modules/dashboard/edit-modal/edit-modal.component.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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 {SCThings} from '@openstapps/core';
|
||||
import {MenuItemInterface} from '../sections/navigation-section/menu-item.interface';
|
||||
import {EditModalTypeEnum} from './edit-modal-type.enum';
|
||||
|
||||
/**
|
||||
* Shows a modal window to sort and enable/disable menu items
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-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: MenuItemInterface[] | SCThings[];
|
||||
|
||||
@Input() selectedValue: string;
|
||||
|
||||
reorderedItems: MenuItemInterface[] | SCThings[];
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
10
src/app/modules/dashboard/section/section.component.html
Normal file
10
src/app/modules/dashboard/section/section.component.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<ion-label class="section-headline"
|
||||
>{{ title }}
|
||||
<ion-icon name="edit" *ngIf="isEditable" (click)="onEditClick()"></ion-icon>
|
||||
<ion-icon
|
||||
[name]="customIcon"
|
||||
*ngIf="customIcon"
|
||||
(click)="onEditClick()"
|
||||
></ion-icon>
|
||||
</ion-label>
|
||||
<ng-content></ng-content>
|
||||
55
src/app/modules/dashboard/section/section.component.scss
Normal file
55
src/app/modules/dashboard/section/section.component.scss
Normal file
@@ -0,0 +1,55 @@
|
||||
@import '../../../../theme/util/mixins';
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
padding: var(--spacing-md) var(--spacing-md) var(--spacing-sm);
|
||||
|
||||
@include ion-md-up {
|
||||
padding: var(--spacing-lg) var(--spacing-xxl) var(--spacing-sm);
|
||||
}
|
||||
|
||||
&.is-editable ::ng-deep {
|
||||
.swiper-button-prev {
|
||||
right: 65px;
|
||||
}
|
||||
|
||||
.swiper-button-next {
|
||||
right: 35px;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-extended {
|
||||
padding-right: 0;
|
||||
ion-icon[name='edit'] {
|
||||
margin-right: var(--spacing-md);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
&:first-of-type {
|
||||
padding-top: var(--spacing-lg);
|
||||
}
|
||||
|
||||
& > ion-label:first-child {
|
||||
font-family: var(--headline-font-family);
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: var(--font-weight-bold);
|
||||
font-stretch: condensed;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: var(--spacing-xs);
|
||||
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: revert;
|
||||
justify-content: space-between;
|
||||
|
||||
ion-icon {
|
||||
color: var(--ion-color-medium-shade);
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
position: relative;
|
||||
bottom: var(--spacing-sm);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
59
src/app/modules/dashboard/section/section.component.ts
Normal file
59
src/app/modules/dashboard/section/section.component.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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,
|
||||
EventEmitter,
|
||||
HostBinding,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
} from '@angular/core';
|
||||
|
||||
/**
|
||||
* Shows a horizontal list of action chips
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-section',
|
||||
templateUrl: 'section.component.html',
|
||||
styleUrls: ['section.component.scss'],
|
||||
})
|
||||
export class SectionComponent implements OnInit {
|
||||
@HostBinding('class.is-extended') isExtendedClass = false;
|
||||
|
||||
@HostBinding('class.is-editable') isEditableClass = false;
|
||||
|
||||
@Input() title = '';
|
||||
|
||||
@Input() isSectionExtended = false;
|
||||
|
||||
@Input() isEditable = false;
|
||||
|
||||
@Input() customIcon?: string = undefined;
|
||||
|
||||
// eslint-disable-next-line @angular-eslint/no-output-on-prefix
|
||||
@Output() onEdit = new EventEmitter<void>();
|
||||
|
||||
ngOnInit() {
|
||||
this.isExtendedClass = this.isSectionExtended;
|
||||
this.isEditableClass = this.isEditable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action when edit is clicked
|
||||
*/
|
||||
onEditClick() {
|
||||
this.onEdit.emit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<stapps-section
|
||||
[title]="'dashboard.favorites.title' | translate"
|
||||
[isEditable]="true"
|
||||
(onEdit)="onSectionEdit()"
|
||||
>
|
||||
<div *ngIf="(items | async)?.length" class="container">
|
||||
<div
|
||||
*ngFor="let item of items | async"
|
||||
class="card clickable"
|
||||
(click)="notifySelect(item)"
|
||||
>
|
||||
<ion-thumbnail class="ion-margin-end">
|
||||
<ion-icon color="dark" [attr.name]="item.type | dataIcon"></ion-icon>
|
||||
</ion-thumbnail>
|
||||
<ion-label>
|
||||
{{ 'name' | thingTranslate: item }}
|
||||
</ion-label>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="!(items | async)?.length">
|
||||
<div class="card">
|
||||
<ion-label>
|
||||
{{ 'dashboard.favorites.no_favorite_prefix' | translate }}
|
||||
<a (click)="onSectionEdit()">{{
|
||||
'dashboard.favorites.no_favorite_link' | translate
|
||||
}}</a>
|
||||
{{ 'dashboard.favorites.no_favorite_suffix' | translate }}
|
||||
</ion-label>
|
||||
</div>
|
||||
</ng-container>
|
||||
</stapps-section>
|
||||
@@ -0,0 +1,36 @@
|
||||
.container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||
gap: var(--spacing-sm);
|
||||
--size: 60px;
|
||||
|
||||
& > * {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
min-height: var(--size);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
|
||||
ion-thumbnail {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
transform: translate(10%, 20%);
|
||||
margin: 0 auto var(--spacing-xs);
|
||||
--size: 60px;
|
||||
|
||||
ion-icon {
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
margin: auto;
|
||||
--ion-color-base: var(--ion-color-light-icon) !important;
|
||||
}
|
||||
}
|
||||
ion-label {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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, OnInit} from '@angular/core';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {AlertController} 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 {FavoritesService} from '../../../favorites/favorites.service';
|
||||
import {ContextMenuService} from '../../../menu/context/context-menu.service';
|
||||
import {ConfigProvider} from '../../../config/config.provider';
|
||||
|
||||
/**
|
||||
* Shows a section with meals of the chosen mensa
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-favorites-section',
|
||||
templateUrl: 'favorites-section.component.html',
|
||||
styleUrls: ['favorites-section.component.scss'],
|
||||
})
|
||||
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,
|
||||
) {
|
||||
super(
|
||||
alertController,
|
||||
dataProvider,
|
||||
contextMenuService,
|
||||
settingsProvider,
|
||||
logger,
|
||||
dataRoutingService,
|
||||
router,
|
||||
route,
|
||||
positionService,
|
||||
configProvider,
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Action when user clicked edit to this section
|
||||
*/
|
||||
onSectionEdit() {
|
||||
void this.router.navigate(['/search']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<swiper
|
||||
[config]="sliderOptions"
|
||||
[navigation]="true"
|
||||
class="mensa-swiper card-swiper"
|
||||
*ngIf="dishes && dishes.length > 0"
|
||||
>
|
||||
<ng-template swiperSlide *ngFor="let dish of dishes">
|
||||
<a [routerLink]="'/data-detail/' + dish.uid" class="card">
|
||||
<ion-label>{{ 'name' | thingTranslate: dish }}</ion-label>
|
||||
</a>
|
||||
</ng-template>
|
||||
</swiper>
|
||||
<div class="card" *ngIf="!dishes || dishes.length === 0">
|
||||
<ion-label>
|
||||
{{ 'dashboard.canteens.no_dishes_available' | translate }}
|
||||
</ion-label>
|
||||
</div>
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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,
|
||||
OnChanges,
|
||||
SimpleChanges,
|
||||
} from '@angular/core';
|
||||
import {SCDish, SCPlace, SCThings} from '@openstapps/core';
|
||||
import {PlaceMensaService} from '../../../data/types/place/special/mensa/place-mensa-service';
|
||||
|
||||
/**
|
||||
* Shows a section with meals of the chosen mensa
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-mensa-section-content',
|
||||
templateUrl: 'mensa-section-content.component.html',
|
||||
styleUrls: ['mensa-section.component.scss'],
|
||||
})
|
||||
export class MensaSectionContentComponent implements OnInit, OnChanges {
|
||||
/**
|
||||
* Slider options
|
||||
*/
|
||||
sliderOptions = {
|
||||
spaceBetween: 12,
|
||||
freeMode: {
|
||||
enabled: true,
|
||||
sticky: true,
|
||||
},
|
||||
width: 120,
|
||||
};
|
||||
|
||||
/**
|
||||
* Map of dishes for each day
|
||||
*/
|
||||
// eslint-disable-next-line unicorn/no-null
|
||||
dishes: SCDish[] | null = [];
|
||||
|
||||
@Input() items: SCThings[];
|
||||
|
||||
constructor(private readonly mensaService: PlaceMensaService) {}
|
||||
|
||||
async ngOnInit() {
|
||||
await this.getDishes();
|
||||
}
|
||||
|
||||
async ngOnChanges(changes: SimpleChanges) {
|
||||
if (typeof changes.items !== 'undefined') {
|
||||
await this.getDishes();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request dishes
|
||||
*/
|
||||
async getDishes() {
|
||||
if (this.items) {
|
||||
for (const item of this.items) {
|
||||
const dishes = await this.mensaService.getAllDishes(item as SCPlace, 1);
|
||||
this.dishes?.push(...dishes[Object.keys(dishes)[0]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<stapps-section
|
||||
[title]="'dashboard.canteens.title' | translate"
|
||||
[isEditable]="true"
|
||||
(onEdit)="onSectionEdit()"
|
||||
>
|
||||
<ng-container *ngIf="(items | async)?.length">
|
||||
<stapps-mensa-section-content
|
||||
[items]="items | async"
|
||||
></stapps-mensa-section-content>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!(items | async)?.length">
|
||||
<div class="card">
|
||||
<ion-label>
|
||||
{{ 'dashboard.canteens.no_favorite_prefix' | translate }}
|
||||
<a (click)="onSectionEdit()">{{
|
||||
'dashboard.canteens.no_favorite_link' | translate
|
||||
}}</a>
|
||||
{{ 'dashboard.canteens.no_favorite_suffix' | translate }}
|
||||
</ion-label>
|
||||
</div>
|
||||
</ng-container>
|
||||
</stapps-section>
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* 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} from '@angular/core';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {AlertController, 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 {FavoritesService} from '../../../favorites/favorites.service';
|
||||
import {ContextMenuService} from '../../../menu/context/context-menu.service';
|
||||
import {ConfigProvider} from '../../../config/config.provider';
|
||||
|
||||
/**
|
||||
* Shows a section with meals of the chosen mensa
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-mensa-section',
|
||||
templateUrl: 'mensa-section.component.html',
|
||||
styleUrls: ['mensa-section.component.scss'],
|
||||
})
|
||||
export class MensaSectionComponent extends FoodDataListComponent {
|
||||
sub: Subscription;
|
||||
|
||||
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,
|
||||
) {
|
||||
super(
|
||||
alertController,
|
||||
dataProvider,
|
||||
contextMenuService,
|
||||
settingsProvider,
|
||||
logger,
|
||||
dataRoutingService,
|
||||
router,
|
||||
route,
|
||||
positionService,
|
||||
configProvider,
|
||||
);
|
||||
}
|
||||
|
||||
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']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface MenuItemInterface {
|
||||
icon: string;
|
||||
label: string;
|
||||
link: string;
|
||||
active: boolean;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import {MenuItemInterface} from './menu-item.interface';
|
||||
|
||||
export const MenuItems: MenuItemInterface[] = [
|
||||
{
|
||||
icon: 'book',
|
||||
label: 'dashboard.navigation.item.catalog',
|
||||
link: '/catalog',
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
icon: 'tools-kitchen',
|
||||
label: 'dashboard.navigation.item.canteen',
|
||||
link: '/canteen',
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
icon: 'map',
|
||||
label: 'dashboard.navigation.item.map',
|
||||
link: '/map',
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
icon: 'settings',
|
||||
label: 'dashboard.navigation.item.settings',
|
||||
link: '/settings',
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
icon: 'search',
|
||||
label: 'dashboard.navigation.item.search',
|
||||
link: '/search',
|
||||
active: false,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,19 @@
|
||||
<stapps-section
|
||||
[title]="'dashboard.navigation.title' | translate"
|
||||
[isEditable]="true"
|
||||
[isSectionExtended]="true"
|
||||
(onEdit)="onSectionEdit()"
|
||||
>
|
||||
<swiper
|
||||
[config]="sliderOptions"
|
||||
slidesPerView="auto"
|
||||
class="navigation-swiper card-swiper"
|
||||
>
|
||||
<ng-template swiperSlide *ngFor="let menuItem of activeMenuItems">
|
||||
<a [routerLink]="menuItem.link" class="card">
|
||||
<ion-icon [name]="menuItem.icon"></ion-icon>
|
||||
<ion-label>{{ menuItem.label | translate }}</ion-label>
|
||||
</a>
|
||||
</ng-template>
|
||||
</swiper>
|
||||
</stapps-section>
|
||||
@@ -0,0 +1,21 @@
|
||||
.navigation-swiper.swiper {
|
||||
|
||||
.swiper-slide {
|
||||
|
||||
a {
|
||||
font-family: var(--ion-font-family);
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: var(--font-weight-semi-bold);
|
||||
text-align: center;
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
ion-icon {
|
||||
display: block;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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, OnInit, ViewEncapsulation} from '@angular/core';
|
||||
import {ModalController} from '@ionic/angular';
|
||||
import {EditModalComponent} from '../../edit-modal/edit-modal.component';
|
||||
import {MenuItems} from './menu-items.config';
|
||||
import {MenuItemInterface} from './menu-item.interface';
|
||||
import {EditModalTypeEnum} from '../../edit-modal/edit-modal-type.enum';
|
||||
import {StorageProvider} from '../../../storage/storage.provider';
|
||||
|
||||
const DASHBOARD_NAVIGATION = 'stapps.dashboard.navigation';
|
||||
|
||||
/**
|
||||
* Shows a horizontal list of navigation items
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-navigation-section',
|
||||
templateUrl: 'navigation-section.component.html',
|
||||
styleUrls: ['navigation-section.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
export class NavigationSectionComponent implements OnInit {
|
||||
/**
|
||||
* Slider options
|
||||
*/
|
||||
sliderOptions = {
|
||||
spaceBetween: 12,
|
||||
freeMode: {
|
||||
enabled: true,
|
||||
sticky: true,
|
||||
},
|
||||
width: 120,
|
||||
};
|
||||
|
||||
menuItems: MenuItemInterface[] = MenuItems;
|
||||
|
||||
activeMenuItems: MenuItemInterface[] = MenuItems;
|
||||
|
||||
constructor(
|
||||
public modalController: ModalController,
|
||||
private storageProvider: StorageProvider,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
void this.getItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current order of items
|
||||
*/
|
||||
async getItems() {
|
||||
if (await this.storageProvider.has(DASHBOARD_NAVIGATION)) {
|
||||
const storedMenuItems: string = await this.storageProvider.get(
|
||||
DASHBOARD_NAVIGATION,
|
||||
);
|
||||
if (storedMenuItems) {
|
||||
const parsedMenuItems = JSON.parse(storedMenuItems);
|
||||
if (Array.isArray(parsedMenuItems)) {
|
||||
this.menuItems = parsedMenuItems;
|
||||
this.activeMenuItems = parsedMenuItems.filter(item => item.active);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save updated order of items
|
||||
*
|
||||
* @param items List of items
|
||||
*/
|
||||
setItems(items: MenuItemInterface[]) {
|
||||
this.menuItems = items;
|
||||
this.activeMenuItems = items.filter(item => item.active);
|
||||
void this.storageProvider.put<string>(
|
||||
DASHBOARD_NAVIGATION,
|
||||
JSON.stringify(items),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Action when user clicked edit to this section
|
||||
*/
|
||||
async onSectionEdit() {
|
||||
const modal = await this.modalController.create({
|
||||
component: EditModalComponent,
|
||||
componentProps: {
|
||||
items: this.menuItems,
|
||||
type: EditModalTypeEnum.CHECKBOXES,
|
||||
},
|
||||
});
|
||||
await modal.present();
|
||||
|
||||
modal.onDidDismiss().then(result => {
|
||||
if (result.data?.items) {
|
||||
this.setItems(result.data.items);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<stapps-section
|
||||
[title]="'dashboard.news.title' | translate"
|
||||
[isEditable]="false"
|
||||
[customIcon]="'news'"
|
||||
class="is-editable"
|
||||
(onEdit)="onMoreNewsClicked()"
|
||||
>
|
||||
<swiper
|
||||
[config]="sliderOptions"
|
||||
slidesPerView="auto"
|
||||
[navigation]="true"
|
||||
*ngIf="news.length > 0"
|
||||
class="news-swiper card-swiper"
|
||||
>
|
||||
<ng-template swiperSlide *ngFor="let newsItem of news">
|
||||
<a [routerLink]="['/data-detail', newsItem.uid]" class="card">
|
||||
<ion-img [src]="newsItem.image"></ion-img>
|
||||
<ion-label>{{ newsItem.name }}</ion-label>
|
||||
</a>
|
||||
</ng-template>
|
||||
<ng-template swiperSlide>
|
||||
<a [routerLink]="['/news']" class="card more-news">
|
||||
<ion-label>{{
|
||||
'dashboard.news.moreNews' | translate | titlecase
|
||||
}}</ion-label>
|
||||
<ion-thumbnail class="ion-margin-end">
|
||||
<ion-icon color="dark" name="news"></ion-icon>
|
||||
</ion-thumbnail>
|
||||
</a>
|
||||
</ng-template>
|
||||
</swiper>
|
||||
</stapps-section>
|
||||
@@ -0,0 +1,48 @@
|
||||
.news-swiper.swiper {
|
||||
|
||||
.swiper-slide {
|
||||
padding: 0;
|
||||
|
||||
.card {
|
||||
padding: 0;
|
||||
|
||||
ion-img {
|
||||
border-radius: var(--border-radius-default) var(--border-radius-default) 0 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
ion-label {
|
||||
margin: var(--spacing-lg);
|
||||
text-align: left;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-bold);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&.more-news {
|
||||
ion-label {
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
ion-thumbnail {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
margin: 0 auto var(--spacing-xs);
|
||||
--size: 160px;
|
||||
|
||||
ion-icon {
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
margin: auto;
|
||||
--ion-color-base: var(--ion-color-light-icon) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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, OnInit, ViewEncapsulation} from '@angular/core';
|
||||
import {Router} from '@angular/router';
|
||||
import {NewsPageComponent} from '../../../news/page/news-page.component';
|
||||
import {NewsProvider} from '../../../news/news.provider';
|
||||
import {SettingsProvider} from '../../../settings/settings.provider';
|
||||
import {
|
||||
newsFilterSettingsFieldsMapping,
|
||||
NewsFilterSettingsNames,
|
||||
} from '../../../news/news-filter-settings';
|
||||
import {DataProvider} from '../../../data/data.provider';
|
||||
import {SCSearchValueFilter} from '@openstapps/core';
|
||||
|
||||
/**
|
||||
* Shows a section with news
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-news-section',
|
||||
templateUrl: 'news-section.component.html',
|
||||
styleUrls: ['news-section.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
export class NewsSectionComponent extends NewsPageComponent implements OnInit {
|
||||
/**
|
||||
* Slider options
|
||||
*/
|
||||
sliderOptions = {
|
||||
spaceBetween: 12,
|
||||
freeMode: {
|
||||
enabled: true,
|
||||
sticky: true,
|
||||
},
|
||||
width: 240,
|
||||
breakpoints: {
|
||||
768: {
|
||||
width: 280,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
pageSize = 5;
|
||||
|
||||
/**
|
||||
* A map of the filters where the keys are settings names
|
||||
*/
|
||||
filtersMap = new Map<NewsFilterSettingsNames, SCSearchValueFilter>();
|
||||
|
||||
constructor(
|
||||
newsProvider: NewsProvider,
|
||||
settingsProvider: SettingsProvider,
|
||||
private router: Router,
|
||||
) {
|
||||
super(newsProvider, settingsProvider);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
await super.ngOnInit();
|
||||
for (const setting of this.settings) {
|
||||
this.filtersMap.set(
|
||||
setting.name as NewsFilterSettingsNames,
|
||||
DataProvider.createValueFilter(
|
||||
newsFilterSettingsFieldsMapping[
|
||||
setting.name as NewsFilterSettingsNames
|
||||
],
|
||||
setting.value as string,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
this.filters = [...this.filtersMap.values()];
|
||||
|
||||
try {
|
||||
await this.fetchNews();
|
||||
} catch {
|
||||
this.news = [];
|
||||
}
|
||||
}
|
||||
|
||||
onMoreNewsClicked() {
|
||||
void this.router.navigate(['/news']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<stapps-section
|
||||
title="{{ 'dashboard.navigation.item.search' | translate }}"
|
||||
[isEditable]="false"
|
||||
>
|
||||
<div class="searchbar">
|
||||
<ion-input
|
||||
type="search"
|
||||
placeholder="{{ 'search.search_bar.placeholder' | translate }}"
|
||||
(submit)="onSubmitSearch()"
|
||||
(keyup.enter)="onSubmitSearch()"
|
||||
[(ngModel)]="searchTerm"
|
||||
></ion-input>
|
||||
<ion-icon
|
||||
name="search"
|
||||
(click)="onSubmitSearch()"
|
||||
class="clickable"
|
||||
></ion-icon>
|
||||
</div>
|
||||
</stapps-section>
|
||||
@@ -0,0 +1,24 @@
|
||||
.searchbar {
|
||||
position: relative;
|
||||
max-width: 700px;
|
||||
|
||||
ion-input {
|
||||
background: var(--ion-color-field-bg);
|
||||
border-radius: var(--border-radius-default);
|
||||
--padding-start: var(--spacing-md);
|
||||
--padding-end: var(--spacing-md);
|
||||
--padding-top: var(--spacing-xl);
|
||||
--padding-bottom: var(--spacing-xl);
|
||||
font-size: var(--font-size-xs);
|
||||
box-shadow: var(--shadow-default);
|
||||
}
|
||||
ion-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: var(--spacing-md);
|
||||
transform: translateY(-50%);
|
||||
z-index: 2;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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} from '@angular/core';
|
||||
import {Router} from '@angular/router';
|
||||
|
||||
/**
|
||||
* Shows a search input field
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-search-section',
|
||||
templateUrl: 'search-section.component.html',
|
||||
styleUrls: ['search-section.component.scss'],
|
||||
})
|
||||
export class SearchSectionComponent {
|
||||
searchTerm = '';
|
||||
|
||||
constructor(private router: Router) {}
|
||||
|
||||
/**
|
||||
* User submits search
|
||||
*/
|
||||
onSubmitSearch() {
|
||||
this.router.navigate(['/search', this.searchTerm]);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
:host ::ng-deep ion-skeleton-text {
|
||||
:host {
|
||||
display: block;
|
||||
padding: var(--spacing-sm);
|
||||
|
||||
::ng-deep ion-skeleton-text {
|
||||
width: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.stack-children {
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
[attr.color]="active ? 'primary' : 'medium'"
|
||||
(click)="emitToggle(value)"
|
||||
>
|
||||
<ion-icon name="checkmark-circle" *ngIf="active"></ion-icon>
|
||||
<ion-icon name="circle-check" *ngIf="active"></ion-icon>
|
||||
<ion-label>{{ displayValue }}</ion-label>
|
||||
</ion-chip>
|
||||
|
||||
@@ -30,33 +30,33 @@ export class DataIconPipe implements PipeTransform {
|
||||
constructor() {
|
||||
this.typeIconMap = {
|
||||
'academic event': 'school',
|
||||
'assessment': 'document-text',
|
||||
'article': 'document',
|
||||
'assessment': 'file-text',
|
||||
'article': 'file',
|
||||
'book': 'book',
|
||||
'building': 'location',
|
||||
'catalog': 'folder',
|
||||
'contact point': 'call',
|
||||
'contact point': 'phone',
|
||||
'course of study': 'school',
|
||||
'date series': 'calendar',
|
||||
'dish': 'restaurant',
|
||||
'dish': 'tools-kitchen-2',
|
||||
'favorite': 'heart',
|
||||
'floor': 'caret-up-circle',
|
||||
'message': 'newspaper',
|
||||
'floor': 'arrow-up-circle',
|
||||
'message': 'news',
|
||||
'organization': 'briefcase',
|
||||
'periodical': 'newspaper',
|
||||
'person': 'person',
|
||||
'periodical': 'news',
|
||||
'person': 'user',
|
||||
'point of interest': 'location',
|
||||
'publication event': 'megaphone',
|
||||
'publication event': 'speakerphone',
|
||||
'room': 'location',
|
||||
'semester': 'school',
|
||||
'setting': 'settings',
|
||||
'sport course': 'football',
|
||||
'sport course': 'ball-football',
|
||||
'study module': 'school',
|
||||
'ticket': 'ticket',
|
||||
'todo': 'checkbox',
|
||||
'tour': 'help-buoy',
|
||||
'video': 'videocam',
|
||||
'diff': 'swap-horizontal',
|
||||
'tour': 'lifebuoy',
|
||||
'video': 'video',
|
||||
'diff': 'arrows-left-right',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import {SearchPageComponent} from './list/search-page.component';
|
||||
|
||||
const dataRoutes: Routes = [
|
||||
{path: 'search', component: SearchPageComponent},
|
||||
{path: 'search/:term', component: SearchPageComponent},
|
||||
{path: 'data-detail/:uid', component: DataDetailComponent},
|
||||
{path: 'canteen', component: FoodDataListComponent},
|
||||
];
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
import {Component, Input, TemplateRef} from '@angular/core';
|
||||
import {SCThings} from '@openstapps/core';
|
||||
import {DataListContext} from '../list/data-list.component';
|
||||
import {ModalController} from '@ionic/angular';
|
||||
|
||||
/**
|
||||
* TODO
|
||||
@@ -31,4 +32,10 @@ export class DataDetailContentComponent {
|
||||
@Input() item: SCThings;
|
||||
|
||||
@Input() contentTemplateRef?: TemplateRef<DataListContext<SCThings>>;
|
||||
|
||||
@Input() openAsModal = false;
|
||||
|
||||
@Input() showModalHeader = false;
|
||||
|
||||
constructor(readonly modalController: ModalController) {}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
<stapps-title-card [item]="item"> </stapps-title-card>
|
||||
<ion-header *ngIf="showModalHeader" translucent>
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
<ion-title>{{ 'name' | thingTranslate: item }}</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button (click)="modalController.dismiss()">{{
|
||||
'app.ui.CLOSE' | translate
|
||||
}}</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<stapps-title-card *ngIf="!showModalHeader" [item]="item"> </stapps-title-card>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
contentTemplateRef || defaultContent;
|
||||
@@ -60,6 +71,7 @@
|
||||
></stapps-place-detail-content>
|
||||
<stapps-place-detail-content
|
||||
[item]="$any(item)"
|
||||
[openAsModal]="openAsModal"
|
||||
*ngSwitchCase="'room'"
|
||||
></stapps-place-detail-content>
|
||||
<stapps-semester-detail-content
|
||||
@@ -73,10 +85,7 @@
|
||||
<ng-container *ngSwitchDefault>
|
||||
<ion-item class="ion-text-wrap" lines="inset">
|
||||
<ion-thumbnail slot="start" class="ion-margin-end">
|
||||
<ion-icon
|
||||
color="medium"
|
||||
[attr.name]="item.type | dataIcon"
|
||||
></ion-icon>
|
||||
<ion-icon color="dark" [attr.name]="item.type | dataIcon"></ion-icon>
|
||||
</ion-thumbnail>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
|
||||
@@ -31,4 +31,22 @@
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
> div {
|
||||
padding: 0 var(--spacing-md);
|
||||
background-color: var(--ion-color-light);
|
||||
|
||||
& > * {
|
||||
display: block;
|
||||
border-radius: var(--border-radius-default);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
top: calc((var(--header-spacing-bottom) - var(--spacing-xl)) * -1);
|
||||
background-color: var(--ion-color-primary-contrast);
|
||||
|
||||
& > ion-thumbnail {
|
||||
background: var(--ion-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ export class DataDetailComponent implements ViewWillEnter {
|
||||
|
||||
this.isDisconnected = new Promise(async resolve => {
|
||||
const isConnected = (await Network.getStatus()).connected;
|
||||
resolve(isConnected);
|
||||
resolve(!isConnected);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ export class DataDetailComponent implements ViewWillEnter {
|
||||
.get(uid)
|
||||
.pipe(take(1))
|
||||
.subscribe(item => {
|
||||
if (typeof item !== undefined) {
|
||||
if (typeof item !== 'undefined') {
|
||||
this.item = item;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -14,18 +14,12 @@
|
||||
-->
|
||||
|
||||
<ion-header *ngIf="defaultHeader">
|
||||
<ion-toolbar color="primary">
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
<ion-buttons slot="start" *ngIf="!isModal">
|
||||
<ion-back-button
|
||||
[defaultHref]="
|
||||
item?.superCatalog
|
||||
? ['/data-detail', item.superCatalog.uid]
|
||||
: item?.catalogs && item?.catalogs.length === 1
|
||||
? ['/data-detail', item.catalogs[0].uid]
|
||||
: undefined
|
||||
"
|
||||
[defaultHref]="'..'"
|
||||
[text]="'back' | translate | titlecase"
|
||||
></ion-back-button>
|
||||
<ion-menu-button></ion-menu-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{ 'data.detail.TITLE' | translate }}</ion-title>
|
||||
<ion-buttons [slot]="isModal ? 'start' : 'primary'">
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
}}</ng-container>
|
||||
</h1>
|
||||
</ion-text>
|
||||
<div *ngIf="item.description">
|
||||
<div *ngIf="item.description" class="description">
|
||||
<br />
|
||||
<div
|
||||
class="text-accordion"
|
||||
|
||||
@@ -19,3 +19,22 @@
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
|
||||
ion-card {
|
||||
border-radius: 0;
|
||||
--background: var(--ion-color-primary);
|
||||
padding: 0 var(--spacing-md);
|
||||
|
||||
ion-card-content {
|
||||
padding: var(--spacing-sm) 0 var(--header-spacing-bottom);
|
||||
|
||||
h1 {
|
||||
color: var(--ion-color-primary-contrast);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
.description * {
|
||||
color: var(--ion-color-light);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ enum AccordionButtonState {
|
||||
@Component({
|
||||
selector: 'stapps-title-card',
|
||||
templateUrl: './title-card.component.html',
|
||||
styleUrls: ['./title-card.component.css'],
|
||||
styleUrls: ['./title-card.component.scss'],
|
||||
})
|
||||
export class TitleCardComponent implements OnInit, OnChanges {
|
||||
/**
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<ion-item
|
||||
class="ion-text-wrap"
|
||||
class="ion-text-wrap ion-margin"
|
||||
button="true"
|
||||
lines="inset"
|
||||
detail="false"
|
||||
(click)="notifySelect()"
|
||||
>
|
||||
<div class="item-height-placeholder"></div>
|
||||
<ion-thumbnail slot="start" *ngIf="!hideThumbnail" class="ion-margin-end">
|
||||
<ion-icon color="medium" [attr.name]="item.type | dataIcon"></ion-icon>
|
||||
<ion-icon color="dark" [attr.name]="item.type | dataIcon"></ion-icon>
|
||||
</ion-thumbnail>
|
||||
<ng-container *ngIf="contentTemplateRef; else defaultContent">
|
||||
<ion-label class="ion-text-wrap" [ngSwitch]="true">
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
:host ::ng-deep {
|
||||
ion-item {
|
||||
--border-color: transparent;
|
||||
border-radius: var(--border-radius-default);
|
||||
overflow: hidden;
|
||||
--inner-padding-end: 0;
|
||||
|
||||
ion-thumbnail {
|
||||
--ion-margin: var(--spacing-xs);
|
||||
}
|
||||
|
||||
ion-label {
|
||||
width: 100%;
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
|
||||
cdk-virtual-scroll-viewport {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
ion-list {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep {
|
||||
|
||||
@@ -20,8 +20,13 @@ import {SearchPageComponent} from './search-page.component';
|
||||
*/
|
||||
@Component({
|
||||
templateUrl: 'search-page.html',
|
||||
styleUrls: ['../../data/list/search-page.scss'],
|
||||
})
|
||||
export class FoodDataListComponent extends SearchPageComponent {
|
||||
title = 'canteens.title';
|
||||
|
||||
showNavigation = false;
|
||||
|
||||
/**
|
||||
* Sets the forced filter to present only places for eating/drinking
|
||||
*/
|
||||
|
||||
@@ -13,11 +13,12 @@
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component, Input, OnInit, OnDestroy} from '@angular/core';
|
||||
import {Router} from '@angular/router';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {Keyboard} from '@capacitor/keyboard';
|
||||
import {AlertController} from '@ionic/angular';
|
||||
import {
|
||||
SCFacet,
|
||||
SCFeatureConfiguration,
|
||||
SCSearchFilter,
|
||||
SCSearchQuery,
|
||||
SCSearchSort,
|
||||
@@ -31,6 +32,7 @@ import {SettingsProvider} from '../../settings/settings.provider';
|
||||
import {DataRoutingService} from '../data-routing.service';
|
||||
import {DataProvider} from '../data.provider';
|
||||
import {PositionService} from '../../map/position.service';
|
||||
import {ConfigProvider} from '../../config/config.provider';
|
||||
|
||||
/**
|
||||
* SearchPageComponent queries things and shows list of things as search results and filter as context menu
|
||||
@@ -38,14 +40,24 @@ import {PositionService} from '../../map/position.service';
|
||||
@Component({
|
||||
selector: 'stapps-search-page',
|
||||
templateUrl: 'search-page.html',
|
||||
styleUrls: ['search-page.scss'],
|
||||
providers: [ContextMenuService],
|
||||
})
|
||||
export class SearchPageComponent implements OnInit, OnDestroy {
|
||||
title = 'search.title';
|
||||
|
||||
isHebisAvailable = false;
|
||||
|
||||
/**
|
||||
* Signalizes that the data is being loaded
|
||||
*/
|
||||
loading = false;
|
||||
|
||||
/**
|
||||
* Display the navigation between default and library search
|
||||
*/
|
||||
@Input() showNavigation = true;
|
||||
|
||||
/**
|
||||
* Show default data (e.g. when there is user interaction)
|
||||
*/
|
||||
@@ -56,6 +68,11 @@ export class SearchPageComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
@Input() showDrawer = true;
|
||||
|
||||
/**
|
||||
* Show "universal search" toolbar
|
||||
*/
|
||||
@Input() showTopToolbar = true;
|
||||
|
||||
/**
|
||||
* Api query filter
|
||||
*/
|
||||
@@ -131,7 +148,9 @@ export class SearchPageComponent implements OnInit, OnDestroy {
|
||||
* @param logger An angular logger
|
||||
* @param dataRoutingService DataRoutingService
|
||||
* @param router Router
|
||||
* @param route ActivatedRoute
|
||||
* @param positionService PositionService
|
||||
* @param configProvider ConfigProvider
|
||||
*/
|
||||
constructor(
|
||||
protected readonly alertController: AlertController,
|
||||
@@ -141,7 +160,9 @@ export class SearchPageComponent implements OnInit, OnDestroy {
|
||||
protected readonly logger: NGXLogger,
|
||||
protected dataRoutingService: DataRoutingService,
|
||||
protected router: Router,
|
||||
private readonly route: ActivatedRoute,
|
||||
protected positionService: PositionService,
|
||||
private readonly configProvider: ConfigProvider,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -314,10 +335,29 @@ export class SearchPageComponent implements OnInit, OnDestroy {
|
||||
),
|
||||
this.dataRoutingService.itemSelectListener().subscribe(item => {
|
||||
if (this.itemRouting) {
|
||||
void this.router.navigate(['data-detail', item.uid]);
|
||||
void this.router.navigate(['/data-detail', item.uid]);
|
||||
}
|
||||
}),
|
||||
);
|
||||
try {
|
||||
const features = this.configProvider.getValue(
|
||||
'features',
|
||||
) as SCFeatureConfiguration;
|
||||
this.isHebisAvailable = !!features.plugins?.['hebis-plugin']?.urlPath;
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize
|
||||
*/
|
||||
async ionViewWillEnter() {
|
||||
const term = this.route.snapshot.paramMap.get('term') || undefined;
|
||||
if (term) {
|
||||
this.queryText = term;
|
||||
this.searchStringChanged(term);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
||||
@@ -1,29 +1,51 @@
|
||||
<stapps-context contentId="data-list"></stapps-context>
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary">
|
||||
<ion-toolbar color="primary" mode="ios" *ngIf="showDrawer">
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button></ion-back-button>
|
||||
<ion-menu-button *ngIf="showDrawer"></ion-menu-button>
|
||||
</ion-buttons>
|
||||
<ion-buttons slot="end">
|
||||
<ion-menu-button menu="context" auto-hide="false">
|
||||
<ion-icon name="options"></ion-icon>
|
||||
</ion-menu-button>
|
||||
<ion-back-button
|
||||
[defaultHref]="'..'"
|
||||
[text]="'back' | translate | titlecase"
|
||||
></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{ title | translate }}</ion-title>
|
||||
</ion-toolbar>
|
||||
<ion-toolbar color="primary">
|
||||
<ion-searchbar
|
||||
(ngModelChange)="searchStringChanged($event)"
|
||||
(keyup.enter)="hideKeyboard()"
|
||||
[(ngModel)]="queryText"
|
||||
placeholder="{{ 'search.search_bar.placeholder' | translate }}"
|
||||
showClearButton="always"
|
||||
placeholder="{{ 'search.search_bar.placeholder' | translate }}"
|
||||
mode="md"
|
||||
type="search"
|
||||
enterkeyhint="search"
|
||||
class="filterable"
|
||||
>
|
||||
<ion-menu-button menu="context" auto-hide="false">
|
||||
<ion-icon name="adjustments"></ion-icon>
|
||||
</ion-menu-button>
|
||||
</ion-searchbar>
|
||||
</ion-toolbar>
|
||||
<ion-toolbar
|
||||
color="primary"
|
||||
class="category-tab"
|
||||
*ngIf="showNavigation && isHebisAvailable"
|
||||
>
|
||||
<ion-buttons class="ion-justify-content-between">
|
||||
<ion-button class="button-active" size="large">{{
|
||||
'search.type' | translate
|
||||
}}</ion-button>
|
||||
<ion-button
|
||||
[routerLink]="['/hebis-search/' + (queryText || '')]"
|
||||
fill="outline"
|
||||
size="large"
|
||||
>{{ 'hebisSearch.type' | translate }}
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<ion-content class="content">
|
||||
<div
|
||||
[style.display]="!showDefaultData && !items && !loading ? 'block' : 'none'"
|
||||
>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
ion-toolbar:first-of-type {
|
||||
padding: 0 var(--spacing-md) var(--spacing-xs);
|
||||
}
|
||||
|
||||
.category-tab {
|
||||
ion-buttons {
|
||||
gap: var(--spacing-md);
|
||||
|
||||
ion-button {
|
||||
width: 50%;
|
||||
margin: 0;
|
||||
text-transform: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ion-content {
|
||||
--background: var(--ion-color-light);
|
||||
}
|
||||
|
||||
.content > div {
|
||||
height: 100%;
|
||||
|
||||
ion-label.centeredMessageContainer {
|
||||
min-height: unset;
|
||||
height: 100%;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ export class SimpleDataListComponent implements OnInit, OnDestroy {
|
||||
if (!this.autoRouting) return;
|
||||
this.subscriptions.push(
|
||||
this.dataRoutingService.itemSelectListener().subscribe(item => {
|
||||
void this.router.navigate(['data-detail', item.uid]);
|
||||
void this.router.navigate(['/data-detail', item.uid]);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<h2 class="name">{{ 'name' | thingTranslate: item }}</h2>
|
||||
<ion-label class="title">{{ 'name' | thingTranslate: item }}</ion-label>
|
||||
<p *ngIf="item.keywords">
|
||||
<stapps-long-inline-text
|
||||
[text]="item.keywords.join(', ')"
|
||||
|
||||
@@ -2,14 +2,12 @@
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<div class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2>
|
||||
<ion-label class="title">
|
||||
{{ 'name' | thingTranslate: item }}
|
||||
</h2>
|
||||
</ion-label>
|
||||
<ion-note *ngIf="item.academicTerm">{{
|
||||
item.academicTerm.name
|
||||
}}</ion-note>
|
||||
<p *ngIf="item.academicTerm" class="title-sub">
|
||||
{{ item.academicTerm.name }}
|
||||
</p>
|
||||
</div>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<div class="ion-text-wrap">
|
||||
<h2 class="name">{{ 'name' | thingTranslate: item }}</h2>
|
||||
<ion-label class="title">{{ 'name' | thingTranslate: item }}</ion-label>
|
||||
<p>
|
||||
<ion-icon name="calendar"></ion-icon>
|
||||
<span *ngIf="item.dates[0] && item.dates[item.dates.length - 1]">
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<div class="ion-text-wrap">
|
||||
<h2 class="name">{{ 'name' | thingTranslate: item }}</h2>
|
||||
<p class="ion-hide-sm-down">
|
||||
<ion-label class="title">{{ 'name' | thingTranslate: item }}</ion-label>
|
||||
<p class="title-sub ion-hide-sm-down">
|
||||
{{ 'description' | thingTranslate: item }}
|
||||
</p>
|
||||
<p>{{ 'categories' | thingTranslate: item | join: ', ' }}</p>
|
||||
|
||||
@@ -2,18 +2,22 @@
|
||||
<ion-row *ngIf="item.type === 'academic event'">
|
||||
<ion-col>
|
||||
<div class="ion-text-wrap">
|
||||
<h2 class="name">{{ item.name }}</h2>
|
||||
<p *ngIf="item.description">{{ item.description }}</p>
|
||||
<p *ngIf="item.academicTerms">{{ item.academicTerms[0].name }}</p>
|
||||
<ion-label class="title">{{ item.name }}</ion-label>
|
||||
<p *ngIf="item.description" class="title-sub">{{ item.description }}</p>
|
||||
<p *ngIf="item.academicTerms" class="title-sub">
|
||||
{{ item.academicTerms[0].name }}
|
||||
</p>
|
||||
<ion-note>{{ item.type }} ({{ item.categories.join(', ') }})</ion-note>
|
||||
</div>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
<ion-row *ngIf="item.type === 'sport course'">
|
||||
<ion-col>
|
||||
<h2 class="name">{{ item.name }}</h2>
|
||||
<p *ngIf="item.description">{{ item.description }}</p>
|
||||
<p *ngIf="item.academicTerms">{{ item.academicTerms[0].name }}</p>
|
||||
<ion-label class="title">{{ item.name }}</ion-label>
|
||||
<p *ngIf="item.description" class="title-sub">{{ item.description }}</p>
|
||||
<p *ngIf="item.academicTerms" class="title-sub">
|
||||
{{ item.academicTerms[0].name }}
|
||||
</p>
|
||||
<ion-note>{{ item.type }}</ion-note>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<div class="ion-text-wrap">
|
||||
<h2 class="name">
|
||||
<ion-label class="title">
|
||||
{{ 'name' | thingTranslate: item }}:
|
||||
{{ 'name' | thingTranslate: item.data }}
|
||||
</h2>
|
||||
<p *ngIf="item.data.description">
|
||||
</ion-label>
|
||||
<p *ngIf="item.data.description" class="title-sub">
|
||||
<stapps-long-inline-text
|
||||
[text]="'description' | thingTranslate: item.data"
|
||||
[size]="80"
|
||||
|
||||
@@ -35,7 +35,10 @@
|
||||
</ion-thumbnail>
|
||||
<stapps-simple-card
|
||||
*ngIf="item.datePublished"
|
||||
content="{{ item.datePublished | amCalendar | sentencecase }}"
|
||||
class="date-published"
|
||||
content="{{ 'news.publishedOn' | translate | titlecase }} {{
|
||||
item.datePublished | amCalendar | sentencecase
|
||||
}}"
|
||||
></stapps-simple-card>
|
||||
<stapps-simple-card content="{{ item.messageBody }}"></stapps-simple-card>
|
||||
<ion-card *ngIf="item.sameAs">
|
||||
@@ -44,7 +47,7 @@
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<a (click)="onLinkClick(item.sameAs)"
|
||||
>{{ item.name }}<ion-icon name="open-outline"></ion-icon>
|
||||
>{{ item.name }}<ion-icon name="external-link"></ion-icon>
|
||||
</a>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
|
||||
@@ -36,4 +36,10 @@
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.date-published {
|
||||
--ion-card-color: var(--ion-color-medium-shade);
|
||||
text-transform: uppercase;
|
||||
font-weight: var(--font-weight-semi-bold);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<div class="ion-text-wrap">
|
||||
<h2 class="name">{{ 'name' | thingTranslate: item }}</h2>
|
||||
<ion-label class="title">{{ 'name' | thingTranslate: item }}</ion-label>
|
||||
<p *ngIf="item.messageBody">
|
||||
<stapps-long-inline-text
|
||||
[text]="item.messageBody"
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<div class="ion-text-wrap">
|
||||
<h2 class="name">{{ 'name' | thingTranslate: item }}</h2>
|
||||
<p *ngIf="item.description">
|
||||
<ion-label class="title">{{ 'name' | thingTranslate: item }}</ion-label>
|
||||
<p *ngIf="item.description" class="title-sub">
|
||||
{{ 'description' | thingTranslate: item }}
|
||||
</p>
|
||||
<ion-note>{{ 'type' | thingTranslate: item }}</ion-note>
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<div class="ion-text-wrap">
|
||||
<h2 class="name">
|
||||
<ion-label class="title">
|
||||
{{ 'name' | thingTranslate: item }}
|
||||
<span *ngIf="item.honorificPrefix">, {{ item.honorificPrefix }}</span>
|
||||
</h2>
|
||||
</ion-label>
|
||||
<p *ngIf="item.telephone || item.email">
|
||||
<span *ngIf="item.telephone">
|
||||
<ion-icon name="call"></ion-icon> {{
|
||||
|
||||
@@ -38,6 +38,8 @@ export class PlaceDetailContentComponent implements OnInit {
|
||||
*/
|
||||
@Input() item: SCBuilding | SCRoom | SCPointOfInterest | SCFloor;
|
||||
|
||||
@Input() openAsModal = false;
|
||||
|
||||
/**
|
||||
* Does it have valid location or not (for showing in in a map widget)
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<stapps-place-mensa-detail-content
|
||||
[item]="item"
|
||||
[openAsModal]="openAsModal"
|
||||
*ngIf="isMensaThing(item)"
|
||||
></stapps-place-mensa-detail-content>
|
||||
<ng-container *ngIf="item.type !== 'floor'">
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<div class="ion-text-wrap">
|
||||
<ion-label>{{ 'name' | thingTranslate: item }}</ion-label>
|
||||
<ion-label class="title">{{ 'name' | thingTranslate: item }}</ion-label>
|
||||
<ng-container *ngIf="item.type !== 'floor'">
|
||||
<p>
|
||||
<ion-note *ngIf="item.openingHours">
|
||||
<p class="title-sub" *ngIf="item.openingHours">
|
||||
{{ item.openingHours | openingHours }}
|
||||
</ion-note>
|
||||
</p>
|
||||
<p>
|
||||
<ion-note
|
||||
|
||||
@@ -18,9 +18,10 @@ import moment, {Moment} from 'moment';
|
||||
import {AfterViewInit, Component, Input, OnDestroy} from '@angular/core';
|
||||
import {SCDish, SCISO8601Date, SCPlace} from '@openstapps/core';
|
||||
import {PlaceMensaService} from './place-mensa-service';
|
||||
import {DataRoutingService} from '../../../../data-routing.service';
|
||||
import {Router} from '@angular/router';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {IonRouterOutlet} from '@ionic/angular';
|
||||
import {DataRoutingService} from '../../../../data-routing.service';
|
||||
|
||||
/**
|
||||
* TODO
|
||||
@@ -48,6 +49,8 @@ export class PlaceMensaDetailComponent implements AfterViewInit, OnDestroy {
|
||||
*/
|
||||
@Input() item: SCPlace;
|
||||
|
||||
@Input() openAsModal = false;
|
||||
|
||||
/**
|
||||
* The currently selected day
|
||||
*/
|
||||
@@ -66,6 +69,7 @@ export class PlaceMensaDetailComponent implements AfterViewInit, OnDestroy {
|
||||
constructor(
|
||||
private readonly mensaService: PlaceMensaService,
|
||||
protected router: Router,
|
||||
readonly routerOutlet: IonRouterOutlet,
|
||||
private readonly dataRoutingService: DataRoutingService,
|
||||
) {
|
||||
this.startingDay = moment().startOf('day');
|
||||
@@ -84,12 +88,14 @@ export class PlaceMensaDetailComponent implements AfterViewInit, OnDestroy {
|
||||
}
|
||||
this.selectedDay = Object.keys(result)[0];
|
||||
});
|
||||
if (!this.openAsModal) {
|
||||
this.subscriptions.push(
|
||||
this.dataRoutingService.itemSelectListener().subscribe(item => {
|
||||
void this.router.navigate(['data-detail', item.uid]);
|
||||
void this.router.navigate(['/data-detail', item.uid]);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove subscriptions when the component is removed
|
||||
|
||||
@@ -16,10 +16,27 @@
|
||||
<div [ngSwitch]="selectedDay">
|
||||
<div *ngFor="let date of dishes | keyvalue">
|
||||
<ion-list *ngSwitchCase="date.key">
|
||||
<ng-container *ngFor="let dish of date.value; index as i">
|
||||
<stapps-data-list-item
|
||||
[item]="dish"
|
||||
*ngFor="let dish of date.value"
|
||||
[hideThumbnail]="true"
|
||||
[id]="'show-dish-in-modal-' + i"
|
||||
></stapps-data-list-item>
|
||||
<ion-modal
|
||||
*ngIf="openAsModal"
|
||||
[trigger]="'show-dish-in-modal-' + i"
|
||||
swipe-to-close="true"
|
||||
[presentingElement]="routerOutlet.nativeEl"
|
||||
class="modal-large"
|
||||
>
|
||||
<ng-template>
|
||||
<stapps-data-detail-content
|
||||
[item]="dish"
|
||||
[showModalHeader]="true"
|
||||
></stapps-data-detail-content>
|
||||
</ng-template>
|
||||
</ion-modal>
|
||||
</ng-container>
|
||||
</ion-list>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<div class="ion-text-wrap">
|
||||
<h2 class="name">{{ 'name' | thingTranslate: item }}</h2>
|
||||
<p>
|
||||
<ion-label class="title">{{ 'name' | thingTranslate: item }}</ion-label>
|
||||
<p class="title-sub">
|
||||
<ion-icon name="calendar"></ion-icon>
|
||||
<span
|
||||
>{{ item.startDate | dateFormat }} -
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<div class="ion-text-wrap">
|
||||
<h2 class="name">{{ 'name' | thingTranslate: item }}</h2>
|
||||
<p *ngIf="item.description">
|
||||
<ion-label class="title">{{ 'name' | thingTranslate: item }}</ion-label>
|
||||
<p *ngIf="item.description" class="title-sub">
|
||||
<stapps-long-inline-text
|
||||
[text]="'description' | thingTranslate: item"
|
||||
[size]="80"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
*/
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {AlertController} from '@ionic/angular';
|
||||
import {Router} from '@angular/router';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {NGXLogger} from 'ngx-logger';
|
||||
import {
|
||||
debounceTime,
|
||||
@@ -31,6 +31,7 @@ import {SearchPageComponent} from '../data/list/search-page.component';
|
||||
import {DataProvider} from '../data/data.provider';
|
||||
import {SettingsProvider} from '../settings/settings.provider';
|
||||
import {PositionService} from '../map/position.service';
|
||||
import {ConfigProvider} from '../config/config.provider';
|
||||
|
||||
/**
|
||||
* The page for showing favorites
|
||||
@@ -38,12 +39,16 @@ import {PositionService} from '../map/position.service';
|
||||
@Component({
|
||||
templateUrl: '../data/list/search-page.html',
|
||||
providers: [ContextMenuService],
|
||||
styleUrls: ['./favorites-page.component.scss'],
|
||||
styleUrls: ['../data/list/search-page.scss'],
|
||||
})
|
||||
export class FavoritesPageComponent
|
||||
extends SearchPageComponent
|
||||
implements OnInit
|
||||
{
|
||||
title = 'favorites.page.TITLE';
|
||||
|
||||
showNavigation = false;
|
||||
|
||||
constructor(
|
||||
alertController: AlertController,
|
||||
dataProvider: DataProvider,
|
||||
@@ -52,8 +57,10 @@ export class FavoritesPageComponent
|
||||
logger: NGXLogger,
|
||||
dataRoutingService: DataRoutingService,
|
||||
router: Router,
|
||||
route: ActivatedRoute,
|
||||
positionService: PositionService,
|
||||
private favoritesService: FavoritesService,
|
||||
configProvider: ConfigProvider,
|
||||
) {
|
||||
super(
|
||||
alertController,
|
||||
@@ -63,7 +70,9 @@ export class FavoritesPageComponent
|
||||
logger,
|
||||
dataRoutingService,
|
||||
router,
|
||||
route,
|
||||
positionService,
|
||||
configProvider,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import {RouterModule, Routes} from '@angular/router';
|
||||
import {MenuModule} from '../menu/menu.module';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
import {DataModule} from '../data/data.module';
|
||||
import {UtilModule} from '../../util/util.module';
|
||||
|
||||
const favoritesRoutes: Routes = [
|
||||
{
|
||||
@@ -38,6 +39,7 @@ const favoritesRoutes: Routes = [
|
||||
MenuModule,
|
||||
TranslateModule,
|
||||
DataModule,
|
||||
UtilModule,
|
||||
],
|
||||
declarations: [FavoritesPageComponent],
|
||||
})
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary">
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button></ion-back-button>
|
||||
<ion-menu-button></ion-menu-button>
|
||||
<ion-back-button
|
||||
[defaultHref]="'..'"
|
||||
[text]="'back' | translate | titlecase"
|
||||
></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{ 'feedback.page.TITLE' | translate }}</ion-title>
|
||||
</ion-toolbar>
|
||||
|
||||
@@ -20,6 +20,7 @@ import {FeedbackPageComponent} from './feedback-page.component';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
import {MarkdownModule} from 'ngx-markdown';
|
||||
import {UtilModule} from '../../util/util.module';
|
||||
|
||||
const feedbackRoutes: Routes = [
|
||||
{
|
||||
@@ -36,6 +37,7 @@ const feedbackRoutes: Routes = [
|
||||
RouterModule.forChild(feedbackRoutes),
|
||||
TranslateModule,
|
||||
MarkdownModule,
|
||||
UtilModule,
|
||||
],
|
||||
declarations: [FeedbackPageComponent],
|
||||
})
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<ng-container *ngSwitchDefault>
|
||||
<ion-item class="ion-text-wrap" lines="inset">
|
||||
<ion-thumbnail slot="start" class="ion-margin-end">
|
||||
<ion-icon color="medium" [attr.name]="item.type | dataIcon"></ion-icon>
|
||||
<ion-icon color="dark" [attr.name]="item.type | dataIcon"></ion-icon>
|
||||
</ion-thumbnail>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
|
||||
@@ -1,31 +1,48 @@
|
||||
<!--
|
||||
~ 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 color="primary">
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button></ion-back-button>
|
||||
<ion-menu-button></ion-menu-button>
|
||||
<ion-back-button
|
||||
[defaultHref]="'..'"
|
||||
[text]="'back' | translate | titlecase"
|
||||
></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{ 'data.detail.TITLE' | translate }}</ion-title>
|
||||
<ion-buttons slot="primary">
|
||||
<stapps-favorite-button
|
||||
*ngIf="item"
|
||||
[item]="item"
|
||||
[item]="$any(item)"
|
||||
></stapps-favorite-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-no-padding">
|
||||
<div [ngSwitch]="true">
|
||||
<ng-container *ngSwitchCase="!item && isDisconnected | async">
|
||||
<div class="notFoundContainer">
|
||||
<ion-icon name="no-connection"> </ion-icon>
|
||||
<ng-container *ngSwitchCase="!item && (isDisconnected | async)">
|
||||
<div class="centeredMessageContainer">
|
||||
<ion-icon name="no-connection"></ion-icon>
|
||||
<ion-label>
|
||||
{{ 'data.detail.COULD_NOT_CONNECT' | translate }}
|
||||
</ion-label>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="item === null">
|
||||
<div class="notFoundContainer">
|
||||
<ion-icon name="broken-link"> </ion-icon>
|
||||
<div class="centeredMessageContainer">
|
||||
<ion-icon name="broken-link"></ion-icon>
|
||||
<ion-label>
|
||||
{{ 'data.detail.NOT_FOUND' | translate }}
|
||||
</ion-label>
|
||||
|
||||
@@ -19,6 +19,7 @@ import {HebisSearchPageComponent} from './list/hebis-search-page.component';
|
||||
|
||||
const hebisRoutes: Routes = [
|
||||
{path: 'hebis-search', component: HebisSearchPageComponent},
|
||||
{path: 'hebis-search/:term', component: HebisSearchPageComponent},
|
||||
{path: 'hebis-detail/:uid', component: HebisDetailComponent},
|
||||
];
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component, Input, OnInit, OnDestroy} from '@angular/core';
|
||||
import {Router} from '@angular/router';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {AlertController} from '@ionic/angular';
|
||||
import {NGXLogger} from 'ngx-logger';
|
||||
import {combineLatest} from 'rxjs';
|
||||
@@ -24,6 +24,7 @@ import {DataRoutingService} from '../../data/data-routing.service';
|
||||
import {SearchPageComponent} from '../../data/list/search-page.component';
|
||||
import {HebisDataProvider} from '../hebis-data.provider';
|
||||
import {PositionService} from '../../map/position.service';
|
||||
import {ConfigProvider} from '../../config/config.provider';
|
||||
|
||||
/**
|
||||
* HebisSearchPageComponent queries things and shows list of things as search results and filter as context menu
|
||||
@@ -31,6 +32,7 @@ import {PositionService} from '../../map/position.service';
|
||||
@Component({
|
||||
selector: 'stapps-hebissearch-page',
|
||||
templateUrl: 'hebis-search-page.html',
|
||||
styleUrls: ['../../data/list/search-page.scss'],
|
||||
})
|
||||
export class HebisSearchPageComponent
|
||||
extends SearchPageComponent
|
||||
@@ -56,7 +58,9 @@ export class HebisSearchPageComponent
|
||||
* @param logger An angular logger
|
||||
* @param dataRoutingService DataRoutingService
|
||||
* @param router Router
|
||||
* @param route Active Route
|
||||
* @param positionService PositionService
|
||||
* @param configProvider ConfigProvider
|
||||
*/
|
||||
constructor(
|
||||
protected readonly alertController: AlertController,
|
||||
@@ -66,7 +70,9 @@ export class HebisSearchPageComponent
|
||||
protected readonly logger: NGXLogger,
|
||||
protected dataRoutingService: DataRoutingService,
|
||||
protected router: Router,
|
||||
route: ActivatedRoute,
|
||||
protected positionService: PositionService,
|
||||
configProvider: ConfigProvider,
|
||||
) {
|
||||
super(
|
||||
alertController,
|
||||
@@ -76,7 +82,9 @@ export class HebisSearchPageComponent
|
||||
logger,
|
||||
dataRoutingService,
|
||||
router,
|
||||
route,
|
||||
positionService,
|
||||
configProvider,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<stapps-context contentId="data-list"></stapps-context>
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary">
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button></ion-back-button>
|
||||
<ion-menu-button></ion-menu-button>
|
||||
</ion-buttons>
|
||||
<ion-buttons slot="end">
|
||||
<ion-menu-button menu="context" auto-hide="false">
|
||||
<ion-icon name="options"></ion-icon>
|
||||
</ion-menu-button>
|
||||
<ion-back-button
|
||||
[defaultHref]="'..'"
|
||||
[text]="'back' | translate | titlecase"
|
||||
></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{ 'hebisSearch.title' | translate }}</ion-title>
|
||||
</ion-toolbar>
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
<ion-searchbar
|
||||
(ngModelChange)="searchStringChanged($event)"
|
||||
(keyup.enter)="hideKeyboard()"
|
||||
[(ngModel)]="queryText"
|
||||
mode="md"
|
||||
placeholder="{{ 'hebisSearch.search_bar.placeholder' | translate }}"
|
||||
showClearButton="always"
|
||||
type="search"
|
||||
@@ -21,6 +21,19 @@
|
||||
>
|
||||
</ion-searchbar>
|
||||
</ion-toolbar>
|
||||
<ion-toolbar color="primary" class="category-tab" mode="ios">
|
||||
<ion-buttons class="ion-justify-content-between">
|
||||
<ion-button
|
||||
[routerLink]="['/search/' + (queryText || '')]"
|
||||
fill="outline"
|
||||
size="large"
|
||||
>{{ 'search.type' | translate }}
|
||||
</ion-button>
|
||||
<ion-button class="button-active" size="large">{{
|
||||
'hebisSearch.type' | translate
|
||||
}}</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary">
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button></ion-back-button>
|
||||
<ion-menu-button></ion-menu-button>
|
||||
<ion-back-button
|
||||
[defaultHref]="'..'"
|
||||
[text]="'back' | translate | titlecase"
|
||||
></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{ 'library.account.title' | translate | titlecase }}</ion-title>
|
||||
</ion-toolbar>
|
||||
@@ -18,11 +20,11 @@
|
||||
<p><ion-skeleton-text animated style="width: 80%"></ion-skeleton-text></p>
|
||||
</ng-template>
|
||||
<ion-item [routerLink]="['profile']">
|
||||
<ion-icon name="person" slot="start"></ion-icon
|
||||
<ion-icon name="user" slot="start"></ion-icon
|
||||
>{{ 'library.account.pages.profile.title' | translate | titlecase }}
|
||||
</ion-item>
|
||||
<ion-item [routerLink]="['holds-and-reservations']">
|
||||
<ion-icon name="library" slot="start"></ion-icon
|
||||
<ion-icon name="books" slot="start"></ion-icon
|
||||
>{{ 'library.account.pages.holds.title' | translate | titlecase }}
|
||||
</ion-item>
|
||||
<ion-item [routerLink]="['checked-out']">
|
||||
@@ -30,7 +32,7 @@
|
||||
>{{ 'library.account.pages.checked_out.title' | translate | titlecase }}
|
||||
</ion-item>
|
||||
<ion-item [routerLink]="['fines']">
|
||||
<ion-icon name="cash-outline" slot="start"></ion-icon
|
||||
<ion-icon name="cash" slot="start"></ion-icon
|
||||
>{{ 'library.account.pages.fines.title' | translate | titlecase }}
|
||||
</ion-item>
|
||||
</ion-content>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary">
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button></ion-back-button>
|
||||
<ion-menu-button></ion-menu-button>
|
||||
<ion-back-button
|
||||
[defaultHref]="'..'"
|
||||
[text]="'back' | translate | titlecase"
|
||||
></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{
|
||||
'library.account.pages.checked_out.title' | translate | titlecase
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user