refactor: migrate to strict template checking

This commit is contained in:
2023-12-06 16:23:26 +01:00
parent 791b5c895d
commit 288a49113f
70 changed files with 204 additions and 236 deletions

View File

@@ -33,7 +33,7 @@
<ion-card-header>
<ion-card-title>
{{ license.name }}
<ion-icon size="16" 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>
<ion-card-subtitle *ngIf="license.authors || license.publisher">

View File

@@ -13,31 +13,29 @@
~ this program. If not, see <https://www.gnu.org/licenses/>.
-->
<div [ngSwitch]="content.type">
<markdown [data]="'value' | translateSimple : content" *ngSwitchCase="'markdown'"></markdown>
<div *ngSwitchCase="'section'">
<ion-card *ngIf="content.card; else noCard">
<ion-card-header>
<ion-card-title>{{ 'title' | translateSimple : content }}</ion-card-title>
</ion-card-header>
<ion-card-content>
<about-page-content [content]="content.content"></about-page-content>
</ion-card-content>
</ion-card>
<ng-template #noCard>
<h2>{{ 'title' | translateSimple : content }}</h2>
<markdown *ngIf="content.type === 'markdown'" [data]="'value' | translateSimple : content"></markdown>
<div *ngIf="content.type ==='section'">
<ion-card *ngIf="content.card; else noCard">
<ion-card-header>
<ion-card-title>{{ 'title' | translateSimple : content }}</ion-card-title>
</ion-card-header>
<ion-card-content>
<about-page-content [content]="content.content"></about-page-content>
</ng-template>
</div>
<ion-grid *ngSwitchCase="'table'">
<ion-row *ngFor="let row of content.rows">
<ion-col *ngFor="let col of row">
<about-page-content [content]="col"></about-page-content>
</ion-col>
</ion-row>
</ion-grid>
<ion-item *ngSwitchCase="'router link'" [routerLink]="content.link">
<ion-icon *ngIf="content.icon" [name]="content.icon" slot="start"></ion-icon>
<ion-label>{{ 'title' | translateSimple : content }}</ion-label>
</ion-item>
</ion-card-content>
</ion-card>
<ng-template #noCard>
<h2>{{ 'title' | translateSimple : content }}</h2>
<about-page-content [content]="content.content"></about-page-content>
</ng-template>
</div>
<ion-grid *ngIf="content.type === 'table'">
<ion-row *ngFor="let row of content.rows">
<ion-col *ngFor="let col of row">
<about-page-content [content]="col"></about-page-content>
</ion-col>
</ion-row>
</ion-grid>
<ion-item *ngIf="content.type === 'router link'" [routerLink]="content.link">
<ion-icon *ngIf="content.icon" [name]="content.icon" slot="start"></ion-icon>
<ion-label>{{ 'title' | translateSimple : content }}</ion-label>
</ion-item>

View File

@@ -27,7 +27,7 @@
#segment
[scrollable]="true"
mode="md"
(ionChange)="sharedAxisChoreographer.changeViewForState(segment.value)"
(ionChange)="sharedAxisChoreographer.changeViewForState($any(segment.value))"
value=""
>
<ion-segment-button *ngFor="let key of assessmentKeys" [value]="key">

View File

@@ -22,7 +22,7 @@ import {SCAssessment, SCCourseOfStudyWithoutReferences} from '@openstapps/core';
styleUrls: ['course-of-study-assessment.scss'],
})
export class CourseOfStudyAssessmentComponent {
@Input() courseOfStudy: SCCourseOfStudyWithoutReferences | null;
@Input() courseOfStudy?: SCCourseOfStudyWithoutReferences | null;
_assessments: Promise<SCAssessment[]>;

View File

@@ -35,7 +35,6 @@
<ion-item *ngFor="let catalog of catalogs" button="true" lines="inset" (click)="notifySelect(catalog)">
<ion-label>
<h2>{{ catalog.name }}</h2>
<h3>{{ catalog.acronym }}</h3>
</ion-label>
</ion-item>
</ion-list>

View File

@@ -20,7 +20,7 @@
</ion-header>
<div #schedule class="schedule">
<a [routerLink]="['/schedule/week-overview']">
<ion-icon size="36" 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 -->

View File

@@ -15,13 +15,13 @@
<stapps-section [title]="'dashboard.favorites.title' | translate">
<ion-button slot="button-end" fill="clear" color="medium" [routerLink]="['/favorites']">
<ion-icon slot="icon-only" name="search" size="24"></ion-icon>
<ion-icon slot="icon-only" name="search" [size]="24"></ion-icon>
</ion-button>
<simple-swiper *ngIf="items | async as items; else noItems" @fade>
<stapps-data-list-item
*ngFor="let item of items"
[hideThumbnail]="true"
[favoriteButton]="false"
[listItemEndInteraction]="false"
[item]="item"
appearance="square"
></stapps-data-list-item>

View File

@@ -15,7 +15,7 @@
<stapps-section [title]="'dashboard.jobs.title' | translate">
<ion-button slot="button-end" fill="clear" color="medium" [routerLink]="['/jobs']">
<ion-icon slot="icon-only" name="search" size="24"></ion-icon>
<ion-icon slot="icon-only" name="search" [size]="24"></ion-icon>
</ion-button>
<simple-swiper *ngIf="jobs | async as jobs; else noItems" @fade>
<stapps-data-list-item
@@ -26,7 +26,7 @@
></stapps-data-list-item>
<ion-item [routerLink]="['/jobs']" class="more-jobs" lines="none">
<ion-label>{{ 'dashboard.jobs.title' | translate | titlecase }}</ion-label>
<ion-icon color="medium" name="read_more" size="40"></ion-icon>
<ion-icon color="medium" name="read_more" [size]="40"></ion-icon>
</ion-item>
</simple-swiper>
<ng-template #noItems>
@@ -37,7 +37,7 @@
</ion-item>
<ion-button slot="button-end" fill="clear" color="medium" [routerLink]="['/jobs']">
<ion-icon slot="icon-only" name="search" size="24"></ion-icon>
<ion-icon slot="icon-only" name="search" [size]="24"></ion-icon>
</ion-button>
</ng-template>
</stapps-section>

View File

@@ -18,9 +18,9 @@
<ng-container *ngFor="let item of items">
<stapps-section @fade [item]="item" [title]="'name' | thingTranslate: item">
<ion-button slot="button-end" fill="clear" color="medium" (click)="favoritesService.delete(item)">
<ion-icon slot="icon-only" name="delete" size="24"></ion-icon>
<ion-icon slot="icon-only" name="delete" [size]="24"></ion-icon>
</ion-button>
<stapps-opening-hours slot="subtitle" [openingHours]="item.openingHours"></stapps-opening-hours>
<stapps-opening-hours slot="subtitle" [openingHours]="$any(item).openingHours"></stapps-opening-hours>
<stapps-mensa-section-content [item]="item"></stapps-mensa-section-content>
</stapps-section>
</ng-container>

View File

@@ -22,7 +22,7 @@
<ion-item [routerLink]="['/news']" class="more-news" lines="none">
<ion-label>{{ 'dashboard.news.moreNews' | translate | titlecase }}</ion-label>
<ion-thumbnail class="ion-margin-end">
<ion-icon color="medium" name="read_more" size="150"></ion-icon>
<ion-icon color="medium" name="read_more" [size]="150"></ion-icon>
</ion-thumbnail>
</ion-item>
</simple-swiper>

View File

@@ -13,7 +13,7 @@
~ this program. If not, see <https://www.gnu.org/licenses/>.
-->
<ion-chip [class.active]="active" (click)="emitToggle(value)">
<ion-icon class="ion-color" name="check_circle" fill="true" *ngIf="active"></ion-icon>
<ion-chip *ngIf="displayValue" [class.active]="active" (click)="emitToggle(value)">
<ion-icon class="ion-color" name="check_circle" [fill]="true" *ngIf="active"></ion-icon>
<ion-label>{{ displayValue }}</ion-label>
</ion-chip>

View File

@@ -30,7 +30,7 @@ export class ChipFilterComponent {
/**
* Text to display on the chip
*/
@Input() displayValue: string;
@Input() displayValue?: string | null;
/**
* Emits when the chip has been activated/deactivated

View File

@@ -73,7 +73,7 @@ import {SkeletonListComponent} from './list/skeleton-list.component';
import {TreeListFragmentComponent} from './list/tree-list-fragment.component';
import {TreeListComponent} from './list/tree-list.component';
import {StAppsWebHttpClient} from './stapps-web-http-client.provider';
import {ArticleContentComponent} from './types/article/article-content.component';
import {ArticleDetailContentComponent} from './types/article/article-detail-content.component';
import {ArticleListItemComponent} from './types/article/article-item.component';
import {BookDetailContentComponent} from './types/book/book-detail-content.component';
import {BookListItemComponent} from './types/book/book-list-item.component';
@@ -176,7 +176,7 @@ import {ShareButtonComponent} from './elements/share-button.component';
TitleCardComponent,
ExternalLinkComponent,
ArticleListItemComponent,
ArticleContentComponent,
ArticleDetailContentComponent,
BookListItemComponent,
BookDetailContentComponent,
PeriodicalListItemComponent,
@@ -238,7 +238,7 @@ import {ShareButtonComponent} from './elements/share-button.component';
FavoriteButtonComponent,
TreeListComponent,
ExternalLinkComponent,
ArticleContentComponent,
ArticleDetailContentComponent,
BookDetailContentComponent,
PeriodicalDetailContentComponent,
TitleCardComponent,

View File

@@ -82,7 +82,7 @@
<ng-container *ngSwitchDefault>
<ion-item class="ion-text-wrap" lines="inset">
<ion-thumbnail slot="start" class="ion-margin-end">
<stapps-icon>{{ item.type | dataIcon }}</stapps-icon>
<ion-icon [name]="item.type | dataIcon"></ion-icon>
</ion-thumbnail>
<ion-grid>
<ion-row>

View File

@@ -50,11 +50,13 @@
<stapps-skeleton-simple-card></stapps-skeleton-simple-card>
</ng-container>
<ng-container *ngSwitchDefault>
<stapps-data-path [item]="item" [autoRouting]="autoRouteDataPath"></stapps-data-path>
<stapps-data-detail-content
[item]="item"
[contentTemplateRef]="contentTemplateRef"
></stapps-data-detail-content>
<ng-container *ngIf="item">
<stapps-data-path [item]="item" [autoRouting]="autoRouteDataPath"></stapps-data-path>
<stapps-data-detail-content
[item]="item"
[contentTemplateRef]="contentTemplateRef"
></stapps-data-detail-content>
</ng-container>
</ng-container>
</ng-container>
</ion-content>

View File

@@ -34,7 +34,7 @@ export class DataPathComponent implements OnInit {
@Input() autoRouting = true;
@Input() maxItems = 2;
@Input() maxItems?: number = 2;
@Input() set item(item: SCThings) {
if (item.type === SCThingType.Catalog && item.superCatalogs) {

View File

@@ -17,7 +17,7 @@
<ion-breadcrumbs
color="light"
[itemsBeforeCollapse]="1"
[itemsAfterCollapse]="($width | async) >= 768 ? 1 : 0"
[itemsAfterCollapse]="(($width | async) ?? 0) >= 768 ? 1 : 0"
[maxItems]="maxItems"
(ionCollapsedClick)="maxItems = undefined"
>
@@ -29,7 +29,7 @@
? '100%'
: stack.length === 2
? '40vw'
: ($width | async) >= 768
: (($width | async) ?? 0) >= 768
? '30vw'
: 'calc(100vw - 120px)'
"

View File

@@ -16,7 +16,7 @@
<ion-button (click)="toggle($event)" color="medium" size="small" fill="clear">
<ion-icon
slot="icon-only"
[fill]="isFavorite$ | async"
[fill]="(isFavorite$ | async) ?? false"
[class.selected]="isFavorite$ | async"
name="grade"
></ion-icon>

View File

@@ -30,5 +30,5 @@ export class LongInlineTextComponent {
/**
* TODO
*/
@Input() text: string;
@Input() text?: string;
}

View File

@@ -12,16 +12,17 @@
~ You should have received a copy of the GNU General Public License along with
~ this program. If not, see <https://www.gnu.org/licenses/>.
-->
<span class="ion-hide-sm-up">
{{ text | slice : 0 : size }}
<span *ngIf="text.length > size" class="ion-hide-sm-up"></span>
</span>
<span class="ion-hide-sm-down ion-hide-md-up">
{{ text | slice : 0 : size * 2 }}
<span *ngIf="text.length > size * 2" class="ion-hide-sm-down ion-hide-md-up"></span>
</span>
<span class="ion-hide-md-down">
{{ text | slice : 0 : size * 3 }}
<span *ngIf="text.length > size * 3" class="ion-hide-md-down"></span>
</span>
<ng-container *ngIf="text !== undefined">
<span class="ion-hide-sm-up">
{{ text | slice : 0 : size }}
<span *ngIf="text.length > size" class="ion-hide-sm-up"></span>
</span>
<span class="ion-hide-sm-down ion-hide-md-up">
{{ text | slice : 0 : size * 2 }}
<span *ngIf="text.length > size * 2" class="ion-hide-sm-down ion-hide-md-up"></span>
</span>
<span class="ion-hide-md-down">
{{ text | slice : 0 : size * 3 }}
<span *ngIf="text.length > size * 3" class="ion-hide-md-down"></span>
</span>
</ng-container>

View File

@@ -15,21 +15,10 @@
import {Component, Input} from '@angular/core';
import {SCAcademicPriceGroup, SCThingThatCanBeOfferedOffer} from '@openstapps/core';
/**
* TODO
*/
@Component({
selector: 'stapps-offers-detail',
templateUrl: 'offers-detail.html',
})
export class OffersDetailComponent {
/**
* TODO
*/
objectKeys = Object.keys;
/**
* TODO
*/
@Input() offers: Array<SCThingThatCanBeOfferedOffer<SCAcademicPriceGroup>>;
}

View File

@@ -19,13 +19,13 @@
<div *ngFor="let offer of offers">
<ion-grid>
<ion-row>
<ion-col>
<ion-col *ngIf="offer.inPlace">
<ion-icon name="pin_drop"></ion-icon>
<a [routerLink]="['/data-detail', offer.inPlace.uid]">
{{ 'name' | thingTranslate : offer.inPlace }}
</a>
</ion-col>
<ion-col>
<ion-col *ngIf="offer.availabilityRange">
<span
*ngIf="offer.availabilityRange.gt ? offer.availabilityRange.gt : offer.availabilityRange.gte"
>
@@ -36,11 +36,13 @@
</ion-row>
</ion-grid>
<ion-grid *ngIf="offer.prices && offer.availability !== 'out of stock'">
<ion-row *ngFor="let group of objectKeys(offer.prices)">
<ion-col *ngIf="group !== 'default'">{{ 'data.detail.offers.' + group | translate }} </ion-col>
<ion-col *ngIf="group !== 'default'" width-20 text-right>
<p>{{ offer.prices[group] | currency : 'EUR' : 'symbol' : undefined : 'de' }}</p>
</ion-col>
<ion-row *ngFor="let group of $any(offer.prices) | keyvalue">
<ng-container *ngIf="group.key !== 'default'">
<ion-col>{{ 'data.detail.offers.' + group.key | translate }} </ion-col>
<ion-col width-20 text-right>
<p>{{ $any(group.value) | currency : 'EUR' : 'symbol' : undefined : 'de' }}</p>
</ion-col>
</ng-container>
</ion-row>
</ion-grid>
<ion-grid *ngIf="offer.availability === 'out of stock'">

View File

@@ -13,7 +13,7 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component, Input} from '@angular/core';
import {SCThingOrigin} from '@openstapps/core';
import {SCThingUserOrigin, SCThingRemoteOrigin} from '@openstapps/core';
/**
* TODO
@@ -26,5 +26,5 @@ export class OriginDetailComponent {
/**
* TODO
*/
@Input() origin: SCThingOrigin;
@Input() origin: SCThingUserOrigin | SCThingRemoteOrigin;
}

View File

@@ -31,7 +31,6 @@
{{ 'data.types.origin.detail.MODIFIED' | translate | titlecase }}: {{ origin.modified | amDateFormat :
'll' }}
</p>
<p *ngIf="origin.name">{{ 'data.types.origin.detail.MAINTAINER' | translate }}: {{ origin.name }}</p>
<p *ngIf="origin.maintainer">
{{ 'data.types.origin.detail.MAINTAINER' | translate }}:
<a [routerLink]="['/data-detail', origin.maintainer.uid]">{{ origin.maintainer.name }}</a>

View File

@@ -13,7 +13,7 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component, Input} from '@angular/core';
import {SCThingOrigin} from '@openstapps/core';
import {SCThingUserOrigin, SCThingRemoteOrigin} from '@openstapps/core';
/**
* TODO
@@ -26,5 +26,5 @@ export class OriginInListComponent {
/**
* TODO
*/
@Input() origin: SCThingOrigin;
@Input() origin: SCThingUserOrigin | SCThingRemoteOrigin;
}

View File

@@ -32,7 +32,7 @@
*ngFor="let i of [5, 4, 3, 2, 1]"
(click)="$event.stopPropagation(); userRating.next(i)"
slot="icon-only"
size="32"
[size]="32"
color="medium"
name="grade"
></ion-icon>

View File

@@ -1,4 +1,4 @@
<ion-button size="small" fill="clear" (click)="share() && toast.present()">
<ion-icon size="24" slot="icon-only" name="share" ios="ios_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>

View File

@@ -15,45 +15,24 @@
import {Component, Input} from '@angular/core';
import {SCThingWithoutReferences} from '@openstapps/core';
/**
* TODO
*/
@Component({
selector: 'stapps-simple-card',
templateUrl: 'simple-card.html',
})
export class SimpleCardComponent {
/**
* TODO
*/
areThings = false;
/**
* TODO
*/
@Input() content: string | string[] | SCThingWithoutReferences[];
@Input() content?: string | string[] | SCThingWithoutReferences[];
/**
* TODO
*/
@Input() isMarkdown = false;
/**
* TODO
*/
@Input() title: string;
/**
* TODO
*/
// eslint-disable-next-line class-methods-use-this
isString(data: unknown): data is string {
return typeof data === 'string';
}
/**
* TODO
*/
// eslint-disable-next-line class-methods-use-this
isThing(something: unknown): something is SCThingWithoutReferences {
// bypass the 'type' field check because of translated values

View File

@@ -16,24 +16,22 @@
<ion-card>
<ion-card-header>{{ title }}</ion-card-header>
<ion-card-content>
<ng-container *ngIf="isString(content); then text; else list"> </ng-container>
<ng-template #text>
<ng-container *ngIf="isString(content); else list">
<ng-container *ngIf="isMarkdown; else plainText">
<markdown [data]="content"></markdown>
</ng-container>
<ng-template #plainText>
<p>{{ content }}</p>
</ng-template>
</ng-template>
</ng-container>
<ng-template #list>
<ng-container *ngIf="isThing(content[0]); then thingList; else textList"> </ng-container>
<ng-template #thingList>
<a [routerLink]="['/data-detail', thing.uid]" *ngFor="let thing of content">
<ng-container *ngIf="content && isThing(content[0]); else textList">
<a [routerLink]="['/data-detail', thing.uid]" *ngFor="let thing of $any(content)">
<p>{{ 'name' | thingTranslate : thing }}</p>
</a>
</ng-template>
</ng-container>
<ng-template #textList>
<p *ngFor="let text of content">{{ text }}</p>
<p *ngFor="let text of $any(content)">{{ text }}</p>
</ng-template>
</ng-template>
</ion-card-content>

View File

@@ -27,8 +27,8 @@
</ion-card-title>
</ion-card-header>
<ion-card-content>
<div *ngIf="$any(item).openingHours" class="opening-hours">
<stapps-opening-hours [openingHours]="item.openingHours"></stapps-opening-hours>
<div *ngIf="$any(item).openingHours as openingHours" class="opening-hours">
<stapps-opening-hours [openingHours]="openingHours"></stapps-opening-hours>
</div>
<!-- TODO obviously this is bad style. Tbd where to put the differentiation. Job Postings always have a description, but it's going to be shown in `stapps-job-posting-detail-content` anyways, no need to repeat here. For this view, I would use other fields of the schema.org JobPosting like the `ThingWithCategory.category` -->
<div *ngIf="item.description && item.type !== 'job posting'" class="description">

View File

@@ -22,7 +22,7 @@
>
<div class="item-height-placeholder"></div>
<ion-thumbnail slot="start" *ngIf="!hideThumbnail" class="ion-margin-end">
<ion-icon color="dark" [name]="item.type | dataIcon" size="36"></ion-icon>
<ion-icon color="dark" [name]="item.type | dataIcon" [size]="36"></ion-icon>
</ion-thumbnail>
<ng-container *ngIf="contentTemplateRef; else defaultContent">
<ion-label class="ion-text-wrap" [ngSwitch]="true">

View File

@@ -48,7 +48,7 @@ export class DataListComponent implements OnChanges, OnInit {
/**
* All SCThings to display
*/
@Input() items?: SCThings[];
@Input() items?: SCThings[] | null;
@ContentChild(TemplateRef) listItemTemplateRef: TemplateRef<DataListContext<SCThings>>;

View File

@@ -16,7 +16,7 @@
<tree-list-fragment
*ngIf="_groups | async as groups"
[items]="groups"
[groupMap]="_groupItems"
[groupMap]="_groupItems!"
[singleType]="singleType"
[listItemTemplateRef]="listItemTemplateRef"
></tree-list-fragment>

View File

@@ -15,16 +15,10 @@
import {Component, Input} from '@angular/core';
import {SCArticle} from '@openstapps/core';
/**
* TODO
*/
@Component({
selector: 'stapps-article-content',
templateUrl: 'article-content.html',
selector: 'stapps-article-detail-content',
templateUrl: 'article-detail-content.html',
})
export class ArticleContentComponent {
/**
* TODO
*/
export class ArticleDetailContentComponent {
@Input() item: SCArticle;
}

View File

@@ -14,7 +14,7 @@
-->
<ng-container *ngIf="isInCalendar | async; else add">
<ion-chip outline="true" color="success" (click)="removeFromCalendar()">
<ion-icon name="event_available" fill="true"></ion-icon>
<ion-icon name="event_available" [fill]="true"></ion-icon>
<ion-label>{{'chips.addEvent.addedToEvents' | translate}}</ion-label>
</ion-chip>
</ng-container>
@@ -47,7 +47,7 @@
</ng-template>
<stapps-simple-card
*ngIf="item.performers"
[title]="'performers' | propertyNameTranslate : item.performers | titlecase"
[title]="'performers' | propertyNameTranslate : item | titlecase"
[content]="item.performers"
></stapps-simple-card>
<stapps-offers-detail *ngIf="item.offers" [offers]="item.offers"></stapps-offers-detail>
@@ -63,4 +63,4 @@
<stapps-data-list-item [item]="$any(item.inPlace)"></stapps-data-list-item>
</ion-card-content>
</ion-card>
<stapps-map-widget *ngIf="item.inPlace?.geo" [place]="item.inPlace"></stapps-map-widget>
<stapps-map-widget *ngIf="item.inPlace && item.inPlace.geo" [place]="item.inPlace"></stapps-map-widget>

View File

@@ -33,15 +33,15 @@
[title]="'organizers' | propertyNameTranslate : item | titlecase"
[content]="item.organizers"
></stapps-simple-card>
<stapps-simple-card
*ngIf="item.type === 'academic event' && item.majors | titlecase"
[title]="'majors' | propertyNameTranslate : item"
[content]="item.majors"
></stapps-simple-card>
<ng-container *ngIf="item.type === 'academic event'">
<stapps-simple-card
*ngIf="item.majors"
[title]="'majors' | propertyNameTranslate : item"
[content]="item.majors"
></stapps-simple-card>
</ng-container>
<ion-card *ngIf="item.catalogs">
<ion-card-header
>{{ $any('superCatalogs' | propertyNameTranslate : 'catalog') | titlecase }}</ion-card-header
>
<ion-card-header>{{ 'superCatalogs' | propertyNameTranslate : 'catalog' | titlecase }}</ion-card-header>
<ion-card-content>
<event-route-path
*ngFor="let item of item.catalogs"

View File

@@ -14,7 +14,7 @@
-->
<ion-breadcrumbs
(ionCollapsedClick)="popover.present($event)"
(ionCollapsedClick)="popover.present($any($event))"
[maxItems]="maxItems"
[itemsAfterCollapse]="itemsAfterCollapse"
[itemsBeforeCollapse]="itemsBeforeCollapse"
@@ -28,12 +28,14 @@
<ion-popover #popover>
<ng-template>
<ion-list lines="none">
<ng-container *ngIf="moreAnchor === 'start' && more | async as more">
<ng-container
*ngFor="let item of more"
[ngTemplateOutlet]="popoverItem"
[ngTemplateOutletContext]="{item}"
></ng-container>
<ng-container *ngIf="moreAnchor === 'start'">
<ng-container *ngIf="more | async as more">
<ng-container
*ngFor="let item of more"
[ngTemplateOutlet]="popoverItem"
[ngTemplateOutletContext]="{item}"
></ng-container>
</ng-container>
</ng-container>
<ng-container *ngIf="showSelfInPopover">
<ng-container
@@ -42,12 +44,14 @@
[ngTemplateOutletContext]="{item}"
></ng-container>
</ng-container>
<ng-container *ngIf="moreAnchor === 'end' && more | async as more">
<ng-container
*ngFor="let item of more"
[ngTemplateOutlet]="popoverItem"
[ngTemplateOutletContext]="{item}"
></ng-container>
<ng-container *ngIf="moreAnchor === 'end'">
<ng-container *ngIf="more | async as more">
<ng-container
*ngFor="let item of more"
[ngTemplateOutlet]="popoverItem"
[ngTemplateOutletContext]="{item}"
></ng-container>
</ng-container>
</ng-container>
</ion-list>
</ng-template>

View File

@@ -43,7 +43,7 @@
<ion-thumbnail *ngIf="item.image" style="background-image: url('{{ item.image }}')">
<ion-img
src="{{ item.image }}"
(ionError)="$event.target.nextSibling.style.display = 'none'"
(ionError)="$any($event.target).nextSibling.style.display = 'none'"
alt="{{ item.name }}"
></ion-img>
</ion-thumbnail>

View File

@@ -22,9 +22,6 @@ import {
} from '@openstapps/core';
import {DataProvider} from '../../data.provider';
/**
* TODO
*/
@Component({
selector: 'stapps-person-detail-content',
templateUrl: 'person-detail-content.html',
@@ -32,7 +29,7 @@ import {DataProvider} from '../../data.provider';
export class PersonDetailContentComponent {
private _item: SCPerson;
contactPoints: SCContactPoint[] | SCContactPointWithoutReferences[];
contactPoints: Array<SCContactPoint | SCContactPointWithoutReferences>;
get item(): SCPerson {
return this._item;
@@ -70,4 +67,10 @@ export class PersonDetailContentComponent {
return contactPoints;
}
isContactPoint(
contactPoint: SCContactPoint | SCContactPointWithoutReferences,
): contactPoint is SCContactPoint {
return 'areaServed' in contactPoint;
}
}

View File

@@ -12,41 +12,41 @@
~ 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-container *ngIf="contactPoints">
<ion-card *ngFor="let contactPoint of contactPoints; let i = index">
<ion-list *ngIf="item.workLocations">
<ion-card *ngFor="let contactPoint of contactPoints; index as i">
<ion-card-header>
<ng-container *ngIf="contactPoints.length > 1">{{ i + 1 }}.</ng-container>
<ng-container *ngIf="item.workLocations.length > 1">{{ i + 1 }}.</ng-container>
{{ 'type' | thingTranslate : contactPoint | titlecase }}
</ion-card-header>
<ion-card-content>
<p *ngIf="contactPoint.telephone">
{{ 'telephone' | propertyNameTranslate : contactPoint | titlecase }}:
{{ 'telephone' | propertyNameTranslate : 'contact point' | titlecase }}:
<a [href]="'tel:' + contactPoint.telephone">{{ contactPoint.telephone }}</a>
</p>
<p *ngIf="contactPoint.email">
{{ 'email' | propertyNameTranslate : contactPoint | titlecase }}:
{{ 'email' | propertyNameTranslate : 'contact point' | titlecase }}:
<a [href]="'mailto:' + contactPoint.email">{{ contactPoint.email }}</a>
</p>
<p *ngIf="contactPoint.faxNumber">
{{ 'faxNumber' | propertyNameTranslate : contactPoint | titlecase }}: {{ contactPoint.faxNumber }}
{{ 'faxNumber' | propertyNameTranslate : 'contact point' | titlecase }}: {{ contactPoint.faxNumber }}
</p>
<p *ngIf="contactPoint.officeHours">
{{ 'officeHours' | propertyNameTranslate : contactPoint | titlecase }}: {{ contactPoint.officeHours }}
{{ 'officeHours' | propertyNameTranslate : 'contact point' | titlecase }}: {{ contactPoint.officeHours
}}
</p>
<p *ngIf="contactPoint.url">
{{ 'url' | propertyNameTranslate : contactPoint | titlecase }}:
{{ 'url' | propertyNameTranslate : 'contact point' | titlecase }}:
<a [href]="contactPoint.url">{{ contactPoint.url }}</a>
</p>
<p *ngIf="contactPoint.areaServed">
<p *ngIf="isContactPoint(contactPoint) && contactPoint.areaServed">
{{ 'areaServed' | propertyNameTranslate : contactPoint | titlecase }}:
<a [routerLink]="['/data-detail', contactPoint.areaServed.uid]">{{ contactPoint.areaServed.name }}</a>
</p>
</ion-card-content>
</ion-card>
</ng-container>
</ion-list>
<stapps-simple-card
*ngIf="item.jobTitles?.length > 0"
*ngIf="item.jobTitles && item.jobTitles.length > 0"
[title]="'jobTitles' | propertyNameTranslate : item | titlecase"
[content]="item.jobTitles"
></stapps-simple-card>

View File

@@ -13,7 +13,7 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component, Input, OnInit} from '@angular/core';
import {SCBuilding, SCFloor, SCPointOfInterest, SCRoom, SCThings} from '@openstapps/core';
import {SCBuilding, SCFloor, SCPointOfInterest, SCRoom, SCThings, SCPlace} from '@openstapps/core';
import {DataProvider} from '../../data.provider';
import {hasValidLocation, isSCFloor} from './place-types';
import {DataRoutingService} from '../../data-routing.service';
@@ -36,7 +36,7 @@ export class PlaceDetailContentComponent implements OnInit {
*/
hasValidLocation = false;
hasCategories(item: SCThings): item is SCThings & {categories: string[]} {
hasCategories(item: object): item is SCThings & {categories: string[]} {
return (item as {categories: string[]}).categories !== undefined;
}
@@ -44,7 +44,7 @@ export class PlaceDetailContentComponent implements OnInit {
* Helper function as 'typeof' is not accessible in HTML
* @param item TODO
*/
isMensaThing(item: SCThings): boolean {
isMensaThing(item: SCThings | SCPlace): item is SCPlace {
return (
this.hasCategories(item) &&
((item.categories as string[]).includes('canteen') ||

View File

@@ -20,23 +20,20 @@
></stapps-place-mensa-detail-content>
<ng-container *ngIf="item.type !== 'floor'">
<stapps-simple-card
*ngIf="item.type !== 'floor' && item.categories"
*ngIf="item.categories"
[title]="'categories' | propertyNameTranslate: item | titlecase"
[content]="'categories' | thingTranslate: item"
></stapps-simple-card>
<stapps-address-detail
*ngIf="item.type !== 'floor' && item.address"
[address]="item.address"
></stapps-address-detail>
<stapps-address-detail *ngIf="item.address" [address]="item.address"></stapps-address-detail>
</ng-container>
<ng-container *ngIf="item.type !== 'building'">
<ion-card *ngIf="item.inPlace">
<ion-card-header> {{ 'inPlace' | propertyNameTranslate: item | titlecase }} </ion-card-header>
<ion-card-content>
<stapps-data-list-item [item]="item.inPlace"></stapps-data-list-item>
<stapps-data-list-item [item]="$any(item.inPlace)"></stapps-data-list-item>
</ion-card-content>
</ion-card>
</ng-container>
<ng-container *ngIf="hasValidLocation">
<stapps-map-widget [place]="item" expandable="true"></stapps-map-widget>
<stapps-map-widget [place]="$any(item)" expandable="true"></stapps-map-widget>
</ng-container>

View File

@@ -52,13 +52,13 @@
</ion-icon>
{{ 'hebisSearch.daia.status_states' + '.' + holding.status | translate }}
<stapps-external-link
*ngIf="['available', 'library_only'].indexOf(holding.status) > -1 && holding.available.href"
*ngIf="(holding.status === 'available' || holding.status === 'library_only') && holding.available && holding.available.href"
[text]="'hebisSearch.daia.order' | translate"
[link]="holding.available.href"
>
</stapps-external-link>
<stapps-external-link
*ngIf="holding.status === 'checked_out' && holding.unavailable.href"
*ngIf="holding.status === 'checked_out' && holding.unavailable && holding.unavailable.href"
[text]="'hebisSearch.daia.reserve' | translate"
[link]="holding.unavailable.href"
>

View File

@@ -14,12 +14,15 @@
-->
<div [ngSwitch]="item.type">
<stapps-book-detail-content [item]="item" *ngSwitchCase="'book'"></stapps-book-detail-content>
<stapps-book-detail-content [item]="$any(item)" *ngSwitchCase="'book'"></stapps-book-detail-content>
<stapps-periodical-detail-content
[item]="item"
[item]="$any(item)"
*ngSwitchCase="'periodical'"
></stapps-periodical-detail-content>
<stapps-article-content [item]="item" *ngSwitchCase="'article'"></stapps-article-content>
<stapps-article-detail-content
[item]="$any(item)"
*ngSwitchCase="'article'"
></stapps-article-detail-content>
<ng-container *ngSwitchDefault>
<ion-item class="ion-text-wrap" lines="inset">
<ion-thumbnail slot="start" class="ion-margin-end">

View File

@@ -43,7 +43,7 @@
<stapps-skeleton-simple-card></stapps-skeleton-simple-card>
</ng-container>
<ng-container *ngSwitchDefault>
<stapps-hebis-detail-content [item]="item"></stapps-hebis-detail-content>
<stapps-hebis-detail-content *ngIf="item" [item]="item"></stapps-hebis-detail-content>
<stapps-daia-availability *ngIf="item"></stapps-daia-availability>
</ng-container>
</div>

View File

@@ -35,7 +35,7 @@
</ng-container>
<ng-template #fallback>
<stapps-skeleton-list-item
hideThumbnail="true"
[hideThumbnail]="true"
*ngIf="!checkedOutItems; else nothingFound"
></stapps-skeleton-list-item>
<ng-template #nothingFound>

View File

@@ -23,7 +23,7 @@
<ng-container *ngIf="!['endtime', 'duedate'].includes(property); else date">
{{ item[property] }}
</ng-container>
<ng-template #date> {{ item[property] | amDateFormat : 'll' }} </ng-template>
<ng-template #date> {{ $any(item[property]) | amDateFormat : 'll' }} </ng-template>
</p>
</ng-container>
<span class="ion-float-right">

View File

@@ -26,8 +26,7 @@
<stapps-fee-item *ngFor="let fine of fines" [fee]="fine"></stapps-fee-item>
</ion-list>
<ng-template #loading>
<stapps-skeleton-list-item *ngFor="let _ of [].constructor(2)" hideThumbnail="true">
</stapps-skeleton-list-item>
<stapps-skeleton-list-item *ngFor="let _ of [0, 1]" [hideThumbnail]="true"></stapps-skeleton-list-item>
</ng-template>
<ion-grid>
<ion-row *ngIf="amount; else amount_loading" class="ion-float-right">

View File

@@ -67,7 +67,7 @@
</ion-list>
<ng-template #fallback>
<stapps-skeleton-list-item
hideThumbnail="true"
[hideThumbnail]="true"
*ngIf="!paiaDocuments; else nothingFound"
></stapps-skeleton-list-item>
<ng-template #nothingFound>

View File

@@ -77,7 +77,7 @@
<ion-icon *ngIf="position !== null; else noLocationIcon" name="my_location"></ion-icon>
<ng-template #noLocationIcon>
<ion-icon
*ngIf="locationStatus.location !== 'denied'; else deniedLocationIcon"
*ngIf="locationStatus?.location !== 'denied'; else deniedLocationIcon"
name="location_searching"
></ion-icon>
</ng-template>
@@ -89,7 +89,7 @@
<ion-card class="map-item">
<stapps-data-list-item *ngIf="items.length === 1" [item]="$any(items[0])"></stapps-data-list-item>
<ion-button fill="clear" class="close" (click)="resetView()">
<ion-icon size="22" name="close" slot="icon-only"></ion-icon>
<ion-icon [size]="22" name="close" slot="icon-only"></ion-icon>
</ion-button>
</ion-card>
</div>

View File

@@ -79,8 +79,8 @@
class="filter-item-label"
>
({{ bucket.count }}) {{ facet.field === 'type' ? (getTranslatedPropertyValue($any(bucket.key),
'type') | titlecase) : (getTranslatedPropertyValue(facet.onlyOnType, facet.field, bucket.key)
| titlecase) }}
'type') | titlecase) : (facet.onlyOnType && getTranslatedPropertyValue(facet.onlyOnType,
facet.field, bucket.key) | titlecase) }}
</ion-checkbox>
</ion-item>
<ion-button

View File

@@ -34,11 +34,11 @@
class="menu-category"
>
<ion-icon slot="end" [name]="category.icon"></ion-icon>
<ion-label> {{ category.translations[language].title | titlecase }} </ion-label>
<ion-label> {{ category.translations[language]?.title | titlecase }} </ion-label>
</ion-item>
<ion-item *ngFor="let item of category.items" [rootLink]="item.route" [redirectedFrom]="item.route">
<ion-icon slot="end" [name]="item.icon"></ion-icon>
<ion-label> {{ item.translations[language].title | titlecase }} </ion-label>
<ion-label> {{ item.translations[language]?.title | titlecase }} </ion-label>
</ion-item>
</ion-list>
</ion-content>

View File

@@ -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" 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" 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" weight="800" name="close" slot="icon-only"></ion-icon
><ion-icon [size]="16" [weight]="800" name="close" slot="icon-only"></ion-icon
></ion-button>

View File

@@ -46,6 +46,6 @@
[tab]="category.title"
>
<ion-icon [name]="category.icon"></ion-icon>
<ion-label>{{ category.translations[language].title | titlecase }}</ion-label>
<ion-label>{{ category.translations[language]?.title | titlecase }}</ion-label>
</ion-tab-button>
</ion-tab-bar>

View File

@@ -23,7 +23,7 @@
</ion-header>
<ion-content parallax (elementSizeChange)="calcPageSize($event)">
<ion-refresher slot="fixed" (ionRefresh)="refresh($event.target)">
<ion-refresher slot="fixed" (ionRefresh)="refresh($any($event.target))">
<ion-refresher-content
pullingIcon="chevron-down-outline"
pullingText="{{ 'data.REFRESH_ACTION' | translate }}"
@@ -54,7 +54,7 @@
<ion-label *ngIf="news.length === 0" class="centered-message-container">
{{ 'search.nothing_found' | translate | titlecase }}
</ion-label>
<ion-infinite-scroll id="infinite-scroll" threshold="20%" (ionInfinite)="loadMore($event.target)">
<ion-infinite-scroll id="infinite-scroll" threshold="20%" (ionInfinite)="loadMore($any($event.target))">
<ion-infinite-scroll-content loading-spinner="crescent"> </ion-infinite-scroll-content>
</ion-infinite-scroll>
</ion-content>

View File

@@ -17,7 +17,7 @@
<div *ngIf="idCards.length === 0">
<div class="log-in">
{{'profile.userInfo.logInPrompt' | translate | sentencecase}}
<ion-icon name="person" fill="true"></ion-icon>
<ion-icon name="person" [fill]="true"></ion-icon>
</div>
</div>
<stapps-id-card *ngFor="let idCard of idCards" [item]="idCard"></stapps-id-card>

View File

@@ -31,7 +31,7 @@
[detail]="false"
>
<div>
<ion-icon [name]="link.icon" size="36" color="dark"></ion-icon>
<ion-icon *ngIf="link.icon" [name]="link.icon" [size]="36" color="dark"></ion-icon>
<ion-label>{{ 'name' | translateSimple : link }}</ion-label>
</div>
</ion-item>

View File

@@ -25,14 +25,11 @@
>
<ion-card-header mode="md">
<ion-card-title>
{{ this.scheduleEvent?.dateSeries?.event?.name | nullishCoalesce : this.scheduleEvent?.dateSeries?.name
}}
{{ this.scheduleEvent.dateSeries.event.name | nullishCoalesce : this.scheduleEvent.dateSeries.name }}
</ion-card-title>
</ion-card-header>
<ion-card-content>
<ion-note> {{ getNote() }} </ion-note>
<ion-text *ngIf="showPlaceName" class="place-name"
>{{ scheduleEvent?.dateSeries?.inPlace?.name }}</ion-text
>
<ion-text *ngIf="showPlaceName" class="place-name">{{ scheduleEvent.dateSeries.inPlace?.name }}</ion-text>
</ion-card-content>
</ion-card>

View File

@@ -28,7 +28,7 @@
[scheduleEvent]="event.event"
[noOffset]="true"
[scale]="scale"
showPlaceName="true"
[showPlaceName]="true"
>
</stapps-schedule-card>
</ion-item>

View File

@@ -59,7 +59,7 @@
<swiper
#mainSwiper
[slidesPerView]="layout.days"
(indexChange)="onPageChange($event)"
(indexChange)="onPageChange($any($event))"
(activeIndexChange)="syncSwiper(mainSwiper, headerSwiper)"
class="full-height"
>

View File

@@ -65,11 +65,11 @@ export class SettingsItemComponent {
* @param title Title of the alert
* @param message Message of the alert
*/
async presentAlert(title: string, message: string) {
async presentAlert(title: string, message: string | null) {
const alert = await this.alertCtrl.create({
buttons: ['OK'],
header: title,
message: message,
message: message ?? undefined,
});
await alert.present();
}

View File

@@ -75,7 +75,7 @@
>
<ion-select-option *ngFor="let val of setting.values; index as i" [value]="val">
<div *ngIf="typeOf(val) !== 'number'">
{{ ('values' | thingTranslate : setting)[i] | titlecase }}
{{ $any(('values' | thingTranslate : setting)?.[i]) | titlecase }}
</div>
<div *ngIf="typeOf(val) === 'number'">{{ val }}</div>
</ion-select-option>
@@ -86,7 +86,7 @@
<ion-select [(ngModel)]="setting.value" multiple="true" (ionChange)="settingChanged()">
<ion-select-option *ngFor="let val of setting.values; index as i" [value]="val">
<div *ngIf="typeOf(val) !== 'number'">
{{ ('values' | thingTranslate : setting)[i] | titlecase }}
{{ $any(('values' | thingTranslate : setting)?.[i]) | titlecase }}
</div>
<div *ngIf="typeOf(val) === 'number'">{{ val }}</div>
</ion-select-option>

View File

@@ -32,9 +32,9 @@
justify="start"
(ionChange)="
setSetting({
sync: $event.detail.checked
sync: $any($event).detail.checked
});
syncCalendar($event.detail.checked)
syncCalendar($any($event).detail.checked)
"
>
{{ 'settings.calendar.sync.syncWithCalendar' | translate }}

View File

@@ -24,6 +24,7 @@ import {ThingTranslateService} from '../../translation/thing-translate.service';
*/
@Pipe({
name: 'settingValueTranslate',
pure: true,
})
export class SettingTranslatePipe implements PipeTransform {
constructor(
@@ -31,12 +32,12 @@ export class SettingTranslatePipe implements PipeTransform {
private readonly thingTranslate: ThingTranslateService,
) {}
transform(setting: SCSetting) {
transform(setting: SCSetting): string | undefined {
const thingTranslatePipe = new ThingTranslatePipe(this.translate, this.thingTranslate);
const translatedSettingValues = thingTranslatePipe.transform('values', setting);
return translatedSettingValues
? translatedSettingValues[setting.values?.indexOf(setting.value as string) as number]
? String(translatedSettingValues[setting.values?.indexOf(setting.value as string) as number])
: undefined;
}
}

View File

@@ -41,6 +41,8 @@ export class PropertyNameTranslatePipe implements PipeTransform, OnDestroy {
this.value = this.thingTranslate.getPropertyName(type as SCThingType, key);
}
transform<K extends string, Q extends keyof Extract<SCThings, {type: K}>>(query: Q, type: K): string;
transform<T extends SCThings, K extends keyof T>(query: K, thing: T): string;
transform(query: unknown, thingOrType: SCThings | string | unknown): unknown {
if (typeof query !== 'string' || query.length <= 0) {
return query;

View File

@@ -51,10 +51,8 @@ export class TranslateSimplePipe implements PipeTransform {
}
}
transform<T extends object, P extends string[] | string | keyof T>(
query: P,
thing: T,
): P extends keyof T ? T[P] : P | unknown {
transform<T extends object, P extends keyof T>(query: P, thing: T): T[P];
transform<T extends object, P extends string | string[]>(query: P, thing: T): P | unknown {
// store the params, in case they change
this.query = query;
this.thing = thing;

View File

@@ -50,7 +50,7 @@ export class IonIconDirective implements OnInit, OnDestroy, OnChanges {
@Input() weight: number;
@Input() size: number;
@Input() size: number | 'small' | 'large';
@Input() grade: number;

View File

@@ -34,7 +34,7 @@
(click)="swiper.scrollBy({left: -swiper.offsetWidth, behavior: 'smooth'})"
[disabled]="firstSlideVisible | async"
>
<ion-icon size="24" slot="icon-only" name="chevron_left"></ion-icon>
<ion-icon [size]="24" slot="icon-only" name="chevron_left"></ion-icon>
</ion-button>
</ion-col>
<ion-col size="auto" class="swiper-button">
@@ -44,7 +44,7 @@
(click)="swiper.scrollBy({left: swiper.offsetWidth, behavior: 'smooth'})"
[disabled]="lastSlideVisible | async"
>
<ion-icon size="24" slot="icon-only" name="chevron_right"></ion-icon>
<ion-icon [size]="24" slot="icon-only" name="chevron_right"></ion-icon>
</ion-button>
</ion-col>
</ng-container>

View File

@@ -16,5 +16,8 @@
"module": "ES2022",
"moduleResolution": "Node"
},
"angularCompilerOptions": {
"strictTemplates": true
},
"exclude": ["**/*.spec.ts"]
}