feat: add HeBIS HDS search

This commit is contained in:
Andy Bastian
2022-01-27 14:11:35 +00:00
committed by Jovan Krunić
parent e4165901bb
commit 9a3241c42a
42 changed files with 2109 additions and 23 deletions

View File

@@ -204,7 +204,7 @@ const sampleBooks: SCBook[] = [
type: SCThingOriginType.Remote,
},
type: SCThingType.Book,
uid: 'book-123',
uid: 'HEB290615194',
},
{
authors: [],

View File

@@ -41,6 +41,7 @@ import {CatalogModule} from './modules/catalog/catalog.module';
import {ConfigModule} from './modules/config/config.module';
import {ConfigProvider} from './modules/config/config.provider';
import {DataModule} from './modules/data/data.module';
import {HebisModule} from './modules/hebis/hebis.module';
import {MapModule} from './modules/map/map.module';
import {MenuModule} from './modules/menu/menu.module';
import {NewsModule} from './modules/news/news.module';
@@ -153,6 +154,7 @@ const providers: Provider[] = [
CommonModule,
ConfigModule,
DataModule,
HebisModule,
EndSessionPageModule,
IonicModule.forRoot(),
FavoritesModule,

View File

@@ -182,6 +182,9 @@ import {TitleCardComponent} from './elements/title-card.component';
SkeletonSimpleCardComponent,
SearchPageComponent,
SimpleDataListComponent,
ArticleDetailContentComponent,
OriginDetailComponent,
FavoriteButtonComponent,
],
})
export class DataModule {}

View File

@@ -66,9 +66,9 @@ export class DataDetailComponent {
* @param translateService he translate provider
*/
constructor(
private readonly route: ActivatedRoute,
protected readonly route: ActivatedRoute,
private readonly dataProvider: DataProvider,
private readonly network: Network,
protected readonly network: Network,
private readonly favoritesService: FavoritesService,
translateService: TranslateService,
) {
@@ -125,9 +125,7 @@ export class DataDetailComponent {
* @param refresher Refresher component that triggers the update
*/
async refresh(refresher: IonRefresher) {
await this.getItem(
this.item?.uid ?? this.route.snapshot.paramMap.get('uid') ?? '',
);
await this.getItem(this.route.snapshot.paramMap.get('uid') ?? '');
await refresher.complete();
}
}

View File

@@ -13,17 +13,24 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component, OnInit} from '@angular/core';
import {AlertController} from '@ionic/angular';
import {Router} from '@angular/router';
import {NGXLogger} from 'ngx-logger';
import {
debounceTime,
distinctUntilChanged,
startWith,
take,
} from 'rxjs/operators';
import {combineLatest} from 'rxjs';
import {SCThingType} from '@openstapps/core';
import {FavoritesService} from './favorites.service';
import {DataRoutingService} from '../data/data-routing.service';
import {Router} from '@angular/router';
import {ContextMenuService} from '../menu/context/context-menu.service';
import {SearchPageComponent} from '../data/list/search-page.component';
import {AlertController} from '@ionic/angular';
import {DataProvider} from '../data/data.provider';
import {SettingsProvider} from '../settings/settings.provider';
import {NGXLogger} from 'ngx-logger';
import {PositionService} from '../map/position.service';
import {take} from 'rxjs/operators';
/**
* The page for showing favorites
@@ -62,9 +69,69 @@ export class FavoritesPageComponent
ngOnInit() {
super.ngOnInit();
for (const subscription of this.subscriptions) {
subscription.unsubscribe();
}
// Recreate subscriptions to handle different routing
this.subscriptions.push(
this.favoritesService.favoritesChanged$.subscribe(_favoritesMap => {
this.fetchAndUpdateItems();
combineLatest([
this.queryTextChanged.pipe(
debounceTime(this.searchQueryDueTime),
distinctUntilChanged(),
startWith(this.queryText),
),
this.contextMenuService.filterQueryChanged$.pipe(
startWith(this.filterQuery),
),
this.contextMenuService.sortQueryChanged$.pipe(
startWith(this.sortQuery),
),
this.favoritesService.favoritesChanged$,
]).subscribe(async query => {
this.queryText = query[0];
this.filterQuery = query[1];
this.sortQuery = query[2];
this.from = 0;
if (
typeof this.filterQuery !== 'undefined' ||
this.queryText?.length > 0 ||
this.showDefaultData
) {
await this.fetchAndUpdateItems();
this.queryChanged.next();
}
}),
this.settingsProvider.settingsActionChanged$.subscribe(
({type, payload}) => {
if (type === 'stapps.settings.changed') {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const {category, name, value} = payload!;
this.logger.log(`received event "settings.changed" with category:
${category}, name: ${name}, value: ${JSON.stringify(value)}`);
}
},
),
this.dataRoutingService.itemSelectListener().subscribe(item => {
if (this.itemRouting) {
if (
[
SCThingType.Book,
SCThingType.Periodical,
SCThingType.Article,
].includes(item.type)
) {
void this.router.navigate([
'hebis-detail',
(item.origin &&
'originalId' in item.origin &&
item.origin['originalId']) ||
'',
]);
} else {
void this.router.navigate(['data-detail', item.uid]);
}
}
}),
);
}

View File

@@ -0,0 +1,138 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion,@typescript-eslint/no-explicit-any */
/*
* Copyright (C) 2018, 2019 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ActivatedRoute, RouterModule} from '@angular/router';
import {IonRefresher} from '@ionic/angular';
import {
TranslateLoader,
TranslateModule,
TranslateService,
} from '@ngx-translate/core';
import {sampleThingsMap} from '../../../_helpers/data/sample-things';
import {HebisRoutingModule} from '../hebis-routing.module';
import {HebisModule} from '../hebis.module';
import {DaiaAvailabilityComponent} from './daia-availability.component';
import {Observable, of} from 'rxjs';
import {StorageProvider} from '../../storage/storage.provider';
import {DaiaDataProvider} from '../daia-data.provider';
const translations: any = {data: {detail: {TITLE: 'Foo'}}};
class TranslateFakeLoader implements TranslateLoader {
getTranslation(_lang: string): Observable<any> {
return of(translations);
}
}
describe('DaiaAvailabilityComponent', () => {
let comp: DaiaAvailabilityComponent;
let fixture: ComponentFixture<DaiaAvailabilityComponent>;
let dataProvider: DaiaDataProvider;
let refresher: IonRefresher;
const sampleThing = sampleThingsMap.book[0];
let translateService: TranslateService;
// @Component({ selector: 'stapps-data-list-item', template: '' })
// class DataListItemComponent {
// @Input() item;
// }
const fakeActivatedRoute = {
snapshot: {
paramMap: {
get: () => {
return sampleThing.uid;
},
},
},
};
const storageProviderSpy = jasmine.createSpyObj('StorageProvider', [
'init',
'get',
'has',
'put',
'search',
]);
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
RouterModule.forRoot([], {relativeLinkResolution: 'legacy'}),
HebisRoutingModule,
HebisModule,
TranslateModule.forRoot({
loader: {provide: TranslateLoader, useClass: TranslateFakeLoader},
}),
],
providers: [
{
provide: ActivatedRoute,
useValue: fakeActivatedRoute,
},
{
provide: StorageProvider,
useValue: storageProviderSpy,
},
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents();
}),
);
beforeEach(async () => {
dataProvider = TestBed.get(DaiaDataProvider);
translateService = TestBed.get(TranslateService);
refresher = jasmine.createSpyObj('refresher', ['complete']);
spyOn(dataProvider, 'get' as any).and.returnValue(
Promise.resolve(sampleThing),
);
spyOn(
DaiaAvailabilityComponent.prototype,
'getAvailability',
).and.callThrough();
fixture = await TestBed.createComponent(DaiaAvailabilityComponent);
comp = fixture.componentInstance;
translateService.use('foo');
fixture.detectChanges();
});
it('should create component', () => expect(comp).toBeDefined());
it('should get the availability of an item', () => {
comp.getAvailability(sampleThing.uid);
expect(
DaiaAvailabilityComponent.prototype.getAvailability,
).toHaveBeenCalledWith(sampleThing.uid);
});
it('should get the availability of an item when the view is entered', () => {
comp.ngOnInit();
expect(
DaiaAvailabilityComponent.prototype.getAvailability,
).toHaveBeenCalledWith(sampleThing.uid);
});
it('should update the data item when refresh is called', async () => {
await comp.refresh(refresher);
expect(
DaiaAvailabilityComponent.prototype.getAvailability,
).toHaveBeenCalledWith(sampleThing.uid);
expect(refresher.complete).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,85 @@
/*
* Copyright (C) 2018, 2019 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {Network} from '@ionic-native/network/ngx';
import {TranslateService} from '@ngx-translate/core';
import {SCUuid} from '@openstapps/core';
import {FavoritesService} from '../../favorites/favorites.service';
import {DataProvider} from '../../data/data.provider';
import {DataDetailComponent} from '../../data/detail/data-detail.component';
import {DaiaDataProvider} from '../daia-data.provider';
import {SCDaiaHoldings} from '../protocol/response';
/**
* A Component to display an SCThing detailed
*/
@Component({
selector: 'stapps-daia-availability',
styleUrls: ['daia-availability.scss'],
templateUrl: 'daia-availability.html',
})
export class DaiaAvailabilityComponent
extends DataDetailComponent
implements OnInit
{
holdings: SCDaiaHoldings[];
/**
*
* @param route the route the page was accessed from
* @param dataProvider the data provider
* @param network the network provider
* @param favoritesService the favorites provider
* @param translateService he translate provider
* @param daiaDataProvider DaiaDataProvider
*/
constructor(
route: ActivatedRoute,
dataProvider: DataProvider,
network: Network,
favoritesService: FavoritesService,
translateService: TranslateService,
private daiaDataProvider: DaiaDataProvider,
) {
super(route, dataProvider, network, favoritesService, translateService);
}
/**
* Check if we have internet
*/
isDisconnected(): boolean {
return this.network.type === this.network.Connection.NONE;
}
/**
* Initialize
*/
async ngOnInit() {
const uid = this.route.snapshot.paramMap.get('uid') || '';
await this.getAvailability(uid ?? '');
}
/**
* Provides data item with given UID
*
* @param uid Unique identifier of a thing
*/
async getAvailability(uid: SCUuid) {
this.daiaDataProvider.getAvailability(uid).then(holdings => {
this.holdings = holdings;
});
}
}

View File

@@ -0,0 +1,63 @@
<ion-card>
<ion-card-header>{{
'hebisSearch.daia.availability' | translate
}}</ion-card-header>
<ion-card-content>
<ng-container *ngFor="let holding of holdings">
<ion-label
><a [href]="holding.href" target="_blank">{{
holding.label
}}</a></ion-label
>
<ion-grid>
<ion-row *ngIf="holding.signature">
<ion-col size="3">{{
'hebisSearch.daia.signature' | translate
}}</ion-col>
<ion-col
size="9"
*ngIf="
!holding ||
!holding.available ||
holding.available.service !== 'openaccess'
"
>{{ holding.signature }}</ion-col
>
<ion-col size="9" *ngIf="holding.available.service === 'openaccess'">
<a [href]="holding.available.href" target="_blank">{{
'hebisSearch.daia.ejournal' | translate
}}</a>
</ion-col>
</ion-row>
<ion-row *ngIf="holding.storage && holding.storage.content">
<ion-col size="3">{{
'hebisSearch.daia.location' | translate
}}</ion-col>
<ion-col size="9" [innerHTML]="holding.storage.content"></ion-col>
</ion-row>
<ion-row *ngIf="holding.about">
<ion-col size="3">{{
'hebisSearch.daia.comment' | translate
}}</ion-col>
<ion-col size="9">{{ holding.about }}</ion-col>
</ion-row>
<ion-row
*ngIf="holding.available && holding.available.service === 'loan'"
>
<ion-col size="3">{{
'hebisSearch.daia.status' | translate
}}</ion-col>
<ion-col size="9"
>{{ 'hebisSearch.daia.available' | translate }}
<a
[href]="holding.available.href"
*ngIf="holding.available.href"
target="_blank"
>{{ 'hebisSearch.daia.order' | translate }}</a
></ion-col
>
</ion-row>
</ion-grid>
</ng-container>
</ion-card-content>
</ion-card>

View File

@@ -0,0 +1,38 @@
ion-card {
margin: 0;
box-shadow: none;
ion-card-content {
h1 {
margin: 0;
}
padding-bottom: 8px;
}
ion-card-header {
color: var(--ion-color-dark);
padding-top: 8px;
padding-bottom: 4px;
font-weight: bold;
}
ion-card-content {
ion-label a {
display: block;
text-decoration: none;
font-weight: 700;
color: var(--ion-color-primary);
margin: 20px 0 5px;
}
ion-grid {
padding: 0;
ion-row {
background-color: var(--ion-color-light);
color: var(--ion-color-light-contrast);
border-bottom: 1px solid #fff;
ion-col:first-child {
font-weight: 700;
}
}
}
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright (C) 2018-2021 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Injectable} from '@angular/core';
import {Client} from '@openstapps/api/lib/client';
import {SCDaiaAvailabilityResponse, SCDaiaHoldings} from './protocol/response';
import {StorageProvider} from '../storage/storage.provider';
import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider';
import {HttpClient} from '@angular/common/http';
import {DataProvider} from '../data/data.provider';
import {environment} from '../../../environments/environment';
/**
* Generated class for the DataProvider provider.
*
* See https://angular.io/guide/dependency-injection for more info on providers
* and Angular DI.
*/
@Injectable({
providedIn: 'root',
})
export class DaiaDataProvider extends DataProvider {
/**
* TODO
*/
storageProvider: StorageProvider;
httpClient: HttpClient;
backendUrl = environment.daia_url;
/**
* TODO
*
* @param stAppsWebHttpClient TODO
* @param storageProvider TODO
* @param httpClient TODO
*/
constructor(
stAppsWebHttpClient: StAppsWebHttpClient,
storageProvider: StorageProvider,
httpClient: HttpClient,
) {
super(stAppsWebHttpClient, storageProvider);
this.storageProvider = storageProvider;
this.httpClient = httpClient;
this.client = new Client(
stAppsWebHttpClient,
this.backendUrl,
this.appVersion,
);
}
async getAvailability(id: string): Promise<SCDaiaHoldings[]> {
return new Promise(resolve =>
this.httpClient
.get<SCDaiaAvailabilityResponse>(this.backendUrl, {params: {id}})
.subscribe((response: SCDaiaAvailabilityResponse) => {
console.error(response);
const holdings: SCDaiaHoldings[] = [];
if (response && Array.isArray(response.document)) {
response.document.map(document => {
Array.isArray(document.item) &&
document.item.map(element => {
try {
const {
department: {
id: departmentId,
content: departmentLabel,
href: departmentLink,
} = {id: 'noDep', content: '', href: ''},
label,
about,
available,
storage,
} = element;
const holdingIndex = holdings.findIndex(
holding => holding.id === departmentId,
);
if (holdingIndex === -1) {
holdings.push({
id: departmentId,
label: departmentLabel,
href: departmentLink,
signature: label,
available:
(Array.isArray(available) &&
(available.find(
item => item.service === 'openaccess',
) ||
available.find(
item => item.service === 'loan',
))) ||
undefined,
storage,
about,
});
}
} catch {
// No element available
}
});
});
}
resolve(holdings);
}),
);
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright (C) 2018-2021 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Injectable} from '@angular/core';
import {Client} from '@openstapps/api/lib/client';
import {SCHebisSearchRequest} from './protocol/request';
import {SCHebisSearchResponse} from './protocol/response';
import {StorageProvider} from '../storage/storage.provider';
import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider';
import {HttpClient} from '@angular/common/http';
import {DataProvider} from '../data/data.provider';
import {SCHebisSearchRoute} from './protocol/route';
/**
* Generated class for the DataProvider provider.
*
* See https://angular.io/guide/dependency-injection for more info on providers
* and Angular DI.
*/
@Injectable({
providedIn: 'root',
})
export class HebisDataProvider extends DataProvider {
/**
* TODO
*/
storageProvider: StorageProvider;
httpClient: HttpClient;
/**
* Instance of hebis search request route
*/
private readonly hebisSearchRoute = new SCHebisSearchRoute();
/**
* TODO
*
* @param stAppsWebHttpClient TODO
* @param storageProvider TODO
* @param httpClient TODO
*/
constructor(
stAppsWebHttpClient: StAppsWebHttpClient,
storageProvider: StorageProvider,
httpClient: HttpClient,
) {
super(stAppsWebHttpClient, storageProvider);
this.storageProvider = storageProvider;
this.httpClient = httpClient;
this.client = new Client(
stAppsWebHttpClient,
this.backendUrl,
this.appVersion,
);
}
/**
* Send a search request to the backend
*
* All results will be returned if no size is set in the request.
*
* @param searchRequest Search request
*/
async hebisSearch(
searchRequest: SCHebisSearchRequest,
): Promise<SCHebisSearchResponse> {
let page: number | undefined = searchRequest.page;
if (typeof page === 'undefined') {
const preFlightResponse =
await this.client.invokeRoute<SCHebisSearchResponse>(
this.hebisSearchRoute,
undefined,
{
...searchRequest,
page: 0,
},
);
page = preFlightResponse.pagination.total;
}
return this.client.invokeRoute<SCHebisSearchResponse>(
this.hebisSearchRoute,
undefined,
{
...searchRequest,
page,
},
);
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component, Input} from '@angular/core';
import {SCThings} from '@openstapps/core';
/**
* TODO
*/
@Component({
selector: 'stapps-hebis-detail-content',
styleUrls: ['hebis-detail-content.scss'],
templateUrl: 'hebis-detail-content.html',
})
export class HebisDetailContentComponent {
/**
* TODO
*/
@Input() item: SCThings;
}

View File

@@ -0,0 +1,36 @@
<div [ngSwitch]="item.type">
<stapps-book-detail-content
[item]="item"
*ngSwitchCase="'book'"
></stapps-book-detail-content>
<stapps-periodical-detail-content
[item]="item"
*ngSwitchCase="'periodical'"
></stapps-periodical-detail-content>
<stapps-hebis-article-content
[item]="item"
*ngSwitchCase="'article'"
></stapps-hebis-article-content>
<ng-container *ngSwitchDefault>
<ion-item class="ion-text-wrap" lines="inset">
<ion-thumbnail slot="start" class="ion-margin-end">
<ion-icon color="medium" [attr.name]="item.type | dataIcon"></ion-icon>
</ion-thumbnail>
<ion-grid>
<ion-row>
<ion-col>
<div class="ion-text-wrap">
<h2 class="name">{{ item.name }}</h2>
<ion-note>{{ item.type }}</ion-note>
</div>
</ion-col>
</ion-row>
</ion-grid>
</ion-item>
<stapps-simple-card
*ngIf="item.description"
[title]="'description' | propertyNameTranslate: item | titlecase"
[content]="'description' | thingTranslate: item"
></stapps-simple-card>
</ng-container>
</div>

View File

@@ -0,0 +1,28 @@
:host ::ng-deep {
ion-card {
margin: 0;
box-shadow: none;
ion-card-content {
h1 {
margin: 0;
}
padding-bottom: 8px;
}
ion-card-header {
color: var(--ion-color-dark);
padding-top: 8px;
padding-bottom: 4px;
font-weight: bold;
}
ion-grid, ion-col {
padding-inline-start: 0;
padding-top: 0;
padding-bottom: 0;
}
}
ion-grid, ion-col {
padding-inline-start: 0;
padding-top: 0;
padding-bottom: 0;
}
}

View File

@@ -0,0 +1,135 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion,@typescript-eslint/no-explicit-any */
/*
* Copyright (C) 2018, 2019 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ActivatedRoute, RouterModule} from '@angular/router';
import {IonRefresher} from '@ionic/angular';
import {
TranslateLoader,
TranslateModule,
TranslateService,
} from '@ngx-translate/core';
import {sampleThingsMap} from '../../../_helpers/data/sample-things';
import {HebisRoutingModule} from '../hebis-routing.module';
import {HebisModule} from '../hebis.module';
import {HebisDataProvider} from '../hebis-data.provider';
import {HebisDetailComponent} from './hebis-detail.component';
import {Observable, of} from 'rxjs';
import {StorageProvider} from '../../storage/storage.provider';
const translations: any = {data: {detail: {TITLE: 'Foo'}}};
class TranslateFakeLoader implements TranslateLoader {
getTranslation(_lang: string): Observable<any> {
return of(translations);
}
}
describe('HebisDetailComponent', () => {
let comp: HebisDetailComponent;
let fixture: ComponentFixture<HebisDetailComponent>;
let dataProvider: HebisDataProvider;
let refresher: IonRefresher;
const sampleThing = sampleThingsMap.book[0];
let translateService: TranslateService;
// @Component({ selector: 'stapps-data-list-item', template: '' })
// class DataListItemComponent {
// @Input() item;
// }
const fakeActivatedRoute = {
snapshot: {
paramMap: {
get: () => {
return sampleThing.uid;
},
},
},
};
const storageProviderSpy = jasmine.createSpyObj('StorageProvider', [
'init',
'get',
'has',
'put',
'search',
]);
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
RouterModule.forRoot([], {relativeLinkResolution: 'legacy'}),
HebisRoutingModule,
HebisModule,
TranslateModule.forRoot({
loader: {provide: TranslateLoader, useClass: TranslateFakeLoader},
}),
],
providers: [
{
provide: ActivatedRoute,
useValue: fakeActivatedRoute,
},
{
provide: StorageProvider,
useValue: storageProviderSpy,
},
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents();
}),
);
beforeEach(async () => {
dataProvider = TestBed.get(HebisDataProvider);
translateService = TestBed.get(TranslateService);
refresher = jasmine.createSpyObj('refresher', ['complete']);
spyOn(dataProvider, 'get' as any).and.returnValue(
Promise.resolve(sampleThing),
);
spyOn(HebisDetailComponent.prototype, 'getItem').and.callThrough();
fixture = await TestBed.createComponent(HebisDetailComponent);
comp = fixture.componentInstance;
translateService.use('foo');
fixture.detectChanges();
});
it('should create component', () => expect(comp).toBeDefined());
it('should get a data item', () => {
comp.getItem(sampleThing.uid);
expect(HebisDetailComponent.prototype.getItem).toHaveBeenCalledWith(
sampleThing.uid,
);
});
it('should get a data item when the view is entered', () => {
comp.ionViewWillEnter();
expect(HebisDetailComponent.prototype.getItem).toHaveBeenCalledWith(
sampleThing.uid,
);
});
it('should update the data item when refresh is called', async () => {
await comp.refresh(refresher);
expect(HebisDetailComponent.prototype.getItem).toHaveBeenCalledWith(
sampleThing.uid,
);
expect(refresher.complete).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 2018, 2019 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {Network} from '@ionic-native/network/ngx';
import {TranslateService} from '@ngx-translate/core';
import {SCUuid} from '@openstapps/core';
import {HebisDataProvider} from '../hebis-data.provider';
import {FavoritesService} from '../../favorites/favorites.service';
import {DataProvider} from '../../data/data.provider';
import {DataDetailComponent} from '../../data/detail/data-detail.component';
import {SCDaiaHoldings} from '../protocol/response';
/**
* A Component to display an SCThing detailed
*/
@Component({
selector: 'stapps-hebis-detail',
styleUrls: ['hebis-detail.scss'],
templateUrl: 'hebis-detail.html',
})
export class HebisDetailComponent extends DataDetailComponent {
holdings: SCDaiaHoldings[];
/**
*
* @param route the route the page was accessed from
* @param dataProvider the data provider
* @param network the network provider
* @param favoritesService the favorites provider
* @param translateService he translate provider
* @param hebisDataProvider HebisDataProvider
*/
constructor(
route: ActivatedRoute,
dataProvider: DataProvider,
network: Network,
favoritesService: FavoritesService,
translateService: TranslateService,
private hebisDataProvider: HebisDataProvider,
) {
super(route, dataProvider, network, favoritesService, translateService);
}
/**
* Check if we have internet
*/
isDisconnected(): boolean {
return this.network.type === this.network.Connection.NONE;
}
/**
* Initialize
*/
async ionViewWillEnter() {
const uid = this.route.snapshot.paramMap.get('uid') || '';
await this.getItem(uid ?? '');
}
/**
* Provides data item with given UID
*
* @param uid Unique identifier of a thing
*/
async getItem(uid: SCUuid) {
this.hebisDataProvider.hebisSearch({query: uid, page: 0}).then(result => {
// eslint-disable-next-line unicorn/no-null
this.item = (result.data && result.data[0]) || null;
});
}
}

View File

@@ -0,0 +1,51 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title>{{ 'data.detail.TITLE' | translate }}</ion-title>
<ion-buttons slot="primary">
<stapps-favorite-button
*ngIf="item"
[item]="item"
></stapps-favorite-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content class="ion-no-padding">
<ion-refresher slot="fixed" (ionRefresh)="refresh($event.target)">
<ion-refresher-content
pullingIcon="chevron-down-outline"
pullingText="{{ 'data.REFRESH_ACTION' | translate }}"
refreshingText="{{ 'data.REFRESHING' | translate }}"
>
</ion-refresher-content>
</ion-refresher>
<div [ngSwitch]="true">
<ng-container *ngSwitchCase="!item && isDisconnected()">
<div class="notFoundContainer">
<ion-icon name="no-connection"> </ion-icon>
<ion-label>
{{ 'data.detail.COULD_NOT_CONNECT' | translate }}
</ion-label>
</div>
</ng-container>
<ng-container *ngSwitchCase="item === null">
<div class="notFoundContainer">
<ion-icon name="broken-link"> </ion-icon>
<ion-label>
{{ 'data.detail.NOT_FOUND' | translate }}
</ion-label>
</div>
</ng-container>
<ng-container *ngSwitchCase="!item && item !== null">
<stapps-skeleton-list-item></stapps-skeleton-list-item>
<stapps-skeleton-simple-card></stapps-skeleton-simple-card>
</ng-container>
<ng-container *ngSwitchDefault>
<stapps-hebis-detail-content [item]="item"></stapps-hebis-detail-content>
<stapps-daia-availability *ngIf="item"></stapps-daia-availability>
</ng-container>
</div>
</ion-content>

View File

@@ -0,0 +1,5 @@
::ng-deep {
ion-card-header {
font-weight: bold;
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2018-2021 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {HebisDetailComponent} from './hebis-detail/hebis-detail.component';
import {HebisSearchPageComponent} from './list/hebis-search-page.component';
const hebisRoutes: Routes = [
{path: 'hebis-search', component: HebisSearchPageComponent},
{path: 'hebis-detail/:uid', component: HebisDetailComponent},
];
/**
* Module defining routes for data module
*/
@NgModule({
exports: [RouterModule],
imports: [RouterModule.forChild(hebisRoutes)],
})
export class HebisRoutingModule {}

View File

@@ -0,0 +1,100 @@
/*
* Copyright (C) 2018-2021 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {ScrollingModule} from '@angular/cdk/scrolling';
import {CommonModule} from '@angular/common';
import {HttpClientModule} from '@angular/common/http';
import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {Network} from '@ionic-native/network/ngx';
import {IonicModule} from '@ionic/angular';
import {TranslateModule} from '@ngx-translate/core';
import {MarkdownModule} from 'ngx-markdown';
import {MomentModule} from 'ngx-moment';
import {ThingTranslateModule} from '../../translation/thing-translate.module';
import {MenuModule} from '../menu/menu.module';
import {StorageModule} from '../storage/storage.module';
import {HebisDetailComponent} from './hebis-detail/hebis-detail.component';
import {HebisDetailContentComponent} from './hebis-detail/hebis-detail-content.component';
import {HebisSearchPageComponent} from './list/hebis-search-page.component';
import {HebisDataProvider} from './hebis-data.provider';
import {DaiaDataProvider} from './daia-data.provider';
import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider';
import {HebisRoutingModule} from './hebis-routing.module';
import {DataModule} from '../data/data.module';
import {BookDetailContentComponent} from './types/book/book-detail-content.component';
import {BookListItemComponent} from './types/book/book-list-item.component';
import {PeriodicalListItemComponent} from './types/periodical/periodical-list-item.component';
import {PeriodicalDetailContentComponent} from './types/periodical/periodical-detail-content.component';
import {HebisArticleListItemComponent} from './types/hebis-article/hebis-article-item.component';
import {HebisArticleContentComponent} from './types/hebis-article/hebis-article-content.component';
import {DataListComponent} from '../data/list/data-list.component';
import {DaiaAvailabilityComponent} from './daia-availability/daia-availability.component';
/**
* Module for handling data
*/
@NgModule({
declarations: [
HebisSearchPageComponent,
HebisDetailComponent,
HebisDetailContentComponent,
DaiaAvailabilityComponent,
BookDetailContentComponent,
BookListItemComponent,
PeriodicalDetailContentComponent,
PeriodicalListItemComponent,
HebisArticleListItemComponent,
HebisArticleContentComponent,
],
entryComponents: [DataListComponent],
imports: [
CommonModule,
DataModule,
FormsModule,
HebisRoutingModule,
HttpClientModule,
IonicModule.forRoot(),
MarkdownModule.forRoot(),
MenuModule,
MomentModule.forRoot({
relativeTimeThresholdOptions: {
m: 59,
},
}),
ScrollingModule,
StorageModule,
TranslateModule.forChild(),
ThingTranslateModule.forChild(),
],
providers: [
HebisDataProvider,
DaiaDataProvider,
Network,
StAppsWebHttpClient,
],
exports: [
HebisSearchPageComponent,
HebisDetailComponent,
HebisDetailContentComponent,
DaiaAvailabilityComponent,
PeriodicalDetailContentComponent,
BookDetailContentComponent,
HebisArticleListItemComponent,
HebisArticleContentComponent,
],
})
export class HebisModule {}

View File

@@ -0,0 +1,186 @@
/*
* Copyright (C) 2018-2021 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component, Input, OnInit, OnDestroy} from '@angular/core';
import {Router} from '@angular/router';
import {AlertController} from '@ionic/angular';
import {NGXLogger} from 'ngx-logger';
import {combineLatest} from 'rxjs';
import {debounceTime, distinctUntilChanged, startWith} from 'rxjs/operators';
import {ContextMenuService} from '../../menu/context/context-menu.service';
import {SettingsProvider} from '../../settings/settings.provider';
import {DataRoutingService} from '../../data/data-routing.service';
import {SearchPageComponent} from '../../data/list/search-page.component';
import {HebisDataProvider} from '../hebis-data.provider';
import {PositionService} from '../../map/position.service';
/**
* HebisSearchPageComponent queries things and shows list of things as search results and filter as context menu
*/
@Component({
selector: 'stapps-hebissearch-page',
templateUrl: 'hebis-search-page.html',
})
export class HebisSearchPageComponent
extends SearchPageComponent
implements OnInit, OnDestroy
{
/**
* If routing should be done if the user clicks on an item
*/
@Input() itemRouting? = true;
/**
* Current page to start query
*/
page = 0;
/**
* Injects the providers and creates subscriptions
*
* @param alertController AlertController
* @param dataProvider HebisProvider
* @param contextMenuService ContextMenuService
* @param settingsProvider SettingsProvider
* @param logger An angular logger
* @param dataRoutingService DataRoutingService
* @param router Router
* @param positionService PositionService
*/
constructor(
protected readonly alertController: AlertController,
protected dataProvider: HebisDataProvider,
protected readonly contextMenuService: ContextMenuService,
protected readonly settingsProvider: SettingsProvider,
protected readonly logger: NGXLogger,
protected dataRoutingService: DataRoutingService,
protected router: Router,
protected positionService: PositionService,
) {
super(
alertController,
dataProvider,
contextMenuService,
settingsProvider,
logger,
dataRoutingService,
router,
positionService,
);
}
/**
* Fetches items with set query configuration
*
* @param append If true fetched data gets appended to existing, override otherwise (default false)
*/
protected async fetchAndUpdateItems(append = false): Promise<void> {
// build query search options
const searchOptions: {page: number; query: string} = {
page: this.page,
query: '',
};
if (this.queryText && this.queryText.length > 0) {
// add query string
searchOptions.query = this.queryText;
}
return this.dataProvider.hebisSearch(searchOptions).then(
async result => {
/*this.singleTypeResponse =
result.facets.find(facet => facet.field === 'type')?.buckets
.length === 1;*/
if (append) {
let items = await this.items;
// append results
items = [...items, ...result.data];
this.items = (async () => items)();
} else {
// override items with results
this.items = (async () => {
return result.data;
})();
}
},
async error => {
const alert: HTMLIonAlertElement = await this.alertController.create({
buttons: ['Dismiss'],
header: 'Error',
subHeader: error.message,
});
await alert.present();
},
);
}
/**
* Loads next page of things
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async loadMore(): Promise<void> {
this.page += 1;
await this.fetchAndUpdateItems(true);
}
ngOnInit() {
//this.fetchAndUpdateItems();
this.initialize();
this.subscriptions.push(
combineLatest([
this.queryTextChanged.pipe(
debounceTime(this.searchQueryDueTime),
distinctUntilChanged(),
startWith(this.queryText),
),
]).subscribe(async query => {
this.queryText = query[0];
this.page = 0;
if (this.queryText?.length > 0 || this.showDefaultData) {
await this.fetchAndUpdateItems();
this.queryChanged.next();
}
}),
this.settingsProvider.settingsActionChanged$.subscribe(
({type, payload}) => {
if (type === 'stapps.settings.changed') {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const {category, name, value} = payload!;
this.logger.log(`received event "settings.changed" with category:
${category}, name: ${name}, value: ${JSON.stringify(value)}`);
}
},
),
this.dataRoutingService.itemSelectListener().subscribe(async item => {
if (this.itemRouting) {
void this.router.navigate([
'hebis-detail',
(item.origin &&
'originalId' in item.origin &&
item.origin['originalId']) ||
'',
]);
}
}),
);
}
ngOnDestroy() {
for (const subscription of this.subscriptions) {
subscription.unsubscribe();
}
}
}

View File

@@ -0,0 +1,39 @@
<stapps-context contentId="data-list"></stapps-context>
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-buttons slot="end">
<ion-menu-button menu="context" auto-hide="false">
<ion-icon name="options"></ion-icon>
</ion-menu-button>
</ion-buttons>
<ion-searchbar
(ngModelChange)="searchStringChanged($event)"
[(ngModel)]="queryText"
showClearButton="always"
placeholder="{{ 'hebisSearch.search_bar.placeholder' | translate }}"
>
</ion-searchbar>
</ion-toolbar>
</ion-header>
<ion-content>
<div
[style.display]="!showDefaultData && !items && !loading ? 'block' : 'none'"
>
<ion-label class="centeredMessageContainer">
{{ 'hebisSearch.instruction' | translate }}
</ion-label>
</div>
<stapps-data-list
id="data-list"
[items]="items | async"
[singleType]="singleTypeResponse"
(loadmore)="loadMore()"
[resetToTop]="queryChanged.asObservable()"
[loading]="loading"
></stapps-data-list>
</ion-content>

View File

@@ -0,0 +1,20 @@
import {SCLicensePlate} from '@openstapps/core';
export interface SCHebisSearchRequest {
/**
* HDS2 will supply results for the speficied insitute / university if available (Defaults to f-u)
*/
institute?: SCLicensePlate;
/**
* Simple pagination support (Defaults to 0)
*/
page?: number;
/**
* Search query for HDS
*/
query: string;
}
export interface SCDaiaAvailabilityRequest {
id: string;
}

View File

@@ -0,0 +1,58 @@
import {
SCArticle,
SCBook,
SCPeriodical,
SCSearchResultPagination,
} from '@openstapps/core';
export interface SCHebisSearchResponse {
/**
* Response Array of SCBook type or Object
*/
data: Array<SCBook | SCArticle | SCPeriodical>;
/**
* Pagination information
*/
pagination: SCSearchResultPagination;
}
export interface SCDaiaAvailabilityResponse {
document: Array<{
item: Array<{
id: string;
label: string;
department: SCDaiaSimpleContent;
available: SCDaiaService[];
unavailable: SCDaiaService[];
debugInfo: string;
about?: string;
storage?: SCDaiaSimpleContent;
}>;
}>;
institution: SCDaiaSimpleContent;
timestamp: string;
}
export interface SCDaiaSimpleContent {
id: string;
content: string;
href?: string;
}
export interface SCDaiaService {
delay?: string;
href?: string;
service: string;
limitations?: SCDaiaSimpleContent[];
}
export interface SCDaiaHoldings {
id: string;
label: string;
href?: string;
signature: string;
storage?: SCDaiaSimpleContent;
available?: SCDaiaService;
about?: string;
}

View File

@@ -0,0 +1,54 @@
import {
SCAbstractRoute,
SCRouteHttpVerbs,
SCInternalServerErrorResponse,
SCMethodNotAllowedErrorResponse,
SCRequestBodyTooLargeErrorResponse,
SCSyntaxErrorResponse,
SCUnsupportedMediaTypeErrorResponse,
SCValidationErrorResponse,
} from '@openstapps/core';
/**
* Route for searching things
*/
export class SCHebisSearchRoute extends SCAbstractRoute {
constructor() {
super();
this.errorNames = [
SCInternalServerErrorResponse,
SCMethodNotAllowedErrorResponse,
SCRequestBodyTooLargeErrorResponse,
SCSyntaxErrorResponse,
SCUnsupportedMediaTypeErrorResponse,
SCValidationErrorResponse,
];
this.method = SCRouteHttpVerbs.POST;
this.requestBodyName = 'SCHebisSearchRequest';
this.responseBodyName = 'SCHebisSearchResponse';
this.statusCodeSuccess = 200;
this.urlPath = '/hebissearch';
}
}
/**
* Route for availability
*/
export class SCDaiaRoute extends SCAbstractRoute {
constructor() {
super();
this.errorNames = [
SCInternalServerErrorResponse,
SCMethodNotAllowedErrorResponse,
SCRequestBodyTooLargeErrorResponse,
SCSyntaxErrorResponse,
SCUnsupportedMediaTypeErrorResponse,
SCValidationErrorResponse,
];
this.method = SCRouteHttpVerbs.GET;
this.requestBodyName = 'SCDaiaAvailabilityRequest';
this.responseBodyName = 'SCDaiaAvailabilityResponse';
this.statusCodeSuccess = 200;
this.urlPath = '/UB_Frankfurt';
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component, Input} from '@angular/core';
import {SCBook} from '@openstapps/core';
/**
* TODO
*/
@Component({
selector: 'stapps-book-detail-content',
templateUrl: 'book-detail-content.html',
})
export class BookDetailContentComponent {
/**
* TODO
*/
@Input() item: SCBook;
}

View File

@@ -0,0 +1,82 @@
<ion-card *ngIf="item.sameAs">
<ion-card-header>{{
'hebisSearch.detail.title' | translate
}}</ion-card-header>
<ion-card-content>
<a [href]="item.sameAs" target="_blank">{{
'name' | thingTranslate: item | titlecase
}}</a>
</ion-card-content>
</ion-card>
<stapps-simple-card
*ngIf="!item.sameAs"
[title]="'name' | propertyNameTranslate: item | titlecase"
[content]="'name' | thingTranslate: item"
>
</stapps-simple-card>
<stapps-simple-card
*ngIf="item.description"
[title]="'hebisSearch.detail.description' | translate"
[content]="item.description"
></stapps-simple-card>
<stapps-simple-card
*ngIf="item.sourceOrganization"
[title]="'sourceOrganization' | propertyNameTranslate: item | titlecase"
[content]="item.sourceOrganization"
></stapps-simple-card>
<ion-card *ngIf="item.authors && item.authors.length > 0">
<ion-card-header>{{
'authors' | propertyNameTranslate: item | titlecase
}}</ion-card-header>
<ion-card-content>
<ion-label *ngFor="let author of item.authors">{{
'name' | thingTranslate: author
}}</ion-label>
</ion-card-content>
</ion-card>
<stapps-simple-card
*ngIf="item.ISBNs"
[title]="'ISBNs' | propertyNameTranslate: item | titlecase"
[content]="item.ISBNs"
>
</stapps-simple-card>
<stapps-simple-card
*ngIf="item.firstPublished && !item.lastPublished"
[title]="'hebisSearch.detail.firstPublished' | translate"
[content]="item.firstPublished"
>
</stapps-simple-card>
<stapps-simple-card
*ngIf="item.firstPublished && item.lastPublished"
[title]="'hebisSearch.detail.firstPublished' | translate"
[content]="[item.firstPublished, item.lastPublished] | join: ' - '"
>
</stapps-simple-card>
<ion-card *ngIf="item.publications">
<ion-card-header>{{
'publications' | propertyNameTranslate: item | titlecase
}}</ion-card-header>
<ion-card-content>
<p *ngFor="let publication of item.publications">
{{ publication.locations | join: ', ' }}
{{ publication.locations && publication.publisher ? ':' : '' }}
{{ publication.publisher }}
</p>
</ion-card-content>
</ion-card>
<ion-card *ngIf="item.categories">
<ion-card-header>{{
'categories' | propertyNameTranslate: item | titlecase
}}</ion-card-header>
<ion-card-content>
<ion-chip [attr.color]="'primary'">
<ion-icon [attr.name]="item.type | dataIcon"></ion-icon>
<ion-label>{{ 'categories' | thingTranslate: item }}</ion-label>
</ion-chip>
</ion-card-content>
</ion-card>

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component, Input} from '@angular/core';
import {SCBook} from '@openstapps/core';
import {DataListItemComponent} from '../../../data/list/data-list-item.component';
/**
* TODO
*/
@Component({
selector: 'stapps-book-list-item',
templateUrl: 'book-list-item.html',
})
export class BookListItemComponent extends DataListItemComponent {
/**
* TODO
*/
@Input() item: SCBook;
}

View File

@@ -0,0 +1,16 @@
<ion-grid>
<ion-row>
<ion-col>
<h2 class="name">{{ 'name' | thingTranslate: item }}</h2>
<p *ngIf="item.keywords">
<stapps-long-inline-text
[text]="item.keywords.join(', ')"
[size]="110"
></stapps-long-inline-text>
</p>
<ion-note>
{{ 'type' | thingTranslate: item }}
</ion-note>
</ion-col>
</ion-row>
</ion-grid>

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component, Input} from '@angular/core';
import {SCArticle} from '@openstapps/core';
/**
* TODO
*/
@Component({
selector: 'stapps-hebis-article-content',
templateUrl: 'hebis-article-content.html',
})
export class HebisArticleContentComponent {
/**
* TODO
*/
@Input() item: SCArticle;
}

View File

@@ -0,0 +1,76 @@
<ion-card *ngIf="item.sameAs">
<ion-card-header>{{
'hebisSearch.detail.title' | translate
}}</ion-card-header>
<ion-card-content>
<a [href]="item.sameAs" target="_blank">{{
'name' | thingTranslate: item | titlecase
}}</a>
</ion-card-content>
</ion-card>
<stapps-simple-card
*ngIf="!item.sameAs"
[title]="'name' | propertyNameTranslate: item | titlecase"
[content]="'name' | thingTranslate: item"
>
</stapps-simple-card>
<stapps-simple-card
*ngIf="item.description"
[title]="'hebisSearch.detail.description' | translate"
[content]="item.description"
></stapps-simple-card>
<stapps-simple-card
*ngIf="item.sourceOrganization"
[title]="'sourceOrganization' | propertyNameTranslate: item | titlecase"
[content]="item.sourceOrganization"
></stapps-simple-card>
<ion-card *ngIf="item.authors && item.authors.length > 0">
<ion-card-header>{{
'authors' | propertyNameTranslate: item | titlecase
}}</ion-card-header>
<ion-card-content>
<ion-label *ngFor="let author of item.authors">{{
'name' | thingTranslate: author
}}</ion-label>
</ion-card-content>
</ion-card>
<stapps-simple-card
*ngIf="item.firstPublished && !item.lastPublished"
[title]="'hebisSearch.detail.firstPublished' | translate"
[content]="item.firstPublished"
>
</stapps-simple-card>
<stapps-simple-card
*ngIf="item.firstPublished && item.lastPublished"
[title]="'hebisSearch.detail.firstPublished' | translate"
[content]="[item.firstPublished, item.lastPublished] | join: ' - '"
>
</stapps-simple-card>
<ion-card *ngIf="item.publications">
<ion-card-header>{{
'publications' | propertyNameTranslate: item | titlecase
}}</ion-card-header>
<ion-card-content>
<p *ngFor="let publication of item.publications">
{{ publication.locations | join: ', ' }}
{{ publication.locations && publication.publisher ? ':' : '' }}
{{ publication.publisher }}
</p>
</ion-card-content>
</ion-card>
<ion-card *ngIf="item.categories">
<ion-card-header>{{
'categories' | propertyNameTranslate: item | titlecase
}}</ion-card-header>
<ion-card-content>
<ion-chip [attr.color]="'primary'">
<ion-icon [attr.name]="item.type | dataIcon"></ion-icon>
<ion-label>{{ 'categories' | thingTranslate: item }}</ion-label>
</ion-chip>
</ion-card-content>
</ion-card>

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component, Input} from '@angular/core';
import {SCArticle} from '@openstapps/core';
import {DataListItemComponent} from '../../../data/list/data-list-item.component';
/**
* TODO
*/
@Component({
selector: 'stapps-hebis-article-item',
templateUrl: 'hebis-article-item.html',
})
export class HebisArticleListItemComponent extends DataListItemComponent {
/**
* TODO
*/
@Input() item: SCArticle;
}

View File

@@ -0,0 +1,16 @@
<ion-grid>
<ion-row>
<ion-col>
<h2 class="name">{{ 'name' | thingTranslate: item }}</h2>
<p *ngIf="item.keywords">
<stapps-long-inline-text
[text]="item.keywords.join(', ')"
[size]="110"
></stapps-long-inline-text>
</p>
<ion-note>
{{ 'type' | thingTranslate: item }}
</ion-note>
</ion-col>
</ion-row>
</ion-grid>

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component, Input} from '@angular/core';
import {SCPeriodical} from '@openstapps/core';
/**
* TODO
*/
@Component({
selector: 'stapps-periodical-detail-content',
templateUrl: 'periodical-detail-content.html',
})
export class PeriodicalDetailContentComponent {
/**
* TODO
*/
@Input() item: SCPeriodical;
}

View File

@@ -0,0 +1,82 @@
<ion-card *ngIf="item.sameAs">
<ion-card-header>{{
'hebisSearch.detail.title' | translate
}}</ion-card-header>
<ion-card-content>
<a [href]="item.sameAs" target="_blank">{{
'name' | thingTranslate: item | titlecase
}}</a>
</ion-card-content>
</ion-card>
<stapps-simple-card
*ngIf="!item.sameAs"
[title]="'name' | propertyNameTranslate: item | titlecase"
[content]="'name' | thingTranslate: item"
>
</stapps-simple-card>
<stapps-simple-card
*ngIf="item.description"
[title]="'hebisSearch.detail.description' | translate"
[content]="item.description"
></stapps-simple-card>
<stapps-simple-card
*ngIf="item.sourceOrganization"
[title]="'sourceOrganization' | propertyNameTranslate: item | titlecase"
[content]="item.sourceOrganization"
></stapps-simple-card>
<ion-card *ngIf="item.authors && item.authors.length > 0">
<ion-card-header>{{
'authors' | propertyNameTranslate: item | titlecase
}}</ion-card-header>
<ion-card-content>
<ion-label *ngFor="let author of item.authors">{{
'name' | thingTranslate: author
}}</ion-label>
</ion-card-content>
</ion-card>
<stapps-simple-card
*ngIf="item.firstPublished && !item.lastPublished"
[title]="'hebisSearch.detail.firstPublished' | translate"
[content]="item.firstPublished"
>
</stapps-simple-card>
<stapps-simple-card
*ngIf="item.firstPublished && item.lastPublished"
[title]="'hebisSearch.detail.firstPublished' | translate"
[content]="[item.firstPublished, item.lastPublished] | join: ' - '"
>
</stapps-simple-card>
<ion-card *ngIf="item.publications">
<ion-card-header>{{
'publications' | propertyNameTranslate: item | titlecase
}}</ion-card-header>
<ion-card-content>
<p *ngFor="let publication of item.publications">
{{ publication.locations | join: ', ' }}
{{ publication.locations && publication.publisher ? ':' : '' }}
{{ publication.publisher }}
</p>
</ion-card-content>
</ion-card>
<ion-card *ngIf="item.categories">
<ion-card-header>{{
'categories' | propertyNameTranslate: item | titlecase
}}</ion-card-header>
<ion-card-content>
<ion-chip [attr.color]="'primary'">
<ion-icon [attr.name]="item.type | dataIcon"></ion-icon>
<ion-label>{{ 'categories' | thingTranslate: item }}</ion-label>
</ion-chip>
</ion-card-content>
</ion-card>
<stapps-simple-card
*ngIf="item.ISSNs"
[title]="'hebisSearch.detail.issn' | translate"
[content]="item.ISSNs | join: ', '"
>
</stapps-simple-card>

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component, Input} from '@angular/core';
import {SCPeriodical} from '@openstapps/core';
import {DataListItemComponent} from '../../../data/list/data-list-item.component';
/**
* TODO
*/
@Component({
selector: 'stapps-periodical-list-item',
templateUrl: 'periodical-list-item.html',
})
export class PeriodicalListItemComponent extends DataListItemComponent {
/**
* TODO
*/
@Input() item: SCPeriodical;
}

View File

@@ -0,0 +1,16 @@
<ion-grid>
<ion-row>
<ion-col>
<h2 class="name">{{ 'name' | thingTranslate: item }}</h2>
<p *ngIf="item.keywords">
<stapps-long-inline-text
[text]="item.keywords.join(', ')"
[size]="110"
></stapps-long-inline-text>
</p>
<ion-note>
{{ 'type' | thingTranslate: item }}
</ion-note>
</ion-col>
</ion-row>
</ion-grid>

View File

@@ -49,6 +49,7 @@ export class NavigationComponent implements OnInit {
*/
public pages = [
{title: 'Search', url: '/search', icon: 'search'},
{title: 'Hebis Search', url: '/hebis-search', icon: 'search'},
{title: 'Schedule', url: '/schedule', icon: 'calendar'},
{title: 'Settings', url: '/settings', icon: 'settings'},
];

View File

@@ -197,6 +197,29 @@
"instruction": "Starte oben zu tippen, um Veranstaltungen, Veranstaltungstermine, Personen, Orte, Essen und mehr zu finden ...",
"nothing_found": "Keine Ergebnisse"
},
"hebisSearch": {
"search_bar": {
"placeholder": "Suche ..."
},
"instruction": "Starte oben zu tippen, um Bücher, mehrteilige Werke, Zeitschriften, E-Books und mehr zu finden ...",
"nothing_found": "Keine Ergebnisse",
"detail": {
"title": "Titel",
"description": "Umfang",
"firstPublished": "Erscheinungsjahr"
},
"daia": {
"availability": "Verfügbarkeit",
"available": "ausleihbar",
"status": "Status",
"location": "Standort",
"signature": "Signatur",
"comment": "Kommentar",
"order": "Bestellen",
"issn": "ISSN",
"ejournal": "ejournal"
}
},
"schedule": {
"recurring": "Stundenplan",
"calendar": "Kalender",

View File

@@ -197,6 +197,29 @@
"instruction": "Start typing above to find events, persons, places, food and more ...",
"nothing_found": "No results"
},
"hebisSearch": {
"search_bar": {
"placeholder": "Search ..."
},
"instruction": "Start typing above to find books, journals, multipart items, e-books and more ...",
"nothing_found": "No results",
"detail": {
"title": "Title",
"description": "Scope",
"firstPublished": "Year of publication"
},
"daia": {
"availability": "Availability",
"available": "available",
"status": "Status",
"location": "Location",
"signature": "Shelfmark",
"comment": "Remark",
"order": "Request",
"issn": "ISSN",
"ejournal": "ejournal"
}
},
"schedule": {
"recurring": "Recurring",
"calendar": "Calendar",

View File

@@ -24,6 +24,7 @@ const appDomain = 'mobile.app.uni-frankfurt.de';
export const environment = {
backend_url: 'https://mobile.server.uni-frankfurt.de',
daia_url: 'https://daia.hebis.de/DAIA2/UB_Frankfurt',
appDomain: 'mobile.app.uni-frankfurt.de',
backend_version: '2.0.0',
use_fake_backend: false,