mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-09-15 15:21:57 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ef3527429 |
@@ -102,7 +102,6 @@ typings/
|
||||
|
||||
# ignore lib
|
||||
lib
|
||||
dist
|
||||
|
||||
# ignore docs
|
||||
docs
|
||||
|
||||
@@ -11,7 +11,6 @@ log.txt
|
||||
*.sublime-workspace
|
||||
.vscode/*
|
||||
npm-debug.log*
|
||||
.browser-data
|
||||
|
||||
# This file is sometimes created automatically, even though
|
||||
# we actually use the capacitor.config.ts
|
||||
|
||||
@@ -21,10 +21,10 @@ as usual.
|
||||
|
||||
The modified `ion-icon` comes with a few extra features:
|
||||
|
||||
- `[style.--fill]` controls the fill color of the icon.
|
||||
- `[style.--weight]` controls the font weight of the icon.
|
||||
- `[style.--size]` controls the font size of the icon.
|
||||
- `[style.--grade]` controls the font grade of the icon.
|
||||
- `[fill]` controls the fill color of the icon.
|
||||
- `[weight]` controls the font weight of the icon.
|
||||
- `[size]` controls the font size of the icon.
|
||||
- `[grade]` controls the font grade of the icon.
|
||||
|
||||
All of these attributes are animated as described
|
||||
[here](https://developers.google.com/fonts/docs/material_symbols).
|
||||
@@ -50,10 +50,11 @@ the config file.
|
||||
Icon font minification is done automatically, but requires you to
|
||||
follow a few simple rules:
|
||||
|
||||
1. Use the Proxy object to reference icon names in TypeScript files and code
|
||||
1. Use the tagged template literal for referencing icon names in
|
||||
TypeScript files and code
|
||||
|
||||
```ts
|
||||
SCIcon.icon_name;
|
||||
SCIcon`icon_name`;
|
||||
```
|
||||
|
||||
2. When using `ion-icon` in HTML, reference either icons that went through
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
describe('favorites', function () {
|
||||
beforeEach(() => {
|
||||
cy.interceptSearch({
|
||||
extends: {query: 'a'},
|
||||
extends: {query: 'test'},
|
||||
fixture: 'search/generic',
|
||||
alias: 'search',
|
||||
});
|
||||
@@ -29,7 +29,7 @@ describe('favorites', function () {
|
||||
it('should add a favorite', function () {
|
||||
cy.visit('/search');
|
||||
cy.patchSearchPage();
|
||||
cy.get('ion-searchbar').type('a');
|
||||
cy.get('ion-searchbar').type('test');
|
||||
let text!: string;
|
||||
cy.get('stapps-data-list-item')
|
||||
.first()
|
||||
@@ -40,7 +40,9 @@ describe('favorites', function () {
|
||||
text = it;
|
||||
});
|
||||
cy.get('stapps-favorite-button').click();
|
||||
cy.get('stapps-favorite-button > ion-button > ion-icon').should('have.class', 'selected');
|
||||
cy.get('stapps-favorite-button > ion-button > ion-icon')
|
||||
.invoke('attr', 'ng-reflect-fill')
|
||||
.should('eq', 'true');
|
||||
});
|
||||
cy.visit('/favorites');
|
||||
cy.get('stapps-data-list-item').within(() => {
|
||||
|
||||
@@ -96,7 +96,6 @@
|
||||
"form-data": "4.0.0",
|
||||
"geojson": "0.5.0",
|
||||
"ionic-appauth": "0.9.0",
|
||||
"ionicons": "7.2.1",
|
||||
"jsonpath-plus": "6.0.1",
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet.markercluster": "1.5.3",
|
||||
|
||||
@@ -25,6 +25,7 @@ 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';
|
||||
@@ -61,12 +62,12 @@ import {RoutingStackService} from './util/routing-stack.service';
|
||||
import {SCLanguageCode, SCSettingValue} from '@openstapps/core';
|
||||
import {DefaultAuthService} from './modules/auth/default-auth.service';
|
||||
import {PAIAAuthService} from './modules/auth/paia/paia-auth.service';
|
||||
import {IonIconModule} from './util/ion-icon/ion-icon.module';
|
||||
import {NavigationModule} from './modules/menu/navigation/navigation.module';
|
||||
import {browserFactory, SimpleBrowser} from './util/browser.factory';
|
||||
import {getDateFnsLocale} from './translation/dfns-locale';
|
||||
import {setDefaultOptions} from 'date-fns';
|
||||
import {DateFnsConfigurationService} from 'ngx-date-fns';
|
||||
import {SCIcon} from './util/ion-icon/icon';
|
||||
import {Capacitor} from '@capacitor/core';
|
||||
import {SplashScreen} from '@capacitor/splash-screen';
|
||||
|
||||
@@ -154,9 +155,8 @@ export function createTranslateLoader(http: HttpClient) {
|
||||
DashboardModule,
|
||||
DataModule,
|
||||
HebisModule,
|
||||
IonicModule.forRoot({
|
||||
backButtonIcon: SCIcon.arrow_back,
|
||||
}),
|
||||
IonicModule.forRoot(),
|
||||
IonIconModule,
|
||||
JobModule,
|
||||
FavoritesModule,
|
||||
LibraryModule,
|
||||
|
||||
@@ -29,12 +29,7 @@
|
||||
<ion-card-header>
|
||||
<ion-card-title>
|
||||
{{ license.name }}
|
||||
<ion-icon
|
||||
[size]="16"
|
||||
[style.--weight]="300"
|
||||
class="supertext-icon"
|
||||
name="open_in_browser"
|
||||
></ion-icon>
|
||||
<ion-icon [size]="16" [weight]="300" class="supertext-icon" name="open_in_browser"></ion-icon>
|
||||
</ion-card-title>
|
||||
@if (license.authors || license.publisher) {
|
||||
<ion-card-subtitle> {{ license.authors || license.publisher }} </ion-card-subtitle>
|
||||
|
||||
@@ -29,6 +29,7 @@ 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';
|
||||
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
|
||||
|
||||
const settingsRoutes: Routes = [
|
||||
{path: 'about', component: AboutPageComponent},
|
||||
@@ -52,8 +53,9 @@ const settingsRoutes: Routes = [
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonIconModule,
|
||||
FormsModule,
|
||||
IonicModule,
|
||||
IonicModule.forRoot(),
|
||||
TranslateModule.forChild(),
|
||||
ThingTranslateModule.forChild(),
|
||||
RouterModule.forChild(settingsRoutes),
|
||||
|
||||
@@ -35,6 +35,7 @@ import {AssessmentsProvider} from './assessments.provider';
|
||||
import {AssessmentsSimpleDataListComponent} from './list/assessments-simple-data-list.component';
|
||||
import {ProtectedRoutes} from '../auth/protected.routes';
|
||||
import {AssessmentsTreeListComponent} from './list/assessments-tree-list.component';
|
||||
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
|
||||
import {UtilModule} from '../../util/util.module';
|
||||
|
||||
const routes: ProtectedRoutes = [
|
||||
@@ -68,6 +69,7 @@ const routes: ProtectedRoutes = [
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
IonIconModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
TranslateModule,
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
<div class="horizontal-flex">
|
||||
<ion-button fill="clear" (click)="export()">
|
||||
{{ 'share' | translate }}
|
||||
<ion-icon slot="end" name="share"></ion-icon>
|
||||
<ion-icon slot="end" md="share" ios="ios_share"></ion-icon>
|
||||
</ion-button>
|
||||
@if (isWeb) {
|
||||
<ion-button fill="outline" (click)="download()">
|
||||
|
||||
@@ -25,13 +25,15 @@ import {FormsModule} from '@angular/forms';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {MomentModule} from 'ngx-moment';
|
||||
import {UtilModule} from '../../util/util.module';
|
||||
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AddEventReviewModalComponent],
|
||||
imports: [
|
||||
IonicModule,
|
||||
IonicModule.forRoot(),
|
||||
TranslateModule.forChild(),
|
||||
ThingTranslateModule.forChild(),
|
||||
IonIconModule,
|
||||
FormsModule,
|
||||
CommonModule,
|
||||
MomentModule,
|
||||
|
||||
@@ -23,6 +23,7 @@ import {DataModule} from '../data/data.module';
|
||||
import {SettingsProvider} from '../settings/settings.provider';
|
||||
import {CatalogComponent} from './catalog.component';
|
||||
import {UtilModule} from '../../util/util.module';
|
||||
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
|
||||
|
||||
const catalogRoutes: Routes = [
|
||||
{path: 'catalog', component: CatalogComponent},
|
||||
@@ -35,10 +36,11 @@ const catalogRoutes: Routes = [
|
||||
@NgModule({
|
||||
declarations: [CatalogComponent],
|
||||
imports: [
|
||||
IonicModule,
|
||||
IonicModule.forRoot(),
|
||||
FormsModule,
|
||||
TranslateModule.forChild(),
|
||||
RouterModule.forChild(catalogRoutes),
|
||||
IonIconModule,
|
||||
CommonModule,
|
||||
MomentModule,
|
||||
DataModule,
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</ion-header>
|
||||
<div #schedule class="schedule">
|
||||
<a [routerLink]="['/schedule/week-overview']">
|
||||
<ion-icon [size]="36" [style.--weight]="300" name="calendar_month"></ion-icon>
|
||||
<ion-icon [size]="36" [weight]="300" name="calendar_month"></ion-icon>
|
||||
<ion-label [innerHTML]="'schedule.recurring' | translate"></ion-label>
|
||||
</a>
|
||||
<!-- Avoid structural directives here, they might interfere with the collapse animation -->
|
||||
|
||||
@@ -30,6 +30,7 @@ import {MensaSectionContentComponent} from './sections/mensa-section/mensa-secti
|
||||
import {FavoritesSectionComponent} from './sections/favorites-section/favorites-section.component';
|
||||
import {ThingTranslateModule} from '../../translation/thing-translate.module';
|
||||
import {UtilModule} from '../../util/util.module';
|
||||
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
|
||||
import {NewsModule} from '../news/news.module';
|
||||
import {JobSectionComponent} from './sections/jobs-section/job-section.component';
|
||||
import {JobModule} from '../jobs/jobs.module';
|
||||
@@ -55,7 +56,8 @@ const catalogRoutes: Routes = [
|
||||
JobSectionComponent,
|
||||
],
|
||||
imports: [
|
||||
IonicModule,
|
||||
IonicModule.forRoot(),
|
||||
IonIconModule,
|
||||
FormsModule,
|
||||
TranslateModule.forChild(),
|
||||
RouterModule.forChild(catalogRoutes),
|
||||
|
||||
@@ -60,7 +60,7 @@ export class AddEventActionChipComponent {
|
||||
/**
|
||||
* Current state of icon fill
|
||||
*/
|
||||
iconFill: 1 | 0;
|
||||
iconFill: boolean;
|
||||
|
||||
/**
|
||||
* Label
|
||||
|
||||
@@ -24,31 +24,31 @@ export enum AddEventStates {
|
||||
|
||||
export const AddEventStatesMap = {
|
||||
[AddEventStates.ADDED_ALL]: {
|
||||
icon: SCIcon.event_available,
|
||||
fill: 1,
|
||||
icon: SCIcon`event_available`,
|
||||
fill: true,
|
||||
label: 'data.chips.add_events.ADDED_ALL',
|
||||
disabled: false,
|
||||
color: 'success',
|
||||
},
|
||||
[AddEventStates.ADDED_SOME]: {
|
||||
icon: SCIcon.event,
|
||||
fill: 1,
|
||||
icon: SCIcon`event`,
|
||||
fill: true,
|
||||
label: 'data.chips.add_events.ADDED_SOME',
|
||||
disabled: false,
|
||||
color: 'success',
|
||||
},
|
||||
[AddEventStates.REMOVED_ALL]: {
|
||||
icon: SCIcon.calendar_today,
|
||||
fill: 0,
|
||||
icon: SCIcon`calendar_today`,
|
||||
fill: false,
|
||||
label: 'data.chips.add_events.REMOVED_ALL',
|
||||
disabled: false,
|
||||
color: 'primary',
|
||||
},
|
||||
[AddEventStates.UNAVAILABLE]: {
|
||||
icon: SCIcon.event_busy,
|
||||
fill: 0,
|
||||
icon: SCIcon`event_busy`,
|
||||
fill: false,
|
||||
label: 'data.chips.add_events.UNAVAILABLE',
|
||||
disabled: true,
|
||||
color: 'dark',
|
||||
},
|
||||
} as const;
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
[color]="color"
|
||||
[outline]="true"
|
||||
>
|
||||
<ion-icon [name]="icon" [style.--fill]="iconFill"></ion-icon>
|
||||
<ion-icon [name]="icon" [fill]="iconFill"></ion-icon>
|
||||
<ion-label>{{ label | translate }}</ion-label>
|
||||
<stapps-edit-modal #editModal (save)="selection.save()">
|
||||
<ng-template>
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
@if (displayValue) {
|
||||
<ion-chip [class.active]="active" (click)="emitToggle(value)">
|
||||
@if (active) {
|
||||
<ion-icon class="ion-color" name="check_circle" [style.--fill]="1"></ion-icon>
|
||||
<ion-icon class="ion-color" name="check_circle" [fill]="true"></ion-icon>
|
||||
}
|
||||
<ion-label>{{ displayValue }}</ion-label>
|
||||
</ion-chip>
|
||||
@@ -16,35 +16,35 @@ import {SCThingType} from '@openstapps/core';
|
||||
import {SCIcon} from '../../util/ion-icon/icon';
|
||||
|
||||
export const DataIcons: Record<SCThingType, string> = {
|
||||
'academic event': SCIcon.school,
|
||||
'assessment': SCIcon.fact_check,
|
||||
'article': SCIcon.article,
|
||||
'book': SCIcon.book,
|
||||
'building': SCIcon.location_city,
|
||||
'certification': SCIcon.contract,
|
||||
'catalog': SCIcon.inventory_2,
|
||||
'contact point': SCIcon.contact_page,
|
||||
'course of study': SCIcon.school,
|
||||
'date series': SCIcon.event,
|
||||
'dish': SCIcon.lunch_dining,
|
||||
'favorite': SCIcon.favorite,
|
||||
'floor': SCIcon.foundation,
|
||||
'id card': SCIcon.badge,
|
||||
'message': SCIcon.newspaper,
|
||||
'organization': SCIcon.business_center,
|
||||
'periodical': SCIcon.feed,
|
||||
'person': SCIcon.person,
|
||||
'point of interest': SCIcon.pin_drop,
|
||||
'publication event': SCIcon.campaign,
|
||||
'room': SCIcon.meeting_room,
|
||||
'semester': SCIcon.date_range,
|
||||
'setting': SCIcon.settings,
|
||||
'sport course': SCIcon.sports_soccer,
|
||||
'study module': SCIcon.view_module,
|
||||
'ticket': SCIcon.confirmation_number,
|
||||
'todo': SCIcon.task,
|
||||
'tour': SCIcon.tour,
|
||||
'video': SCIcon.movie,
|
||||
'diff': SCIcon.difference,
|
||||
'job posting': SCIcon.work,
|
||||
'academic event': SCIcon`school`,
|
||||
'assessment': SCIcon`fact_check`,
|
||||
'article': SCIcon`article`,
|
||||
'book': SCIcon`book`,
|
||||
'building': SCIcon`location_city`,
|
||||
'certification': SCIcon`contract`,
|
||||
'catalog': SCIcon`inventory_2`,
|
||||
'contact point': SCIcon`contact_page`,
|
||||
'course of study': SCIcon`school`,
|
||||
'date series': SCIcon`event`,
|
||||
'dish': SCIcon`lunch_dining`,
|
||||
'favorite': SCIcon`favorite`,
|
||||
'floor': SCIcon`foundation`,
|
||||
'id card': SCIcon`badge`,
|
||||
'message': SCIcon`newspaper`,
|
||||
'organization': SCIcon`business_center`,
|
||||
'periodical': SCIcon`feed`,
|
||||
'person': SCIcon`person`,
|
||||
'point of interest': SCIcon`pin_drop`,
|
||||
'publication event': SCIcon`campaign`,
|
||||
'room': SCIcon`meeting_room`,
|
||||
'semester': SCIcon`date_range`,
|
||||
'setting': SCIcon`settings`,
|
||||
'sport course': SCIcon`sports_soccer`,
|
||||
'study module': SCIcon`view_module`,
|
||||
'ticket': SCIcon`confirmation_number`,
|
||||
'todo': SCIcon`task`,
|
||||
'tour': SCIcon`tour`,
|
||||
'video': SCIcon`movie`,
|
||||
'diff': SCIcon`difference`,
|
||||
'job posting': SCIcon`work`,
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@ import {MarkdownModule} from 'ngx-markdown';
|
||||
import {MomentModule} from 'ngx-moment';
|
||||
import {ThingTranslateModule} from '../../translation/thing-translate.module';
|
||||
import {SimpleBrowser, browserFactory} from '../../util/browser.factory';
|
||||
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
|
||||
import {RoutingStackService} from '../../util/routing-stack.service';
|
||||
import {UtilModule} from '../../util/util.module';
|
||||
import {CalendarService} from '../calendar/calendar.service';
|
||||
@@ -106,6 +107,7 @@ import {SemesterListItemComponent} from './types/semester/semester-list-item.com
|
||||
import {VideoDetailContentComponent} from './types/video/video-detail-content.component';
|
||||
import {VideoListItemComponent} from './types/video/video-list-item.component';
|
||||
import {ShareButtonComponent} from './elements/share-button.component';
|
||||
import {DataFilterComponent} from './filter/data-filter.component';
|
||||
|
||||
/**
|
||||
* Module for handling data
|
||||
@@ -183,14 +185,16 @@ import {ShareButtonComponent} from './elements/share-button.component';
|
||||
ShareButtonComponent,
|
||||
],
|
||||
imports: [
|
||||
DataFilterComponent,
|
||||
CommonModule,
|
||||
DataRoutingModule,
|
||||
FormsModule,
|
||||
HttpClientModule,
|
||||
IonicModule,
|
||||
IonicModule.forRoot(),
|
||||
LeafletModule,
|
||||
MarkdownModule.forRoot(),
|
||||
MenuModule,
|
||||
IonIconModule,
|
||||
MomentModule.forRoot({
|
||||
relativeTimeThresholdOptions: {
|
||||
m: 59,
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<ion-button (click)="toggle($event)" color="medium" size="small" fill="clear">
|
||||
<ion-icon
|
||||
slot="icon-only"
|
||||
[style.--fill]="(isFavorite$ | async) ? 1 : 0"
|
||||
[fill]="(isFavorite$ | async) ?? false"
|
||||
[class.selected]="isFavorite$ | async"
|
||||
name="grade"
|
||||
></ion-icon>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<ion-button size="small" fill="clear" (click)="share() && toast.present()">
|
||||
<ion-icon [size]="24" slot="icon-only" name="share"></ion-icon>
|
||||
<ion-icon [size]="24" slot="icon-only" name="share" ios="ios_share"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-toast [message]="'toast.TITLE_COPIED' | translate" #toast [duration]="2000"></ion-toast>
|
||||
|
||||
@@ -16,11 +16,10 @@
|
||||
import {Component, ElementRef, HostListener, Input, OnChanges, OnInit, ViewChild} from '@angular/core';
|
||||
import {SCThings} from '@openstapps/core';
|
||||
import {SCIcon} from '../../../util/ion-icon/icon';
|
||||
import {MaterialSymbol} from 'material-symbols';
|
||||
|
||||
const AccordionButtonState = {
|
||||
collapsed: SCIcon.expand_more,
|
||||
expanded: SCIcon.expand_less,
|
||||
collapsed: SCIcon`expand_more`,
|
||||
expanded: SCIcon`expand_less`,
|
||||
};
|
||||
|
||||
@Component({
|
||||
@@ -36,7 +35,7 @@ export class TitleCardComponent implements OnInit, OnChanges {
|
||||
|
||||
@ViewChild('accordionTextArea') accordionTextArea: ElementRef;
|
||||
|
||||
buttonState: MaterialSymbol = AccordionButtonState.collapsed;
|
||||
buttonState = AccordionButtonState.collapsed;
|
||||
|
||||
buttonShown = true;
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import {AsyncPipe, CommonModule} from '@angular/common';
|
||||
import {ChangeDetectionStrategy, Component, Input} from '@angular/core';
|
||||
import {IonicModule} from '@ionic/angular';
|
||||
import {IonIconModule} from 'src/app/util/ion-icon/ion-icon.module';
|
||||
import {DataFilterProvider} from './data-filter.provider';
|
||||
import {ThingTranslateModule} from 'src/app/translation/thing-translate.module';
|
||||
import {PropertyValueTranslatePipe} from 'src/app/translation/property-value-translate.pipe';
|
||||
|
||||
@Component({
|
||||
selector: 'stapps-data-filter',
|
||||
templateUrl: 'data-filter.html',
|
||||
styleUrls: ['data-filter.scss'],
|
||||
standalone: true,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
IonIconModule,
|
||||
AsyncPipe,
|
||||
ThingTranslateModule,
|
||||
PropertyValueTranslatePipe,
|
||||
],
|
||||
})
|
||||
export class DataFilterComponent {
|
||||
@Input() expandBucketCount = 3;
|
||||
|
||||
constructor(readonly filterProvider: DataFilterProvider) {}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<ion-chip (click)="sortMenu.present($event)">
|
||||
<ion-label>Relevance</ion-label>
|
||||
<ion-icon name="expand_more"></ion-icon>
|
||||
</ion-chip>
|
||||
<ion-popover #sortMenu side="bottom" alignment="start">
|
||||
<ng-template>
|
||||
<ion-content class="ion-padding">
|
||||
<ion-radio-group value="relevance">
|
||||
<ion-radio value="relevance">Relevance</ion-radio>
|
||||
<ion-radio value="name">Name</ion-radio>
|
||||
<ion-radio value="type">Type</ion-radio>
|
||||
</ion-radio-group>
|
||||
</ion-content>
|
||||
</ng-template>
|
||||
</ion-popover>
|
||||
<div class="separator"></div>
|
||||
<ng-container *ngIf="filterProvider.context | async as context">
|
||||
<ng-container *ngFor="let facet of context.facets">
|
||||
<ng-container *ngIf="facet.buckets.length > 1">
|
||||
<ng-template #facetLabel>
|
||||
<ng-container *ngIf="facet.onlyOnType; else notOnlyOnType">
|
||||
<strong>{{facet.onlyOnType | propertyValueTranslate: 'type': facet.onlyOnType | titlecase}}</strong>
|
||||
/ {{facet.field | propertyNameTranslate: facet.onlyOnType | titlecase}}
|
||||
</ng-container>
|
||||
<ng-template #notOnlyOnType>
|
||||
{{facet.field | propertyNameTranslate: $any('building') | titlecase}}
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
<ng-template #bucketLabel let-bucket>
|
||||
{{bucket.key | propertyValueTranslate: facet.field: (facet.onlyOnType ?? $any(bucket.key)) |
|
||||
titlecase}}
|
||||
</ng-template>
|
||||
|
||||
<ng-container *ngIf="facet.buckets.length <= expandBucketCount; else expandableFacet">
|
||||
<div class="separator"></div>
|
||||
<div class="expanded-facet">
|
||||
<div class="buckets">
|
||||
<ion-chip *ngFor="let bucket of facet.buckets" [outline]="true">
|
||||
<ion-label>
|
||||
<ng-container *ngTemplateOutlet="bucketLabel; context: {$implicit: bucket}"></ng-container>
|
||||
</ion-label>
|
||||
</ion-chip>
|
||||
</div>
|
||||
<div class="facet-label">
|
||||
<ng-container *ngTemplateOutlet="facetLabel"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator"></div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #expandableFacet>
|
||||
<ion-chip (click)="filterMenu.present($event)" [outline]="true">
|
||||
<ion-label>
|
||||
<ng-container *ngTemplateOutlet="facetLabel"></ng-container>
|
||||
</ion-label>
|
||||
<ion-icon name="expand_more"></ion-icon>
|
||||
</ion-chip>
|
||||
|
||||
<ion-popover #filterMenu side="bottom" alignment="start">
|
||||
<ng-template>
|
||||
<ion-content>
|
||||
<ion-list>
|
||||
<ion-item *ngFor="let bucket of facet.buckets">
|
||||
<ion-checkbox slot="start"></ion-checkbox>
|
||||
<ion-label>{{bucket.key}}</ion-label>
|
||||
<ion-note slot="end">{{bucket.count}}</ion-note>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
</ng-template>
|
||||
</ion-popover>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
@@ -0,0 +1,12 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {SCSearchResult} from '@openstapps/core';
|
||||
import {BehaviorSubject} from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class DataFilterProvider {
|
||||
readonly context = new BehaviorSubject<SCSearchResult | undefined>(undefined);
|
||||
|
||||
readonly userSortOption = new BehaviorSubject<string | undefined>(undefined);
|
||||
|
||||
readonly userFilterOption = new BehaviorSubject(new Map<string, string | number>());
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
:host {
|
||||
overflow-x: auto;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-block: var(--spacing-xs);
|
||||
|
||||
> * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.expanded-facet {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> .facet-label {
|
||||
margin-inline: var(--spacing-md);
|
||||
font-size: 0.7em;
|
||||
}
|
||||
}
|
||||
|
||||
.separator {
|
||||
align-self: center;
|
||||
|
||||
width: 1px;
|
||||
height: 1.25em;
|
||||
margin-inline: var(--spacing-xs);
|
||||
|
||||
opacity: 0.2;
|
||||
background: currentcolor;
|
||||
|
||||
&:last-child,
|
||||
+ .separator {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -12,94 +12,109 @@
|
||||
* 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 {PositionService} from '../../map/position.service';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {MapPosition} from '../../map/position.service';
|
||||
import {SearchPageComponent} from './search-page.component';
|
||||
import {Geolocation} from '@capacitor/geolocation';
|
||||
import {BehaviorSubject, catchError} from 'rxjs';
|
||||
import {BehaviorSubject} from 'rxjs';
|
||||
import {pauseWhen} from '../../../util/rxjs/pause-when';
|
||||
import {SCSearchFilter} from '@openstapps/core';
|
||||
import {ContextMenuService} from '../../menu/context/context-menu.service';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
/**
|
||||
* Presents a list of places for eating/drinking
|
||||
*/
|
||||
@Component({
|
||||
templateUrl: 'food-data-list.html',
|
||||
templateUrl: 'search-page.html',
|
||||
styleUrls: ['../../data/list/search-page.scss'],
|
||||
})
|
||||
export class FoodDataListComponent {
|
||||
export class FoodDataListComponent extends SearchPageComponent implements OnInit {
|
||||
title = 'canteens.title';
|
||||
|
||||
showNavigation = false;
|
||||
|
||||
isNotInView$ = new BehaviorSubject(true);
|
||||
|
||||
forcedFilter: SCSearchFilter = {
|
||||
arguments: {
|
||||
filters: [
|
||||
{
|
||||
arguments: {
|
||||
field: 'categories',
|
||||
value: 'canteen',
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
{
|
||||
arguments: {
|
||||
field: 'categories',
|
||||
value: 'student canteen',
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
{
|
||||
arguments: {
|
||||
field: 'categories',
|
||||
value: 'cafe',
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
{
|
||||
arguments: {
|
||||
field: 'categories',
|
||||
value: 'restaurant',
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
operation: 'or',
|
||||
},
|
||||
type: 'boolean',
|
||||
};
|
||||
|
||||
constructor(positionService: PositionService, contextMenuService: ContextMenuService) {
|
||||
positionService
|
||||
/**
|
||||
* Sets the forced filter to present only places for eating/drinking
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.positionService
|
||||
.watchCurrentLocation({enableHighAccuracy: false, maximumAge: 1000})
|
||||
.pipe(
|
||||
pauseWhen(this.isNotInView$),
|
||||
takeUntilDestroyed(),
|
||||
catchError(async _error => {
|
||||
await Geolocation.checkPermissions();
|
||||
}),
|
||||
)
|
||||
.pipe(pauseWhen(this.isNotInView$), takeUntilDestroyed(this.destroy$))
|
||||
.subscribe({
|
||||
next(position) {
|
||||
if (!position) return;
|
||||
positionService.position = position;
|
||||
contextMenuService.sortQuery.next([
|
||||
{
|
||||
type: 'distance',
|
||||
order: 'asc',
|
||||
arguments: {
|
||||
field: 'geo',
|
||||
position: [position.longitude, position.latitude],
|
||||
},
|
||||
},
|
||||
]);
|
||||
next: (position: MapPosition) => {
|
||||
this.positionService.position = position;
|
||||
},
|
||||
async error() {
|
||||
positionService.position = undefined;
|
||||
error: async _error => {
|
||||
this.positionService.position = undefined;
|
||||
await Geolocation.checkPermissions();
|
||||
},
|
||||
});
|
||||
this.showDefaultData = true;
|
||||
|
||||
this.sortQuery = [
|
||||
{
|
||||
arguments: {field: 'name'},
|
||||
order: 'asc',
|
||||
type: 'ducet',
|
||||
},
|
||||
];
|
||||
|
||||
this.forcedFilter = {
|
||||
arguments: {
|
||||
filters: [
|
||||
{
|
||||
arguments: {
|
||||
field: 'categories',
|
||||
value: 'canteen',
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
{
|
||||
arguments: {
|
||||
field: 'categories',
|
||||
value: 'student canteen',
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
{
|
||||
arguments: {
|
||||
field: 'categories',
|
||||
value: 'cafe',
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
{
|
||||
arguments: {
|
||||
field: 'categories',
|
||||
value: 'restaurant',
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
operation: 'or',
|
||||
},
|
||||
type: 'boolean',
|
||||
};
|
||||
|
||||
if (this.positionService.position) {
|
||||
this.sortQuery = [
|
||||
{
|
||||
type: 'distance',
|
||||
order: 'asc',
|
||||
arguments: {
|
||||
field: 'geo',
|
||||
position: [this.positionService.position.longitude, this.positionService.position.latitude],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
super.ngOnInit();
|
||||
}
|
||||
|
||||
ionViewWillEnter() {
|
||||
async ionViewWillEnter() {
|
||||
await super.ionViewWillEnter();
|
||||
this.isNotInView$.next(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<stapps-search-page
|
||||
[title]="'canteens.title' | translate"
|
||||
[navigation]="[]"
|
||||
[showDefaultData]="true"
|
||||
[forcedFilter]="forcedFilter"
|
||||
>
|
||||
</stapps-search-page>
|
||||
@@ -12,7 +12,7 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component, DestroyRef, inject, Input, OnInit} from '@angular/core';
|
||||
import {Component, DestroyRef, Inject, inject, Input, OnInit} from '@angular/core';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {Keyboard} from '@capacitor/keyboard';
|
||||
import {AlertController, AnimationBuilder, AnimationController} from '@ionic/angular';
|
||||
@@ -36,6 +36,7 @@ import {PositionService} from '../../map/position.service';
|
||||
import {ConfigProvider} from '../../config/config.provider';
|
||||
import {searchPageSwitchAnimation} from './search-page-switch-animation';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
import {DataFilterProvider} from '../filter/data-filter.provider';
|
||||
|
||||
/**
|
||||
* SearchPageComponent queries things and shows list of things as search results and filter as context menu
|
||||
@@ -44,7 +45,7 @@ import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
selector: 'stapps-search-page',
|
||||
templateUrl: 'search-page.html',
|
||||
styleUrls: ['search-page.scss'],
|
||||
providers: [ContextMenuService],
|
||||
providers: [ContextMenuService, DataFilterProvider],
|
||||
})
|
||||
export class SearchPageComponent implements OnInit {
|
||||
@Input() title = 'search.title';
|
||||
@@ -55,18 +56,17 @@ export class SearchPageComponent implements OnInit {
|
||||
|
||||
@Input() backUrl?: string;
|
||||
|
||||
isHebisAvailable = false;
|
||||
|
||||
/**
|
||||
* Signalizes that the data is being loaded
|
||||
*/
|
||||
loading = false;
|
||||
|
||||
/**
|
||||
* Navigation elements between search pages
|
||||
* Display the navigation between default and library search
|
||||
*/
|
||||
@Input() navigation: Array<{label: string; routerLink?: string[]}> = [
|
||||
{label: 'search.type'},
|
||||
{label: 'hebisSearch.type', routerLink: ['/hebis-search']},
|
||||
];
|
||||
@Input() showNavigation = true;
|
||||
|
||||
/**
|
||||
* Show default data (e.g. when there is user interaction)
|
||||
@@ -83,8 +83,6 @@ export class SearchPageComponent implements OnInit {
|
||||
*/
|
||||
@Input() showTopToolbar = true;
|
||||
|
||||
@Input() showContextMenu = true;
|
||||
|
||||
/**
|
||||
* Api query filter
|
||||
*/
|
||||
@@ -113,7 +111,7 @@ export class SearchPageComponent implements OnInit {
|
||||
/**
|
||||
* Page size of queries
|
||||
*/
|
||||
@Input() pageSize = 30;
|
||||
pageSize = 30;
|
||||
|
||||
/**
|
||||
* Search value from search bar
|
||||
@@ -147,6 +145,8 @@ export class SearchPageComponent implements OnInit {
|
||||
|
||||
destroy$ = inject(DestroyRef);
|
||||
|
||||
dataFilterProvider = inject(DataFilterProvider);
|
||||
|
||||
routeAnimation: AnimationBuilder;
|
||||
|
||||
/**
|
||||
@@ -219,6 +219,7 @@ export class SearchPageComponent implements OnInit {
|
||||
|
||||
try {
|
||||
const result = await this.dataProvider.search(searchOptions);
|
||||
this.dataFilterProvider.context.next(result);
|
||||
this.singleTypeResponse = result.facets.find(facet => facet.field === 'type')?.buckets.length === 1;
|
||||
if (append) {
|
||||
// append results
|
||||
@@ -346,13 +347,8 @@ export class SearchPageComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
try {
|
||||
// TODO: make this hack more generic
|
||||
if (this.navigation[1]?.routerLink?.[0] === '/hebis-search') {
|
||||
const features = this.configProvider.getValue('features') as SCFeatureConfiguration;
|
||||
if (features.plugins?.['hebis-plugin']?.urlPath === undefined) {
|
||||
this.navigation = [];
|
||||
}
|
||||
}
|
||||
const features = this.configProvider.getValue('features') as SCFeatureConfiguration;
|
||||
this.isHebisAvailable = !!features.plugins?.['hebis-plugin']?.urlPath;
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
|
||||
@@ -13,74 +13,44 @@
|
||||
~ this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
@if (showContextMenu) {
|
||||
<stapps-context contentId="data-list"></stapps-context>
|
||||
}
|
||||
<stapps-context contentId="data-list"></stapps-context>
|
||||
<ion-header>
|
||||
@if (showDrawer && showTopToolbar) {
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [defaultHref]="backUrl"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{ title | translate }}</ion-title>
|
||||
</ion-toolbar>
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [defaultHref]="backUrl"></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()"
|
||||
(search)="hideKeyboard()"
|
||||
[(ngModel)]="queryText"
|
||||
showClearButton="always"
|
||||
placeholder="{{ placeholder | translate }}"
|
||||
mode="md"
|
||||
type="search"
|
||||
enterkeyhint="search"
|
||||
class="filterable"
|
||||
[autofocus]="!showDefaultData"
|
||||
>
|
||||
@if (showContextMenu) {
|
||||
<ion-menu-button menu="context" auto-hide="false">
|
||||
<ion-icon name="tune"></ion-icon>
|
||||
</ion-menu-button>
|
||||
}
|
||||
<ion-searchbar (ngModelChange)="searchStringChanged($event)" (keyup.enter)="hideKeyboard()"
|
||||
(search)="hideKeyboard()" [(ngModel)]="queryText" showClearButton="always"
|
||||
placeholder="{{ placeholder | translate }}" mode="md" type="search" enterkeyhint="search" class="filterable"
|
||||
[autofocus]="!showDefaultData">
|
||||
<ion-menu-button menu="context" auto-hide="false">
|
||||
<ion-icon name="tune"></ion-icon>
|
||||
</ion-menu-button>
|
||||
</ion-searchbar>
|
||||
</ion-toolbar>
|
||||
@if (navigation.length > 0) {
|
||||
<ion-toolbar color="primary" class="category-tab">
|
||||
<ion-buttons class="ion-justify-content-between">
|
||||
@for (target of navigation; track target) {
|
||||
@if (target.routerLink) {
|
||||
<ion-button
|
||||
[routerLink]="target.routerLink"
|
||||
queryParamsHandling="merge"
|
||||
[routerAnimation]="routeAnimation"
|
||||
fill="outline"
|
||||
size="large"
|
||||
>{{ target.label | translate }}
|
||||
</ion-button>
|
||||
} @else {
|
||||
<ion-button class="button-active" size="large">{{ target.label | translate }}</ion-button>
|
||||
}
|
||||
}
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
@if (showNavigation && isHebisAvailable) {
|
||||
<ion-toolbar color="primary" class="category-tab">
|
||||
<ion-buttons class="ion-justify-content-between">
|
||||
<ion-button class="button-active" size="large">{{ 'search.type' | translate }}</ion-button>
|
||||
<ion-button [routerLink]="['/hebis-search']" queryParamsHandling="merge" [routerAnimation]="routeAnimation"
|
||||
fill="outline" size="large">{{ 'hebisSearch.type' | translate }}
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
}
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="content">
|
||||
<div
|
||||
[class.no-results]="!showDefaultData && !items && !loading"
|
||||
[style.display]="!showDefaultData && !items && !loading ? 'block' : 'none'"
|
||||
>
|
||||
<div [class.no-results]="!showDefaultData && !items && !loading"
|
||||
[style.display]="!showDefaultData && !items && !loading ? 'block' : 'none'">
|
||||
<ion-label class="centered-message-container"> {{ searchInstruction | translate }} </ion-label>
|
||||
</div>
|
||||
<stapps-data-list
|
||||
id="data-list"
|
||||
[items]="items | async"
|
||||
[singleType]="singleTypeResponse"
|
||||
(loadmore)="loadMore()"
|
||||
[resetToTop]="queryChanged.asObservable()"
|
||||
[loading]="loading"
|
||||
></stapps-data-list>
|
||||
<stapps-data-filter></stapps-data-filter>
|
||||
<stapps-data-list id="data-list" [items]="items | async" [singleType]="singleTypeResponse" (loadmore)="loadMore()"
|
||||
[resetToTop]="queryChanged.asObservable()" [loading]="loading"></stapps-data-list>
|
||||
</ion-content>
|
||||
|
||||
@@ -22,9 +22,6 @@
|
||||
|
||||
ion-toolbar {
|
||||
--ion-color-base: none !important;
|
||||
|
||||
// account for back button
|
||||
min-height: 42px;
|
||||
}
|
||||
|
||||
ion-toolbar:first-of-type {
|
||||
|
||||
@@ -22,7 +22,6 @@ import {DataListItemComponent} from '../../list/data-list-item.component';
|
||||
@Component({
|
||||
selector: 'stapps-article-item',
|
||||
templateUrl: 'article-list-item.html',
|
||||
styleUrl: 'article-list-item.scss',
|
||||
})
|
||||
export class ArticleListItemComponent extends DataListItemComponent {
|
||||
/**
|
||||
|
||||
@@ -13,20 +13,26 @@
|
||||
~ this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<ion-label class="title">{{ 'name' | thingTranslate: item }}</ion-label>
|
||||
<p class="title-sub">
|
||||
@for (author of item.authors; track author) {
|
||||
{{ 'name' | thingTranslate: author }}
|
||||
}
|
||||
@if (item.authors && item.authors && item.firstPublished) {
|
||||
,
|
||||
}
|
||||
@if (item.firstPublished && !item.lastPublished) {
|
||||
{{ item.firstPublished }}
|
||||
} @else {
|
||||
@if (item.firstPublished && item.lastPublished) {
|
||||
{{ [item.firstPublished, item.lastPublished] | join: ' - ' }}
|
||||
}
|
||||
}
|
||||
</p>
|
||||
<ion-note> {{ 'categories' | thingTranslate: item }} </ion-note>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<h2 class="name">{{ 'name' | thingTranslate: item }}</h2>
|
||||
<p>
|
||||
@for (author of item.authors; track author) {
|
||||
{{ 'name' | thingTranslate: author }}
|
||||
}
|
||||
@if (item.authors && item.authors && item.firstPublished) {
|
||||
,
|
||||
}
|
||||
@if (item.firstPublished && !item.lastPublished) {
|
||||
{{ item.firstPublished }}
|
||||
} @else {
|
||||
@if (item.firstPublished && item.lastPublished) {
|
||||
{{ [item.firstPublished, item.lastPublished] | join: ' - ' }}
|
||||
}
|
||||
}
|
||||
</p>
|
||||
<ion-note> {{ 'categories' | thingTranslate: item }} </ion-note>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
p.title-sub {
|
||||
white-space: unset;
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import {DataListItemComponent} from '../../list/data-list-item.component';
|
||||
@Component({
|
||||
selector: 'stapps-book-list-item',
|
||||
templateUrl: 'book-list-item.html',
|
||||
styleUrl: 'book-list-item.scss',
|
||||
})
|
||||
export class BookListItemComponent extends DataListItemComponent {
|
||||
/**
|
||||
|
||||
@@ -13,20 +13,26 @@
|
||||
~ this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<ion-label class="title">{{ 'name' | thingTranslate: item }}</ion-label>
|
||||
<p class="title-sub">
|
||||
@for (author of item.authors; track author) {
|
||||
{{ 'name' | thingTranslate: author }}
|
||||
}
|
||||
@if (item.authors && item.authors && item.firstPublished) {
|
||||
,
|
||||
}
|
||||
@if (item.firstPublished && !item.lastPublished) {
|
||||
{{ item.firstPublished }}
|
||||
} @else {
|
||||
@if (item.firstPublished && item.lastPublished) {
|
||||
{{ [item.firstPublished, item.lastPublished] | join: ' - ' }}
|
||||
}
|
||||
}
|
||||
</p>
|
||||
<ion-note> {{ 'categories' | thingTranslate: item }} </ion-note>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<h2 class="name">{{ 'name' | thingTranslate: item }}</h2>
|
||||
<p>
|
||||
@for (author of item.authors; track author) {
|
||||
{{ 'name' | thingTranslate: author }}
|
||||
}
|
||||
@if (item.authors && item.authors && item.firstPublished) {
|
||||
,
|
||||
}
|
||||
@if (item.firstPublished && !item.lastPublished) {
|
||||
{{ item.firstPublished }}
|
||||
} @else {
|
||||
@if (item.firstPublished && item.lastPublished) {
|
||||
{{ [item.firstPublished, item.lastPublished] | join: ' - ' }}
|
||||
}
|
||||
}
|
||||
</p>
|
||||
<ion-note> {{ 'categories' | thingTranslate: item }} </ion-note>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
p.title-sub {
|
||||
white-space: unset;
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
-->
|
||||
@if (isInCalendar | async) {
|
||||
<ion-chip outline="true" color="success" (click)="removeFromCalendar()">
|
||||
<ion-icon name="event_available" [style.--fill]="1"></ion-icon>
|
||||
<ion-icon name="event_available" [fill]="true"></ion-icon>
|
||||
<ion-label>{{ 'chips.addEvent.addedToEvents' | translate }}</ion-label>
|
||||
</ion-chip>
|
||||
} @else {
|
||||
|
||||
@@ -22,7 +22,6 @@ import {DataListItemComponent} from '../../list/data-list-item.component';
|
||||
@Component({
|
||||
selector: 'stapps-periodical-list-item',
|
||||
templateUrl: 'periodical-list-item.html',
|
||||
styleUrl: 'periodical-list-item.scss',
|
||||
})
|
||||
export class PeriodicalListItemComponent extends DataListItemComponent {
|
||||
/**
|
||||
|
||||
@@ -13,20 +13,26 @@
|
||||
~ this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<ion-label class="title">{{ 'name' | thingTranslate: item }}</ion-label>
|
||||
<p class="title-sub">
|
||||
@for (author of item.authors; track author) {
|
||||
{{ 'name' | thingTranslate: author }}
|
||||
}
|
||||
@if (item.authors && item.authors && item.firstPublished) {
|
||||
,
|
||||
}
|
||||
@if (item.firstPublished && !item.lastPublished) {
|
||||
{{ item.firstPublished }}
|
||||
} @else {
|
||||
@if (item.firstPublished && item.lastPublished) {
|
||||
{{ [item.firstPublished, item.lastPublished] | join: ' - ' }}
|
||||
}
|
||||
}
|
||||
</p>
|
||||
<ion-note> {{ 'categories' | thingTranslate: item }} </ion-note>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<h2 class="name">{{ 'name' | thingTranslate: item }}</h2>
|
||||
<p>
|
||||
@for (author of item.authors; track author) {
|
||||
{{ 'name' | thingTranslate: author }}
|
||||
}
|
||||
@if (item.authors && item.authors && item.firstPublished) {
|
||||
,
|
||||
}
|
||||
@if (item.firstPublished && !item.lastPublished) {
|
||||
{{ item.firstPublished }}
|
||||
} @else {
|
||||
@if (item.firstPublished && item.lastPublished) {
|
||||
{{ [item.firstPublished, item.lastPublished] | join: ' - ' }}
|
||||
}
|
||||
}
|
||||
</p>
|
||||
<ion-note> {{ 'categories' | thingTranslate: item }} </ion-note>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
p.title-sub {
|
||||
white-space: unset;
|
||||
}
|
||||
@@ -12,29 +12,36 @@
|
||||
~ You should have received a copy of the GNU General Public License along with
|
||||
~ this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<ng-template #distanceView>
|
||||
@if (distance | async; as distance) {
|
||||
<ion-label class="distance" @fade>
|
||||
<ion-icon name="directions_walk"></ion-icon>
|
||||
{{ distance | metersLocalized }}
|
||||
</ion-label>
|
||||
}
|
||||
</ng-template>
|
||||
<ion-label class="title">{{ 'name' | thingTranslate: _item }}</ion-label>
|
||||
@if (_item.type !== 'floor') {
|
||||
<p class="title-sub">
|
||||
<stapps-opening-hours [openingHours]="_item.openingHours"></stapps-opening-hours>
|
||||
@if (_item.type !== 'building' && _item.inPlace) {
|
||||
<ion-icon name="pin_drop"></ion-icon>
|
||||
<ion-label>{{ 'name' | thingTranslate: _item.inPlace }}</ion-label>
|
||||
}
|
||||
</p>
|
||||
<ion-note>
|
||||
@if (_item.categories && _item.type !== 'building') {
|
||||
<stapps-opening-hours [openingHours]="_item.openingHours"></stapps-opening-hours>
|
||||
@if (_item.categories && _item.type !== 'building') {
|
||||
<ion-note>
|
||||
<ion-label> {{ 'categories' | thingTranslate: _item | join: ', ' | titlecase }} </ion-label>
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="distanceView"></ng-container>
|
||||
</ion-note>
|
||||
} @else {
|
||||
<ion-note>
|
||||
<ion-label> {{ 'type' | thingTranslate: _item | titlecase }} </ion-label>
|
||||
}
|
||||
@if (distance | async; as distance) {
|
||||
<ion-label @fade>
|
||||
<ion-icon name="directions_walk"></ion-icon>
|
||||
{{ distance | metersLocalized }}
|
||||
</ion-label>
|
||||
}
|
||||
</ion-note>
|
||||
<ng-container *ngTemplateOutlet="distanceView"></ng-container>
|
||||
</ion-note>
|
||||
}
|
||||
}
|
||||
@if (_item.description) {
|
||||
<p>{{ 'description' | thingTranslate: _item }}</p>
|
||||
}
|
||||
@if (_item.type !== 'building') {
|
||||
@if (_item.inPlace) {
|
||||
<ion-col size="auto" class="in-place">
|
||||
<ion-icon name="pin_drop"></ion-icon><ion-label>{{ 'name' | thingTranslate: _item.inPlace }}</ion-label>
|
||||
</ion-col>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
|
||||
ion-note {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
|
||||
> ion-label {
|
||||
display: inline-flex;
|
||||
@@ -25,16 +23,17 @@ ion-note {
|
||||
}
|
||||
}
|
||||
|
||||
p.title-sub {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
ion-label + ion-label::before {
|
||||
ion-label + ion-label.distance::before {
|
||||
content: '•';
|
||||
margin-inline: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.in-place {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
stapps-opening-hours ::ng-deep div {
|
||||
padding-block: var(--spacing-xs);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import {MenuModule} from '../menu/menu.module';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
import {DataModule} from '../data/data.module';
|
||||
import {UtilModule} from '../../util/util.module';
|
||||
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
|
||||
|
||||
const favoritesRoutes: Routes = [
|
||||
{
|
||||
@@ -39,6 +40,7 @@ const favoritesRoutes: Routes = [
|
||||
MenuModule,
|
||||
TranslateModule,
|
||||
DataModule,
|
||||
IonIconModule,
|
||||
UtilModule,
|
||||
],
|
||||
declarations: [FavoritesPageComponent],
|
||||
|
||||
@@ -21,6 +21,7 @@ import {RouterModule, Routes} from '@angular/router';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
import {MarkdownModule} from 'ngx-markdown';
|
||||
import {UtilModule} from '../../util/util.module';
|
||||
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
|
||||
|
||||
const feedbackRoutes: Routes = [
|
||||
{
|
||||
@@ -34,6 +35,7 @@ const feedbackRoutes: Routes = [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
IonicModule,
|
||||
IonIconModule,
|
||||
RouterModule.forChild(feedbackRoutes),
|
||||
TranslateModule,
|
||||
MarkdownModule,
|
||||
|
||||
@@ -21,7 +21,6 @@ import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {DataProvider} from '../data/data.provider';
|
||||
import {SCHebisSearchRoute} from './protocol/route';
|
||||
import {SCSearchRequest, SCSearchResponse} from '@openstapps/core';
|
||||
|
||||
const HEBIS_PREFIX = 'HEB';
|
||||
|
||||
@@ -47,6 +46,12 @@ export class HebisDataProvider extends DataProvider {
|
||||
*/
|
||||
private readonly hebisSearchRoute = new SCHebisSearchRoute();
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* @param stAppsWebHttpClient TODO
|
||||
* @param storageProvider TODO
|
||||
* @param httpClient TODO
|
||||
*/
|
||||
constructor(
|
||||
stAppsWebHttpClient: StAppsWebHttpClient,
|
||||
storageProvider: StorageProvider,
|
||||
@@ -58,23 +63,6 @@ export class HebisDataProvider extends DataProvider {
|
||||
this.client = new Client(stAppsWebHttpClient, this.backendUrl, this.appVersion);
|
||||
}
|
||||
|
||||
override async search(query: SCSearchRequest): Promise<SCSearchResponse> {
|
||||
const response = await this.hebisSearch({
|
||||
query: query.query ?? '',
|
||||
page: query.from,
|
||||
});
|
||||
console.log(response.pagination);
|
||||
|
||||
return {
|
||||
data: response.data,
|
||||
facets: [],
|
||||
pagination: response.pagination,
|
||||
stats: {
|
||||
time: Number.NaN,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a search request to the backend
|
||||
*
|
||||
|
||||
@@ -27,6 +27,7 @@ import {HebisDetailComponent} from './hebis-detail.component';
|
||||
import {Observable, of} from 'rxjs';
|
||||
import {StorageProvider} from '../../storage/storage.provider';
|
||||
import {IonicModule} from '@ionic/angular';
|
||||
import {IonIconModule} from '../../../util/ion-icon/ion-icon.module';
|
||||
import {LoggerModule, NgxLoggerLevel} from 'ngx-logger';
|
||||
|
||||
const translations: any = {data: {detail: {TITLE: 'Foo'}}};
|
||||
@@ -68,6 +69,7 @@ describe('HebisDetailComponent', () => {
|
||||
HebisRoutingModule,
|
||||
HebisModule,
|
||||
IonicModule,
|
||||
IonIconModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {provide: TranslateLoader, useClass: TranslateFakeLoader},
|
||||
}),
|
||||
|
||||
@@ -34,6 +34,7 @@ import {HebisRoutingModule} from './hebis-routing.module';
|
||||
import {DataModule} from '../data/data.module';
|
||||
import {DaiaAvailabilityComponent} from './daia-availability/daia-availability.component';
|
||||
import {UtilModule} from '../../util/util.module';
|
||||
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
|
||||
import {DaiaHoldingComponent} from './daia-availability/daia-holding.component';
|
||||
|
||||
/**
|
||||
@@ -52,8 +53,9 @@ import {DaiaHoldingComponent} from './daia-availability/daia-holding.component';
|
||||
DataModule,
|
||||
FormsModule,
|
||||
HebisRoutingModule,
|
||||
IonIconModule,
|
||||
HttpClientModule,
|
||||
IonicModule,
|
||||
IonicModule.forRoot(),
|
||||
MarkdownModule.forRoot(),
|
||||
MenuModule,
|
||||
MomentModule.forRoot({
|
||||
|
||||
@@ -12,12 +12,13 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component} from '@angular/core';
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {combineLatest} from 'rxjs';
|
||||
import {debounceTime, distinctUntilChanged, startWith} from 'rxjs/operators';
|
||||
import {SearchPageComponent} from '../../data/list/search-page.component';
|
||||
import {HebisDataProvider} from '../hebis-data.provider';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
import {DataProvider} from '../../data/data.provider';
|
||||
import {DataRoutingService} from '../../data/data-routing.service';
|
||||
import {Router} from '@angular/router';
|
||||
|
||||
/**
|
||||
* HebisSearchPageComponent queries things and shows list of things as search results and filter as context menu
|
||||
@@ -28,16 +29,111 @@ import {Router} from '@angular/router';
|
||||
styleUrls: ['../../data/list/search-page.scss'],
|
||||
providers: [{provide: DataProvider, useClass: HebisDataProvider}],
|
||||
})
|
||||
export class HebisSearchPageComponent {
|
||||
constructor(dataRoutingService: DataRoutingService, router: Router) {
|
||||
dataRoutingService
|
||||
export class HebisSearchPageComponent extends SearchPageComponent implements OnInit {
|
||||
/**
|
||||
* If routing should be done if the user clicks on an item
|
||||
*/
|
||||
@Input() itemRouting? = true;
|
||||
|
||||
/**
|
||||
* Current page to start query
|
||||
*/
|
||||
page = 0;
|
||||
|
||||
/**
|
||||
* Fetches items with set query configuration
|
||||
* @param append If true fetched data gets appended to existing, override otherwise (default false)
|
||||
*/
|
||||
protected async fetchAndUpdateItems(append = false): Promise<void> {
|
||||
// build query search options
|
||||
const searchOptions: {page: number; query: string} = {
|
||||
page: this.page,
|
||||
query: '',
|
||||
};
|
||||
|
||||
if (this.queryText && this.queryText.length > 0) {
|
||||
// add query string
|
||||
searchOptions.query = this.queryText;
|
||||
}
|
||||
|
||||
return (this.dataProvider as HebisDataProvider).hebisSearch(searchOptions).then(
|
||||
async result => {
|
||||
/*this.singleTypeResponse =
|
||||
result.facets.find(facet => facet.field === 'type')?.buckets
|
||||
.length === 1;*/
|
||||
if (append) {
|
||||
let items = await this.items;
|
||||
// append results
|
||||
items = [...items, ...result.data];
|
||||
this.items = (async () => items)();
|
||||
} else {
|
||||
// override items with results
|
||||
this.items = (async () => {
|
||||
return result.data;
|
||||
})();
|
||||
}
|
||||
},
|
||||
async error => {
|
||||
const alert: HTMLIonAlertElement = await this.alertController.create({
|
||||
buttons: ['Dismiss'],
|
||||
header: 'Error',
|
||||
subHeader: error.message,
|
||||
});
|
||||
|
||||
await alert.present();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads next page of things
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async loadMore(): Promise<void> {
|
||||
this.page += 1;
|
||||
await this.fetchAndUpdateItems(true);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
//this.fetchAndUpdateItems();
|
||||
this.initialize();
|
||||
|
||||
combineLatest([
|
||||
this.queryTextChanged.pipe(
|
||||
debounceTime(this.searchQueryDueTime),
|
||||
distinctUntilChanged(),
|
||||
startWith(this.queryText),
|
||||
),
|
||||
])
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(async query => {
|
||||
this.queryText = query[0];
|
||||
this.page = 0;
|
||||
if (this.queryText?.length > 0 || this.showDefaultData) {
|
||||
await this.fetchAndUpdateItems();
|
||||
this.queryChanged.next();
|
||||
}
|
||||
});
|
||||
this.settingsProvider.settingsActionChanged$
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(({type, payload}) => {
|
||||
if (type === 'stapps.settings.changed') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const {category, name, value} = payload!;
|
||||
this.logger.log(`received event "settings.changed" with category:
|
||||
${category}, name: ${name}, value: ${JSON.stringify(value)}`);
|
||||
}
|
||||
});
|
||||
this.dataRoutingService
|
||||
.itemSelectListener()
|
||||
.pipe(takeUntilDestroyed())
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(async item => {
|
||||
void router.navigate(
|
||||
['hebis-detail', (item.origin && 'originalId' in item.origin && item.origin['originalId']) || ''],
|
||||
{state: {item}},
|
||||
);
|
||||
if (this.itemRouting) {
|
||||
void this.router.navigate(
|
||||
['hebis-detail', (item.origin && 'originalId' in item.origin && item.origin['originalId']) || ''],
|
||||
{state: {item}},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,52 @@
|
||||
~ this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<stapps-search-page
|
||||
[placeholder]="'hebisSearch.search_bar.placeholder' | translate"
|
||||
[title]="'hebisSearch.title' | translate"
|
||||
[searchInstruction]="'hebisSearch.instruction' | translate"
|
||||
[itemRouting]="false"
|
||||
[showContextMenu]="false"
|
||||
[pageSize]="1"
|
||||
[navigation]="[{label: 'search.type', routerLink: ['/search']}, {label: 'hebisSearch.type'}]"
|
||||
>
|
||||
</stapps-search-page>
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button></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()"
|
||||
(search)="hideKeyboard()"
|
||||
[(ngModel)]="queryText"
|
||||
mode="md"
|
||||
placeholder="{{ 'hebisSearch.search_bar.placeholder' | translate }}"
|
||||
showClearButton="always"
|
||||
type="search"
|
||||
enterkeyhint="search"
|
||||
>
|
||||
</ion-searchbar>
|
||||
</ion-toolbar>
|
||||
<ion-toolbar color="primary" class="category-tab">
|
||||
<ion-buttons class="ion-justify-content-between">
|
||||
<ion-button
|
||||
[routerLink]="['/search']"
|
||||
queryParamsHandling="preserve"
|
||||
[routerAnimation]="routeAnimation"
|
||||
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>
|
||||
<div [style.display]="!showDefaultData && !items && !loading ? 'block' : 'none'">
|
||||
<ion-label class="centered-message-container"> {{ 'hebisSearch.instruction' | translate }} </ion-label>
|
||||
</div>
|
||||
<stapps-data-list
|
||||
id="data-list"
|
||||
[items]="items | async"
|
||||
[singleType]="singleTypeResponse"
|
||||
(loadmore)="loadMore()"
|
||||
[resetToTop]="queryChanged.asObservable()"
|
||||
[loading]="loading"
|
||||
></stapps-data-list>
|
||||
</ion-content>
|
||||
|
||||
@@ -5,6 +5,7 @@ import {TranslateModule} from '@ngx-translate/core';
|
||||
import {MomentModule} from 'ngx-moment';
|
||||
import {DataModule} from '../data/data.module';
|
||||
import {UtilModule} from '../../util/util.module';
|
||||
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
|
||||
import {ConfigProvider} from '../config/config.provider';
|
||||
import {ThingTranslateModule} from '../../translation/thing-translate.module';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
@@ -15,10 +16,11 @@ const jobsRoutes: Routes = [{path: 'jobs', component: JobsPageComponent}];
|
||||
@NgModule({
|
||||
declarations: [JobsPageComponent],
|
||||
imports: [
|
||||
IonicModule,
|
||||
IonicModule.forRoot(),
|
||||
ThingTranslateModule.forChild(),
|
||||
TranslateModule.forChild(),
|
||||
RouterModule.forChild(jobsRoutes),
|
||||
IonIconModule,
|
||||
CommonModule,
|
||||
MomentModule,
|
||||
DataModule,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
[title]="'jobs.title' | translate"
|
||||
[placeholder]="'jobs.placeholder' | translate"
|
||||
[showDefaultData]="true"
|
||||
[navigation]="[]"
|
||||
[showNavigation]="false"
|
||||
[forcedFilter]="forcedFilter"
|
||||
[backUrl]="'/'"
|
||||
></stapps-search-page>
|
||||
|
||||
@@ -32,6 +32,7 @@ import {MomentModule} from 'ngx-moment';
|
||||
import {FeeItemComponent} from './account/elements/fee-item/fee-item.component';
|
||||
import {DataModule} from '../data/data.module';
|
||||
import {UtilModule} from '../../util/util.module';
|
||||
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
|
||||
|
||||
const routes: ProtectedRoutes | Routes = [
|
||||
{
|
||||
@@ -71,6 +72,7 @@ const routes: ProtectedRoutes | Routes = [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
IonicModule,
|
||||
IonIconModule,
|
||||
RouterModule.forChild(routes),
|
||||
TranslateModule,
|
||||
MomentModule,
|
||||
|
||||
@@ -32,6 +32,7 @@ import {MapPageComponent} from './page/map-page.component';
|
||||
import {MapListModalComponent} from './page/map-list-modal.component';
|
||||
import {NgModule} from '@angular/core';
|
||||
import {UtilModule} from '../../util/util.module';
|
||||
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
|
||||
import {GeoNavigationDirective} from './geo-navigation.directive';
|
||||
|
||||
/**
|
||||
@@ -58,8 +59,9 @@ const mapRoutes: Routes = [
|
||||
exports: [],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
IonicModule.forRoot(),
|
||||
LeafletModule,
|
||||
IonIconModule,
|
||||
LeafletMarkerClusterModule,
|
||||
RouterModule.forChild(mapRoutes),
|
||||
TranslateModule.forChild(),
|
||||
|
||||
@@ -52,10 +52,10 @@ export class MapProvider {
|
||||
icon: divIcon({
|
||||
className: className,
|
||||
html: `<span
|
||||
name="${SCIcon.location_on}"
|
||||
name="${SCIcon`location_on`}"
|
||||
class="material-symbols-rounded map-location-pin"
|
||||
style="font-size: ${iconSize}px;"
|
||||
>${SCIcon.location_on}</span>`,
|
||||
>${SCIcon`location_on`}</span>`,
|
||||
iconSize: [iconSize, iconSize],
|
||||
iconAnchor: [iconSize / 2, iconSize],
|
||||
}),
|
||||
@@ -75,10 +75,10 @@ export class MapProvider {
|
||||
html:
|
||||
position.heading === undefined
|
||||
? `<span
|
||||
name="${SCIcon.person_pin_circle}"
|
||||
name="${SCIcon`person_pin_circle`}"
|
||||
class="material-symbols-rounded map-location-pin"
|
||||
style="font-size: ${iconSize}px; color: var(--ion-color-primary);"
|
||||
>${SCIcon.person_pin_circle}</span>`
|
||||
>${SCIcon`person_pin_circle`}</span>`
|
||||
: `<span
|
||||
class="material-symbols-rounded map-location-pin"
|
||||
style="
|
||||
@@ -87,7 +87,7 @@ export class MapProvider {
|
||||
font-size: ${iconSize}px;
|
||||
color: var(--ion-color-primary);
|
||||
"
|
||||
>${SCIcon.navigation}</span>`,
|
||||
>${SCIcon`navigation`}</span>`,
|
||||
iconSize: [iconSize, iconSize],
|
||||
}),
|
||||
zIndexOffset: 1000,
|
||||
|
||||
@@ -52,7 +52,7 @@ describe('ContextMenuComponent', async () => {
|
||||
],
|
||||
imports: [
|
||||
FormsModule,
|
||||
IonicModule,
|
||||
IonicModule.forRoot(),
|
||||
TranslateModule.forRoot(),
|
||||
CommonModule,
|
||||
SettingsModule,
|
||||
|
||||
@@ -22,6 +22,7 @@ import {TranslateModule} from '@ngx-translate/core';
|
||||
import {SettingsModule} from '../settings/settings.module';
|
||||
import {ContextMenuComponent} from './context/context-menu.component';
|
||||
import {ContextMenuService} from './context/context-menu.service';
|
||||
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
|
||||
|
||||
/**
|
||||
* Menu module
|
||||
@@ -31,8 +32,9 @@ import {ContextMenuService} from './context/context-menu.service';
|
||||
exports: [ContextMenuComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonIconModule,
|
||||
FormsModule,
|
||||
IonicModule,
|
||||
IonicModule.forRoot(),
|
||||
RouterModule,
|
||||
SettingsModule,
|
||||
TranslateModule.forChild(),
|
||||
|
||||
@@ -18,13 +18,14 @@ import {NavigationComponent} from './navigation.component';
|
||||
import {TabsComponent} from './tabs.component';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {IonicModule} from '@ionic/angular';
|
||||
import {IonIconModule} from '../../../util/ion-icon/ion-icon.module';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {OfflineNoticeComponent} from './offline-notice.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [RootLinkDirective, NavigationComponent, TabsComponent, OfflineNoticeComponent],
|
||||
imports: [CommonModule, IonicModule, TranslateModule, RouterModule],
|
||||
imports: [CommonModule, IonicModule, IonIconModule, TranslateModule, RouterModule],
|
||||
exports: [TabsComponent, RootLinkDirective, NavigationComponent],
|
||||
})
|
||||
export class NavigationModule {}
|
||||
|
||||
@@ -77,7 +77,9 @@ ion-router-outlet {
|
||||
}
|
||||
|
||||
.link-active > * {
|
||||
--fill: 1;
|
||||
|
||||
color: var(--ion-color-primary);
|
||||
|
||||
::ng-deep stapps-icon {
|
||||
--fill: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
~ this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<ion-button class="offline-button" color="warning">
|
||||
<ion-icon slot="start" [size]="16" [style.--weight]="800" name="cloud_off"></ion-icon>
|
||||
<ion-icon slot="start" [size]="16" [weight]="800" name="cloud_off"></ion-icon>
|
||||
<ion-label>{{ 'app.errors.OFFLINE' | translate }}</ion-label>
|
||||
</ion-button>
|
||||
<ion-button class="error-button" color="danger" (click)="retry()">
|
||||
<ion-icon #spinIcon slot="start" [size]="16" [style.--weight]="800" name="refresh"></ion-icon>
|
||||
<ion-icon #spinIcon slot="start" [size]="16" [weight]="800" name="refresh"></ion-icon>
|
||||
<ion-label>{{ 'app.errors.CONNECTION_ERROR' | translate }}</ion-label>
|
||||
</ion-button>
|
||||
<ion-button class="close" fill="clear" color="light" (click)="offlineProvider.dismissError()"
|
||||
><ion-icon [size]="16" [style.--weight]="800" name="close" slot="icon-only"></ion-icon
|
||||
><ion-icon [size]="16" [weight]="800" name="close" slot="icon-only"></ion-icon
|
||||
></ion-button>
|
||||
|
||||
@@ -60,6 +60,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.tab-selected ion-icon {
|
||||
.tab-selected ::ng-deep stapps-icon {
|
||||
--fill: 1;
|
||||
}
|
||||
|
||||
@@ -24,10 +24,11 @@ import {SettingsProvider} from '../settings/settings.provider';
|
||||
import {NewsItemComponent} from './item/news-item.component';
|
||||
import {NewsPageComponent} from './page/news-page.component';
|
||||
import {SkeletonNewsItemComponent} from './item/skeleton-news-item.component';
|
||||
import {ChipFilterComponent} from './elements/news-filter-settings/chip-filter.component';
|
||||
import {ChipFilterComponent} from '../data/chips/filter/chip-filter.component';
|
||||
import {SettingsModule} from '../settings/settings.module';
|
||||
import {NewsSettingsFilterComponent} from './elements/news-filter-settings/news-settings-filter.component';
|
||||
import {UtilModule} from '../../util/util.module';
|
||||
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
|
||||
|
||||
const newsRoutes: Routes = [{path: 'news', component: NewsPageComponent}];
|
||||
|
||||
@@ -43,10 +44,11 @@ const newsRoutes: Routes = [{path: 'news', component: NewsPageComponent}];
|
||||
NewsSettingsFilterComponent,
|
||||
],
|
||||
imports: [
|
||||
IonicModule,
|
||||
IonicModule.forRoot(),
|
||||
ThingTranslateModule.forChild(),
|
||||
TranslateModule.forChild(),
|
||||
RouterModule.forChild(newsRoutes),
|
||||
IonIconModule,
|
||||
CommonModule,
|
||||
MomentModule,
|
||||
DataModule,
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<div>
|
||||
<div class="log-in">
|
||||
{{ 'profile.userInfo.logInPrompt' | translate | sentencecase }}
|
||||
<ion-icon name="person" [style.--fill]="1"></ion-icon>
|
||||
<ion-icon name="person" [fill]="true"></ion-icon>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import {ProfilePageComponent} from './page/profile-page.component';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
import {SwiperModule} from 'swiper/angular';
|
||||
import {UtilModule} from '../../util/util.module';
|
||||
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
|
||||
import {ProfilePageSectionComponent} from './page/profile-page-section.component';
|
||||
import {ThingTranslateModule} from '../../translation/thing-translate.module';
|
||||
import {DataModule} from '../data/data.module';
|
||||
@@ -40,6 +41,7 @@ const routes: Routes = [
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
IonIconModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
TranslateModule,
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
~ this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<stapps-search-page
|
||||
[navigation]="[]"
|
||||
[showNavigation]="false"
|
||||
[showDefaultData]="false"
|
||||
[forcedFilter]="forcedFilter"
|
||||
[backUrl]="'..'"
|
||||
|
||||
@@ -35,6 +35,7 @@ import {ScheduleDayComponent} from './page/grid/schedule-day.component';
|
||||
import {ThingTranslateModule} from '../../translation/thing-translate.module';
|
||||
import {InfiniteSwiperComponent} from './page/grid/infinite-swiper.component';
|
||||
import {CalendarComponent} from './page/components/calendar.component';
|
||||
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
|
||||
import {ChooseEventsPageComponent} from './page/choose-events-page.component';
|
||||
|
||||
const settingsRoutes: Routes = [
|
||||
@@ -68,7 +69,8 @@ const settingsRoutes: Routes = [
|
||||
CommonModule,
|
||||
DataModule,
|
||||
FormsModule,
|
||||
IonicModule,
|
||||
IonicModule.forRoot(),
|
||||
IonIconModule,
|
||||
MomentModule,
|
||||
RouterModule.forChild(settingsRoutes),
|
||||
SwiperModule,
|
||||
|
||||
@@ -33,6 +33,7 @@ import {CalendarService} from '../calendar/calendar.service';
|
||||
import {CalendarModule} from '../calendar/calendar.module';
|
||||
import {BackgroundModule} from '../background/background.module';
|
||||
import {UtilModule} from '../../util/util.module';
|
||||
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
|
||||
|
||||
const settingsRoutes: Routes = [{path: 'settings', component: SettingsPageComponent}];
|
||||
|
||||
@@ -51,8 +52,9 @@ const settingsRoutes: Routes = [{path: 'settings', component: SettingsPageCompon
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
CalendarModule,
|
||||
IonIconModule,
|
||||
BackgroundModule,
|
||||
IonicModule,
|
||||
IonicModule.forRoot(),
|
||||
TranslateModule.forChild(),
|
||||
ThingTranslateModule.forChild(),
|
||||
RouterModule.forChild(settingsRoutes),
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
import {ThingTranslateService} from './thing-translate.service';
|
||||
import {SCThingType, SCThings} from '@openstapps/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'propertyValueTranslate',
|
||||
standalone: true,
|
||||
})
|
||||
export class PropertyValueTranslatePipe implements PipeTransform {
|
||||
constructor(private readonly thingTranslate: ThingTranslateService) {}
|
||||
|
||||
transform<
|
||||
K extends string | undefined,
|
||||
T extends SCThingType,
|
||||
U extends keyof Extract<SCThings, {type: T}>,
|
||||
>(value: K, field: (U & string) | string, type: T): K {
|
||||
return this.thingTranslate.getPropertyValue(type, field, value) as K;
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
import {ThingTranslateDefaultParser, ThingTranslateParser} from './thing-translate.parser';
|
||||
import {ThingTranslatePipe} from './thing-translate.pipe';
|
||||
import {ThingTranslateService} from './thing-translate.service';
|
||||
import {IonIconModule} from '../util/ion-icon/ion-icon.module';
|
||||
import {TranslateSimplePipe} from './translate-simple.pipe';
|
||||
import {PropertyNameTranslatePipe} from './property-name-translate.pipe';
|
||||
|
||||
@@ -37,6 +38,7 @@ export interface ThingTranslateModuleConfig {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [IonIconModule],
|
||||
declarations: [
|
||||
ArrayJoinPipe,
|
||||
DurationLocalizedPipe,
|
||||
@@ -54,6 +56,7 @@ export interface ThingTranslateModuleConfig {
|
||||
IsNumericPipe,
|
||||
],
|
||||
exports: [
|
||||
IonIconModule,
|
||||
ArrayJoinPipe,
|
||||
DurationLocalizedPipe,
|
||||
NumberLocalizedPipe,
|
||||
|
||||
@@ -113,4 +113,8 @@ export class ThingTranslateService {
|
||||
|
||||
return this.getParsedResult(translatedPropertyNames, keyPath);
|
||||
}
|
||||
|
||||
public getPropertyValue(type: SCThingType, field: string, key: string | undefined): string | undefined {
|
||||
return this.translator.translatedPropertyValue(type, field, key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ export class InternetConnectionService {
|
||||
private doRetry(error: unknown, retryCount: number): ObservableInput<unknown> {
|
||||
return race(
|
||||
this.offline$.pipe(
|
||||
tap(it => console.log(it)),
|
||||
filter(it => !it),
|
||||
take(1),
|
||||
delay(Math.min(retryCount ** 4 + 100, 10_000)),
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export function matchTagProperties(tag: string): RegExp;
|
||||
|
||||
export function matchPropertyContent(properties: string[]): RegExp;
|
||||
|
||||
export function matchPropertyAccess(objectName: string): RegExp;
|
||||
@@ -14,7 +14,7 @@
|
||||
*/
|
||||
/* eslint-disable unicorn/no-null */
|
||||
|
||||
import {matchPropertyContent, matchTagProperties, matchPropertyAccess} from './icon-match.mjs';
|
||||
import {matchPropertyContent, matchTagProperties} from './icon-match';
|
||||
|
||||
describe('matchTagProperties', function () {
|
||||
const regex = matchTagProperties('test');
|
||||
@@ -59,30 +59,3 @@ describe('matchPropertyContent', function () {
|
||||
expect(`no="content" [no]="'content'"`.match(regex)).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('matchPropertyAccess', function () {
|
||||
const property = '0_20a_boninAo0_';
|
||||
const object = 'test';
|
||||
const regex = matchPropertyAccess(object);
|
||||
|
||||
it('should match property access', function () {
|
||||
expect(`${object}.${property}`.match(regex)).toEqual([property]);
|
||||
});
|
||||
|
||||
it('should respect whitespace', function () {
|
||||
expect(`${object}. ${property}`.match(regex)).toEqual([property]);
|
||||
expect(`${object} .${property}`.match(regex)).toEqual([property]);
|
||||
expect(`${object} . ${property}`.match(regex)).toEqual([property]);
|
||||
expect(`${object} \n . \n ${property}`.match(regex)).toEqual([property]);
|
||||
});
|
||||
|
||||
it('should not include invalid trailing stuff', function () {
|
||||
expect(`${object}.${property}!`.match(regex)).toEqual([property]);
|
||||
expect(`${object}.${property}.`.match(regex)).toEqual([property]);
|
||||
expect(`${object}.${property}-`.match(regex)).toEqual([property]);
|
||||
expect(`${object}.${property}]`.match(regex)).toEqual([property]);
|
||||
expect(`${object}.${property}}`.match(regex)).toEqual([property]);
|
||||
expect(`${object}.${property};`.match(regex)).toEqual([property]);
|
||||
expect(`${object}.${property}:`.match(regex)).toEqual([property]);
|
||||
});
|
||||
});
|
||||
|
||||
+4
-11
@@ -14,24 +14,17 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string} tag
|
||||
*
|
||||
*/
|
||||
export function matchTagProperties(tag) {
|
||||
export function matchTagProperties(tag: string) {
|
||||
return new RegExp(`(?<=<${tag})[\\s\\S]*?(?=>\\s*<\\/${tag}\\s*>)`, 'g');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} properties
|
||||
*
|
||||
*/
|
||||
export function matchPropertyContent(properties) {
|
||||
export function matchPropertyContent(properties: string[]) {
|
||||
const names = properties.join('|');
|
||||
|
||||
return new RegExp(`((?<=(${names})=")[\\w-]+(?="))|((?<=\\[(${names})]="')[\\w-]+(?='"))`, 'g');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} objectName
|
||||
*/
|
||||
export function matchPropertyAccess(objectName) {
|
||||
return new RegExp(`(?<=${objectName}\\s*\\.\\s*)\\w+`, 'g');
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2023 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {ChangeDetectionStrategy, Component, HostBinding, Input} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'stapps-icon',
|
||||
templateUrl: 'icon.html',
|
||||
styleUrls: ['icon.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class IconComponent {
|
||||
@HostBinding('style.--size')
|
||||
@Input()
|
||||
size?: number;
|
||||
|
||||
@HostBinding('style.--weight')
|
||||
@Input()
|
||||
weight?: number;
|
||||
|
||||
@HostBinding('style.--grade')
|
||||
@Input()
|
||||
grade?: number;
|
||||
|
||||
@Input()
|
||||
fill: boolean;
|
||||
|
||||
@HostBinding('innerHtml')
|
||||
@Input()
|
||||
name: string;
|
||||
|
||||
@HostBinding('style.--fill') get fillStyle(): number | undefined {
|
||||
return this.fill ? 1 : undefined;
|
||||
}
|
||||
|
||||
@HostBinding('class.material-symbols-rounded') hostClass = true;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
<ng-content></ng-content>
|
||||
@@ -0,0 +1,31 @@
|
||||
/*!
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
:host {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
font-size: inherit;
|
||||
font-variation-settings:
|
||||
'wght' var(--weight),
|
||||
'GRAD' var(--grade),
|
||||
'FILL' var(--fill);
|
||||
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
@@ -12,8 +12,10 @@
|
||||
* 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 {MaterialSymbol} from 'material-symbols';
|
||||
|
||||
export const SCIcon = new Proxy({} as {[key in MaterialSymbol]: key}, {
|
||||
get: (_target, property: string) => property as MaterialSymbol,
|
||||
});
|
||||
/**
|
||||
* A noop function to aid parsing icon names
|
||||
*/
|
||||
export function SCIcon(strings: TemplateStringsArray, ..._keys: string[]): string {
|
||||
return strings.join('');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2023 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {
|
||||
DestroyRef,
|
||||
Directive,
|
||||
ElementRef,
|
||||
Host,
|
||||
inject,
|
||||
OnInit,
|
||||
Optional,
|
||||
Self,
|
||||
ViewContainerRef,
|
||||
} from '@angular/core';
|
||||
import {SCIcon} from './icon';
|
||||
import {IconReplacer} from './replace-util';
|
||||
import {TranslateService} from '@ngx-translate/core';
|
||||
import {IonBackButton} from '@ionic/angular';
|
||||
import {TitleCasePipe} from '@angular/common';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
@Directive({
|
||||
selector: 'ion-back-button',
|
||||
})
|
||||
export class IonBackButtonDirective extends IconReplacer implements OnInit {
|
||||
destroy$ = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
element: ElementRef,
|
||||
viewContainerRef: ViewContainerRef,
|
||||
@Host() @Self() @Optional() private ionBackButton: IonBackButton,
|
||||
private translateService: TranslateService,
|
||||
private titleCasePipe: TitleCasePipe,
|
||||
) {
|
||||
super(element, viewContainerRef, 'shadow');
|
||||
}
|
||||
|
||||
replace() {
|
||||
this.replaceIcon(this.host.querySelector('.button-inner'), {
|
||||
md: SCIcon`arrow_back`,
|
||||
ios: SCIcon`arrow_back_ios`,
|
||||
size: 24,
|
||||
});
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
await super.ngOnInit();
|
||||
this.translateService
|
||||
.stream('back')
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe((value: string) => {
|
||||
this.ionBackButton.text = this.titleCasePipe.transform(value);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 {Directive, ElementRef, ViewContainerRef} from '@angular/core';
|
||||
import {SCIcon} from './icon';
|
||||
import {IconReplacer} from './replace-util';
|
||||
|
||||
@Directive({
|
||||
selector: 'ion-breadcrumb',
|
||||
})
|
||||
export class IonBreadcrumbDirective extends IconReplacer {
|
||||
constructor(element: ElementRef, viewContainerRef: ViewContainerRef) {
|
||||
super(element, viewContainerRef, 'shadow');
|
||||
}
|
||||
|
||||
replace() {
|
||||
this.replaceIcon(
|
||||
this.host.querySelector('span[part="separator"]'),
|
||||
{
|
||||
name: SCIcon`arrow_forward_ios`,
|
||||
size: 16,
|
||||
style: `color: var(--ion-color-tint);`,
|
||||
},
|
||||
'-separator',
|
||||
);
|
||||
this.replaceIcon(
|
||||
this.host.querySelector('button[part="collapsed-indicator"]'),
|
||||
{
|
||||
name: SCIcon`more_horiz`,
|
||||
size: 24,
|
||||
},
|
||||
'-collapsed',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
import {MaterialSymbol} from 'material-symbols';
|
||||
import {SCIcon} from './icon';
|
||||
import {
|
||||
arrowBackOutline,
|
||||
caretDownSharp,
|
||||
chevronBackOutline,
|
||||
chevronExpand,
|
||||
chevronForwardOutline,
|
||||
close,
|
||||
closeCircle,
|
||||
closeSharp,
|
||||
ellipsisHorizontal,
|
||||
menu,
|
||||
menuSharp,
|
||||
searchOutline,
|
||||
searchSharp,
|
||||
} from 'ionicons/icons';
|
||||
|
||||
const styles = new CSSStyleSheet();
|
||||
styles.replaceSync(/* css */ `
|
||||
:host {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
|
||||
font-size: inherit;
|
||||
font-variation-settings: 'wght' var(--weight, 400), 'GRAD' var(--grade, 1), 'FILL' var(--fill, 0);
|
||||
|
||||
transition: font-variation-settings 0.2s ease-in-out;
|
||||
|
||||
direction: ltr;
|
||||
font-family: 'Material Symbols Rounded';
|
||||
font-feature-settings: 'liga';
|
||||
font-style: normal;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
text-rendering: optimizelegibility;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
`);
|
||||
|
||||
export class IonIconCustomElement extends HTMLElement {
|
||||
static observedAttributes = ['name', 'size', 'color'];
|
||||
|
||||
set name(value: MaterialSymbol) {
|
||||
this.shadowRoot!.textContent =
|
||||
document.querySelector('ion-app.ios') && iosAlias.has(value) ? iosAlias.get(value)! : value;
|
||||
}
|
||||
|
||||
set icon(value: string) {
|
||||
if (iconMap.has(value)) {
|
||||
this.name = iconMap.get(value)!;
|
||||
} else {
|
||||
this.style.setProperty('color', 'red');
|
||||
// @ts-expect-error not assignable
|
||||
this.name = '??';
|
||||
console.error(
|
||||
`The icon "${value}" is missing from the icon map (did Ionic change something?)`,
|
||||
'\n\n',
|
||||
new Error('...')!.stack,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
set size(value: string) {
|
||||
switch (value) {
|
||||
case 'small': {
|
||||
this.style.setProperty('font-size', '1.125rem');
|
||||
break;
|
||||
}
|
||||
case 'large': {
|
||||
this.style.setProperty('font-size', '2rem');
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
this.style.setProperty('font-size', Number.isNaN(Number(value)) ? value : `${value}px`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set color(value: string) {
|
||||
this.style.setProperty('color', `var(--ion-color-${value}`);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const shadow = this.attachShadow({mode: 'open'});
|
||||
shadow.adoptedStyleSheets = [styles];
|
||||
}
|
||||
}
|
||||
|
||||
const iosAlias = new Map<MaterialSymbol, MaterialSymbol>([
|
||||
[SCIcon.arrow_back, SCIcon.arrow_back_ios],
|
||||
[SCIcon.arrow_forward, SCIcon.arrow_forward_ios],
|
||||
[SCIcon.share, SCIcon.ios_share],
|
||||
]);
|
||||
|
||||
/**
|
||||
* Maps hardcoded icon SVGs to our icon names.
|
||||
* Thanks, Ionic.
|
||||
*/
|
||||
const iconMap = new Map<string, MaterialSymbol>([
|
||||
[arrowBackOutline, SCIcon.arrow_back],
|
||||
[chevronBackOutline, SCIcon.arrow_back],
|
||||
[chevronForwardOutline, SCIcon.arrow_forward],
|
||||
[menu, SCIcon.menu],
|
||||
[menuSharp, SCIcon.menu],
|
||||
[searchOutline, SCIcon.search],
|
||||
[searchSharp, SCIcon.search],
|
||||
[chevronExpand, SCIcon.expand_more],
|
||||
[caretDownSharp, SCIcon.expand_more],
|
||||
[close, SCIcon.close],
|
||||
[closeSharp, SCIcon.close],
|
||||
[closeCircle, SCIcon.cancel],
|
||||
[ellipsisHorizontal, SCIcon.more_horiz],
|
||||
['chevron-down-outline', SCIcon.expand_more],
|
||||
['arrow_back', SCIcon.arrow_back],
|
||||
]);
|
||||
@@ -1,14 +1,132 @@
|
||||
import {Directive, Input} from '@angular/core';
|
||||
import {MaterialSymbol} from 'material-symbols';
|
||||
/*
|
||||
* 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 {
|
||||
ComponentRef,
|
||||
Directive,
|
||||
ElementRef,
|
||||
Host,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Optional,
|
||||
Self,
|
||||
ViewContainerRef,
|
||||
} from '@angular/core';
|
||||
import {IconComponent} from './icon.component';
|
||||
import {IonIcon} from '@ionic/angular';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
const noop = () => {};
|
||||
const noopProperty = {
|
||||
set: noop,
|
||||
get: noop,
|
||||
};
|
||||
|
||||
@Directive({
|
||||
selector: 'ion-icon',
|
||||
standalone: true,
|
||||
})
|
||||
export class IonIconDirective {
|
||||
@Input({required: true}) name: MaterialSymbol;
|
||||
export class IonIconDirective implements OnInit, OnDestroy, OnChanges {
|
||||
@Input() name: string;
|
||||
|
||||
@Input() size: 'large' | 'small' | number | string;
|
||||
@Input() md: string;
|
||||
|
||||
@Input() color: 'primary' | 'secondary' | 'light' | 'medium' | 'dark' | string;
|
||||
@Input() ios: string;
|
||||
|
||||
@Input() fill = false;
|
||||
|
||||
@Input() weight: number;
|
||||
|
||||
@Input() size: number | 'small' | 'large';
|
||||
|
||||
@Input() grade: number;
|
||||
|
||||
@Input() style: string;
|
||||
|
||||
private mutationObserver: MutationObserver;
|
||||
|
||||
iconComponent?: ComponentRef<IconComponent>;
|
||||
|
||||
private static get mode(): 'md' | 'ios' {
|
||||
return document.querySelector(':root')?.getAttribute('mode') as 'md' | 'ios';
|
||||
}
|
||||
|
||||
constructor(
|
||||
private element: ElementRef,
|
||||
private viewContainerRef: ViewContainerRef,
|
||||
@Host() @Self() @Optional() private ionIcon: IonIcon,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.iconComponent = this.viewContainerRef.createComponent(IconComponent, {});
|
||||
|
||||
this.element.nativeElement.insertBefore(
|
||||
this.iconComponent.location.nativeElement,
|
||||
this.element.nativeElement.firstChild,
|
||||
);
|
||||
|
||||
this.mutationObserver = new MutationObserver(() => {
|
||||
const inner = this.element.nativeElement.shadowRoot.querySelector('.icon-inner');
|
||||
if (!inner) return;
|
||||
|
||||
inner.insertBefore(document.createElement('slot'), inner.firstChild);
|
||||
});
|
||||
this.mutationObserver.observe(this.element.nativeElement.shadowRoot, {
|
||||
childList: true,
|
||||
});
|
||||
|
||||
this.ngOnChanges();
|
||||
// this will effectively completely disable the ion-icon component
|
||||
for (const name of ['src', 'name', 'icon', 'md', 'ios']) {
|
||||
this.disableProperty(name);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.mutationObserver.disconnect();
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
if (!this.iconComponent) return;
|
||||
for (const key of ['name', 'weight', 'fill', 'size', 'grade'] as Array<
|
||||
keyof IconComponent & keyof IonIconDirective
|
||||
>) {
|
||||
// @ts-expect-error type mismatch
|
||||
this.iconComponent.instance[key] = this[key];
|
||||
}
|
||||
|
||||
for (const mode of ['md', 'ios'] as Array<'md' | 'ios'>) {
|
||||
if (this[mode] && IonIconDirective.mode === mode) {
|
||||
this.iconComponent.instance.name = this[mode];
|
||||
}
|
||||
}
|
||||
|
||||
if (this.size) {
|
||||
this.element.nativeElement.style.cssText = `font-size: ${this.size}px;`;
|
||||
}
|
||||
if (this.style) {
|
||||
this.element.nativeElement.style.cssText += this.style;
|
||||
}
|
||||
}
|
||||
|
||||
disableProperty(name: string) {
|
||||
Object.defineProperty(
|
||||
Object.getPrototypeOf((this.ionIcon as unknown as {el: HTMLElement}).el),
|
||||
name,
|
||||
noopProperty,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 {NgModule} from '@angular/core';
|
||||
import {IconComponent} from './icon.component';
|
||||
import {IonIconDirective} from './ion-icon.directive';
|
||||
import {IonBackButtonDirective} from './ion-back-button.directive';
|
||||
import {IonSearchbarDirective} from './ion-searchbar.directive';
|
||||
import {IonBreadcrumbDirective} from './ion-breadcrumb.directive';
|
||||
import {IonReorderDirective} from './ion-reorder.directive';
|
||||
import {TranslateModule, TranslateService} from '@ngx-translate/core';
|
||||
import {CommonModule, TitleCasePipe} from '@angular/common';
|
||||
|
||||
@NgModule({
|
||||
imports: [TranslateModule, CommonModule],
|
||||
declarations: [
|
||||
IconComponent,
|
||||
IonIconDirective,
|
||||
IonBackButtonDirective,
|
||||
IonSearchbarDirective,
|
||||
IonBreadcrumbDirective,
|
||||
IonReorderDirective,
|
||||
],
|
||||
exports: [
|
||||
IonIconDirective,
|
||||
IonReorderDirective,
|
||||
IonBackButtonDirective,
|
||||
IonSearchbarDirective,
|
||||
IonBreadcrumbDirective,
|
||||
],
|
||||
providers: [TranslateService, TitleCasePipe],
|
||||
})
|
||||
export class IonIconModule {}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 {Directive, ElementRef, ViewContainerRef} from '@angular/core';
|
||||
import {SCIcon} from './icon';
|
||||
import {IconReplacer} from './replace-util';
|
||||
|
||||
@Directive({
|
||||
selector: 'ion-reorder',
|
||||
})
|
||||
export class IonReorderDirective extends IconReplacer {
|
||||
constructor(element: ElementRef, viewContainerRef: ViewContainerRef) {
|
||||
super(element, viewContainerRef, 'shadow');
|
||||
}
|
||||
|
||||
replace() {
|
||||
this.replaceIcon(this.host, {
|
||||
name: SCIcon`reorder`,
|
||||
size: 24,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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 {Directive, ElementRef, ViewContainerRef} from '@angular/core';
|
||||
import {SCIcon} from './icon';
|
||||
import {IconReplacer} from './replace-util';
|
||||
|
||||
@Directive({
|
||||
selector: 'ion-searchbar',
|
||||
})
|
||||
export class IonSearchbarDirective extends IconReplacer {
|
||||
constructor(element: ElementRef, viewContainerRef: ViewContainerRef) {
|
||||
super(element, viewContainerRef, 'light');
|
||||
}
|
||||
|
||||
replace() {
|
||||
this.replaceIcon(this.host.querySelector('.searchbar-input-container'), {
|
||||
name: SCIcon`search`,
|
||||
size: 24,
|
||||
});
|
||||
this.replaceIcon(this.host.querySelector('.searchbar-clear-button'), {
|
||||
name: SCIcon`close`,
|
||||
size: 24,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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 {ComponentRef, Directive, ElementRef, OnDestroy, OnInit, ViewContainerRef} from '@angular/core';
|
||||
import {IonIcon} from '@ionic/angular';
|
||||
import {IonIconDirective} from './ion-icon.directive';
|
||||
import {waitForElement} from './shadow-attacher';
|
||||
|
||||
export type IconData = Omit<
|
||||
Partial<IonIconDirective>,
|
||||
'ngOnChanges' | 'ngOnInit' | 'viewContainerRef' | 'ngOnDestroy' | 'element' | 'ionIcon' | 'disableProperty'
|
||||
>;
|
||||
|
||||
/**
|
||||
* A utility class to replace ion-icons in other ionic components.
|
||||
*/
|
||||
@Directive()
|
||||
export abstract class IconReplacer implements OnInit, OnDestroy {
|
||||
private mutationObserver: MutationObserver;
|
||||
|
||||
protected slotName = 'sc-icon';
|
||||
|
||||
protected maxAttempts = 10;
|
||||
|
||||
protected retryAfterMs = 10;
|
||||
|
||||
/**
|
||||
* The host element
|
||||
*
|
||||
* This will be either element.nativeElement.shadowRoot or element.nativeElement
|
||||
* depending on the iconDomLocation
|
||||
*/
|
||||
protected get host() {
|
||||
return this.iconDomLocation === 'shadow'
|
||||
? this.element.nativeElement.shadowRoot
|
||||
: this.element.nativeElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param element The host element
|
||||
* @param viewContainerRef The view container ref
|
||||
* @param iconDomLocation If the icon is placed inside the shadow dom or not
|
||||
* @protected
|
||||
*/
|
||||
protected constructor(
|
||||
private readonly element: ElementRef,
|
||||
private readonly viewContainerRef: ViewContainerRef,
|
||||
private readonly iconDomLocation: 'shadow' | 'light',
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Replace the icons here
|
||||
*/
|
||||
abstract replace(): void;
|
||||
|
||||
async ngOnInit() {
|
||||
if (this.host) {
|
||||
this.attachObserver();
|
||||
} else {
|
||||
console.warn("Shadow root didn't exist for ion icon replacer, retrying...");
|
||||
await waitForElement(() => this.host);
|
||||
this.replace();
|
||||
}
|
||||
}
|
||||
|
||||
private attachObserver() {
|
||||
this.mutationObserver = new MutationObserver(() => this.replace());
|
||||
this.mutationObserver.observe(this.host, {
|
||||
childList: true,
|
||||
});
|
||||
}
|
||||
|
||||
replaceIcon(parent: HTMLElement | null, iconData: IconData, slotName = '') {
|
||||
if (!parent) return;
|
||||
|
||||
const icon = parent.querySelector('ion-icon');
|
||||
if (!icon) return;
|
||||
|
||||
const scIcon = this.createIcon(iconData);
|
||||
// @ts-expect-error can be spread
|
||||
scIcon.location.nativeElement.classList.add(...icon.classList);
|
||||
|
||||
if (this.iconDomLocation === 'shadow') {
|
||||
// shadow dom needs to utilize slotting, to put it outside the shadow dom
|
||||
// otherwise it won't receive any css data
|
||||
const slot = document.createElement('slot');
|
||||
slot.name = this.slotName + slotName;
|
||||
icon.replaceWith(slot);
|
||||
|
||||
scIcon.location.nativeElement.slot = this.slotName + slotName;
|
||||
this.element.nativeElement.append(scIcon.location.nativeElement);
|
||||
} else {
|
||||
icon.replaceWith(scIcon.location.nativeElement);
|
||||
}
|
||||
}
|
||||
|
||||
private createIcon(iconData: IconData): ComponentRef<IonIcon> {
|
||||
const ionIcon = this.viewContainerRef.createComponent(IonIcon, {});
|
||||
const iconDirective = new IonIconDirective(ionIcon.location, this.viewContainerRef, ionIcon.instance);
|
||||
for (const key in iconData) {
|
||||
// @ts-expect-error type mismatch
|
||||
iconDirective[key] = iconData[key];
|
||||
}
|
||||
iconDirective.ngOnInit();
|
||||
|
||||
return ionIcon;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.mutationObserver?.disconnect();
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ export const profilePageSections: SCSection[] = [
|
||||
links: [
|
||||
{
|
||||
name: 'Favorites',
|
||||
icon: SCIcon.grade,
|
||||
icon: SCIcon`grade`,
|
||||
link: ['/favorites'],
|
||||
translations: {
|
||||
de: {
|
||||
@@ -73,7 +73,7 @@ export const profilePageSections: SCSection[] = [
|
||||
},
|
||||
{
|
||||
name: 'Schedule',
|
||||
icon: SCIcon.calendar_today,
|
||||
icon: SCIcon`calendar_today`,
|
||||
link: ['/schedule'],
|
||||
translations: {
|
||||
de: {
|
||||
@@ -84,7 +84,7 @@ export const profilePageSections: SCSection[] = [
|
||||
},
|
||||
{
|
||||
name: 'Course Catalog',
|
||||
icon: SCIcon.inventory_2,
|
||||
icon: SCIcon`inventory_2`,
|
||||
link: ['/catalog'],
|
||||
translations: {
|
||||
de: {
|
||||
@@ -95,7 +95,7 @@ export const profilePageSections: SCSection[] = [
|
||||
},
|
||||
{
|
||||
name: 'Settings',
|
||||
icon: SCIcon.settings,
|
||||
icon: SCIcon`settings`,
|
||||
link: ['/settings'],
|
||||
translations: {
|
||||
de: {
|
||||
@@ -106,7 +106,7 @@ export const profilePageSections: SCSection[] = [
|
||||
},
|
||||
{
|
||||
name: 'Feedback',
|
||||
icon: SCIcon.rate_review,
|
||||
icon: SCIcon`rate_review`,
|
||||
link: ['/feedback'],
|
||||
translations: {
|
||||
de: {
|
||||
@@ -117,7 +117,7 @@ export const profilePageSections: SCSection[] = [
|
||||
},
|
||||
{
|
||||
name: 'About',
|
||||
icon: SCIcon.info,
|
||||
icon: SCIcon`info`,
|
||||
link: ['/about'],
|
||||
translations: {
|
||||
de: {
|
||||
@@ -140,7 +140,7 @@ export const profilePageSections: SCSection[] = [
|
||||
links: [
|
||||
{
|
||||
name: 'Assessments',
|
||||
icon: SCIcon.fact_check,
|
||||
icon: SCIcon`fact_check`,
|
||||
link: ['/assessments'],
|
||||
needsAuth: true,
|
||||
translations: {
|
||||
@@ -164,7 +164,7 @@ export const profilePageSections: SCSection[] = [
|
||||
links: [
|
||||
{
|
||||
name: 'Library Catalog',
|
||||
icon: SCIcon.local_library,
|
||||
icon: SCIcon`local_library`,
|
||||
link: ['/hebis-search'],
|
||||
translations: {
|
||||
de: {
|
||||
@@ -175,7 +175,7 @@ export const profilePageSections: SCSection[] = [
|
||||
},
|
||||
{
|
||||
name: 'Library Account',
|
||||
icon: SCIcon.badge,
|
||||
icon: SCIcon`badge`,
|
||||
needsAuth: true,
|
||||
link: ['/library-account/profile'],
|
||||
translations: {
|
||||
@@ -187,7 +187,7 @@ export const profilePageSections: SCSection[] = [
|
||||
},
|
||||
{
|
||||
name: 'Orders & Reservations',
|
||||
icon: SCIcon.collections_bookmark,
|
||||
icon: SCIcon`collections_bookmark`,
|
||||
needsAuth: true,
|
||||
link: ['/library-account/holds'],
|
||||
translations: {
|
||||
@@ -199,7 +199,7 @@ export const profilePageSections: SCSection[] = [
|
||||
},
|
||||
{
|
||||
name: 'Checked out items',
|
||||
icon: SCIcon.library_books,
|
||||
icon: SCIcon`library_books`,
|
||||
needsAuth: true,
|
||||
link: ['/library-account/checked-out'],
|
||||
translations: {
|
||||
@@ -211,7 +211,7 @@ export const profilePageSections: SCSection[] = [
|
||||
},
|
||||
{
|
||||
name: 'Fines',
|
||||
icon: SCIcon.request_page,
|
||||
icon: SCIcon`request_page`,
|
||||
needsAuth: true,
|
||||
link: ['/library-account/fines'],
|
||||
translations: {
|
||||
|
||||
@@ -21,7 +21,7 @@ export const environment = {
|
||||
backend_url: 'https://mobile.server.uni-frankfurt.de',
|
||||
app_host: 'mobile.app.uni-frankfurt.de',
|
||||
custom_url_scheme: 'de.anyschool.app',
|
||||
backend_version: '3.1.0',
|
||||
backend_version: '4.1.0',
|
||||
production: true,
|
||||
};
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export const environment = {
|
||||
backend_url: 'https://mobile.server.uni-frankfurt.de',
|
||||
app_host: 'mobile.app.uni-frankfurt.de',
|
||||
custom_url_scheme: 'de.anyschool.app',
|
||||
backend_version: '3.1.0',
|
||||
backend_version: '4.1.0',
|
||||
production: false,
|
||||
};
|
||||
|
||||
|
||||
@@ -36,6 +36,13 @@
|
||||
|
||||
/* StApps */
|
||||
|
||||
stapps-icon {
|
||||
--size-unit: 1px;
|
||||
--weight: 400;
|
||||
--grade: 0;
|
||||
--fill: 0;
|
||||
}
|
||||
|
||||
.map-location-pin {
|
||||
font-variation-settings: 'FILL' 1;
|
||||
color: var(--ion-color-tertiary);
|
||||
|
||||
@@ -12,14 +12,10 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// override the ion-icon before everything else
|
||||
customElements.define('ion-icon', IonIconCustomElement);
|
||||
|
||||
import {platformBrowser} from '@angular/platform-browser';
|
||||
import {environment} from './environments/environment';
|
||||
import {enableProdMode} from '@angular/core';
|
||||
import {platformBrowser} from '@angular/platform-browser';
|
||||
import {AppModule} from './app/app.module';
|
||||
import {IonIconCustomElement} from './app/util/ion-icon/ion-icon-custom-element';
|
||||
import {environment} from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
|
||||
@@ -13,10 +13,6 @@
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.can-go-back ion-header ion-back-button {
|
||||
display: block;
|
||||
}
|
||||
|
||||
app-root {
|
||||
.button {
|
||||
--padding-top: var(--spacing-sm);
|
||||
|
||||
@@ -33,14 +33,9 @@ app-root ion-searchbar[class*='sc-ion-searchbar-'] {
|
||||
width: $icon-size;
|
||||
height: $icon-size;
|
||||
|
||||
font-size: $icon-size;
|
||||
color: var(--ion-color-medium-shade);
|
||||
}
|
||||
|
||||
ion-icon.searchbar-clear-icon {
|
||||
font-size: $icon-size;
|
||||
}
|
||||
|
||||
input.searchbar-input {
|
||||
padding-top: var(--spacing-xs);
|
||||
padding-bottom: var(--spacing-xs);
|
||||
|
||||
@@ -68,7 +68,6 @@ app-root ion-toolbar.in-toolbar {
|
||||
|
||||
ion-back-button {
|
||||
--icon-margin-end: var(--spacing-xs);
|
||||
--icon-margin-start: 0;
|
||||
|
||||
height: 42px; // this prevents the back button to become a .x px value
|
||||
}
|
||||
|
||||
+1
-1
@@ -30,12 +30,12 @@
|
||||
"devDependencies": {
|
||||
"@changesets/changelog-git": "0.1.14",
|
||||
"@changesets/cli": "2.26.1",
|
||||
"prettier": "3.1.1",
|
||||
"cobertura-merge": "1.0.4",
|
||||
"deepmerge": "4.3.1",
|
||||
"dotenv-cli": "7.2.1",
|
||||
"glob": "10.3.10",
|
||||
"junit-report-merger": "6.0.3",
|
||||
"prettier": "3.1.1",
|
||||
"syncpack": "12.3.0",
|
||||
"turbo": "1.10.16",
|
||||
"turbo-ignore": "1.10.16",
|
||||
|
||||
Generated
+39
-5
@@ -830,9 +830,6 @@ importers:
|
||||
ionic-appauth:
|
||||
specifier: 0.9.0
|
||||
version: 0.9.0(rxjs@7.8.1)
|
||||
ionicons:
|
||||
specifier: 7.2.1
|
||||
version: 7.2.1
|
||||
jsonpath-plus:
|
||||
specifier: 6.0.1
|
||||
version: 6.0.1
|
||||
@@ -4549,7 +4546,7 @@ packages:
|
||||
object-assign: 4.1.1
|
||||
open: 8.4.0
|
||||
proxy-middleware: 0.15.0
|
||||
send: 0.18.0
|
||||
send: 1.0.0-beta.2
|
||||
serve-index: 1.9.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -6531,6 +6528,12 @@ packages:
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/@stencil/core@4.5.0:
|
||||
resolution: {integrity: sha512-XRbHdb9t4SQzCCbF9qsh0dexvnlArEzCDJl19BJzxzazVBM398SeJUKCBh4p91AZIWveN0gHuZSIGMhLWR7qSA==}
|
||||
engines: {node: '>=16.0.0', npm: '>=7.10.0'}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/@swc/helpers@0.4.14:
|
||||
resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==}
|
||||
dependencies:
|
||||
@@ -10086,6 +10089,17 @@ packages:
|
||||
dependencies:
|
||||
ms: 2.0.0
|
||||
|
||||
/debug@3.1.0:
|
||||
resolution: {integrity: sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
dependencies:
|
||||
ms: 2.0.0
|
||||
dev: true
|
||||
|
||||
/debug@3.2.7(supports-color@5.5.0):
|
||||
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
|
||||
peerDependencies:
|
||||
@@ -12858,7 +12872,7 @@ packages:
|
||||
/ionicons@7.2.1:
|
||||
resolution: {integrity: sha512-2pvCM7DGVEtbbj48PJzQrCADCQrqjU1nUYX9l9PyEWz3ZfdnLdAouqwPxLdl8tbaF9cE7OZRSlyQD7oLOLnGoQ==}
|
||||
dependencies:
|
||||
'@stencil/core': 4.12.6
|
||||
'@stencil/core': 4.5.0
|
||||
dev: false
|
||||
|
||||
/ionicons@7.2.2:
|
||||
@@ -17292,6 +17306,26 @@ packages:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
/send@1.0.0-beta.2:
|
||||
resolution: {integrity: sha512-k1yHu/FNK745PULKdsGpQ+bVSXYNwSk+bWnYzbxGZbt5obZc0JKDVANsCRuJD1X/EG15JtP9eZpwxkhUxIYEcg==}
|
||||
engines: {node: '>= 0.10'}
|
||||
dependencies:
|
||||
debug: 3.1.0
|
||||
destroy: 1.2.0
|
||||
encodeurl: 1.0.2
|
||||
escape-html: 1.0.3
|
||||
etag: 1.8.1
|
||||
fresh: 0.5.2
|
||||
http-errors: 2.0.0
|
||||
mime-types: 2.1.35
|
||||
ms: 2.1.3
|
||||
on-finished: 2.4.1
|
||||
range-parser: 1.2.1
|
||||
statuses: 2.0.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/serialize-javascript@6.0.0:
|
||||
resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==}
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user