diff --git a/frontend/app/src/app/modules/data/data.module.ts b/frontend/app/src/app/modules/data/data.module.ts index 09697966..56ba82f4 100644 --- a/frontend/app/src/app/modules/data/data.module.ts +++ b/frontend/app/src/app/modules/data/data.module.ts @@ -107,6 +107,7 @@ import {SemesterListItemComponent} from './types/semester/semester-list-item.com import {VideoDetailContentComponent} from './types/video/video-detail-content.component'; import {VideoListItemComponent} from './types/video/video-list-item.component'; import {ShareButtonComponent} from './elements/share-button.component'; +import {DataFilterComponent} from './filter/data-filter.component'; /** * Module for handling data @@ -184,6 +185,7 @@ import {ShareButtonComponent} from './elements/share-button.component'; ShareButtonComponent, ], imports: [ + DataFilterComponent, CommonModule, DataRoutingModule, FormsModule, diff --git a/frontend/app/src/app/modules/data/filter/data-filter.component.ts b/frontend/app/src/app/modules/data/filter/data-filter.component.ts new file mode 100644 index 00000000..f591076a --- /dev/null +++ b/frontend/app/src/app/modules/data/filter/data-filter.component.ts @@ -0,0 +1,28 @@ +import {AsyncPipe, CommonModule} from '@angular/common'; +import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; +import {IonicModule} from '@ionic/angular'; +import {IonIconModule} from 'src/app/util/ion-icon/ion-icon.module'; +import {DataFilterProvider} from './data-filter.provider'; +import {ThingTranslateModule} from 'src/app/translation/thing-translate.module'; +import {PropertyValueTranslatePipe} from 'src/app/translation/property-value-translate.pipe'; + +@Component({ + selector: 'stapps-data-filter', + templateUrl: 'data-filter.html', + styleUrls: ['data-filter.scss'], + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + CommonModule, + IonicModule, + IonIconModule, + AsyncPipe, + ThingTranslateModule, + PropertyValueTranslatePipe, + ], +}) +export class DataFilterComponent { + @Input() expandBucketCount = 3; + + constructor(readonly filterProvider: DataFilterProvider) {} +} diff --git a/frontend/app/src/app/modules/data/filter/data-filter.html b/frontend/app/src/app/modules/data/filter/data-filter.html new file mode 100644 index 00000000..f8d2b4a0 --- /dev/null +++ b/frontend/app/src/app/modules/data/filter/data-filter.html @@ -0,0 +1,75 @@ + + Relevance + + + + + + + Relevance + Name + Type + + + + +
+ + + + + + {{facet.onlyOnType | propertyValueTranslate: 'type': facet.onlyOnType | titlecase}} + / {{facet.field | propertyNameTranslate: facet.onlyOnType | titlecase}} + + + {{facet.field | propertyNameTranslate: $any('building') | titlecase}} + + + + {{bucket.key | propertyValueTranslate: facet.field: (facet.onlyOnType ?? $any(bucket.key)) | + titlecase}} + + + +
+
+
+ + + + + +
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + {{bucket.key}} + {{bucket.count}} + + + + + + +
+
+
diff --git a/frontend/app/src/app/modules/data/filter/data-filter.provider.ts b/frontend/app/src/app/modules/data/filter/data-filter.provider.ts new file mode 100644 index 00000000..ba133826 --- /dev/null +++ b/frontend/app/src/app/modules/data/filter/data-filter.provider.ts @@ -0,0 +1,12 @@ +import {Injectable} from '@angular/core'; +import {SCSearchResult} from '@openstapps/core'; +import {BehaviorSubject} from 'rxjs'; + +@Injectable() +export class DataFilterProvider { + readonly context = new BehaviorSubject(undefined); + + readonly userSortOption = new BehaviorSubject(undefined); + + readonly userFilterOption = new BehaviorSubject(new Map()); +} diff --git a/frontend/app/src/app/modules/data/filter/data-filter.scss b/frontend/app/src/app/modules/data/filter/data-filter.scss new file mode 100644 index 00000000..c9895d31 --- /dev/null +++ b/frontend/app/src/app/modules/data/filter/data-filter.scss @@ -0,0 +1,36 @@ +:host { + overflow-x: auto; + display: flex; + align-items: flex-start; + margin-block: var(--spacing-xs); + + > * { + flex-shrink: 0; + } +} + +.expanded-facet { + display: flex; + flex-direction: column; + + > .facet-label { + margin-inline: var(--spacing-md); + font-size: 0.7em; + } +} + +.separator { + align-self: center; + + width: 1px; + height: 1.25em; + margin-inline: var(--spacing-xs); + + opacity: 0.2; + background: currentcolor; + + &:last-child, + + .separator { + display: none; + } +} 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 90ff6d8b..4c164ddb 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 @@ -12,7 +12,7 @@ * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ -import {Component, DestroyRef, inject, Input, OnInit} from '@angular/core'; +import {Component, DestroyRef, Inject, inject, Input, OnInit} from '@angular/core'; import {ActivatedRoute, Router} from '@angular/router'; import {Keyboard} from '@capacitor/keyboard'; import {AlertController, AnimationBuilder, AnimationController} from '@ionic/angular'; @@ -36,6 +36,7 @@ import {PositionService} from '../../map/position.service'; import {ConfigProvider} from '../../config/config.provider'; import {searchPageSwitchAnimation} from './search-page-switch-animation'; import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; +import {DataFilterProvider} from '../filter/data-filter.provider'; /** * SearchPageComponent queries things and shows list of things as search results and filter as context menu @@ -44,7 +45,7 @@ import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; selector: 'stapps-search-page', templateUrl: 'search-page.html', styleUrls: ['search-page.scss'], - providers: [ContextMenuService], + providers: [ContextMenuService, DataFilterProvider], }) export class SearchPageComponent implements OnInit { @Input() title = 'search.title'; @@ -144,6 +145,8 @@ export class SearchPageComponent implements OnInit { destroy$ = inject(DestroyRef); + dataFilterProvider = inject(DataFilterProvider); + routeAnimation: AnimationBuilder; /** @@ -216,6 +219,7 @@ export class SearchPageComponent implements OnInit { try { const result = await this.dataProvider.search(searchOptions); + this.dataFilterProvider.context.next(result); this.singleTypeResponse = result.facets.find(facet => facet.field === 'type')?.buckets.length === 1; if (append) { // append results 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 ee89ae42..e8fc7a92 100644 --- a/frontend/app/src/app/modules/data/list/search-page.html +++ b/frontend/app/src/app/modules/data/list/search-page.html @@ -16,62 +16,41 @@ @if (showDrawer && showTopToolbar) { - - - - - {{ title | translate }} - + + + + + {{ title | translate }} + } - + @if (showNavigation && isHebisAvailable) { - - - {{ 'search.type' | translate }} - {{ 'hebisSearch.type' | translate }} - - - + + + {{ 'search.type' | translate }} + {{ 'hebisSearch.type' | translate }} + + + } -
+
{{ searchInstruction | translate }}
- + + diff --git a/frontend/app/src/app/translation/property-value-translate.pipe.ts b/frontend/app/src/app/translation/property-value-translate.pipe.ts new file mode 100644 index 00000000..0b9fe8fb --- /dev/null +++ b/frontend/app/src/app/translation/property-value-translate.pipe.ts @@ -0,0 +1,19 @@ +import {Pipe, PipeTransform} from '@angular/core'; +import {ThingTranslateService} from './thing-translate.service'; +import {SCThingType, SCThings} from '@openstapps/core'; + +@Pipe({ + name: 'propertyValueTranslate', + standalone: true, +}) +export class PropertyValueTranslatePipe implements PipeTransform { + constructor(private readonly thingTranslate: ThingTranslateService) {} + + transform< + K extends string | undefined, + T extends SCThingType, + U extends keyof Extract, + >(value: K, field: (U & string) | string, type: T): K { + return this.thingTranslate.getPropertyValue(type, field, value) as K; + } +} diff --git a/frontend/app/src/app/translation/thing-translate.service.ts b/frontend/app/src/app/translation/thing-translate.service.ts index cf1122c9..7cbfcc84 100644 --- a/frontend/app/src/app/translation/thing-translate.service.ts +++ b/frontend/app/src/app/translation/thing-translate.service.ts @@ -113,4 +113,8 @@ export class ThingTranslateService { return this.getParsedResult(translatedPropertyNames, keyPath); } + + public getPropertyValue(type: SCThingType, field: string, key: string | undefined): string | undefined { + return this.translator.translatedPropertyValue(type, field, key); + } }