diff --git a/frontend/app/src/app/modules/data/chips/action-chip-list.component.ts b/frontend/app/src/app/modules/data/chips/action-chip-list.component.ts index a2f7fed8..1be498c4 100644 --- a/frontend/app/src/app/modules/data/chips/action-chip-list.component.ts +++ b/frontend/app/src/app/modules/data/chips/action-chip-list.component.ts @@ -12,8 +12,11 @@ * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ -import {Component, Input} from '@angular/core'; -import {SCDateSeries, SCThingType, SCThings} from '@openstapps/core'; +import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; +import {SCDateSeries, SCThings, SCThingType} from '@openstapps/core'; +import {LocateActionChipComponent} from './data/locate-action-chip.component'; +import {NavigateActionChipComponent} from './data/navigate-action-chip.component'; +import {AddEventActionChipComponent} from './data/add-event-action-chip.component'; /** * Shows a horizontal list of action chips @@ -21,7 +24,10 @@ import {SCDateSeries, SCThingType, SCThings} from '@openstapps/core'; @Component({ selector: 'stapps-action-chip-list', templateUrl: 'action-chip-list.html', - styleUrls: ['action-chip-list.scss'], + styleUrl: 'action-chip-list.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [LocateActionChipComponent, NavigateActionChipComponent, AddEventActionChipComponent], }) export class ActionChipListComponent { private _item: SCThings; diff --git a/frontend/app/src/app/modules/data/chips/action-chip-list.html b/frontend/app/src/app/modules/data/chips/action-chip-list.html index fe44b8c9..325cc20b 100644 --- a/frontend/app/src/app/modules/data/chips/action-chip-list.html +++ b/frontend/app/src/app/modules/data/chips/action-chip-list.html @@ -13,7 +13,13 @@ ~ this program. If not, see . --> - - - - +@if (applicable.locate) { + +} +@if (applicable.navigate) { + +} +@if (applicable.event) { + + +} diff --git a/frontend/app/src/app/modules/data/chips/data/add-event-action-chip.component.ts b/frontend/app/src/app/modules/data/chips/data/add-event-action-chip.component.ts index 12faccfc..e930375c 100644 --- a/frontend/app/src/app/modules/data/chips/data/add-event-action-chip.component.ts +++ b/frontend/app/src/app/modules/data/chips/data/add-event-action-chip.component.ts @@ -38,7 +38,6 @@ import {EditEventSelectionComponent} from '../edit-event-selection.component'; import {AddEventReviewModalComponent} from '../../../calendar/add-event-review-modal.component'; import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; import {AsyncPipe, NgIf, TitleCasePipe} from '@angular/common'; -import {DataModule} from '../../data.module'; import {TranslateModule} from '@ngx-translate/core'; import {EditModalComponent} from '../../../../util/edit-modal.component'; import {IonContentParallaxDirective} from '../../../../util/ion-content-parallax.directive'; @@ -56,7 +55,6 @@ import {IonContentParallaxDirective} from '../../../../util/ion-content-parallax IonChip, AsyncPipe, NgIf, - DataModule, IonContent, IonIcon, IonLabel, @@ -68,6 +66,7 @@ import {IonContentParallaxDirective} from '../../../../util/ion-content-parallax IonSkeletonText, TitleCasePipe, IonContentParallaxDirective, + EditEventSelectionComponent, ], }) export class AddEventActionChipComponent { diff --git a/frontend/app/src/app/modules/data/chips/data/navigate-action-chip.component.ts b/frontend/app/src/app/modules/data/chips/data/navigate-action-chip.component.ts index 2c6bf523..0f5c9e82 100644 --- a/frontend/app/src/app/modules/data/chips/data/navigate-action-chip.component.ts +++ b/frontend/app/src/app/modules/data/chips/data/navigate-action-chip.component.ts @@ -12,14 +12,18 @@ * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ -import {Component, Input} from '@angular/core'; +import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; import {SCPlaceWithoutReferences, SCThings} from '@openstapps/core'; +import {IonChip, IonIcon, IonLabel} from '@ionic/angular/standalone'; +import {GeoNavigationDirective} from '../../../map/geo-navigation.directive'; +import {TranslateModule} from '@ngx-translate/core'; @Component({ selector: 'stapps-navigate-action-chip', templateUrl: 'navigate-action-chip.html', - styleUrl: 'navigate-action-chip.scss', + changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, + imports: [IonChip, GeoNavigationDirective, IonIcon, IonLabel, TranslateModule], }) export class NavigateActionChipComponent { place: SCPlaceWithoutReferences; diff --git a/frontend/app/src/app/modules/data/chips/data/navigate-action-chip.scss b/frontend/app/src/app/modules/data/chips/data/navigate-action-chip.scss deleted file mode 100644 index bc381711..00000000 --- a/frontend/app/src/app/modules/data/chips/data/navigate-action-chip.scss +++ /dev/null @@ -1,14 +0,0 @@ -/*! - * 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 . - */ diff --git a/frontend/app/src/app/modules/data/chips/edit-event-selection.component.ts b/frontend/app/src/app/modules/data/chips/edit-event-selection.component.ts index dd0ea5e0..be967e46 100644 --- a/frontend/app/src/app/modules/data/chips/edit-event-selection.component.ts +++ b/frontend/app/src/app/modules/data/chips/edit-event-selection.component.ts @@ -13,7 +13,15 @@ * this program. If not, see . */ import {ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; -import {ModalController} from '@ionic/angular/standalone'; +import { + IonCheckbox, + IonIcon, + IonItem, + IonList, + IonListHeader, + IonText, + ModalController, +} from '@ionic/angular/standalone'; import {SCDateSeries} from '@openstapps/core'; import { DateSeriesRelevantData, @@ -30,6 +38,10 @@ import { differenceBy, } from '@openstapps/collection-utils'; import {SelectionValue, TreeNode} from './tree-node'; +import {TranslateModule} from '@ngx-translate/core'; +import {ThingTranslateModule} from '../../../translation/thing-translate.module'; +import {MomentModule} from 'ngx-moment'; +import {KeyValuePipe, TitleCasePipe} from '@angular/common'; /** * Shows a horizontal list of action chips @@ -38,6 +50,20 @@ import {SelectionValue, TreeNode} from './tree-node'; selector: 'stapps-edit-event-selection', templateUrl: 'edit-event-selection.html', styleUrls: ['edit-event-selection.scss'], + standalone: true, + imports: [ + IonItem, + IonCheckbox, + IonListHeader, + TranslateModule, + IonList, + IonText, + IonIcon, + ThingTranslateModule, + MomentModule, + KeyValuePipe, + TitleCasePipe, + ], }) export class EditEventSelectionComponent implements OnInit { /** diff --git a/frontend/app/src/app/modules/data/chips/edit-event-selection.html b/frontend/app/src/app/modules/data/chips/edit-event-selection.html index d3ecc525..e8a1cfd5 100644 --- a/frontend/app/src/app/modules/data/chips/edit-event-selection.html +++ b/frontend/app/src/app/modules/data/chips/edit-event-selection.html @@ -22,7 +22,7 @@ {{ 'data.chips.add_events.popover.ALL' | translate }} - +@for (frequency of selection.children; track $index) { - - - - - {{ date.item.dates[0] | amDateFormat: 'dddd, LT' }} - - {{ date.item.dates[0] | amAdd: date.item.duration | amDateFormat: 'LT' }} - -
- - {{ date.item.dates[0] | amDateFormat: 'LL' }} - - {{ date.item.dates[date.item.dates.length - 1] | amDateFormat: 'LL' }} - -
- - - {{ time | amDateFormat: 'LL, LT' }} - {{ time | amAdd: date.item.duration | amDateFormat: 'LT' }} - - + @for (date of frequency.children; track $index) { + + + @if (date.item.dates.length > 1) { + + {{ date.item.dates[0] | amDateFormat: 'dddd, LT' }} - + {{ date.item.dates[0] | amAdd: date.item.duration | amDateFormat: 'LT' }} + +
+ + {{ date.item.dates[0] | amDateFormat: 'LL' }} - + {{ date.item.dates.at(-1) | amDateFormat: 'LL' }} + + } @else if (date.item.dates.length === 1) { + + {{ date.item.dates[0] | amDateFormat: 'LL, LT' }} - + {{ date.item.dates[0] | amAdd: date.item.duration | amDateFormat: 'LT' }} + + } @else { {{ 'data.chips.add_events.popover.DATA_ERROR' | translate }}
- - {{ id.key }}: {{ id.value }} + @for (id of date.item.identifiers | keyvalue; track $index) { + {{ id.key }}: {{ id.value }} + } + } + @if (date.item.inPlace) { +
+ + + {{ 'inPlace.name' | thingTranslate: date.item }} -
-
- -
- - - {{ 'inPlace.name' | thingTranslate: date.item }} - -
-
-
+ } + + + }
-
+} diff --git a/frontend/app/src/app/modules/data/chips/filter/chip-filter.component.ts b/frontend/app/src/app/modules/data/chips/filter/chip-filter.component.ts index ae4656a0..50142f5a 100644 --- a/frontend/app/src/app/modules/data/chips/filter/chip-filter.component.ts +++ b/frontend/app/src/app/modules/data/chips/filter/chip-filter.component.ts @@ -12,8 +12,9 @@ * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ -import {Component, EventEmitter, Input, Output} from '@angular/core'; -import {IonChip, IonLabel} from '@ionic/angular/standalone'; +import {ChangeDetectionStrategy, Component, EventEmitter, Input, Output} from '@angular/core'; +import {IonChip, IonIcon, IonLabel} from '@ionic/angular/standalone'; + /** * Shows a chip filter */ @@ -21,8 +22,9 @@ import {IonChip, IonLabel} from '@ionic/angular/standalone'; selector: 'stapps-chip-filter', templateUrl: './chip-filter.component.html', styleUrls: ['./chip-filter.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [IonChip, IonLabel], + imports: [IonChip, IonLabel, IonIcon], }) export class ChipFilterComponent { /** diff --git a/frontend/app/src/app/modules/data/data-icon.pipe.ts b/frontend/app/src/app/modules/data/data-icon.pipe.ts index d33deb3c..5e09ec02 100644 --- a/frontend/app/src/app/modules/data/data-icon.pipe.ts +++ b/frontend/app/src/app/modules/data/data-icon.pipe.ts @@ -21,6 +21,8 @@ import {DataIcons} from './data-icon.config'; */ @Pipe({ name: 'dataIcon', + pure: true, + standalone: true, }) export class DataIconPipe implements PipeTransform { /** diff --git a/frontend/app/src/app/modules/data/data.module.ts b/frontend/app/src/app/modules/data/data.module.ts index ff77b4ef..d3e7e87b 100644 --- a/frontend/app/src/app/modules/data/data.module.ts +++ b/frontend/app/src/app/modules/data/data.module.ts @@ -26,59 +26,20 @@ 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'; import {ScheduleProvider} from '../calendar/schedule.provider'; import {GeoNavigationDirective} from '../map/geo-navigation.directive'; import {SettingsProvider} from '../settings/settings.provider'; import {StorageModule} from '../storage/storage.module'; -import {ActionChipListComponent} from './chips/action-chip-list.component'; -import {AddEventActionChipComponent} from './chips/data/add-event-action-chip.component'; -import {LocateActionChipComponent} from './chips/data/locate-action-chip.component'; -import {NavigateActionChipComponent} from './chips/data/navigate-action-chip.component'; -import {EditEventSelectionComponent} from './chips/edit-event-selection.component'; import {CoordinatedSearchProvider} from './coordinated-search.provider'; import {DataFacetsProvider} from './data-facets.provider'; -import {DataIconPipe} from './data-icon.pipe'; import {DataRoutingModule} from './data-routing.module'; import {DataProvider} from './data.provider'; import {DataDetailContentComponent} from './detail/data-detail-content.component'; import {DataDetailComponent} from './detail/data-detail.component'; import {DataPathComponent} from './detail/data-path.component'; -import {AddressDetailComponent} from './elements/address-detail.component'; -import {CertificationsInDetailComponent} from './elements/certifications-in-detail.component'; -import {ExternalLinkComponent} from './elements/external-link.component'; -import {FavoriteButtonComponent} from './elements/favorite-button.component'; -import {LongInlineTextComponent} from './elements/long-inline-text.component'; -import {OffersDetailComponent} from './elements/offers-detail.component'; -import {OffersInListComponent} from './elements/offers-in-list.component'; -import {OriginDetailComponent} from './elements/origin-detail.component'; -import {OriginInListComponent} from './elements/origin-in-list.component'; -import {StappsRatingComponent} from './elements/rating.component'; -import {SimpleCardComponent} from './elements/simple-card.component'; -import {SkeletonListItemComponent} from './elements/skeleton-list-item.component'; -import {SkeletonSegmentComponent} from './elements/skeleton-segment-button.component'; -import {SkeletonSimpleCardComponent} from './elements/skeleton-simple-card.component'; -import {TitleCardComponent} from './elements/title-card.component'; -import {DataListItemHostDefaultComponent} from './list/data-list-item-host-default.component'; -import {DataListItemHostDirective} from './list/data-list-item-host.directive'; -import {DataListItemComponent} from './list/data-list-item.component'; -import {DataListComponent} from './list/data-list.component'; import {FoodDataListComponent} from './list/food-data-list.component'; -import {SearchPageComponent} from './list/search-page.component'; -import {SimpleDataListComponent} from './list/simple-data-list.component'; -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 {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'; -import {CatalogDetailContentComponent} from './types/catalog/catalog-detail-content.component'; -import {CatalogListItemComponent} from './types/catalog/catalog-list-item.component'; -import {DateSeriesDetailContentComponent} from './types/date-series/date-series-detail-content.component'; -import {DateSeriesListItemComponent} from './types/date-series/date-series-list-item.component'; import {DishCharacteristicsComponent} from './types/dish/dish-characteristics.component'; import {DishDetailContentComponent} from './types/dish/dish-detail-content.component'; import {DishListItemComponent} from './types/dish/dish-list-item.component'; @@ -110,71 +71,33 @@ import {VideoListItemComponent} from './types/video/video-list-item.component'; */ @NgModule({ declarations: [ - ActionChipListComponent, - AddEventActionChipComponent, - NavigateActionChipComponent, - EditEventSelectionComponent, - AddressDetailComponent, - CatalogDetailContentComponent, - CatalogListItemComponent, - CertificationsInDetailComponent, DishCharacteristicsComponent, DataDetailComponent, DataDetailContentComponent, - DataIconPipe, - DataListComponent, - DataListItemComponent, DataPathComponent, EventRoutePathComponent, - DateSeriesDetailContentComponent, - DateSeriesListItemComponent, DishDetailContentComponent, DishListItemComponent, EventDetailContentComponent, EventListItemComponent, - FavoriteButtonComponent, FavoriteDetailContentComponent, - SkeletonListComponent, FavoriteListItemComponent, FoodDataListComponent, - LocateActionChipComponent, - LongInlineTextComponent, MessageDetailContentComponent, MessageListItemComponent, JobPostingDetailContentComponent, JobPostingListItemComponent, - OffersDetailComponent, - OffersInListComponent, OrganizationDetailContentComponent, OrganizationListItemComponent, - OriginDetailComponent, - OriginInListComponent, PersonDetailContentComponent, PersonListItemComponent, PlaceDetailContentComponent, PlaceListItemComponent, PlaceMensaDetailComponent, - SearchPageComponent, SemesterDetailContentComponent, SemesterListItemComponent, - DataListItemHostDirective, - DataListItemHostDefaultComponent, - SimpleCardComponent, - SkeletonListItemComponent, - SkeletonSegmentComponent, - StappsRatingComponent, - SkeletonSimpleCardComponent, - TreeListComponent, - TreeListFragmentComponent, VideoDetailContentComponent, VideoListItemComponent, - SimpleDataListComponent, - TitleCardComponent, - ExternalLinkComponent, - ArticleListItemComponent, - ArticleContentComponent, - BookListItemComponent, - BookDetailContentComponent, PeriodicalListItemComponent, PeriodicalDetailContentComponent, ], @@ -191,7 +114,6 @@ import {VideoListItemComponent} from './types/video/video-list-item.component'; StorageModule, TranslateModule.forChild(), ThingTranslateModule.forChild(), - UtilModule, GeoNavigationDirective, ], providers: [ @@ -213,25 +135,8 @@ import {VideoListItemComponent} from './types/video/video-list-item.component'; exports: [ DataDetailComponent, DataDetailContentComponent, - DataIconPipe, - DataListComponent, - DataListItemComponent, - DateSeriesListItemComponent, PlaceListItemComponent, - SimpleCardComponent, - SkeletonListItemComponent, - SkeletonSimpleCardComponent, - SearchPageComponent, - SimpleDataListComponent, - OriginDetailComponent, - FavoriteButtonComponent, - TreeListComponent, - ExternalLinkComponent, - ArticleContentComponent, - BookDetailContentComponent, PeriodicalDetailContentComponent, - TitleCardComponent, - EditEventSelectionComponent, ], }) export class DataModule {} diff --git a/frontend/app/src/app/modules/data/list/data-list-item-host-default.component.ts b/frontend/app/src/app/modules/data/list/data-list-item-host-default.component.ts index 5ffdf7ad..841b7252 100644 --- a/frontend/app/src/app/modules/data/list/data-list-item-host-default.component.ts +++ b/frontend/app/src/app/modules/data/list/data-list-item-host-default.component.ts @@ -12,12 +12,17 @@ * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ -import {Component, Input} from '@angular/core'; +import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; import {SCThings} from '@openstapps/core'; +import {ThingTranslateModule} from '../../../translation/thing-translate.module'; +import {LongInlineTextComponent} from '../elements/long-inline-text.component'; @Component({ selector: 'data-list-item-host-default', templateUrl: 'data-list-item-host-default.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ThingTranslateModule, LongInlineTextComponent], }) export class DataListItemHostDefaultComponent { @Input() item: SCThings; diff --git a/frontend/app/src/app/modules/data/list/data-list-item-host-default.html b/frontend/app/src/app/modules/data/list/data-list-item-host-default.html index 003173a0..51d2d43c 100644 --- a/frontend/app/src/app/modules/data/list/data-list-item-host-default.html +++ b/frontend/app/src/app/modules/data/list/data-list-item-host-default.html @@ -14,9 +14,11 @@ -->

{{ 'name' | thingTranslate: item }}

-

- -

+@if (item.description) { +

+ +

+} diff --git a/frontend/app/src/app/modules/data/list/data-list-item-host.directive.ts b/frontend/app/src/app/modules/data/list/data-list-item-host.directive.ts index 7b839536..d4186088 100644 --- a/frontend/app/src/app/modules/data/list/data-list-item-host.directive.ts +++ b/frontend/app/src/app/modules/data/list/data-list-item-host.directive.ts @@ -59,6 +59,7 @@ const DataListItemIndex: Partial>> = { @Directive({ selector: '[dataListItemHost]', + standalone: true, }) export class DataListItemHostDirective { private type?: Type; diff --git a/frontend/app/src/app/modules/data/list/data-list-item.component.ts b/frontend/app/src/app/modules/data/list/data-list-item.component.ts index 90ca5ffe..ed80a7c0 100644 --- a/frontend/app/src/app/modules/data/list/data-list-item.component.ts +++ b/frontend/app/src/app/modules/data/list/data-list-item.component.ts @@ -12,18 +12,46 @@ * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ -import {Component, ContentChild, HostBinding, Input, TemplateRef} from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + ContentChild, + HostBinding, + Input, + TemplateRef, +} from '@angular/core'; import {SCThings} from '@openstapps/core'; import {DataRoutingService} from '../data-routing.service'; import {DataListContext} from './data-list.component'; +import {IonIcon, IonItem, IonLabel, IonThumbnail} from '@ionic/angular/standalone'; +import {StappsRatingComponent} from '../elements/rating.component'; +import {FavoriteButtonComponent} from '../elements/favorite-button.component'; +import {ActionChipListComponent} from '../chips/action-chip-list.component'; +import {NgTemplateOutlet} from '@angular/common'; +import {DataListItemHostDirective} from './data-list-item-host.directive'; +import {DataIconPipe} from '../data-icon.pipe'; /** * Shows data items in lists such es search result */ @Component({ selector: 'stapps-data-list-item', - styleUrls: ['data-list-item.scss'], templateUrl: 'data-list-item.html', + styleUrl: 'data-list-item.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + IonItem, + IonThumbnail, + IonIcon, + IonLabel, + StappsRatingComponent, + FavoriteButtonComponent, + ActionChipListComponent, + NgTemplateOutlet, + DataListItemHostDirective, + DataIconPipe, + ], }) export class DataListItemComponent { /** diff --git a/frontend/app/src/app/modules/data/list/data-list-item.html b/frontend/app/src/app/modules/data/list/data-list-item.html index 3d478f66..089a99c7 100644 --- a/frontend/app/src/app/modules/data/list/data-list-item.html +++ b/frontend/app/src/app/modules/data/list/data-list-item.html @@ -21,32 +21,38 @@ (click)="notifySelect()" >
- - - - - + @if (!hideThumbnail) { + + + + } + @if (contentTemplateRef) { + + +
+ +
+
+
+ } @else { +
- + + @if (listItemChipInteraction && appearance !== 'square') { + + }
-
+ } - - - - + @if (listItemChipInteraction) { + @switch (item.type) { + @case ('dish') { + + } + @default { + + } + } + } - - - -
- - -
-
-
diff --git a/frontend/app/src/app/modules/data/list/data-list.component.ts b/frontend/app/src/app/modules/data/list/data-list.component.ts index b32489ab..a9f42cbd 100644 --- a/frontend/app/src/app/modules/data/list/data-list.component.ts +++ b/frontend/app/src/app/modules/data/list/data-list.component.ts @@ -13,6 +13,7 @@ * this program. If not, see . */ import { + ChangeDetectionStrategy, Component, ContentChild, DestroyRef, @@ -29,8 +30,12 @@ import { } from '@angular/core'; import {SCThings} from '@openstapps/core'; import {BehaviorSubject, Observable} from 'rxjs'; -import {IonInfiniteScroll} from '@ionic/angular/standalone'; +import {IonInfiniteScroll, IonInfiniteScrollContent, IonLabel, IonList} from '@ionic/angular/standalone'; import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; +import {AsyncPipe, NgTemplateOutlet, TitleCasePipe} from '@angular/common'; +import {TranslateModule} from '@ngx-translate/core'; +import {SkeletonListComponent} from './skeleton-list.component'; +import {DataListItemComponent} from './data-list-item.component'; export interface DataListContext { $implicit: T; @@ -42,7 +47,21 @@ export interface DataListContext { @Component({ selector: 'stapps-data-list', templateUrl: 'data-list.html', - styleUrls: ['data-list.scss'], + styleUrl: 'data-list.scss', + changeDetection: ChangeDetectionStrategy.Default, + standalone: true, + imports: [ + IonList, + IonInfiniteScroll, + IonInfiniteScrollContent, + IonLabel, + AsyncPipe, + NgTemplateOutlet, + TranslateModule, + TitleCasePipe, + SkeletonListComponent, + DataListItemComponent, + ], }) export class DataListComponent implements OnChanges, OnInit { /** diff --git a/frontend/app/src/app/modules/data/list/data-list.html b/frontend/app/src/app/modules/data/list/data-list.html index 2b120840..31b8719e 100644 --- a/frontend/app/src/app/modules/data/list/data-list.html +++ b/frontend/app/src/app/modules/data/list/data-list.html @@ -13,25 +13,25 @@ ~ this program. If not, see . --> - +@if (itemStream | async; as items) { - + @for (item of items; track $index) { - + } @empty { + + {{ 'search.nothing_found' | translate | titlecase }} + + } - -
- - {{ 'search.nothing_found' | translate | titlecase }} - -
- +} @else if (loading) { + +} diff --git a/frontend/app/src/app/modules/data/list/sc-thing-list-item-virtual-scroll-strategy.directive.ts b/frontend/app/src/app/modules/data/list/sc-thing-list-item-virtual-scroll-strategy.directive.ts deleted file mode 100644 index c56468f0..00000000 --- a/frontend/app/src/app/modules/data/list/sc-thing-list-item-virtual-scroll-strategy.directive.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 . - */ - -import {Directive, forwardRef, Input, Output, ViewChild} from '@angular/core'; -import {CdkVirtualForOf, VIRTUAL_SCROLL_STRATEGY} from '@angular/cdk/scrolling'; -import {ScThingListItemVirtualScrollStrategy} from './sc-thing-list-item-virtual-scroll-strategy'; - -/** - * - */ -function factory(directive: SCThingListItemVirtualScrollStrategyDirective) { - return directive.scrollStrategy; -} - -@Directive({ - selector: 'cdk-virtual-scroll-viewport[scThingListItemVirtualScrollStrategy]', - providers: [ - { - provide: VIRTUAL_SCROLL_STRATEGY, - useFactory: factory, - deps: [forwardRef(() => SCThingListItemVirtualScrollStrategyDirective)], - }, - ], -}) -export class SCThingListItemVirtualScrollStrategyDirective { - scrollStrategy = new ScThingListItemVirtualScrollStrategy(); - - @ViewChild(CdkVirtualForOf) virtualForOf: CdkVirtualForOf; - - @Output() readonly loadMore = this.scrollStrategy.loadMore; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - @Input() set trackGroupBy(value: (item: any) => unknown) { - this.scrollStrategy.trackGroupBy = value; - } - - @Input() set minimumHeight(value: number) { - this.scrollStrategy.approximateItemHeight = value; - } - - @Input() set buffer(value: number) { - this.scrollStrategy.buffer = value; - } - - @Input() set gap(value: number) { - this.scrollStrategy.gap = value; - } - - @Input() set itemRenderTimeout(value: number) { - this.scrollStrategy.itemRenderTimeout = value; - } -} diff --git a/frontend/app/src/app/modules/data/list/sc-thing-list-item-virtual-scroll-strategy.ts b/frontend/app/src/app/modules/data/list/sc-thing-list-item-virtual-scroll-strategy.ts deleted file mode 100644 index 9e6e040d..00000000 --- a/frontend/app/src/app/modules/data/list/sc-thing-list-item-virtual-scroll-strategy.ts +++ /dev/null @@ -1,286 +0,0 @@ -/* - * 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 . - */ - -import {CdkVirtualForOf, CdkVirtualScrollViewport, VirtualScrollStrategy} from '@angular/cdk/scrolling'; -import {BehaviorSubject, Subject, Subscription, takeUntil, timer} from 'rxjs'; -import {debounceTime, distinctUntilChanged, tap} from 'rxjs/operators'; -import {SCThingType} from '@openstapps/core'; - -export class ScThingListItemVirtualScrollStrategy implements VirtualScrollStrategy { - private viewport?: CdkVirtualScrollViewport; - - private virtualForOf?: CdkVirtualForOf; - - private index$ = new Subject(); - - private heights = new Map(); - - private groupHeights = new Map(); - - private offsets: number[] = []; - - private totalHeight = 0; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private _items?: readonly unknown[]; - - private _groups?: readonly unknown[]; - - /** - * We use this to track loadMore - */ - private currentLength = 0; - - private dataStreamSubscription?: Subscription; - - private mutationObserver?: MutationObserver; - - approximateItemHeight = 67; - - approximateGroupSizes: Map = new Map([[SCThingType.AcademicEvent, 139]]); - - buffer = 4000; - - gap = 8; - - itemRenderTimeout = 1000; - - heightUpdateDebounceTime = 25; - - heightSetDebounceTime = 100; - - loadMore = new Subject(); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - trackGroupBy: (value: any) => unknown = it => it.type; - - attach(viewport: CdkVirtualScrollViewport): void { - this.viewport = viewport; - // @ts-expect-error private property - this.virtualForOf = viewport._forOf; - this.dataStreamSubscription = this.virtualForOf?.dataStream.subscribe(items => { - this.items = items; - }); - - this.mutationObserver = new MutationObserver(() => { - const renderedItems = this.renderedItems; - if (!renderedItems) { - this.mutationObserver?.disconnect(); - return this.onPrematureDisconnect(); - } - - this.intersectionObserver?.disconnect(); - this.intersectionObserver = new IntersectionObserver(this.observeIntersection.bind(this), { - rootMargin: `${this.buffer - 64}px`, - threshold: 1, - }); - - for (const node of renderedItems) { - const [item, group] = this.getItemByNode(node, renderedItems); - - if (this.heights.has(item)) { - node.style.height = `${this.getHeight(item, group)}px`; - } else { - this.intersectionObserver.observe(node); - } - } - }); - - const contentWrapper = this.contentWrapper; - if (!contentWrapper) return this.onPrematureDisconnect(); - this.mutationObserver.observe(contentWrapper, {childList: true, subtree: true}); - - this.setTotalContentSize(); - } - - detach(): void { - this.index$.complete(); - this.dataStreamSubscription?.unsubscribe(); - this.mutationObserver?.disconnect(); - delete this.viewport; - } - - private getHeight(item: unknown, group: unknown) { - return ( - this.heights.get(item) || - this.groupHeights.get(group) || - this.approximateGroupSizes.get(group) || - this.approximateItemHeight - ); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private set items(value: readonly unknown[]) { - const trackBy = this.virtualForOf?.cdkVirtualForTrackBy; - const tracks = value.map((it, i) => (trackBy ? trackBy(i, it) : it)); - if ( - this._items && - tracks.length === this._items.length && - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - tracks.every((it, i) => it === this._items![i]) - ) - return; - - this._items = tracks; - this._groups = value.map(this.trackGroupBy); - this.currentLength = tracks.length; - - this.updateHeights(); - } - - scrolledIndexChange = this.index$.pipe(distinctUntilChanged()); - - private getOffsetFromIndex(index: number): number { - return this.offsets[index]; - } - - private getIndexFromOffset(offset: number): number { - return Math.max( - 0, - this.offsets.findIndex((it, i, array) => it >= offset || i === array.length - 1), - ); - } - - private updateHeights() { - if (!this._items) return; - const heights = this._items.map((it, i) => this.getHeight(it, this._groups![i]) + this.gap); - this.offsets = Array.from({length: heights.length}, () => 0); - this.totalHeight = heights.reduce((a, b, index) => (this.offsets[index + 1] = a + b), 0) + this.gap; - - this.setTotalContentSize(); - this.updateRenderedRange(); - } - - private updateRenderedRange() { - if (!this.viewport) return; - const offset = this.viewport.measureScrollOffset('top'); - const viewportSize = this.viewport.getViewportSize(); - const firstVisibleIndex = Math.max(0, this.getIndexFromOffset(offset) - 1); - const range = { - start: this.getIndexFromOffset(Math.max(0, offset - this.buffer)), - end: this.getIndexFromOffset(offset + viewportSize + this.buffer), - }; - const {start, end} = this.viewport.getRenderedRange(); - if (range.start === start && range.end === end) return; - - if (this.currentLength !== 0 && range.end === this.currentLength) { - this.currentLength++; - this.loadMore.next(); - } - - this.viewport.setRenderedRange(range); - this.viewport.setRenderedContentOffset(this.getOffsetFromIndex(range.start), 'to-start'); - - this.index$.next(firstVisibleIndex); - } - - private setTotalContentSize() { - this.viewport?.setTotalContentSize(this.totalHeight); - // @ts-expect-error TODO - this.viewport?._measureViewportSize(); - } - - observeIntersection(entries: IntersectionObserverEntry[], observer: IntersectionObserver) { - const renderedItems = this.renderedItems; - if (!renderedItems) return this.onPrematureDisconnect(); - - const update = new Subject(); - for (const entry of entries) { - if (!entry.isIntersecting) continue; - - const outerNode = entry.target as HTMLElement; - const [item] = this.getItemByNode(outerNode, renderedItems); - const node = outerNode.firstChild! as HTMLElement; - - const height = new BehaviorSubject(node.offsetHeight); - const resizeObserver = new ResizeObserver(() => { - const renderedItems = this.renderedItems; - if (!renderedItems) { - resizeObserver.disconnect(); - return this.onPrematureDisconnect(); - } - const [newItem] = this.getItemByNode(node, renderedItems); - if (newItem !== item) { - this.heights.delete(item); - resizeObserver.disconnect(); - return; - } - height.next(node.offsetHeight); - }); - resizeObserver.observe(node); - height - .pipe( - distinctUntilChanged(), - debounceTime(this.heightSetDebounceTime), - takeUntil(timer(this.itemRenderTimeout)), - tap({complete: () => resizeObserver.disconnect()}), - ) - .subscribe(height => { - this.heights.set(item, height); - outerNode.style.height = `${height}px`; - update.next(); - }); - - observer.unobserve(node); - } - update - .pipe(debounceTime(this.heightUpdateDebounceTime), takeUntil(timer(this.itemRenderTimeout))) - .subscribe(() => { - this.updateHeights(); - }); - } - - intersectionObserver?: IntersectionObserver; - - get contentWrapper() { - return this.viewport?._contentWrapper.nativeElement; - } - - get renderedItems() { - const contentWrapper = this.contentWrapper; - return contentWrapper - ? // eslint-disable-next-line unicorn/prefer-spread - (Array.from(contentWrapper.children).filter(it => it instanceof HTMLElement) as HTMLElement[]) - : undefined; - } - - getItemByNode(node: HTMLElement, renderedItems: HTMLElement[]) { - const {start} = this.viewport!.getRenderedRange(); - const index = renderedItems.indexOf(node) + start; - return [this._items![index], this._groups![index]]; - } - - // eslint-disable-next-line @typescript-eslint/no-empty-function - onContentRendered(): void {} - - onContentScrolled() { - this.updateRenderedRange(); - } - - onPrematureDisconnect() { - console.warn('Virtual Scroll strategy was disconnected unexpectedly', new Error('foo').stack); - } - - onDataLengthChanged(): void { - this.setTotalContentSize(); - } - - // eslint-disable-next-line @typescript-eslint/no-empty-function - onRenderedOffsetChanged(): void {} - - scrollToIndex(index: number, behavior: ScrollBehavior): void { - this.viewport?.scrollToOffset(this.getOffsetFromIndex(index), behavior); - } -} diff --git a/frontend/app/src/app/modules/data/list/search-page.component.ts b/frontend/app/src/app/modules/data/list/search-page.component.ts index dd747819..f283e3d8 100644 --- a/frontend/app/src/app/modules/data/list/search-page.component.ts +++ b/frontend/app/src/app/modules/data/list/search-page.component.ts @@ -13,9 +13,24 @@ * this program. If not, see . */ import {Component, DestroyRef, inject, Input, OnInit} from '@angular/core'; -import {ActivatedRoute, Router} from '@angular/router'; +import {ActivatedRoute, Router, RouterLink} from '@angular/router'; import {Keyboard} from '@capacitor/keyboard'; -import {AlertController, AnimationBuilder, AnimationController} from '@ionic/angular/standalone'; +import { + AlertController, + AnimationBuilder, + AnimationController, + IonBackButton, + IonButton, + IonButtons, + IonContent, + IonHeader, + IonIcon, + IonLabel, + IonMenuButton, + IonSearchbar, + IonTitle, + IonToolbar, +} from '@ionic/angular/standalone'; import {Capacitor} from '@capacitor/core'; import { SCFacet, @@ -36,6 +51,12 @@ 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 {ContextMenuComponent} from '../../menu/context/context-menu.component'; +import {TranslateModule} from '@ngx-translate/core'; +import {FormsModule} from '@angular/forms'; +import {SearchbarAutofocusDirective} from '../../../util/searchbar-autofocus.directive'; +import {DataListComponent} from './data-list.component'; +import {AsyncPipe} from '@angular/common'; /** * SearchPageComponent queries things and shows list of things as search results and filter as context menu @@ -43,8 +64,29 @@ import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; @Component({ selector: 'stapps-search-page', templateUrl: 'search-page.html', - styleUrls: ['search-page.scss'], + styleUrl: 'search-page.scss', providers: [ContextMenuService], + standalone: true, + imports: [ + ContextMenuComponent, + IonHeader, + IonToolbar, + IonButtons, + IonBackButton, + IonTitle, + TranslateModule, + IonSearchbar, + FormsModule, + SearchbarAutofocusDirective, + IonMenuButton, + IonIcon, + IonButton, + RouterLink, + IonContent, + IonLabel, + DataListComponent, + AsyncPipe, + ], }) export class SearchPageComponent implements OnInit { @Input() title = 'search.title'; @@ -146,19 +188,6 @@ export class SearchPageComponent implements OnInit { routeAnimation: AnimationBuilder; - /** - * Injects the providers and creates subscriptions - * @param alertController AlertController - * @param dataProvider DataProvider - * @param contextMenuService ContextMenuService - * @param settingsProvider SettingsProvider - * @param logger An angular logger - * @param dataRoutingService DataRoutingService - * @param router Router - * @param route ActivatedRoute - * @param positionService PositionService - * @param configProvider ConfigProvider - */ constructor( protected readonly alertController: AlertController, protected dataProvider: DataProvider, diff --git a/frontend/app/src/app/modules/data/list/search-page.html b/frontend/app/src/app/modules/data/list/search-page.html index b145724e..27118dc9 100644 --- a/frontend/app/src/app/modules/data/list/search-page.html +++ b/frontend/app/src/app/modules/data/list/search-page.html @@ -15,12 +15,14 @@ - - - - - {{ title | translate }} - + @if (showDrawer && showTopToolbar) { + + + + + {{ title | translate }} + + } - - - {{ 'search.type' | translate }} - {{ 'hebisSearch.type' | translate }} - - - + @if (showNavigation && isHebisAvailable) { + + + {{ 'search.type' | translate }} + {{ 'hebisSearch.type' | translate }} + + + + } diff --git a/frontend/app/src/app/modules/data/list/search-provider.ts b/frontend/app/src/app/modules/data/list/search-provider.ts deleted file mode 100644 index b088485d..00000000 --- a/frontend/app/src/app/modules/data/list/search-provider.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2022 StApps - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - */ - -import {SCThings} from '@openstapps/core'; - -export abstract class SearchProvider { - abstract fetchAndUpdateItems(append: boolean): Promise | void; - - abstract route(item: SCThings): Promise | void; -} diff --git a/frontend/app/src/app/modules/data/list/simple-data-list.component.ts b/frontend/app/src/app/modules/data/list/simple-data-list.component.ts index 256d43a6..94330796 100644 --- a/frontend/app/src/app/modules/data/list/simple-data-list.component.ts +++ b/frontend/app/src/app/modules/data/list/simple-data-list.component.ts @@ -12,12 +12,25 @@ * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ -import {Component, ContentChild, DestroyRef, inject, Input, OnInit, TemplateRef} from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + ContentChild, + DestroyRef, + inject, + Input, + OnInit, + TemplateRef, +} from '@angular/core'; import {SCThings} from '@openstapps/core'; import {Router} from '@angular/router'; import {DataRoutingService} from '../data-routing.service'; import {DataListContext} from './data-list.component'; import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; +import {AsyncPipe, NgTemplateOutlet} from '@angular/common'; +import {IonLabel, IonList, IonListHeader, IonText} from '@ionic/angular/standalone'; +import {SkeletonListItemComponent} from '../elements/skeleton-list-item.component'; +import {DataListItemComponent} from './data-list-item.component'; /** * Shows the list of items @@ -25,7 +38,19 @@ import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; @Component({ selector: 'stapps-simple-data-list', templateUrl: 'simple-data-list.html', - styleUrls: ['simple-data-list.scss'], + styleUrl: 'simple-data-list.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + AsyncPipe, + IonList, + IonLabel, + SkeletonListItemComponent, + IonListHeader, + IonText, + DataListItemComponent, + NgTemplateOutlet, + ], }) export class SimpleDataListComponent implements OnInit { @Input() items?: Promise; diff --git a/frontend/app/src/app/modules/data/list/simple-data-list.html b/frontend/app/src/app/modules/data/list/simple-data-list.html index 1ca26866..824b0d58 100644 --- a/frontend/app/src/app/modules/data/list/simple-data-list.html +++ b/frontend/app/src/app/modules/data/list/simple-data-list.html @@ -13,35 +13,32 @@ ~ this program. If not, see . --> - +@if (items | async; as items) { - - + @if (listHeader) { + + +

{{ listHeader }}

+
+
+ } + @for (item of items; track $index) { -
+ } @empty { + @if (emptyListMessage) { + {{ emptyListMessage }} + } + }
- {{ - emptyListMessage - }} -
- +} @else { - - + @for (skeleton of [].constructor(skeletonItems); track $index) { + + } - - - - -

{{ listHeader }}

-
-
-
+} diff --git a/frontend/app/src/app/modules/data/list/skeleton-list.component.ts b/frontend/app/src/app/modules/data/list/skeleton-list.component.ts index ed122e41..06996543 100644 --- a/frontend/app/src/app/modules/data/list/skeleton-list.component.ts +++ b/frontend/app/src/app/modules/data/list/skeleton-list.component.ts @@ -13,11 +13,13 @@ * this program. If not, see . */ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; @Component({ selector: 'skeleton-list', templateUrl: 'skeleton-list.html', - styleUrls: ['skeleton-list.scss'], + styleUrl: 'skeleton-list.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, }) export class SkeletonListComponent {} diff --git a/frontend/app/src/app/modules/data/list/tree-list-fragment.component.ts b/frontend/app/src/app/modules/data/list/tree-list-fragment.component.ts index 90352410..fa12ae3d 100644 --- a/frontend/app/src/app/modules/data/list/tree-list-fragment.component.ts +++ b/frontend/app/src/app/modules/data/list/tree-list-fragment.component.ts @@ -12,15 +12,21 @@ * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ -import {Component, Input, TemplateRef} from '@angular/core'; +import {ChangeDetectionStrategy, Component, Input, TemplateRef} from '@angular/core'; import {SCThings, SCThingWithoutReferences, SCUuid} from '@openstapps/core'; import type {Tree} from '@openstapps/collection-utils'; import {DataListContext} from './data-list.component'; +import {IonAccordion, IonAccordionGroup, IonList} from '@ionic/angular/standalone'; +import {DataListItemComponent} from './data-list-item.component'; +import {NgTemplateOutlet} from '@angular/common'; @Component({ selector: 'tree-list-fragment', templateUrl: 'tree-list-fragment.html', styleUrls: ['tree-list-fragment.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [IonAccordionGroup, IonAccordion, IonList, DataListItemComponent, NgTemplateOutlet], }) export class TreeListFragmentComponent { entries?: [string, Tree][]; diff --git a/frontend/app/src/app/modules/data/list/tree-list-fragment.html b/frontend/app/src/app/modules/data/list/tree-list-fragment.html index 5a0af94b..4f0ede95 100644 --- a/frontend/app/src/app/modules/data/list/tree-list-fragment.html +++ b/frontend/app/src/app/modules/data/list/tree-list-fragment.html @@ -13,28 +13,35 @@ ~ this program. If not, see . --> - - -
- -
- -
- -
- -
-
-
+@if (entries; as entries) { + + @for (entry of entries; track $index) { + + @if (groupMap[entry[0]]; as header) { +
+ } + + @for (item of entry[1]._ || []; track $index) { +
+ } + +
+
+ } +
+} diff --git a/frontend/app/src/app/modules/data/list/tree-list.component.ts b/frontend/app/src/app/modules/data/list/tree-list.component.ts index fee3cdd9..1e81155a 100644 --- a/frontend/app/src/app/modules/data/list/tree-list.component.ts +++ b/frontend/app/src/app/modules/data/list/tree-list.component.ts @@ -12,15 +12,20 @@ * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ -import {Component, ContentChild, Input, TemplateRef} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ContentChild, Input, TemplateRef} from '@angular/core'; import {DataListContext} from './data-list.component'; import {SCThings, SCThingWithoutReferences, SCUuid} from '@openstapps/core'; import {Tree, treeGroupBy} from '@openstapps/collection-utils'; +import {AsyncPipe} from '@angular/common'; +import {TreeListFragmentComponent} from './tree-list-fragment.component'; @Component({ selector: 'tree-list', templateUrl: 'tree-list.html', - styleUrls: ['tree-list.scss'], + styleUrl: 'tree-list.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [AsyncPipe, TreeListFragmentComponent], }) export class TreeListComponent { _items?: Promise; diff --git a/frontend/app/src/app/modules/data/list/tree-list.html b/frontend/app/src/app/modules/data/list/tree-list.html index 58114e46..e4085d95 100644 --- a/frontend/app/src/app/modules/data/list/tree-list.html +++ b/frontend/app/src/app/modules/data/list/tree-list.html @@ -13,10 +13,11 @@ ~ this program. If not, see . --> - +@if (_groups | async; as groups) { + +} diff --git a/frontend/app/src/app/modules/data/types/article/article-content.component.ts b/frontend/app/src/app/modules/data/types/article/article-content.component.ts index ea3d4429..e92b0219 100644 --- a/frontend/app/src/app/modules/data/types/article/article-content.component.ts +++ b/frontend/app/src/app/modules/data/types/article/article-content.component.ts @@ -14,19 +14,31 @@ */ import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; import {SCArticle} from '@openstapps/core'; +import {IonCard, IonCardContent, IonCardHeader, IonChip, IonLabel} from '@ionic/angular/standalone'; +import {ExternalLinkComponent} from '../../elements/external-link.component'; +import {ThingTranslateModule} from '../../../../translation/thing-translate.module'; +import {TranslateModule} from '@ngx-translate/core'; +import {SimpleCardComponent} from '../../elements/simple-card.component'; +import {DataIconPipe} from '../../data-icon.pipe'; -/** - * TODO - */ @Component({ selector: 'stapps-article-content', templateUrl: 'article-content.html', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, + imports: [ + IonCard, + IonCardHeader, + IonCardContent, + ExternalLinkComponent, + ThingTranslateModule, + TranslateModule, + SimpleCardComponent, + IonLabel, + IonChip, + DataIconPipe, + ], }) export class ArticleContentComponent { - /** - * TODO - */ @Input() item: SCArticle; } diff --git a/frontend/app/src/app/modules/data/types/article/article-content.html b/frontend/app/src/app/modules/data/types/article/article-content.html index fa575ba4..26ab5c88 100644 --- a/frontend/app/src/app/modules/data/types/article/article-content.html +++ b/frontend/app/src/app/modules/data/types/article/article-content.html @@ -12,81 +12,101 @@ ~ You should have received a copy of the GNU General Public License along with ~ this program. If not, see . --> +@if (item.sameAs) { + + {{ 'hebisSearch.detail.title' | translate | sentencecase }} + + + + +} @else { + + +} - - {{ 'hebisSearch.detail.title' | translate | sentencecase }} - - - - +@if (item.description) { + +} - - +@if (item.sourceOrganization) { + +} - +@if (item.authors && item.authors.length > 0) { + + {{ 'authors' | propertyNameTranslate: item | sentencecase }} + + @for (author of item.authors; track $index) { + {{ 'name' | thingTranslate: author }} + } + + +} - +@if (item.firstPublished && !item.lastPublished) { + + +} - - {{ 'authors' | propertyNameTranslate: item | sentencecase }} - - {{ 'name' | thingTranslate: author }} - - +@if (item.firstPublished && item.lastPublished) { + + +} - - +@if (item.publications) { + + {{ 'publications' | propertyNameTranslate: item | sentencecase }} + + @for (publication of item.publications; track $index) { +

+ {{ publication.locations | join: ', ' }} + {{ publication.locations && publication.publisher ? ':' : '' }} {{ publication.publisher }} +

+ } +
+
+} - - +@if (item.reference) { + +} - - {{ 'publications' | propertyNameTranslate: item | sentencecase }} - -

- {{ publication.locations | join: ', ' }} - {{ publication.locations && publication.publisher ? ':' : '' }} {{ publication.publisher }} -

-
-
+@if (item.isPartOf) { + +} - - - - - {{ 'categories' | propertyNameTranslate: item | sentencecase }} - - - - {{ 'categories' | thingTranslate: item }} - - - +@if (item.categories) { + + {{ 'categories' | propertyNameTranslate: item | sentencecase }} + + + + {{ 'categories' | thingTranslate: item }} + + + +} diff --git a/frontend/app/src/app/modules/data/types/article/article-item.component.ts b/frontend/app/src/app/modules/data/types/article/article-item.component.ts index df7e923b..7c50b326 100644 --- a/frontend/app/src/app/modules/data/types/article/article-item.component.ts +++ b/frontend/app/src/app/modules/data/types/article/article-item.component.ts @@ -14,19 +14,16 @@ */ import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; import {SCArticle} from '@openstapps/core'; +import {IonCol, IonGrid, IonNote, IonRow} from '@ionic/angular/standalone'; +import {ThingTranslateModule} from '../../../../translation/thing-translate.module'; -/** - * TODO - */ @Component({ selector: 'stapps-article-item', templateUrl: 'article-list-item.html', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, + imports: [IonGrid, IonRow, IonCol, ThingTranslateModule, IonNote], }) export class ArticleListItemComponent { - /** - * TODO - */ @Input() item: SCArticle; } diff --git a/frontend/app/src/app/modules/data/types/article/article-list-item.html b/frontend/app/src/app/modules/data/types/article/article-list-item.html index 3bc34e56..4252954e 100644 --- a/frontend/app/src/app/modules/data/types/article/article-list-item.html +++ b/frontend/app/src/app/modules/data/types/article/article-list-item.html @@ -18,16 +18,17 @@

{{ 'name' | thingTranslate: item }}

- {{ 'name' | thingTranslate: author }} - {{ - item.firstPublished - }}{{ - [item.firstPublished, item.lastPublished] | join: ' - ' - }} + @for (author of item.authors; track $index) { + {{ 'name' | thingTranslate: author }} + } + @if (item.authors && item.firstPublished) { + ,  + } + @if (item.firstPublished && !item.lastPublished) { + {{ item.firstPublished }} + } @else if (item.firstPublished && item.lastPublished) { + {{ [item.firstPublished, item.lastPublished] | join: ' - ' }} + }

{{ 'categories' | thingTranslate: item }}
diff --git a/frontend/app/src/app/modules/data/types/book/book-detail-content.component.ts b/frontend/app/src/app/modules/data/types/book/book-detail-content.component.ts index e7edfc59..22ef51a4 100644 --- a/frontend/app/src/app/modules/data/types/book/book-detail-content.component.ts +++ b/frontend/app/src/app/modules/data/types/book/book-detail-content.component.ts @@ -14,19 +14,31 @@ */ import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; import {SCBook} from '@openstapps/core'; +import {IonCard, IonCardContent, IonCardHeader, IonChip, IonLabel} from '@ionic/angular/standalone'; +import {ExternalLinkComponent} from '../../elements/external-link.component'; +import {TranslateModule} from '@ngx-translate/core'; +import {ThingTranslateModule} from '../../../../translation/thing-translate.module'; +import {SimpleCardComponent} from '../../elements/simple-card.component'; +import {DataIconPipe} from '../../data-icon.pipe'; -/** - * TODO - */ @Component({ selector: 'stapps-book-detail-content', templateUrl: 'book-detail-content.html', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, + imports: [ + IonCard, + IonCardHeader, + IonCardContent, + ExternalLinkComponent, + TranslateModule, + ThingTranslateModule, + SimpleCardComponent, + IonChip, + IonLabel, + DataIconPipe, + ], }) export class BookDetailContentComponent { - /** - * TODO - */ @Input() item: SCBook; } diff --git a/frontend/app/src/app/modules/data/types/book/book-detail-content.html b/frontend/app/src/app/modules/data/types/book/book-detail-content.html index 2f0741ad..fbf7a672 100644 --- a/frontend/app/src/app/modules/data/types/book/book-detail-content.html +++ b/frontend/app/src/app/modules/data/types/book/book-detail-content.html @@ -12,80 +12,100 @@ ~ You should have received a copy of the GNU General Public License along with ~ this program. If not, see . --> +@if (item.sameAs) { + + {{ 'hebisSearch.detail.title' | translate | sentencecase }} + + + + +} @else { + + +} - - {{ 'hebisSearch.detail.title' | translate | sentencecase }} - - - - +@if (item.edition) { + + +} - - +@if (item.description) { + +} - - +@if (item.sourceOrganization) { + +} - +@if (item.authors && item.authors.length > 0) { + + {{ 'authors' | propertyNameTranslate: item | sentencecase }} + + @for (author of item.authors; track $index) { + {{ 'name' | thingTranslate: author }} + } + + +} - +@if (item.ISBNs) { + + +} - - {{ 'authors' | propertyNameTranslate: item | sentencecase }} - - {{ 'name' | thingTranslate: author }} - - +@if (item.firstPublished && !item.lastPublished) { + + +} - - - - - - - - {{ 'publications' | propertyNameTranslate: item | sentencecase }} - -

- {{ publication.locations | join: ', ' }} - {{ publication.locations && publication.publisher ? ':' : '' }} {{ publication.publisher }} -

-
-
- - {{ 'categories' | propertyNameTranslate: item | sentencecase }} - - - - {{ 'categories' | thingTranslate: item }} - - - +@if (item.firstPublished && item.lastPublished) { + + +} + +@if (item.publications) { + + {{ 'publications' | propertyNameTranslate: item | sentencecase }} + + @for (publication of item.publications; track $index) { +

+ {{ publication.locations | join: ', ' }} + {{ publication.locations && publication.publisher ? ':' : '' }} {{ publication.publisher }} +

+ } +
+
+} + +@if (item.categories) { + + {{ 'categories' | propertyNameTranslate: item | sentencecase }} + + + + {{ 'categories' | thingTranslate: item }} + + + +} diff --git a/frontend/app/src/app/modules/data/types/book/book-list-item.component.ts b/frontend/app/src/app/modules/data/types/book/book-list-item.component.ts index ebfa1227..c00ebcf8 100644 --- a/frontend/app/src/app/modules/data/types/book/book-list-item.component.ts +++ b/frontend/app/src/app/modules/data/types/book/book-list-item.component.ts @@ -14,19 +14,16 @@ */ import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; import {SCBook} from '@openstapps/core'; +import {IonCol, IonGrid, IonNote, IonRow} from '@ionic/angular/standalone'; +import {ThingTranslateModule} from '../../../../translation/thing-translate.module'; -/** - * TODO - */ @Component({ selector: 'stapps-book-list-item', templateUrl: 'book-list-item.html', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, + imports: [IonGrid, IonRow, IonCol, ThingTranslateModule, IonNote], }) export class BookListItemComponent { - /** - * TODO - */ @Input() item: SCBook; } diff --git a/frontend/app/src/app/modules/data/types/book/book-list-item.html b/frontend/app/src/app/modules/data/types/book/book-list-item.html index 3bc34e56..5c776cc7 100644 --- a/frontend/app/src/app/modules/data/types/book/book-list-item.html +++ b/frontend/app/src/app/modules/data/types/book/book-list-item.html @@ -18,18 +18,19 @@

{{ 'name' | thingTranslate: item }}

- {{ 'name' | thingTranslate: author }} - {{ - item.firstPublished - }}{{ - [item.firstPublished, item.lastPublished] | join: ' - ' - }} + @for (author of item.authors; track $index) { + {{ '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: ' - ' }} + }

- {{ 'categories' | thingTranslate: item }} + {{ 'categories' | thingTranslate: item }}
diff --git a/frontend/app/src/app/modules/data/types/catalog/catalog-detail-content.component.ts b/frontend/app/src/app/modules/data/types/catalog/catalog-detail-content.component.ts index 4499b0ce..22f7f493 100644 --- a/frontend/app/src/app/modules/data/types/catalog/catalog-detail-content.component.ts +++ b/frontend/app/src/app/modules/data/types/catalog/catalog-detail-content.component.ts @@ -15,13 +15,17 @@ import {ChangeDetectionStrategy, Component, Input, OnInit} from '@angular/core'; import {SCCatalog, SCThings} from '@openstapps/core'; import {DataProvider} from '../../data.provider'; +import {SimpleDataListComponent} from '../../list/simple-data-list.component'; +import {ThingTranslateModule} from '../../../../translation/thing-translate.module'; +import {TitleCasePipe} from '@angular/common'; +import {TranslateModule} from '@ngx-translate/core'; @Component({ selector: 'stapps-catalog-detail-content', templateUrl: 'catalog-detail-content.html', - styleUrl: 'catalog-detail-content.scss', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, + imports: [SimpleDataListComponent, ThingTranslateModule, TitleCasePipe, TranslateModule], }) export class CatalogDetailContentComponent implements OnInit { @Input() item: SCCatalog; diff --git a/frontend/app/src/app/modules/data/types/catalog/catalog-detail-content.html b/frontend/app/src/app/modules/data/types/catalog/catalog-detail-content.html index e67413ac..0a49459f 100644 --- a/frontend/app/src/app/modules/data/types/catalog/catalog-detail-content.html +++ b/frontend/app/src/app/modules/data/types/catalog/catalog-detail-content.html @@ -14,7 +14,6 @@ --> . - */ diff --git a/frontend/app/src/app/modules/data/types/catalog/catalog-list-item.component.ts b/frontend/app/src/app/modules/data/types/catalog/catalog-list-item.component.ts index 97810372..9799d3c7 100644 --- a/frontend/app/src/app/modules/data/types/catalog/catalog-list-item.component.ts +++ b/frontend/app/src/app/modules/data/types/catalog/catalog-list-item.component.ts @@ -18,9 +18,6 @@ import {DataListItemComponent} from '../../list/data-list-item.component'; import {IonCol, IonGrid, IonLabel, IonRow} from '@ionic/angular/standalone'; import {ThingTranslateModule} from '../../../../translation/thing-translate.module'; -/** - * TODO - */ @Component({ selector: 'stapps-catalog-list-item', templateUrl: 'catalog-list-item.html', @@ -29,8 +26,5 @@ import {ThingTranslateModule} from '../../../../translation/thing-translate.modu imports: [IonGrid, IonRow, IonCol, IonLabel, ThingTranslateModule], }) export class CatalogListItemComponent extends DataListItemComponent { - /** - * TODO - */ @Input() item: SCCatalog; } diff --git a/frontend/app/src/app/modules/data/types/date-series/date-series-detail-content.component.ts b/frontend/app/src/app/modules/data/types/date-series/date-series-detail-content.component.ts index b71bcdc3..854ca49f 100644 --- a/frontend/app/src/app/modules/data/types/date-series/date-series-detail-content.component.ts +++ b/frontend/app/src/app/modules/data/types/date-series/date-series-detail-content.component.ts @@ -26,6 +26,9 @@ import {TranslateModule} from '@ngx-translate/core'; import {ThingTranslateModule} from '../../../../translation/thing-translate.module'; import {MomentModule} from 'ngx-moment'; import {MapWidgetComponent} from '../../../map/widget/map-widget.component'; +import {SimpleCardComponent} from '../../elements/simple-card.component'; +import {DataListItemComponent} from '../../list/data-list-item.component'; +import {OffersDetailComponent} from '../../elements/offers-detail.component'; @Component({ selector: 'stapps-date-series-detail-content', @@ -45,6 +48,9 @@ import {MapWidgetComponent} from '../../../map/widget/map-widget.component'; IonCardHeader, IonCardContent, IonCard, + SimpleCardComponent, + DataListItemComponent, + OffersDetailComponent, ], }) export class DateSeriesDetailContentComponent implements OnInit { diff --git a/frontend/app/src/app/modules/data/types/date-series/date-series-detail-content.html b/frontend/app/src/app/modules/data/types/date-series/date-series-detail-content.html index 700f6be1..479f14b8 100644 --- a/frontend/app/src/app/modules/data/types/date-series/date-series-detail-content.html +++ b/frontend/app/src/app/modules/data/types/date-series/date-series-detail-content.html @@ -12,57 +12,67 @@ ~ You should have received a copy of the GNU General Public License along with ~ this program. If not, see . --> - +@if (isInCalendar | async) { {{ 'chips.addEvent.addedToEvents' | translate }} - - +} @else { {{ 'chips.addEvent.addEvent' | translate }} - +} - - + +@if (item.dates.length > 1) { +} @else { + - - - +} + +@if (item.performers) { + +} + +@if (item.offers) { + +} + - {{ 'event' | propertyNameTranslate: item | titlecase }} + {{ $any('event' | propertyNameTranslate: item) | titlecase }} + @if (item.inPlace) { - {{ 'inPlace' | propertyNameTranslate: item | titlecase }} + {{ $any('inPlace' | propertyNameTranslate: item) | titlecase }} } - + +@if (item.inPlace?.geo) { + +} diff --git a/frontend/app/src/app/modules/data/types/date-series/date-series-list-item.component.ts b/frontend/app/src/app/modules/data/types/date-series/date-series-list-item.component.ts index e8c6f455..91ca1512 100644 --- a/frontend/app/src/app/modules/data/types/date-series/date-series-list-item.component.ts +++ b/frontend/app/src/app/modules/data/types/date-series/date-series-list-item.component.ts @@ -12,17 +12,20 @@ * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ -import {Component, Input} from '@angular/core'; +import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; import {SCDateSeries} from '@openstapps/core'; import {DataListItemComponent} from '../../list/data-list-item.component'; +import {IonCol, IonGrid, IonIcon, IonLabel, IonNote, IonRow} from '@ionic/angular/standalone'; +import {ThingTranslateModule} from '../../../../translation/thing-translate.module'; +import {OffersInListComponent} from '../../elements/offers-in-list.component'; -/** - * TODO - */ @Component({ selector: 'stapps-date-series-list-item', templateUrl: 'date-series-list-item.html', - styleUrls: ['date-series-list-item.scss'], + styleUrl: 'date-series-list-item.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [IonGrid, IonIcon, IonRow, IonCol, IonLabel, ThingTranslateModule, IonNote, OffersInListComponent], }) export class DateSeriesListItemComponent extends DataListItemComponent { /** @@ -30,8 +33,5 @@ export class DateSeriesListItemComponent extends DataListItemComponent { */ @Input() compact = false; - /** - * TODO - */ @Input() item: SCDateSeries; } diff --git a/frontend/app/src/app/modules/data/types/date-series/date-series-list-item.html b/frontend/app/src/app/modules/data/types/date-series/date-series-list-item.html index eced9f71..3f8a8c9d 100644 --- a/frontend/app/src/app/modules/data/types/date-series/date-series-list-item.html +++ b/frontend/app/src/app/modules/data/types/date-series/date-series-list-item.html @@ -20,23 +20,29 @@ {{ 'event.name' | thingTranslate: item }}

- - - {{ item.repeatFrequency | durationLocalized: true | sentencecase }}, - {{ item.dates[0] | dateFormat: 'weekday:long' }} - + @if (item.dates[0] && item.dates.at(-1)) { - ({{ item.dates[0] | dateFormat }} - {{ item.dates[item.dates.length - 1] | dateFormat }}) + @if (item.repeatFrequency) { + + {{ item.repeatFrequency | durationLocalized: true | sentencecase }}, + {{ item.dates[0] | dateFormat: 'weekday:long' }} + + } + + ({{ item.dates[0] | dateFormat }} - {{ item.dates[item.dates.length - 1] | dateFormat }}) + - + }

- {{ - 'categories' | thingTranslate: item.event | join: ', ' - }} + @if (item.event.type === 'academic event') { + {{ 'categories' | thingTranslate: item.event | join: ', ' }} + } - + @if (item.offers) { + + } diff --git a/frontend/app/src/app/modules/menu/context/context-menu.service.ts b/frontend/app/src/app/modules/menu/context/context-menu.service.ts index 81efbe3e..804fefce 100644 --- a/frontend/app/src/app/modules/menu/context/context-menu.service.ts +++ b/frontend/app/src/app/modules/menu/context/context-menu.service.ts @@ -30,9 +30,7 @@ import {transformFacets} from './facet-filter'; /** * ContextMenuService provides bidirectional communication of context menu options and search queries */ -@Injectable({ - providedIn: 'root', -}) +@Injectable() export class ContextMenuService { /** * Local filter context object