diff --git a/nietzsche-beta-app/package.json b/nietzsche-beta-app/package.json index ff79cee..fbc020b 100644 --- a/nietzsche-beta-app/package.json +++ b/nietzsche-beta-app/package.json @@ -1,57 +1,60 @@ { "name": "nietzsche-app-beta", - "version": "0.5.2.1", + "version": "0.6.0.0", "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e", "compodoc": "./node_modules/.bin/compodoc -p tsconfig.app.json", "sparqljs": "./node_modules/sparqljs/sparql.js" }, "private": true, "dependencies": { "@angular/animations": "~8.2.14", "@angular/cdk": "~8.2.3", "@angular/common": "~8.2.14", "@angular/compiler": "~8.2.14", "@angular/core": "~8.2.14", "@angular/forms": "~8.2.14", "@angular/material": "^8.2.3", "@angular/platform-browser": "~8.2.14", "@angular/platform-browser-dynamic": "~8.2.14", "@angular/router": "~8.2.14", + "@ctrl/ngx-codemirror": "^5.0.0", "@types/rdf-js": "^2.0.11", + "codemirror": "^5.62.0", "lodash": "^4.17.20", + "n3": "^1.10.0", + "ngx-csv": "^0.3.2", "ngx-mat-standoff-markup": "^0.7.3", "rdfjs": "^0.0.1", "rxjs": "~6.4.0", - "n3":"^1.10.0", "sparqljs": "^3.0.1", "tslib": "^1.10.0", "zone.js": "~0.9.1" }, "devDependencies": { "@angular-devkit/build-angular": "^0.803.25", "@angular/cli": "~8.3.24", "@angular/compiler-cli": "~8.2.14", "@angular/language-service": "~8.2.14", "@types/jasmine": "~3.3.8", "@types/jasminewd2": "~2.0.3", "@types/node": "~8.9.4", "codelyzer": "^5.0.0", "jasmine-core": "~3.4.0", "jasmine-spec-reporter": "~4.2.1", "karma": "~4.1.0", "karma-chrome-launcher": "~2.2.0", "karma-coverage-istanbul-reporter": "~2.0.1", "karma-jasmine": "~2.0.1", "karma-jasmine-html-reporter": "^1.4.0", "protractor": "~5.4.0", "ts-node": "~7.0.0", "tslint": "~5.15.0", "typescript": "~3.5.3" } } diff --git a/nietzsche-beta-app/src/app/app.module.ts b/nietzsche-beta-app/src/app/app.module.ts index a0f06b7..d402738 100644 --- a/nietzsche-beta-app/src/app/app.module.ts +++ b/nietzsche-beta-app/src/app/app.module.ts @@ -1,80 +1,82 @@ import { AppComponent } from './app.component'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { FormsModule, ReactiveFormsModule} from '@angular/forms'; import { NgModule } from '@angular/core'; import {MatCardModule} from '@angular/material/card'; import { MatToolbarModule, MatButtonModule} from '@angular/material'; import {MatProgressBarModule} from '@angular/material/progress-bar'; import {MatTreeModule} from '@angular/material/tree'; import { MatExpansionModule } from '@angular/material/expansion'; import {MatIconModule} from '@angular/material/icon'; import { MatListModule } from '@angular/material/list'; import { MatMenuModule} from '@angular/material/menu'; import { MatSidenavModule } from '@angular/material'; import { MatSelectModule } from '@angular/material/select'; import {MatTabsModule} from '@angular/material/tabs'; import {MatTooltipModule} from '@angular/material'; import { CommonModule } from '@angular/common'; import { HttpClientModule } from '@angular/common/http'; import { QueryService } from './services/query.service'; import {routing} from './app.routing'; import {HomeComponent} from './home.component'; import { ManuscriptViewComponentComponent } from './manuscript-view-component/manuscript-view-component.component'; import { ContentViewTabComponentComponent } from './content-view-tab-component/content-view-tab-component.component'; import { RhizomeViewComponentComponent } from './rhizome-view-component/rhizome-view-component.component'; import { MainMenuComponentComponent } from './main-menu-component/main-menu-component.component'; import { PageViewWrapperComponent } from './page-view-wrapper-component/page-view-wrapper.component'; import { NavigationListComponentComponent } from './navigation-list-component/navigation-list-component.component'; import {NavigationServiceService} from './services/navigation-service.service'; import { TlnEditionModule} from './tln-edition/tln-edition.module'; import { NavTree } from './navigation-list-component/navtree-directive.directive'; import { LazyImageLoadDirectiveDirective } from './content-view-tab-component/lazy-image-load-directive.directive'; import { ImpressumComponent } from './impressum.component'; import { ProjectComponent } from './project.component'; import { NavigationlistListComponentComponent } from './navigation-list-component/navigationlist-list-component/navigationlist-list-component.component'; +import { QuantComponent } from './quant/quant.component'; @NgModule({ declarations: [ AppComponent, HomeComponent, ManuscriptViewComponentComponent, ContentViewTabComponentComponent, RhizomeViewComponentComponent, MainMenuComponentComponent, PageViewWrapperComponent, NavigationListComponentComponent, NavTree, LazyImageLoadDirectiveDirective, ImpressumComponent, ProjectComponent, - NavigationlistListComponentComponent + NavigationlistListComponentComponent, + QuantComponent ], imports: [ routing, BrowserModule, BrowserAnimationsModule, CommonModule, HttpClientModule, MatButtonModule, MatCardModule, MatExpansionModule, MatIconModule, MatListModule, MatMenuModule, MatProgressBarModule, MatSelectModule, // for themes selection MatSidenavModule, MatTabsModule, MatToolbarModule, MatTooltipModule, MatTreeModule, FormsModule, TlnEditionModule, ReactiveFormsModule ], providers: [ NavigationServiceService, QueryService ], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/nietzsche-beta-app/src/app/content-view-tab-component/content-view-routes.ts b/nietzsche-beta-app/src/app/content-view-tab-component/content-view-routes.ts index b15cd32..bc7d26e 100644 --- a/nietzsche-beta-app/src/app/content-view-tab-component/content-view-routes.ts +++ b/nietzsche-beta-app/src/app/content-view-tab-component/content-view-routes.ts @@ -1,14 +1,16 @@ import { Routes } from '@angular/router'; import {ManuscriptViewComponentComponent} from "../manuscript-view-component/manuscript-view-component.component"; import {RhizomeViewComponentComponent} from "../rhizome-view-component/rhizome-view-component.component"; import {PageViewWrapperComponent} from "../page-view-wrapper-component/page-view-wrapper.component"; -import { TLN_CROSSREF_ROUTE, TLN_MANUSCRIPT_ROUTE, TLN_SEARCH_ROUTE, TLN_VIEWER_ROUTE, TLN_CROSSREF_EDITOR_ROUTE } from '../tln-edition/constants'; +import { TLN_QUANT_ROUTE, TLN_CROSSREF_ROUTE, TLN_MANUSCRIPT_ROUTE, TLN_SEARCH_ROUTE, TLN_VIEWER_ROUTE, TLN_CROSSREF_EDITOR_ROUTE } from '../tln-edition/constants'; import { TlnFulltextComponent } from '../tln-edition/tln-fulltext/tln-fulltext.component'; +import { QuantComponent } from '../quant/quant.component'; export const CONTENT_ROUTES: Routes = [ { path: TLN_MANUSCRIPT_ROUTE, component: ManuscriptViewComponentComponent }, { path: TLN_VIEWER_ROUTE, component: PageViewWrapperComponent }, { path: TLN_CROSSREF_ROUTE, component: RhizomeViewComponentComponent }, { path: TLN_SEARCH_ROUTE, component: TlnFulltextComponent}, + { path: TLN_QUANT_ROUTE, component: QuantComponent}, { path: '', redirectTo: TLN_MANUSCRIPT_ROUTE, pathMatch: 'prefix' } ]; diff --git a/nietzsche-beta-app/src/app/content-view-tab-component/content-view-tab-component.component.ts b/nietzsche-beta-app/src/app/content-view-tab-component/content-view-tab-component.component.ts index 7ae9fad..3e6041a 100644 --- a/nietzsche-beta-app/src/app/content-view-tab-component/content-view-tab-component.component.ts +++ b/nietzsche-beta-app/src/app/content-view-tab-component/content-view-tab-component.component.ts @@ -1,137 +1,137 @@ import {Component, OnInit} from '@angular/core'; import {ActivatedRoute, NavigationEnd, Params, Router} from '@angular/router'; import {Subscription} from "rxjs/index"; import {NavigationServiceService} from "../services/navigation-service.service"; import { TLN_CROSSREF_EDITOR_ROUTE, TLN_CROSSREF_ROUTE, TLN_MANUSCRIPT_ROUTE, TLN_SEARCH_ROUTE, - TLN_VIEWER_ROUTE + TLN_VIEWER_ROUTE, TLN_QUANT_ROUTE } from '../tln-edition/constants'; @Component({ selector: 'app-content-view-tab-component', templateUrl: './content-view-tab-component.component.html', styleUrls: ['./content-view-tab-component.component.scss'], providers: [ NavigationServiceService ] }) export class ContentViewTabComponentComponent implements OnInit { // navigation tabs for the content view (manuscript view, page view, rhizome view) navTabLinks: any[]; // navbar on th left for navigating navBarOpenState = false; fullscreen = false; navBarOpenMode: string; queryParams: Params = {}; queryParamSubscription: Subscription; routeSubscription: Subscription; constructor(private router: Router, private activatedRoute: ActivatedRoute, private naviService: NavigationServiceService) { // The links/tabs for routing the correct view-component this.navTabLinks = [ { label: 'Manuskriptansicht', link: TLN_MANUSCRIPT_ROUTE, index: 0, isActive: true, context: 'manuscript', // in which context the viewers are opened disabled: false }, { label: 'Seitenansicht', link: TLN_VIEWER_ROUTE, index: 1, isActive: false, context: 'page', // in which context the viewers are opened disabled: false }, { label: 'Querverweise', link: TLN_CROSSREF_ROUTE, index: 2, isActive: false, disabled: false }, { label: 'Suche', link: TLN_SEARCH_ROUTE, index: 3, isActive: false, disabled: false }, { - label: 'Querverweis-Editor', - link: TLN_CROSSREF_EDITOR_ROUTE, + label: 'Datenabfrage', + link: TLN_QUANT_ROUTE, index: 4, isActive: false, - disabled: true + disabled: false }, ]; // subscribe to route event and for marking the active navTab as active this.routeSubscription = this.router.events.subscribe((event) => { if ( event instanceof NavigationEnd) { // if the navigated link is not active, we set it acive if ( !this.navTabLinks.find(nl => nl.link === this.activatedRoute.snapshot.children.pop().routeConfig.path).isActive ) { this.setActiveLink(this.activatedRoute.snapshot.children.pop().routeConfig.path); } } }); this.queryParamSubscription = this.activatedRoute.queryParams.subscribe( (queryParams: Params ) => { this.queryParams = queryParams; if (queryParams.navBarOpenState) { this.navBarOpenState = JSON.parse(queryParams.navBarOpenState.toLowerCase()); } if (queryParams.fullscreen) { this.fullscreen = JSON.parse(queryParams.fullscreen.toLowerCase()); } }); } ngOnInit() { this.navBarOpenMode = 'side'; // side || over || push this.setParamsOnInit(); // If url pasted or page refreshed --> resetting this.queryparams to the query params of the url; // needed for active routing in the nav tabs & for general use in the template } // Sets the isActive prop of a navTabLink to true and all others to false . setActiveLink(link: string) { this.navTabLinks.forEach((navTabLink, index ) => { if (navTabLink.link === link) { this.navTabLinks[index].isActive = true; if (navTabLink.context) { window.setTimeout(() => this.naviService.updateRoute({contextView : navTabLink.context}), 300); } } else { this.navTabLinks[index].isActive = false; } }); } /** * setParamsOnInit checks if the page is loaded with query params. If a certain query param is missing, it will be set to a default value * */ setParamsOnInit() { const qParams: Params = {}; // Set the NavBarOpenstate to true if it is not defined explicitely as false in the url query param onInit. if (this.activatedRoute.snapshot.queryParamMap.get('navBarOpenState') !== 'false') { qParams.navBarOpenState = 'true'; } // viewMode if (!this.activatedRoute.snapshot.queryParamMap.get('viewMode')) { qParams.viewMode = 'Transkription/Faksimile'; } // contextView if (!this.activatedRoute.snapshot.queryParamMap.get('contextView')) { qParams.contextView = 'manuscript'; } // navTabIdx: if none default is null if (!this.activatedRoute.snapshot.queryParamMap.get('navTabIdx')) { if (this.activatedRoute.snapshot.queryParamMap.get('contextView') === 'manuscript' || qParams.contextView === 'manuscript') { qParams.navTabIdx = '0'; } else { qParams.navTabIdx = '1'; } } this.naviService.updateRoute(qParams); } } diff --git a/nietzsche-beta-app/src/app/quant/quant.component.html b/nietzsche-beta-app/src/app/quant/quant.component.html new file mode 100644 index 0000000..9fd4a7c --- /dev/null +++ b/nietzsche-beta-app/src/app/quant/quant.component.html @@ -0,0 +1,5 @@ +
+ + + +
diff --git a/nietzsche-beta-app/src/app/quant/quant.component.scss b/nietzsche-beta-app/src/app/quant/quant.component.scss new file mode 100644 index 0000000..296d0ec --- /dev/null +++ b/nietzsche-beta-app/src/app/quant/quant.component.scss @@ -0,0 +1,18 @@ +.content-area { + height: 100%; + width: 80%; +} + +.nav-container { + height:100%; + //min-width: unset; // needed because angular sets an own min width!? + width:50%; + display: table-cell; +} + +.content-container { + height:100%; + // min-width: unset; // needed because angular sets an own min width!? + // width:20px; + display: table-cell; +} diff --git a/nietzsche-beta-app/src/app/quant/quant.component.spec.ts b/nietzsche-beta-app/src/app/quant/quant.component.spec.ts new file mode 100644 index 0000000..cfc0656 --- /dev/null +++ b/nietzsche-beta-app/src/app/quant/quant.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { QuantComponent } from './quant.component'; + +describe('QuantComponent', () => { + let component: QuantComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ QuantComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(QuantComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/nietzsche-beta-app/src/app/quant/quant.component.ts b/nietzsche-beta-app/src/app/quant/quant.component.ts new file mode 100644 index 0000000..18c41e3 --- /dev/null +++ b/nietzsche-beta-app/src/app/quant/quant.component.ts @@ -0,0 +1,21 @@ +import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import {ActivatedRoute, Params} from '@angular/router'; + +@Component({ + selector: 'app-quant', + templateUrl: './quant.component.html', + styleUrls: ['./quant.component.scss'] +}) +export class QuantComponent implements OnInit { + private readonly leftOffset = 361; + width: number = 1000; + + constructor(private activatedRoute: ActivatedRoute) { } + + ngOnInit() { + this.activatedRoute.queryParams.subscribe( (queryParams: Params) =>{ + this.width = (queryParams.navBarOpenState === 'true') ? window.innerWidth - this.leftOffset : window.innerWidth - 20; + }); + } + +} diff --git a/nietzsche-beta-app/src/app/tln-edition/common/page-result-filter.pipe.ts b/nietzsche-beta-app/src/app/tln-edition/common/page-result-filter.pipe.ts new file mode 100644 index 0000000..5c138b8 --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/common/page-result-filter.pipe.ts @@ -0,0 +1,16 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { ResultRange } from './paginator-result-status'; + +@Pipe({ + name: 'pageResultFilter' +}) +export class PageResultFilterPipe implements PipeTransform { + + transform(results: any[], range: ResultRange): any[] { + if (results.length <= range.start){ + return results; + } + return results.slice(range.start, range.end); + } + +} diff --git a/nietzsche-beta-app/src/app/tln-edition/common/paginator-result-status.ts b/nietzsche-beta-app/src/app/tln-edition/common/paginator-result-status.ts new file mode 100644 index 0000000..bb041cf --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/common/paginator-result-status.ts @@ -0,0 +1,38 @@ +import {PageEvent} from '@angular/material/paginator'; +import { FoundPage} from '../datatypes/search'; +import { PageIndexUpdater } from '../models'; + +export interface ResultRange { + start: number; + end: number; +} + +export class PaginatorResultStatus { + resultLength: number; + resultRange: ResultRange; + resultIndex: number = 0; + pageIndexUpdater: PageIndexUpdater; + pageSizeOptions: number[] = [5, 10, 25, 100]; + + constructor(resultLength: number, pageIndexUpdater?: PageIndexUpdater){ + this.resultLength = resultLength; + this.resultRange = { start: 0, end: this.resultLength-1 }; + this.pageIndexUpdater = pageIndexUpdater; + } + updateResultRange(index: number){ + this.resultRange = null; + this.resultIndex = index; + let newStart = index*this.resultLength + let newEnd = newStart+this.resultLength; + this.resultRange = { start: newStart, end: newEnd }; + console.log(this.resultRange); + } + showResults(event: PageEvent){ + this.resultLength = event.pageSize; + this.updateResultRange(event.pageIndex); + if (this.pageIndexUpdater != null){ + this.pageIndexUpdater.change.emit(event.pageIndex) + } + } + +} diff --git a/nietzsche-beta-app/src/app/tln-edition/common/select-array.ts b/nietzsche-beta-app/src/app/tln-edition/common/select-array.ts new file mode 100644 index 0000000..988eacb --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/common/select-array.ts @@ -0,0 +1,29 @@ +import { OnInit } from '@angular/core'; +import { DataProcessor } from '../models'; + +export interface CompareMapping { + compareValueKey: string; + sourceArrayKey: string; + targetArrayKey: string[]; + commonPropertyKey: string; +} +export class SelectFromArray implements DataProcessor { + constructor(private onInit: OnInit, private compareMapping: CompareMapping){} + + private getItem(item: any, path: string[]): any { + if (path.length == 1){ + return item[path[0]]; + } else { + return this.getItem(item[path[0]], path.slice(1)); + } + } + public processData(){ + const target = this.getItem(this.onInit, this.compareMapping.targetArrayKey.slice()); + const compareValue = this.onInit[this.compareMapping.compareValueKey] + this.onInit[this.compareMapping.sourceArrayKey].forEach(item =>{ + if(item[this.compareMapping.commonPropertyKey] === compareValue && target.indexOf(item) == -1){ + target.push(item) + } + }); + } +} diff --git a/nietzsche-beta-app/src/app/tln-edition/constants.ts b/nietzsche-beta-app/src/app/tln-edition/constants.ts index 4f092b1..ff23fa8 100644 --- a/nietzsche-beta-app/src/app/tln-edition/constants.ts +++ b/nietzsche-beta-app/src/app/tln-edition/constants.ts @@ -1,83 +1,102 @@ export {HIGHTLIGHT_CASES} from '../page-view/highlight_status'; export enum VIEW_OPTIONS { TRANSKRIPTION = 'Transkription', FAKSIMILE = 'Faksimile', SYNOPSIS = 'Transkription/Faksimile', SYNOPSIS_B = 'Faksimile/Transkription' } export const DEFAULT_VIEW_OPTION: string = VIEW_OPTIONS.SYNOPSIS_B; export const ONTOLOTY_PREFIX: string = 'http://www.nie.org/ontology/nietzsche#' /** * Route for TlnCrossrefComponent **/ export const TLN_CROSSREF_ROUTE: string = 'tln-crossref'; - /** * Route for TlnCrossrefComponent **/ export const TLN_CROSSREF_EDITOR_ROUTE: string = 'tln-crossref-editor'; /** * Route for TlnFulltextComponent **/ export const TLN_SEARCH_ROUTE: string = 'tln-search'; /** * Route for TlnManuscriptViewComponent **/ export const TLN_MANUSCRIPT_ROUTE: string = 'tln-manuscript'; +/** + * Route for TlnQuantComponent + **/ +export const TLN_QUANT_ROUTE: string = 'tln-quant'; /** * Route for TlnViewerComponent **/ export const TLN_VIEWER_ROUTE: string = 'tln-viewer'; /** * Param that refers to the context that should be shown, i.e. 'page' or 'manuscript'. **/ export const TLN_CONTEXT_VIEW_PARAM: string = 'contextView'; /** * Param that toggles fullscreen, value type: boolean. **/ export const TLN_FULLSCREEN_PARAM: string = 'fullscreen'; /** * Param for find text in page. **/ export const TLN_FIND_PARAM: string = 'find'; /** * Param for manuscript iri. **/ export const TLN_MANUSCRIPT_PARAM: string = 'manuscript'; /** * Param for navigation bar open state. **/ export const TLN_NAV_BAR_OPEN_STATE_PARAM: string = 'navBarOpenState'; /** * Param for page iri. **/ export const TLN_PAGE_PARAM: string = 'page'; /** * Param for result index, type: number. **/ -export const TLN_RESULT_INDEX_PARAM: string = 'resutlIndex'; +export const TLN_QUERY_PARAM: string = 'query'; +/** + * Param for quant query params, type: complex. + **/ +export const TLN_QUANT_QUERY_PARAM: string = 'quantQuery'; +/** + * Param for result index, type: number. + **/ +export const TLN_QUANT_RESULT_INDEX_PARAM: string = 'quantResultIndex'; +/** + * Param for result index, type: number. + **/ +export const TLN_RESULT_INDEX_PARAM: string = 'resultIndex'; +/** + * Param for search query params, type: complex. + **/ +export const TLN_SEARCH_QUERY_PARAM: string = 'searchQuery'; /** * Param for selected lines. **/ export const TLN_SELECTED_LINES_PARAM: string = 'selectedLines'; /** * Param for selected lines. **/ export const TLN_SELECTED_WORDS_PARAM: string = 'selectedWords'; /** * Param for iri of a genetic order of text versions. **/ export const TLN_TEXT_GENETIC_ORDER_PARAM: string = 'geneticOrder'; /** * Param for selected view option, e.g. 'Transkription', 'Faksimile', etc. **/ export const TLN_VIEW_OPTION_PARAM: string = 'viewMode'; /** * Param for multi instance zoom (i.e. tln-crossref and tln-fulltext). **/ export const TLN_MULTI_INSTANCE_ZOOM_PARAM: string = 'multiInstanceZoom'; /** * Param for zoom in tln-viewer. **/ export const TLN_ZOOM_PARAM: string = 'zoom'; diff --git a/nietzsche-beta-app/src/app/tln-edition/data_handler.ts b/nietzsche-beta-app/src/app/tln-edition/data_handler.ts index b3a5a6c..16bd4cc 100644 --- a/nietzsche-beta-app/src/app/tln-edition/data_handler.ts +++ b/nietzsche-beta-app/src/app/tln-edition/data_handler.ts @@ -1,163 +1,203 @@ import { OnInit, EventEmitter} from '@angular/core'; import { first, takeUntil } from 'rxjs/operators'; -import { BasicResultBindingElement, AskResult} from './datatypes/basic_datatype'; -import { DataProcessor, TlnQueryServiceInterface } from './models'; +import { BasicResultBindingElement, FusekiResults, AskResult} from './datatypes/basic_datatype'; +import { DataProcessor, StorageHandler, TlnQueryServiceInterface } from './models'; export interface KeyIriMapping { key: string; iri: string; } export interface ComplexKeyIriMapping { idIndex: number; mapping: KeyIriMapping[]; } /** * This interface can be used in order to handle data * of type {@link /classes/BasicResultBindingElement.html|BasicResultBindingElement}. **/ export interface Handler { /** * a class that instantiates data of type {@link /classes/BasicResultBindingElement.html|BasicResultBindingElement} **/ handler: typeof BasicResultBindingElement; /** * data handler's next key for retrieving and instantiating data. **/ next_key?: string; /** * a service that informs its listeners about its handler's data. **/ service?: any; process_data?: DataProcessor; + storage_handler?: StorageHandler; } /** * This class retrieves data from a query service and instantiates it using * corresponding handlers. **/ export class DataHandler { + dataTimestamp: number = 0; /** * the query services with which data is retrieved **/ queryService: TlnQueryServiceInterface; debug: boolean = false; /** * whether or not DataHandler is ready to retrieve data **/ ready: boolean = false; stop_processing = new EventEmitter(); start_processing = new EventEmitter(); processing_finished = new EventEmitter(); /** * @param component the component that uses this data handler **/ - constructor(private component: OnInit){} + constructor(protected component: OnInit){} /** * add a {@link /interfaces/Handler.html|Handler} * or an Array of handler keys to DataHandler. **/ public addHandler(key: string, handler: Handler | string[]) { this[key] = handler; } + private processData(results: FusekiResults, handler: typeof BasicResultBindingElement, key: string, is_target_array: boolean, iri?: string, next_iri?: string){ + if (is_target_array){ + this.processArrayData(results, handler, key, iri, next_iri) + } else { + this.processObjectData(results, handler, key, iri, next_iri) + } + } + private processArrayData(results: FusekiResults, handler: typeof BasicResultBindingElement, key: string, iri?: string, next_iri?: string){ + this.component[key] = (handler.use_id) ? handler.convertData(results, iri, this[key]['service']) : handler.convertData(results,null, this[key]['service']); + this.processing_finished.emit(true); + if (this.component[key].length > 0 && this[key]['next_key'] != null){ + let use_next_iri = (next_iri != null) ? next_iri : this.component[key][0].id; + this.getData(this[key]['next_key'], use_next_iri); + } else if(this[key]['process_data'] != undefined && this[key]['process_data'] != null){ + this[key]['process_data'].processData(); + } + } + private processObjectData(results: FusekiResults, handler: typeof BasicResultBindingElement, key: string, iri?: string, next_iri?: string){ + this.component[key] = handler.convertData(results, iri, this[key]['service'])[0]; + this.processing_finished.emit(true); + if (next_iri != null && this[key]['next_key'] != null){ + this.getData(this[key]['next_key'], next_iri); + } else if(this[key]['process_data'] != undefined && this[key]['process_data'] != null){ + this[key]['process_data'].processData(); + } + } /** * Retrieve and instantiate data * @param key data handler key * @param iri iri that should be passed to query * @param next_iri use next_iri instead of the iri of the first item in the current data array. **/ public getData(key: string, iri?: string, next_iri?: string) { if (Array.isArray(this[key])){ this[key].forEach(value =>this.getData(value, iri)); if (next_iri != null && this[key]['next_key'] != null){ this.getData(this[key]['next_key'], next_iri); } } else { this.start_processing.emit(true); - let handler = this[key]['handler']; + const handler = this[key]['handler']; if (this.debug && key == 'geneticOrders' ) { //console.log(this[key]['handler'], key, iri); console.log(handler.getQuery(iri, handler.query_key)) //console.log(this[key]['service']); } - let is_target_array = Array.isArray(this.component[key]); - if (!is_target_array){ + const is_target_array = Array.isArray(this.component[key]); + const query = handler.getQuery(iri, handler.query_key) + const queryKey = encodeURI(query).replace('+', ''); + const results = null//this.retrieveLocalData(queryKey); + if (results != null) { + this.processData(results, handler, key, is_target_array, iri, next_iri); + } else { this.queryService.getData(handler.getQuery(iri, handler.query_key)).pipe(takeUntil(this.stop_processing) || first()).subscribe(results => { - this.component[key] = handler.convertData(results, iri, this[key]['service'])[0]; - if (next_iri != null && this[key]['next_key'] != null){ - this.getData(this[key]['next_key'], next_iri); - } else if(this[key]['process_data'] != undefined && this[key]['process_data'] != null){ - this[key]['process_data'].processData(); - } + //this.storeDataLocally(results, queryKey); + this.processData(results, handler, key, is_target_array, iri, next_iri); }); - } else { - this.queryService.getData(handler.getQuery(iri, handler.query_key)).pipe(takeUntil(this.stop_processing)).subscribe(results => { - this.component[key] = (handler.use_id) ? handler.convertData(results, iri, this[key]['service']) : handler.convertData(results,null, this[key]['service']); - if (this.component[key].length > 0 && this[key]['next_key'] != null){ - let use_next_iri = (next_iri != null) ? next_iri : this.component[key][0].id; - this.getData(this[key]['next_key'], use_next_iri); - } else if(this[key]['process_data'] != undefined && this[key]['process_data'] != null){ - this[key]['process_data'].processData(); - } - }); - } - this.processing_finished.emit(true); + } } } public getData4Keys(key: string, datatypeKeyIriMapping: ComplexKeyIriMapping) { if (Array.isArray(this[key])){ this[key].forEach(value =>this.getData4Keys(value, datatypeKeyIriMapping)); } else { let handler = this[key]['handler']; if (this.debug) { console.log(this[key]['handler'], datatypeKeyIriMapping); } let is_target_array = Array.isArray(this.component[key]); let iri = datatypeKeyIriMapping.mapping[datatypeKeyIriMapping.idIndex]; if (!is_target_array){ this.queryService.getData(handler.getComplexQuery(datatypeKeyIriMapping.mapping)).pipe(takeUntil(this.stop_processing) || first()).subscribe(results => { this.component[key] = handler.convertData(results, iri, this[key]['service'])[0]; }); } else { this.queryService.getData(handler.getQuery(datatypeKeyIriMapping.mapping)).pipe(takeUntil(this.stop_processing)).subscribe(results => { this.component[key] = (handler.use_id) ? handler.convertData(results, iri) : handler.convertData(results); if (this.component[key].length > 0 && this[key]['next_key'] != null){ datatypeKeyIriMapping.mapping[datatypeKeyIriMapping.idIndex] = this.component[key][0].id; this.getData4Keys(this[key]['next_key'], datatypeKeyIriMapping); } }); } } } public getDataWithNewHandlerIf(key: string, subjectIri: string, typeIri: string, handlerTrue: Handler, handlerFalse: Handler, iri?: string, next_iri?: string) { let complexMapping: KeyIriMapping[] = [ { key: 'id', iri: subjectIri }, { key: 'type', iri: typeIri } ] this.queryService.getData(AskResult.getComplexQuery(complexMapping)).pipe(first()).subscribe(result => { this[key] = (AskResult.getAnswer(result)) ? handlerTrue : handlerFalse; console.log(key, subjectIri, typeIri, result, this[key]); this.getData(key, iri, next_iri); }); } public conditionalAddHandler(askQuery: string, key: string, handlerTrue: Handler, handlerFalse: Handler) { this.queryService.getData(askQuery).pipe(first()).subscribe(result => { this[key] = (AskResult.getAnswer(result)) ? handlerTrue : handlerFalse; }); } - + protected retrieveLocalData(queryKey: string): FusekiResults { + const rawData = sessionStorage.getItem(queryKey) + if (rawData != null){ + const data = JSON.parse(rawData); + if (data.timestamp > this.dataTimestamp){ + return data.results; + } + } + return null; + } + protected storeDataLocally(results: FusekiResults, queryKey: string){ + const data = { timestamp: Date.now(), results: results }; + try { + sessionStorage.setItem(queryKey, JSON.stringify(data)); + console.log(sessionStorage.length); + } catch(e){ + console.log(e); + sessionStorage.clear(); + } + } /** * reset all data belonging to key **/ public resetData(key){ this.queryService.resetData(key) if (Array.isArray(this[key])){ this[key].forEach(value =>this.resetData(value)); } else { this.component[key] = (Array.isArray(this.component[key])) ? [] : null; } } /** * set a query service to DataHandler and switch status ready to true. **/ public setQueryService(queryService: TlnQueryServiceInterface){ this.queryService = queryService; this.ready = true; + this.queryService.error_emitter.subscribe( + error =>{this.processing_finished.emit(true); + }); } } diff --git a/nietzsche-beta-app/src/app/tln-edition/datatypes/basic_datatype.ts b/nietzsche-beta-app/src/app/tln-edition/datatypes/basic_datatype.ts index 19f16cc..07e8a91 100644 --- a/nietzsche-beta-app/src/app/tln-edition/datatypes/basic_datatype.ts +++ b/nietzsche-beta-app/src/app/tln-edition/datatypes/basic_datatype.ts @@ -1,218 +1,239 @@ import { Parser, Generator } from 'sparqljs'; import { KeyIriMapping } from '../data_handler'; /** * this interface specifies the head of {@link /interfaces/FusekiResults.html|FusekiResults}. **/ interface FusekiVars { vars: string[]; } /** * this interface specifies the bindings of {@link /interfaces/FusekiResults.html|FusekiResults}. **/ -interface FusekiBindings { +export interface FusekiBindings { bindings: []; } /** * this interface specifies the results as they are retrieved from an Apache Jena Fuseki server. **/ export interface FusekiResults { results: FusekiBindings; head: FusekiVars } export interface FusekiBoolean { head: any; boolean: boolean; } +export class FusekiResultsInstance { + public static fixFusekiResults(fusekiResults: FusekiResults){ + if (fusekiResults.results.bindings.length > 0 + && Object.keys(Array.of(...fusekiResults.results.bindings)[0]).length < fusekiResults.head.vars.length) { + fusekiResults.head.vars = fusekiResults.head.vars.filter(key => Object.keys(Array.of(...fusekiResults.results.bindings)[0]).includes(key)); + } + } +} /** * This is the basic datatype that instantiates an element of {@link /interfaces/FusekiResults.html|FusekiResults}. * * All datatypes can be subclassed from this type in order to create SPARQL-queries, retrieve data and convert it * to the corresponding datatypes. **/ export class BasicResultBindingElement { /** * the internal default key for replacing {@link /classes/BasicResultBindingElement.html#query|query} by "id" * in {@link /classes/BasicResultBindingElement.html#getQuery|getQuery} if "key" is omitted. **/ protected static readonly default_key: string = 'id'; /** * the SPARQL-query of this datatype. **/ static readonly query: string = `SELECT ?id ?p ?o WHERE { ?id ?p ?o. }`; /** * the public key for replacing {@link /classes/BasicResultBindingElement.html#query|query} by "id". **/ public static readonly query_key: string = null; /** * the id of this datatype. **/ public id: string; /** * the raw data of this datatype, i.e. a singular bindings element of {@link /interfaces/FusekiBindings.html|FusekiBindings}. **/ protected data: any; /** * whether or not to pass the id used for the query to the constructor and * use it as the value of the property specified by query_key. **/ public static readonly use_id: boolean = false; /** * a service that this datatype can use in order to communicate with its data holder. **/ protected service: any; /** * The constructor creates a datatype from the data. * * @param id if omitted the id will be retrieved from data **/ constructor(data: any, id?: string, service?: any){ this.data = data; this.service = service; if (id != undefined && id != null && id != ''){ let key = (Object.getPrototypeOf(this).constructor.use_id && Object.getPrototypeOf(this).constructor.query_key != null) ? Object.getPrototypeOf(this).constructor.query_key : 'id'; this[key] = id; } if (this.id == null){ this.id = this.getData4Key('id'); } } /** * This function returns the value of the content specified by "key" from {@link /classes/BasicResultBindingElement.html#data|data}. * * @param key the key that specifies the content * * @returns {any} the value of the content if key exists else null **/ protected getData4Key(key: string): any { if (!this.data.hasOwnProperty(key)) { return null; } if (this.data[key].datatype == 'http://www.w3.org/2001/XMLSchema#boolean'){ return JSON.parse(this.data[key].value); } else if (this.data[key].datatype == 'http://www.w3.org/2001/XMLSchema#integer'){ return Number(this.data[key].value); } return this.data[key].value; } + public removeService(){ + this.service = null; + } /** * This method returns the SPARQL query of this BasicResultBindingElement. * The query can be modified by providing an "id" and "key" such that every "key" in * the query will be replaced by "id". * * If "key" is omitted {@link /classes/BasicResultBindingElement.html#default_key|default_key} will be used. * * @param id will replace key in query * @param key will be replaced by id. **/ public static getQuery(id?: string, key?: string): string { if (typeof(id) === 'undefined' || id === null || id == ''){ return this.query; } else { if (key == null || key == ''){ key = this.default_key; } let parser = new Parser(); let sparqlGenerator = new Generator({}); let parsedQuery = parser.parse(this.query) for (var k = 0; k < parsedQuery.where.length; k++){ if (parsedQuery.where[k].patterns != undefined){ for (var j = 0; j < parsedQuery.where[k].patterns.length; j++){ if (parsedQuery.where[k].patterns[j].triples != undefined) { for (var i = 0; i < parsedQuery.where[k].patterns[j].triples.length; i++){ if(parsedQuery.where[k].patterns[j].triples[i]['subject']['value'] == key){ parsedQuery.where[k].patterns[j].triples[i]['subject'] = { termType: "NamedNode", value: id }; } else if(parsedQuery.where[k].patterns[j].triples[i]['object']['value'] == key){ parsedQuery.where[k].patterns[j].triples[i]['object'] = { termType: "NamedNode", value: id }; } else if(parsedQuery.where[k].patterns[j].triples[i]['predicate']['value'] == key){ parsedQuery.where[k].patterns[j].triples[i]['predicate'] = { termType: "NamedNode", value: id }; } } } } } else if (parsedQuery.where[k].triples != undefined){ for (var i = 0; i < parsedQuery.where[k].triples.length; i++){ if(parsedQuery.where[k].triples[i]['subject']['value'] == key){ parsedQuery.where[k].triples[i]['subject'] = { termType: "NamedNode", value: id }; } else if (parsedQuery.where[k].triples[i]['object']['value'] == key){ parsedQuery.where[k].triples[i]['object'] = { termType: "NamedNode", value: id }; } else if (parsedQuery.where[k].triples[i]['predicate']['value'] == key){ parsedQuery.where[k].triples[i]['predicate'] = { termType: "NamedNode", value: id }; } } } } return sparqlGenerator.stringify(parsedQuery); } } + public static contentConforms2Type(data: FusekiResults): boolean { + let parser = new Parser(); + let parsedQuery = parser.parse(this.query) + let variableCounter = parsedQuery['variables'].length; + parsedQuery['variables'].forEach(item =>{ + if(data.head.vars.includes(item.value)){ + variableCounter--; + } + }); + return variableCounter == 0; + } public static getComplexQuery(keyIriMapping: KeyIriMapping[]): string { let parser = new Parser(); let sparqlGenerator = new Generator({}); let parsedQuery = parser.parse(this.query) for (let mapping of keyIriMapping){ let key = mapping.key; let id = mapping.iri; for (var i = 0; i < parsedQuery.where[0].triples.length; i++){ if(parsedQuery.where[0].triples[i]['subject']['value'] == key){ parsedQuery.where[0].triples[i]['subject'] = { termType: "NamedNode", value: id }; } else if (parsedQuery.where[0].triples[i]['object']['value'] == key){ parsedQuery.where[0].triples[i]['object'] = { termType: "NamedNode", value: id }; } else if (parsedQuery.where[0].triples[i]['predicate']['value'] == key){ parsedQuery.where[0].triples[i]['predicate'] = { termType: "NamedNode", value: id }; } } } return sparqlGenerator.stringify(parsedQuery); } - /** * This function returns 'results.bindings' of {@link /interfaces/FusekiResults.html|FusekiResults}. **/ public static getContent(data: FusekiResults): [] { return data['results']['bindings']; } /** * This static function instantiates the subclasses of {@link /classes/BasicResultBindingElement.html|BasicResultBindingElement} from * the data retrieved by executing the query that is provided by {@link /classes/BasicResultBindingElement.html#getQuery|getQuery}. * * @param this a subclass of BasicResultBindingElement * @param data the fuseki result json * @param id the id that has been used in order to retrieve the data and that will identify the instantiation of the subclass. * @param service a means to communicate with the data holder. * * @returns Array of subclass instantiations **/ public static convertData(this: T, data: FusekiResults, id?: string, service?: any): Array> { let elements = []; let content = this.getContent(data); for (var i = 0; i < content.length; i++){ let element = new this(content[i], id, service) as InstanceType; elements.push(element); } return elements; } } export class AskResult extends BasicResultBindingElement { static readonly query: string = ` PREFIX tln: ASK { ?id a ?type. }`; public static getAnswer(answer: FusekiBoolean): boolean { return answer.boolean; } } export class IsReconstructedKonvolut extends AskResult { static readonly query: string = ` PREFIX tln: ASK { ?id a tln:ReconstructedKonvolut. }`; public static readonly query_key: string = 'id'; } diff --git a/nietzsche-beta-app/src/app/tln-edition/datatypes/earlier_version.ts b/nietzsche-beta-app/src/app/tln-edition/datatypes/earlier_version.ts index 8a27dd7..cd8a220 100644 --- a/nietzsche-beta-app/src/app/tln-edition/datatypes/earlier_version.ts +++ b/nietzsche-beta-app/src/app/tln-edition/datatypes/earlier_version.ts @@ -1,54 +1,31 @@ import { BasicResultBindingElement } from './basic_datatype'; +import { WordStub } from './word'; /** * This is the 'earlier version' stub instantiation of an element of {@link /interfaces/FusekiResults.html|FusekiResults}. - * It extends {@link /classes/BasicResultBindingElement.html|BasicResultBindingElement}. **/ -export class TlnEarlierVersionStub extends BasicResultBindingElement { - /** - * the internal default key for replacing {@link /classes/TlnWord.html#query|query} by "id" - * in {@link /classes/TlnWord.html#getQuery|getQuery} if "key" is omitted. - **/ - static readonly default_key: string = 'word'; - /** - * the public key for replacing {@link /classes/TlnWord.html#query|query} by "id". - **/ - static readonly query_key: string = 'word'; +export class TlnEarlierVersionStub extends WordStub { /** * the SPARQL-query of this datatype. **/ static readonly query: string = ` PREFIX tln: PREFIX rdf: SELECT ?id ?text WHERE { ?word tln:wordHasEarlierVersion ?id. ?id tln:hasText ?text. }`; - /** - * text of earlier version - **/ - text: string; - - /** - * The constructor creates a datatype from the data. - * - * @param id if omitted the id will be retrieved from data - **/ - constructor(data: any, id?: string, service?: any){ - super(data, id, service) - this.text = this.getData4Key('text'); - } } export class TlnOverwrittenStub extends TlnEarlierVersionStub { /** * the SPARQL-query of this datatype. **/ static readonly query: string = ` PREFIX tln: PREFIX rdf: SELECT ?id ?text WHERE { ?word (tln:wordHasWordParts/rdf:rest*/rdf:first/tln:overwritesWord|tln:overwritesWord) ?id. ?id tln:hasText ?text. }`; } diff --git a/nietzsche-beta-app/src/app/tln-edition/datatypes/manuscript.ts b/nietzsche-beta-app/src/app/tln-edition/datatypes/manuscript.ts index 9f78a48..b883ebc 100644 --- a/nietzsche-beta-app/src/app/tln-edition/datatypes/manuscript.ts +++ b/nietzsche-beta-app/src/app/tln-edition/datatypes/manuscript.ts @@ -1,237 +1,307 @@ import { BasicResultBindingElement, FusekiResults } from './basic_datatype'; import { Manuscript, Page } from '../models'; import { TlnPositionalStyleMarkup } from './positional-markup'; import { PageStub, TlnPage } from './page'; export class ManuscriptStub extends BasicResultBindingElement implements Manuscript { static readonly query: string = ` PREFIX data: PREFIX tln: SELECT ?id ?title ?type WHERE { ?id a tln:ArchivalManuscriptUnity ; tln:hasTitle ?title; tln:hasManuscriptType ?type. }`; title: string; type: string; constructor (data: any, id?: string, service?: any) { super(data, id, service); this.title = this.getData4Key('title'); this.type = this.getData4Key('type'); } } export class TlnExtManuscript extends ManuscriptStub { /** * the internal default key for replacing {@link /classes/TlnLine.html#query|query} by "id" * in {@link /classes/TlnLine.html#getQuery|getQuery} if "key" is omitted. **/ static readonly default_key: string = 'manuscript'; static readonly query: string = ` PREFIX data: PREFIX tln: PREFIX rdf: PREFIX stoff: SELECT DISTINCT ?gsaSignature ?title ?thumbImage ?type ?archivalicSignature WHERE { ?manuscript tln:hasPages/rdf:first/tln:hasFaksimileImage/tln:hasThumburl ?thumbImage; tln:hasGsaSignature ?gsaSignature; tln:hasTitle ?title; tln:hasManuscriptType ?type. OPTIONAL { ?manuscript tln:hasArchivalicSignature ?archivalicSignature. } }`; /** * the public key for replacing {@link /classes/TlnLine.html#query|query} by "id". **/ static readonly query_key: string = 'manuscript'; thumbImage: string; gsaSignature?: string; archivalicSignature?: string; constructor (data: any, id?: string, service?: any) { super(data, id, service); this.thumbImage = this.getData4Key('thumbImage'); this.gsaSignature = this.getData4Key('gsaSignature'); this.archivalicSignature = this.getData4Key('archivalicSignature'); } } +export class Manuscript4Selection extends ManuscriptStub { + static readonly query: string = ` + PREFIX data: + PREFIX tln: + PREFIX rdf: + PREFIX stoff: + + SELECT DISTINCT ?id ?title ?type WHERE { + ?id a tln:ArchivalManuscriptUnity; + tln:hasTitle ?title; + tln:hasManuscriptType ?type. + FILTER (?type = "Mappe") + }`; +} +export class ManuscriptPages extends ManuscriptStub { + /** + * the internal default key for replacing {@link /classes/TlnLine.html#query|query} by "id" + * in {@link /classes/TlnLine.html#getQuery|getQuery} if "key" is omitted. + **/ + static readonly default_key: string = 'id'; + static readonly query: string = ` + PREFIX data: + PREFIX tln: + PREFIX rdf: + PREFIX stoff: + + SELECT DISTINCT ?id ?title ?type ?page ?number WHERE { + ?id a tln:ArchivalManuscriptUnity; + tln:hasTitle ?title; + tln:hasManuscriptType ?type; + tln:hasPages/rdf:rest*/rdf:first ?page. + ?page tln:hasNumber ?number. + FILTER (?type = "Mappe") + }`; + /** + * the public key for replacing {@link /classes/TlnLine.html#query|query} by "id". + **/ + static readonly query_key: string = 'id'; + pages: Page[] = []; + + constructor (data: any, id?: string, service?: any) { + super(data, id, service); + } + public static convertData(this: T, data: FusekiResults, id?: string, service?: any): Array> { + let elements = []; + let content = this.getContent(data); + for (var i = 0; i < content.length; i++){ + let element = new ManuscriptPages(content[i], id, service); + let pages = []; + if (content[i]['page'] != undefined && content[i]['page'] != null){ + pages = (content[i]['title'] != undefined && content[i]['title'] != null) + ? TlnPage.convertData({ head: { vars: []}, results: { bindings: [ content[i] ] } }, content[i]['page']['value']) + : PageStub.convertData({ head: { vars: []}, results: { bindings: [ content[i] ] } }, content[i]['page']['value']); + } + if (elements.length > 0 && elements[elements.length-1].id == element.id){ + if (pages.length > 0){ + elements[elements.length-1].pages.push(pages[0]); + } + } else { + if (pages.length > 0){ + element.pages.push(pages[0]); + } + elements.push(element) + } + } + //console.log(elements) + return elements; + } + +} export class ReconstructedKonvolut extends ManuscriptStub { /** * the internal default key for replacing {@link /classes/TlnLine.html#query|query} by "id" * in {@link /classes/TlnLine.html#getQuery|getQuery} if "key" is omitted. **/ static readonly default_key: string = 'manuscript'; static readonly query: string = ` PREFIX data: PREFIX tln: PREFIX rdf: PREFIX stoff: SELECT DISTINCT ?id ?manuscriptTitle ?title ?type ?page ?number ?description WHERE { ?manuscript tln:partsBelongToReconstructedKonvolut ?id. ?id tln:hasTitle ?manuscriptTitle; tln:hasDescription/tln:textHasContent ?description; tln:hasManuscriptType ?type; tln:hasPages/rdf:rest*/rdf:first ?page. OPTIONAL { ?page tln:hasNumber ?number.} OPTIONAL { ?archivalicUnity a tln:ArchivalManuscriptUnity; tln:hasPages/rdf:rest*/rdf:first ?page; tln:hasTitle ?title. } }`; /** * the public key for replacing {@link /classes/TlnLine.html#query|query} by "id". **/ static readonly query_key: string = 'manuscript'; pages: Page[] = []; description: string; constructor (data: any, id?: string, service?: any) { super(data, id, service); this.title = this.getData4Key('manuscriptTitle'); this.description = this.getData4Key('description'); } public static convertData(this: T, data: FusekiResults, id?: string, service?: any): Array> { let elements = []; let content = this.getContent(data); for (var i = 0; i < content.length; i++){ let element = new ReconstructedKonvolut(content[i], id, service); let pages = []; if (content[i]['page'] != undefined && content[i]['page'] != null){ pages = (content[i]['title'] != undefined && content[i]['title'] != null) ? TlnPage.convertData({ head: { vars: []}, results: { bindings: [ content[i] ] } }, content[i]['page']['value']) : PageStub.convertData({ head: { vars: []}, results: { bindings: [ content[i] ] } }, content[i]['page']['value']); } if (elements.length > 0 && elements[elements.length-1].id == element.id){ if (pages.length > 0){ elements[elements.length-1].pages.push(pages[0]); } } else { if (pages.length > 0){ element.pages.push(pages[0]); } elements.push(element) } } //console.log(elements) return elements; } } export class ManuscriptDescription extends BasicResultBindingElement { /** * the internal default key for replacing {@link /classes/TlnLine.html#query|query} by "id" * in {@link /classes/TlnLine.html#getQuery|getQuery} if "key" is omitted. **/ static readonly default_key: string = 'manuscript'; static readonly query: string = ` PREFIX data: PREFIX tln: PREFIX rdf: PREFIX stoff: SELECT DISTINCT ?id ?text ?description_markup ?sStyle ?start ?end WHERE { ?manuscript tln:hasDescription ?id. ?id tln:textHasContent ?text. OPTIONAL { ?id tln:textHasMarkup ?description_markup. ?description_markup stoff:hasCSS ?sStyle; stoff:standoffMarkupHasStartIndex ?start; stoff:standoffMarkupHasEndIndex ?end.} }`; /** * the public key for replacing {@link /classes/TlnLine.html#query|query} by "id". **/ static readonly query_key: string = 'manuscript'; text: string; markups: TlnPositionalStyleMarkup[]; constructor (data: any, id?: string, service?: any) { super(data, id, service); this.text = this.getData4Key('text'); this.markups = []; } public static convertData(this: T, data: FusekiResults, id?: string, service?: any): Array> { let elements = []; let content = this.getContent(data); for (var i = 0; i < content.length; i++){ let element = new ManuscriptDescription(content[i], id, service); let markups = (content[i]['description_markup'] != undefined && content[i]['description_markup'] != null) ? TlnPositionalStyleMarkup.convertData({ head: { vars: []}, results: { bindings: [ content[i] ] } }, content[i]['description_markup']['value']) : []; if (elements.length > 0 && elements[elements.length-1].id == element.id){ if (markups.length > 0){ elements[elements.length-1].markups.push(markups[0]); } } else { if (markups.length > 0){ element.markups.push(markups[0]); } elements.push(element) } } //console.log(elements) return elements; } } export class ManuscriptEarlierDescription extends BasicResultBindingElement { /** * the internal default key for replacing {@link /classes/TlnLine.html#query|query} by "id" * in {@link /classes/TlnLine.html#getQuery|getQuery} if "key" is omitted. **/ static readonly default_key: string = 'manuscript'; static readonly query: string = ` PREFIX data: PREFIX tln: PREFIX stoff: SELECT ?id ?text ?author ?citation ?description_markup ?sStyle ?start ?end WHERE { ?manuscript tln:hasEarlierDescriptions ?id. ?id tln:textHasContent ?text; tln:hasAuthor ?author; tln:hasCitation ?citation. OPTIONAL { ?id tln:textHasMarkup ?description_markup. ?description_markup stoff:hasCSS ?sStyle; stoff:standoffMarkupHasStartIndex ?start; stoff:standoffMarkupHasEndIndex ?end.} }`; /** * the public key for replacing {@link /classes/TlnLine.html#query|query} by "id". **/ static readonly query_key: string = 'manuscript'; text: string; author: string; citation: string; markups: TlnPositionalStyleMarkup[]; constructor (data: any, id?: string, service?: any) { super(data, id, service); this.text = this.getData4Key('text'); this.author = this.getData4Key('author'); this.citation = this.getData4Key('citation'); this.markups = []; } public static convertData(this: T, data: FusekiResults, id?: string, service?: any): Array> { let elements = []; let content = this.getContent(data); for (var i = 0; i < content.length; i++){ let element = new ManuscriptEarlierDescription(content[i], id, service); let markups = (content[i]['description_markup'] != undefined && content[i]['description_markup'] != null) ? TlnPositionalStyleMarkup.convertData({ head: { vars: []}, results: { bindings: [ content[i] ] } }, content[i]['description_markup']['value']) : []; if (elements.length > 0 && elements[elements.length-1].id == element.id){ if (markups.length > 0){ elements[elements.length-1].markups.push(markups[0]); } } else { if (markups.length > 0){ element.markups.push(markups[0]); } elements.push(element) } } //console.log(elements) return elements; } } diff --git a/nietzsche-beta-app/src/app/tln-edition/datatypes/quant.ts b/nietzsche-beta-app/src/app/tln-edition/datatypes/quant.ts new file mode 100644 index 0000000..aebb29a --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/datatypes/quant.ts @@ -0,0 +1,251 @@ +import { Parser, Generator } from 'sparqljs'; +import { BasicResultBindingElement, FusekiResults} from './basic_datatype'; +import { ManuscriptPages } from './manuscript'; +import { TextQuality } from '../models'; + + +export class QueryJson { + public static createFilterObject(filters: Object[] ): Object{ + let filterObject = { type: "filter", expression: null }; + if (filters.length > 1){ + filterObject.expression = { type: "operation", operator: "||", args: [] } + let filter = filters.pop() + this.pushFilter2Args(filters, filterObject.expression.args, filter); + } else { + filterObject.expression = filters[0]; + } + return filterObject; + } + public static createOperation(id: string, variable_name: string): Object { + return { type: "operation", operator: "=", args: [ + { termType: "Variable", value: variable_name}, + { termType: "NamedNode", value: id } + ]}; + } + private static pushFilter2Args(filters: Object[], args: Object[], latestFilter: Object){ + if (filters.length > 1){ + let filter = filters.pop(); + let disjunction = { type: "operation", operator: "||", args: [] } + disjunction.args.push(filter); + disjunction.args.push(latestFilter); + this.pushFilter2Args(filters, args, disjunction); + } else { + args.push(latestFilter); + args.push(filters.pop()); + } + } + public static hasSyntaxError(query: string): boolean { + const parser = new Parser(); + try { + const parsedQuery = parser.parse(query); + } catch(e){ + return true; + } + return false; + } + public static getSyntaxError(query: string): string { + const parser = new Parser(); + try { + const parsedQuery = parser.parse(query); + } catch(e){ + return String(e); + } + return ''; + } + +} +export class NumericResultRow extends BasicResultBindingElement { + static readonly hasText = "http://www.nie.org/ontology/nietzsche#hasText"; + static readonly hasCleanText = "http://www.nie.org/ontology/nietzsche#hasCleanText"; + static readonly hasEditedText = "http://www.nie.org/ontology/nietzsche#hasEditedText"; + static readonly hasCleanEditedText = "http://www.nie.org/ontology/nietzsche#hasCleanEditedText"; + static readonly manuscript_variable = "manuscript"; + static readonly text_variable = "id"; + static readonly raw_text_variable = "raw_text"; + static readonly edited_text_variable = "edited_text"; + static readonly word_variable = "word"; + static readonly bindObject = { type: "bind", variable: { termType: "Variable", value: NumericResultRow.text_variable }, + expression: { type: "operation", operator: "if", args: [ + { type: "operation", operator: "bound", args: [ { termType: "Variable", value: NumericResultRow.edited_text_variable } ] }, + { termType: "Variable", value: NumericResultRow.edited_text_variable }, + { termType: "Variable", value: NumericResultRow.raw_text_variable }, + ]} + }; + static readonly orderObject = { expression: { termType: "Variable", value: NumericResultRow.text_variable }, descending: false }; + static readonly punctuationPattern = /[.,!;:\-_–()“„]/g + static readonly query: string = ` + PREFIX tln: + PREFIX rdf: + + SELECT ?id ?word ?numText ?total WHERE { + ?manuscript a tln:ArchivalManuscriptUnity; + tln:hasManuscriptType "Mappe"; + tln:hasPages/rdf:rest*/rdf:first ?page. + ?page a tln:Page; + tln:hasWords/rdf:rest*/rdf:first ?word. + }`; + wordIds: string[] = []; + numText: number; + numProperties: number = 1; + numPropertiesPercent: number = 1; + numPropertiesIncludeMulti: number = 1; + numTextPercent: number = 1; + numPropertyTextPercent: number = 1; + total: number; + + constructor (data: any, id?: string, service?: any) { + super(data, id, service); + this.numText = this.getData4Key('numText'); + this.total = this.getData4Key('total'); + this.wordIds.push(this.getData4Key('word')); + this.updatePercentages(); + } + public updateResult(item: NumericResultRow) { + this.wordIds = this.wordIds.concat(item.wordIds); + this.numPropertiesIncludeMulti = this.wordIds.length + this.numProperties = (new Set(this.wordIds)).size; + this.updatePercentages(); + } + private updatePercentages(){ + this.numPropertiesPercent = Math.round((this.numProperties/this.total)*10000)/100; + this.numTextPercent = Math.round((this.numText/this.total)*10000)/100; + this.numPropertyTextPercent = Math.round((this.numProperties/this.numText)*10000)/100; + } + public static convertData(this: T, data: FusekiResults, id?: string, service?: any): Array> { + if (!this.contentConforms2Type(data)){ + return []; + } + let elements = []; + let content = this.getContent(data); + for (var i = 0; i < content.length; i++){ + let element = new NumericResultRow(content[i], service); + if (elements.length > 0 && elements[elements.length-1].id == element.id){ + elements[elements.length-1].updateResult(element); + } else { + elements.push(element) + } + } + //console.log(elements) + return elements; + } + private static createGroup(whereItems: Object[], aggregate_variable: string, group?: string): Object { + if (group != undefined && group != null) { + return { + type: "group", + patterns: [ + { queryType: "SELECT", + variables: [ + { expression: { type: "aggregate", aggregation: "count", distinct: false, expression: { termType: "Variable", value: group }}, + variable: { termType: "Variable", value: aggregate_variable } }, + { termType: "Variable", value: group } + ], + where: whereItems, + type: "query", + group: [ { expression: { termType: "Variable", value: group }} ] + } ] + }; + } else { + return { + type: "group", + patterns: [ + { queryType: "SELECT", + variables: [ + { expression: { type: "aggregate", aggregation: "count", distinct: false, expression: { termType: "Variable", value: this.word_variable }}, + variable: { termType: "Variable", value: aggregate_variable } } + ], + where: [{ type: "bgp", triples: whereItems }], + type: "query" + } ] + }; + } + } + private static hasTextObject(textQuality: TextQuality): Object { + const hasText = (textQuality.clean) ? NumericResultRow.hasCleanText : NumericResultRow.hasText; + const objectVariable = (textQuality.preferEditedText) ? NumericResultRow.raw_text_variable : NumericResultRow.text_variable; + return { + subject: { termType: "Variable", value: NumericResultRow.word_variable }, + predicate: { termType: "NamedNode", value: hasText }, + object: { termType: "Variable", value: objectVariable } + }; + } + private static optionalEditedTextObject(textQuality: TextQuality): Object { + const hasEditedText = (textQuality.clean) ? NumericResultRow.hasCleanEditedText : NumericResultRow.hasEditedText; + return { type: "optional", patterns: [ { type: "bgp", triples: [ { + subject: { termType: "Variable", value: NumericResultRow.word_variable }, + predicate: { termType: "NamedNode", value: hasEditedText }, + object: { termType: "Variable", value: NumericResultRow.edited_text_variable } + } ] } ] + }; + } + private static insertTextConditions(parsedQuery: Object, textQuality: TextQuality){ + parsedQuery['where'][0].triples.push(this.hasTextObject(textQuality)); + if(textQuality.preferEditedText){ + parsedQuery['where'].push(this.optionalEditedTextObject(textQuality)); + parsedQuery['where'].push(this.bindObject); + } + } + public static getSelectableQuery(selectableProperties: SelectableWordProperty[], scopus: ManuscriptPages[], textQuality: TextQuality, text?: string, ignoreCase?: boolean, orderDesc?: boolean): string { + let parser = new Parser(); + let sparqlGenerator = new Generator({}); + let parsedQuery = parser.parse(this.query) + let basicWhereTriples = parsedQuery.where[0].triples.slice(); + this.insertTextConditions(parsedQuery, textQuality); + const whereBeforeProperties = JSON.parse(JSON.stringify(parsedQuery.where));//deep cloning + selectableProperties.forEach(selectableProperty =>{ + parsedQuery.where[0].triples.push({ + subject: { termType: "Variable", value: this.word_variable }, + predicate: { termType: "NamedNode", value: selectableProperty.id }, + object: { termType: "Variable", value: selectableProperty.id.substring(selectableProperty.id.indexOf('#')+1) } + }) + }); + if (text != undefined && text != null && text != '') { + let regexFilter = { type: "filter", expression: { + type: "operation", operator: "regex", args: [ + { termType: "Variable", value: this.text_variable }, + { termType: "Literal", value: text } + ] + } + } + if (ignoreCase != undefined && ignoreCase){ + regexFilter.expression.args.push({ termType: "Literal", value: "i" }); + } + parsedQuery.where.push(regexFilter); + } + let totalGroup = this.createGroup(basicWhereTriples, "total"); + let numGroup = this.createGroup(whereBeforeProperties, "numText", this.text_variable); + if (scopus.length > 0){ + let filters = scopus.map(manuscript =>QueryJson.createOperation(manuscript.id, this.manuscript_variable)); + let filterObject = QueryJson.createFilterObject(filters); + parsedQuery.where.push(filterObject); + totalGroup['patterns'][0].where.push(filterObject); + numGroup['patterns'][0].where.push(filterObject); + } + parsedQuery.where.push(totalGroup); + parsedQuery.where.push(numGroup); + parsedQuery['order'] = [ this.orderObject ] + return sparqlGenerator.stringify(parsedQuery); + } +} + +export class SelectableWordProperty extends BasicResultBindingElement{ + static readonly query: string = ` + PREFIX tln: + PREFIX rdfs: + PREFIX skos: + + SELECT DISTINCT ?id ?label ?propName WHERE { + ?id rdfs:subPropertyOf tln:selectableWordProperty; + skos:prefLabel ?label. + #BIND(STRAFTER(STR(?id), STR(tln:)) as ?propName) + }`; + id: string; + label: string; + //propName: string; + + constructor (data: any, id?: string, service?: any) { + super(data, id, service); + this.id = this.getData4Key('id'); + this.label = this.getData4Key('label'); + //this.propName = this.getData4Key('propName'); + } +} diff --git a/nietzsche-beta-app/src/app/tln-edition/datatypes/search.ts b/nietzsche-beta-app/src/app/tln-edition/datatypes/search.ts index 9210e46..82280e7 100644 --- a/nietzsche-beta-app/src/app/tln-edition/datatypes/search.ts +++ b/nietzsche-beta-app/src/app/tln-edition/datatypes/search.ts @@ -1,130 +1,132 @@ import { BasicResultBindingElement, FusekiResults} from './basic_datatype'; +import { TLN_SEARCH_ROUTE } from '../constants'; import { TlnWord } from './word'; export class TlnExtWord extends TlnWord { startLine?: string; endLine?: string; constructor(data: any, id?: string, service?: any){ super(data, id, service) this.startLine = this.getData4Key('startLine'); this.endLine = this.getData4Key('endLine'); } } export class PageResult { words: TlnExtWord[] = []; constructor(words: TlnExtWord[]){ this.words = words; } public getWords(): string[] { return this.words.map(word =>word.id); } public getStartLine(): string { let sortedWords = this.words.sort((word0, word1) =>word0.line_number - word1.line_number) if (sortedWords.length == 0){ return ''; } return (sortedWords[0].startLine != undefined && sortedWords[0].startLine != null) ? sortedWords[0].startLine : sortedWords[0].line; } public getEndLine(): string { let sortedWords = this.words.sort((word0, word1) =>word0.line_number - word1.line_number) if (sortedWords.length == 0){ return ''; } return (sortedWords[sortedWords.length-1].endLine != undefined && sortedWords[sortedWords.length-1].endLine != null) ? sortedWords[sortedWords.length-1].endLine : sortedWords[sortedWords.length-1].line; } } export class FoundPage extends BasicResultBindingElement{ + static readonly storagePrefix = TLN_SEARCH_ROUTE + '_' + FoundPage.name static readonly query: string = ` PREFIX tln: PREFIX rdf: SELECT DISTINCT ?id ?manuscript ?title ?number ?word ?text ?line ?line_number ?startLine ?endLine WHERE { ?id a tln:Page; tln:hasNumber ?number; tln:hasPseudoText ?fulltext. FILTER regex(?fulltext, "#find#", "s"). ?manuscript a tln:ArchivalManuscriptUnity; tln:hasManuscriptType "Mappe"; tln:hasPages/rdf:rest*/rdf:first ?id; tln:hasTitle ?title. ?id tln:hasWords/rdf:rest*/rdf:first ?word. ?word tln:hasOutputText ?text; tln:wordBelongsToLine ?line. ?line tln:lineHasNumber ?line_number. OPTIONAL{ ?previouseNode rdf:rest/rdf:first ?line; rdf:first ?startLine.} OPTIONAL{ ?myNode rdf:first ?line; rdf:rest/rdf:first ?endLine.} #FILTER(). } order by ?id ?line_number`; title: string number: string; manuscript: string results: PageResult[] = []; constructor (data: any, id?: string, service?: any) { super(data, id, service); this.title = this.getData4Key('title'); this.number = this.getData4Key('number'); this.manuscript = this.getData4Key('manuscript'); } public removeIncompleteResults(searchTerms: string[]) { this.results = this.results.filter(result =>searchTerms.every(searchText =>result.words.filter(word =>word.text.match('^[^\w\s]*' + searchText + '.*')).length > 0)) } /** * This method returns the parametrized SPARQL query of this FoundPage * * If "key" is omitted {@link /classes/BasicResultBindingElement.html#default_key|default_key} will be used. * * @param find the search text * @param key will be ignored. **/ public static getQuery(find?: string, key?: string): string { if(find == undefined || find == null){ return this.query; } let words = find.split(' ') let find_regex = words.join('.*') + '.*' let filter = 'FILTER regex(?text, "^[^\\\\w]?(' + words.join('.*|') + '.*)")'; let query = this.query.replace('#find#', find_regex).replace('#FILTER()', filter); //console.log(query); return query; } public static convertData(this: T, data: FusekiResults, id?: string, service?: any): Array> { let elements = []; let pages = []; let searchTerms = service.getSearchTerms(); let content = this.getContent(data); let currentResult: PageResult = null; let currentPage: FoundPage = null; for (var i = 0; i < content.length; i++){ let page = new FoundPage(content[i], id, service); if(content[i]['word'] != undefined && content[i]['word'] != null) { let words = TlnExtWord.convertData({ head: { vars: []}, results: { bindings: [ content[i] ] } }, content[i]['word']['value']); if (pages.length == 0 || pages.map(page =>page.id).indexOf(page.id) == -1){ currentResult = new PageResult(words); currentPage = page; currentPage.results.push(currentResult) pages.push(currentPage) } else { if (currentResult.words.indexOf(words[0]) == -1){ if (currentResult.words.length > 0 && (Math.abs(Math.min(...currentResult.words.map(word =>word.line_number)) - words[0].line_number) > 4 || Math.abs(Math.max(...currentResult.words.map(word =>word.line_number)) - words[0].line_number) > 4)){ currentResult = new PageResult(words); currentPage.results.push(currentResult); } else { currentResult.words.push(words[0]); } } } } } pages.forEach(page =>page.removeIncompleteResults(searchTerms)); return pages.filter(page =>page.results.length > 0); } } diff --git a/nietzsche-beta-app/src/app/tln-edition/datatypes/word.ts b/nietzsche-beta-app/src/app/tln-edition/datatypes/word.ts index 77d0247..2e8186f 100644 --- a/nietzsche-beta-app/src/app/tln-edition/datatypes/word.ts +++ b/nietzsche-beta-app/src/app/tln-edition/datatypes/word.ts @@ -1,98 +1,139 @@ +import { BasicResultBindingElement } from './basic_datatype'; import { TlnPositionalObject } from './positional_object'; import { Word } from '../models'; /** * This is the word instantiation of an element of {@link /interfaces/FusekiResults.html|FusekiResults}. * It extends {@link /classes/BasicResultBindingElement.html|BasicResultBindingElement}. **/ export class TlnWord extends TlnPositionalObject implements Word { /** * the SPARQL-query of this datatype. **/ static readonly query: string = ` PREFIX tln: PREFIX rdf: SELECT ?id ?text ?edited_text ?left ?top ?width ?height ?transform ?line ?line_number ?deleted ?deletion_path WHERE { ?page tln:hasWords/rdf:rest*/rdf:first ?id. ?id tln:wordBelongsToLine ?line; tln:hasText ?text; tln:hasTranskriptionPosition ?tp. ?tp tln:hasLeft ?left; tln:hasTop ?top; tln:hasWidth ?width; tln:hasHeight ?height. ?line tln:lineHasNumber ?line_number. BIND(exists{ {?id tln:wordIsDeletedByPath ?path} UNION{ ?id tln:wordHasWordParts/rdf:rest*/rdf:first ?word_part. ?word_part tln:hasTranskriptionPosition ?tp; tln:wordIsDeletedByPath ?path} } as ?deleted) OPTIONAl { ?tp tln:hasTransform ?transform.} OPTIONAl { ?id tln:hasEditedText ?edited_text.} OPTIONAl { ?id tln:wordIsDeletedByPath/tln:hasDAttribute ?deletion_path.} OPTIONAl { ?id tln:wordHasWordParts/rdf:rest*/rdf:first ?word_part. ?word_part tln:hasTranskriptionPosition ?tp; tln:wordIsDeletedByPath/tln:hasDAttribute ?deletion_path} } `; /** * the text of this word **/ text: string; /** * the text of this word as it has been edited by the editors. **/ edited_text?: string; /** * the id of the line to which this word belongs. **/ line: string; /** * the number of the line to which this word belongs. * */ line_number: number; /** * is this word deleted. **/ deleted: boolean; /** * a deletion path **/ deletion_path?: string; /** * The constructor creates a datatype from the data. * * @param id if omitted the id will be retrieved from data **/ constructor(data: any, id?: string, service?: any){ super(data, id, service) this.text = this.getData4Key('text'); this.edited_text = this.getData4Key('edited_text'); this.line = this.getData4Key('line'); this.line_number = this.getData4Key('line_number'); this.deleted = this.getData4Key('deleted'); this.deletion_path = this.getData4Key('deletion_path'); } } /** * This is the faksimile word instantiation of an element of {@link /interfaces/FusekiResults.html|FusekiResults}. * It extends {@link /classes/BasicResultBindingElement.html|BasicResultBindingElement}. **/ export class FaksimileWord extends TlnWord { /** * the SPARQL-query of this datatype. **/ static readonly query: string = ` PREFIX tln: PREFIX rdf: SELECT ?id ?text ?edited_text ?left ?top ?width ?height ?transform ?line ?line_number ?deleted WHERE { ?page tln:hasWords/rdf:rest*/rdf:first ?id. ?id tln:wordBelongsToLine ?line; tln:hasText ?text; tln:hasFaksimilePosition ?fp. ?fp tln:hasLeft ?left; tln:hasTop ?top; tln:hasWidth ?width; tln:hasHeight ?height. ?line tln:lineHasNumber ?line_number. BIND(exists{{?id tln:wordIsDeletedByPath ?path} UNION { ?id tln:wordHasWordParts/rdf:rest*/rdf:first/tln:wordIsDeletedByPath ?path} } as ?deleted) OPTIONAl { ?fp tln:hasTransform ?transform.} OPTIONAl { ?id tln:hasEditedText ?edited_text.} } `; } +/** + * This is the word stub instantiation of an element of {@link /interfaces/FusekiResults.html|FusekiResults}. + * It extends {@link /classes/BasicResultBindingElement.html|BasicResultBindingElement}. + **/ +export class WordStub extends BasicResultBindingElement { + /** + * the internal default key for replacing {@link /classes/TlnWord.html#query|query} by "id" + * in {@link /classes/TlnWord.html#getQuery|getQuery} if "key" is omitted. + **/ + static readonly default_key: string = 'word'; + /** + * the public key for replacing {@link /classes/TlnWord.html#query|query} by "id". + **/ + static readonly query_key: string = 'word'; + /** + * the SPARQL-query of this datatype. + **/ + static readonly query: string = ` + PREFIX tln: + PREFIX rdf: + + SELECT ?id ?text WHERE { + ?id a tln:Word; + tln:hasText ?text. + }`; + /** + * text of word + **/ + text: string; + + /** + * The constructor creates a datatype from the data. + * + * @param id if omitted the id will be retrieved from data + **/ + constructor(data: any, id?: string, service?: any){ + super(data, id, service) + this.text = this.getData4Key('text'); + } +} diff --git a/nietzsche-beta-app/src/app/tln-edition/datatypes/word_presentation.ts b/nietzsche-beta-app/src/app/tln-edition/datatypes/word_presentation.ts new file mode 100644 index 0000000..5357225 --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/datatypes/word_presentation.ts @@ -0,0 +1,82 @@ +import { Parser, Generator } from 'sparqljs'; +import { BasicResultBindingElement, FusekiResults} from './basic_datatype'; +import { ManuscriptStub } from './manuscript'; +import { PageStub } from './page'; +import { WordStub } from './word'; +import { QueryJson } from './quant'; + +export class PresentationWord extends WordStub { + line_number: number; + + constructor (data: any, id?: string, service?: any) { + super(data, id, service); + this.line_number = this.getData4Key('line_number'); + } +} +export class PageWords extends PageStub { + words: WordStub[] = []; + + constructor (data: any, id?: string, service?: any) { + super(data, id, service); + } +} +export class ManuscriptPageWords extends ManuscriptStub { + static readonly query_key: string = 'word'; + static readonly default_key: string = 'word'; + static readonly query: string = ` + PREFIX tln: + PREFIX rdf: + + SELECT ?id ?word ?title ?type ?page ?number ?text ?line_number WHERE { + ?word tln:hasText ?text; + tln:wordBelongsToLine/tln:lineHasNumber ?line_number. + ?page a tln:Page; + tln:hasWords/rdf:rest*/rdf:first ?word; + tln:hasNumber ?number. + ?id a tln:ArchivalManuscriptUnity; + tln:hasPages/rdf:rest*/rdf:first ?page; + tln:hasManuscriptType ?type; + tln:hasTitle ?title. + } ORDER BY ?id ?page`; + pages: PageWords[] = []; + + constructor (data: any, id?: string, service?: any) { + super(data, id, service); + } + public static convertData(this: T, data: FusekiResults, id?: string, service?: any): Array> { + let manuscripts = []; + let content = this.getContent(data); + for (var i = 0; i < content.length; i++){ + let manuscript = new ManuscriptPageWords(content[i], id, service); + let page = PageWords.convertData({ head: { vars: []}, results: { bindings: [ content[i] ] } }, content[i]['page']['value'])[0] + let word = PresentationWord.convertData({ head: { vars: []}, results: { bindings: [ content[i] ] } }, content[i]['word']['value'])[0] + if (manuscripts.length > 0 && manuscript.id == manuscripts[manuscripts.length-1].id){ + if (manuscripts[manuscripts.length-1].pages.length > 0 + && page.id == manuscripts[manuscripts.length-1].pages[manuscripts[manuscripts.length-1].pages.length-1].id){ + manuscripts[manuscripts.length-1].pages[manuscripts[manuscripts.length-1].pages.length-1].words.push(word); + } else { + if (manuscripts[manuscripts.length-1].pages.length > 0){ + console.log(page) + } + page.words.push(word) + manuscripts[manuscripts.length-1].pages.push(page) + } + } else { + page.words.push(word) + manuscript.pages.push(page); + manuscripts.push(manuscript); + } + } + console.log(manuscripts); + return manuscripts; + } + public static getParameterizedQuery(wordIds: string[]): string { + let parser = new Parser(); + let sparqlGenerator = new Generator({}); + let parsedQuery = parser.parse(this.query) + let filters = wordIds.map(id =>QueryJson.createOperation(id, this.query_key)); + let filterObject = QueryJson.createFilterObject(filters); + parsedQuery.where.push(filterObject); + return sparqlGenerator.stringify(parsedQuery); + } +} diff --git a/nietzsche-beta-app/src/app/tln-edition/models.ts b/nietzsche-beta-app/src/app/tln-edition/models.ts index 8dbc037..8152adc 100644 --- a/nietzsche-beta-app/src/app/tln-edition/models.ts +++ b/nietzsche-beta-app/src/app/tln-edition/models.ts @@ -1,64 +1,106 @@ import { Observable } from 'rxjs'; +import {HttpErrorResponse } from '@angular/common/http'; +import {Sort} from '@angular/material/sort'; import {EventEmitter} from '@angular/core'; import { Page } from '../page-view/models'; +import { Handler } from './data_handler'; +import { Manuscript4Selection} from './datatypes/manuscript'; +import { SelectableWordProperty} from './datatypes/quant'; export { externalAssignClass, externalAssignStyle, Configuration, Continuation, Copyright, Identifier, Image, Line, LineStub, Reference, Manuscript, Page, Point, Position, PositionalObject, TextField, TextByForeignHand, Word, USE_EXTERNAL_TOOLTIP } from '../page-view/models'; +export interface AccordionItemStatus { + expanded: boolean; + disabled: boolean; +} +export interface AccordionStatus { + [name: string]: AccordionItemStatus; +} export interface ManuscriptUnity { numberOfPages: number; title: string; } export interface NavigationPage extends Page { title: string; index?: number; } export interface TextVersion { id: string; title: string; manuscript?: string; textUnities: TextUnity[]; } export interface TextUnity { id: string; number: string; startLine?: number; endLine?: number; belongsToPage?: string; hasFaksimileImage?: boolean; } /** * This interface specifies a query service that returns * the response from a HttpClient.post as an Observable. * */ export interface TlnQueryServiceInterface { - reset_data: EventEmitter; + error_emitter: EventEmitter; /** * @param query: The query to run. * @returns response: the response as an Observable. * */ getData(query: string): Observable; resetData(key: string): void; + reset_data: EventEmitter; + baseUrl: string; } export interface TextGeneticOrder { id: string; textVersions: TextVersion[]; } +export interface TextQuality { + clean: boolean; + preferEditedText: boolean; +} export interface DataProcessor { processData(): void; } +export interface QueryProperties { + ignoreCase: boolean; + resultIndex?: number; + restrictKorpusOnContext: boolean; + selectedManuscripts: Manuscript4Selection[]; +} +export interface QuantQuery extends QueryProperties { + altQuery?: string; + filterValue?: string; + selectedWordProperties: SelectableWordProperty[]; + textQuality: TextQuality; + currentTable: number; + resultIndices: number[]; + sortArray: Sort[]; + text?: string; + dataKey?: string; +} +export interface StorageHandler { + getStorageKey(handler: Handler): string; + getStorageDuration(handler: Handler): number; +} +export interface PageIndexUpdater { + change: EventEmitter; +} diff --git a/nietzsche-beta-app/src/app/tln-edition/quant_data_handler.ts b/nietzsche-beta-app/src/app/tln-edition/quant_data_handler.ts new file mode 100644 index 0000000..6e8deb7 --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/quant_data_handler.ts @@ -0,0 +1,36 @@ +import { OnInit, EventEmitter} from '@angular/core'; +import { first, takeUntil } from 'rxjs/operators'; +import { BasicResultBindingElement, AskResult} from './datatypes/basic_datatype'; +import { DataHandler, Handler } from './data_handler'; + +/** + * This class retrieves quantitative data from a query service and instantiates it using + * corresponding handlers. + **/ +export class QuantitativeDataHandler extends DataHandler { + /** + * @param component the component that uses this data handler + **/ + constructor(protected component: OnInit){ + super(component) + } + /** + * Retrieve and instantiate data + * @param key data handler key + * @param query iri that should be passed to query + **/ + public getData(key: string, query: string, raw_data_key?: string) { + this.start_processing.emit(true); + let handler = this[key]['handler']; + this.queryService.getData(query).pipe(takeUntil(this.stop_processing) || first()).subscribe(results => { + this.component[key] = handler.convertData(results); + if (raw_data_key != undefined && raw_data_key != null){ + this.component[raw_data_key] = results; + } + this.processing_finished.emit(true); + if (this[key]['process_data'] != undefined && this[key]['process_data'] != null){ + this[key]['process_data'].processData(); + } + }); + } +} diff --git a/nietzsche-beta-app/src/app/tln-edition/route-reader.ts b/nietzsche-beta-app/src/app/tln-edition/route-reader.ts index c3b394f..482f5ee 100644 --- a/nietzsche-beta-app/src/app/tln-edition/route-reader.ts +++ b/nietzsche-beta-app/src/app/tln-edition/route-reader.ts @@ -1,47 +1,51 @@ import { OnInit } from '@angular/core'; import { Router, ActivatedRoute, Params } from '@angular/router'; export interface ParamContent { param: string; type: string; debug?: string; ignore?: boolean; } export interface Mapping { [name: string]: ParamContent; } export class RouteReader implements OnInit { protected mapping: Mapping; protected routerParams: Params; constructor(protected router: Router, protected activatedRoute: ActivatedRoute ) { } ngOnInit() { this.activatedRoute.queryParams.subscribe(params => { this.readParams(params) }); } protected readParams(params: Params) { this.routerParams = params; for(let key of Object.keys(this.mapping)){ if (this.mapping[key]['ignore'] == undefined || !this.mapping[key]['ignore']){ let paramsKey = this.mapping[key]['param']; if (this.routerParams[paramsKey] != null){ if (this.mapping[key]['type'] == 'number'){ this[key] = Number(this.routerParams[paramsKey]) } else if (this.mapping[key]['type'] == 'boolean'){ this[key] = (this.routerParams[paramsKey] == 'true') + } else if (this.mapping[key]['type'] == 'URI' || this.mapping[key]['type'] == 'URL'){ + this[key] = decodeURI(this.routerParams[paramsKey]) + } else if (this.mapping[key]['type'] == 'complex'){ + this[key] = JSON.parse(this.routerParams[paramsKey]) } else if (Array.isArray(this[key]) && !Array.isArray(this.routerParams[paramsKey])) { this[key] = JSON.parse(this.routerParams[paramsKey]) } else { this[key] = this.routerParams[paramsKey]; } if (this.mapping[key]['debug'] != null){ console.log(this.mapping[key]['debug'], this[key]); } } } } } } diff --git a/nietzsche-beta-app/src/app/tln-edition/route-updater.ts b/nietzsche-beta-app/src/app/tln-edition/route-updater.ts index 569326c..df246ef 100644 --- a/nietzsche-beta-app/src/app/tln-edition/route-updater.ts +++ b/nietzsche-beta-app/src/app/tln-edition/route-updater.ts @@ -1,56 +1,72 @@ import { Router, ActivatedRoute, Params } from '@angular/router'; import { Mapping, RouteReader } from './route-reader'; export class RouteUpdater extends RouteReader { protected mapping: Mapping; protected routerParams: Params; protected currentRoute: string; parentActivatedRoute: ActivatedRoute; constructor(protected router: Router, protected activatedRoute: ActivatedRoute ) { super(router, activatedRoute); if(this.currentRoute == undefined || this.currentRoute == null){ this.currentRoute = (this.activatedRoute.snapshot.routeConfig != null) ? this.activatedRoute.snapshot.routeConfig.path : null; } } + public setParam(key: string, value: any){ + if(this.mapping.hasOwnProperty(key)){ + this[key] = value; + this.updateParams(); + } + } + private removeData(dataBearer: any) { + if (typeof dataBearer != 'string' && typeof dataBearer != 'number' && typeof dataBearer != 'boolean'){ + if (Array.isArray(dataBearer)){ + dataBearer.forEach(data =>this.removeData(data)); + } else { + dataBearer['data'] = null; + Object.values(dataBearer).forEach(item =>{ + if (item != null){ + this.removeData(item); + } + }); + } + } + } protected updateParams(launch?: boolean) { let newRouterParam = {}; for(let key of Object.keys(this.mapping)){ let paramsKey = this.mapping[key]['param']; if(this[key] != null){ - if (Array.isArray(this[key]) && this[key].length > 0){ + if (this.mapping[key]['type'] == 'complex' || (Array.isArray(this[key]) && this[key].length > 0)){ + this.removeData(this[key]); newRouterParam[paramsKey] = JSON.stringify(this[key]); + } else if (this.mapping[key]['type'] == 'URI' || this.mapping[key]['type'] == 'URL') { + newRouterParam[paramsKey] = encodeURI(this[key]); } else { newRouterParam[paramsKey] = this[key]; } } } - /*for(let key of Object.keys(this.routerParams)){ - if(newRouterParam[key] == null){ - newRouterParam[key] = this.routerParams[key]; - } - }*/ let parentActivatedRoute = (this.activatedRoute.parent != null) ? this.activatedRoute.parent : this.parentActivatedRoute; if(parentActivatedRoute != undefined && parentActivatedRoute != null){ parentActivatedRoute.url.subscribe(url=>{ let parentPath = url[0].path; if (launch != undefined && launch){ - let link = this.router.createUrlTree([ parentPath + '/' + this.currentRoute], { queryParams: newRouterParam }); + let link = this.router.createUrlTree([ parentPath + '/' + this.currentRoute], { queryParams: newRouterParam, queryParamsHandling: 'merge' }); window.open(link.toString(), '_blank') } else { - //this.router.navigate([ parentPath + '/' + this.currentRoute], { queryParams: newRouterParam }); this.router.navigate([parentPath + '/' + this.currentRoute], { queryParams: newRouterParam, queryParamsHandling: 'merge' }); } }); } else { if (launch != undefined && launch){ - let link = this.router.createUrlTree([ this.currentRoute], { queryParams: newRouterParam }); + let link = this.router.createUrlTree([ this.currentRoute], { queryParams: newRouterParam, queryParamsHandling: 'merge' }); window.open(link.toString(), '_blank') } else { - //this.router.navigate([ this.currentRoute], { queryParams: newRouterParam }); this.router.navigate([this.currentRoute], { queryParams: newRouterParam, queryParamsHandling: 'merge' }); } } } } diff --git a/nietzsche-beta-app/src/app/tln-edition/services.ts b/nietzsche-beta-app/src/app/tln-edition/services.ts index 5cd2f3d..b614c15 100644 --- a/nietzsche-beta-app/src/app/tln-edition/services.ts +++ b/nietzsche-beta-app/src/app/tln-edition/services.ts @@ -1,3 +1,4 @@ export { TlnQueryService } from './tln-query.service'; +export { TlnCacheQueryService } from './tln-query.service'; export { PageViewService } from '../page-view/page-view.service'; export { ConfigurableComponent } from '../page-view/configurable-component'; diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-crossref/navigation/navigation.component.ts b/nietzsche-beta-app/src/app/tln-edition/tln-crossref/navigation/navigation.component.ts index 358b874..a38728b 100644 --- a/nietzsche-beta-app/src/app/tln-edition/tln-crossref/navigation/navigation.component.ts +++ b/nietzsche-beta-app/src/app/tln-edition/tln-crossref/navigation/navigation.component.ts @@ -1,107 +1,107 @@ import { Component, OnInit, Input } from '@angular/core'; import { Router, ActivatedRoute, Params } from '@angular/router'; import { TlnQueryServiceInterface, Reference, ManuscriptUnity, NavigationPage } from '../../models'; import { TlnManuscriptUnity, TlnNavigationPage} from '../../datatypes/navigation'; import { DEFAULT_VIEW_OPTION, TLN_VIEWER_ROUTE, TLN_CROSSREF_ROUTE, TLN_CONTEXT_VIEW_PARAM, TLN_FULLSCREEN_PARAM, TLN_FIND_PARAM, TLN_PAGE_PARAM, TLN_MANUSCRIPT_PARAM, TLN_SELECTED_LINES_PARAM, TLN_VIEW_OPTION_PARAM, TLN_ZOOM_PARAM, VIEW_OPTIONS, ONTOLOTY_PREFIX } from '../../constants'; import { IsReconstructedKonvolut } from '../../datatypes/basic_datatype'; import { TlnLine} from '../../datatypes/line'; import { TlnTextGeneticOrder} from '../../datatypes/text_version'; import { Mapping } from '../../route-reader'; import { RouteUpdater } from '../../route-updater'; import { ComplexKeyIriMapping, DataHandler, KeyIriMapping } from '../../data_handler'; -import { PageViewService, TlnQueryService } from '../../services'; +import { PageViewService, TlnCacheQueryService } from '../../services'; import { TlnInformationComponent, ParentInformation } from '../../tln-information/tln-information.component'; import { PageInformation } from '../../tln-information/page-information'; @Component({ selector: 'crossref-navigation', templateUrl: './navigation.component.html', styleUrls: ['./navigation.component.css'] }) export class NavigationComponent extends RouteUpdater { /** * OPTIONAL pass a queryService with method * {@link /interfaces/TlnQueryServiceInterface.html#getData|getData} * to TlnPageViewComponent. **/ @Input() queryService: TlnQueryServiceInterface; /** * whether or not to show page view in fullscreen mode. **/ fullscreen: boolean = false; current_iri: string; current_manuscript_iri: string; current_page: NavigationPage; pageInformation: PageInformation; previous_page: NavigationPage; next_page: NavigationPage; showArchivalManuscriptUnity: boolean = false; dataHandler: DataHandler = new DataHandler(this); geneticOrders: TlnTextGeneticOrder[] = []; selectedLines: string[] = []; private readonly PAGE_CONTEXT_VIEW: string = TLN_PAGE_PARAM; private readonly MANUSCRIPT_CONTEXT_VIEW: string = TLN_MANUSCRIPT_PARAM; contextView: string = this.PAGE_CONTEXT_VIEW; private readonly increment: number = 0.333; private readonly decrement: number = this.increment*-1; protected currentRoute: string = TLN_CROSSREF_ROUTE; protected mapping: Mapping = { contextView: { param: TLN_CONTEXT_VIEW_PARAM, type: "string" }, current_iri: { param: TLN_PAGE_PARAM, type: "string" }, current_manuscript_iri: { param: TLN_MANUSCRIPT_PARAM, type: "string" }, fullscreen: { param: TLN_FULLSCREEN_PARAM, type: "boolean" } } routerParams: Params; selectedViewOption: string = DEFAULT_VIEW_OPTION updating: boolean = false; viewOptions: string[] = [ VIEW_OPTIONS.TRANSKRIPTION, VIEW_OPTIONS.FAKSIMILE, VIEW_OPTIONS.SYNOPSIS, VIEW_OPTIONS.SYNOPSIS_B ]; - constructor(private pageViewService: PageViewService, private localQueryService: TlnQueryService, protected router: Router, protected activatedRoute: ActivatedRoute ) { + constructor(private pageViewService: PageViewService, private localQueryService: TlnCacheQueryService, protected router: Router, protected activatedRoute: ActivatedRoute ) { super(router, activatedRoute); } ngOnInit() { let tlnQueryService = (this.queryService != null) ? this.queryService : this.localQueryService; this.dataHandler.addHandler('navigation_page', ['current_page', 'geneticOrders'] ); this.dataHandler.addHandler('current_page', { 'handler': TlnNavigationPage }); this.dataHandler.addHandler('geneticOrders', { 'handler': TlnTextGeneticOrder}); this.dataHandler.setQueryService(tlnQueryService); this.dataHandler.start_processing.subscribe( (started: boolean) =>{ this.updating = true; }); this.dataHandler.processing_finished.subscribe( (finished: boolean) =>{ this.updating = false; }); super.ngOnInit(); } changeContext(){ this.contextView = (this.contextView == this.PAGE_CONTEXT_VIEW) ? this.MANUSCRIPT_CONTEXT_VIEW : this.PAGE_CONTEXT_VIEW; //this.current_genetic_order_iri = 'none'; this.updateParams(); } private getPageTitle(page?: NavigationPage, numPages?: number): string { if (page == null){ return ''; } let indexPrefix = (numPages != null) ? page.index + '/' + numPages : page.index; return indexPrefix + ': ' + page.title + ' ' + page.number; } protected readParams(params: Params){ super.readParams(params); if (this.dataHandler.ready && (this.current_page == null || this.current_page.id != this.current_iri)){ this.dataHandler.resetData('navigation_page') this.dataHandler.getData('navigation_page', this.current_iri); } } private setCurrentIri(pageIri: string){ this.dataHandler.stop_processing.emit(true); this.current_iri = pageIri; this.currentRoute = TLN_VIEWER_ROUTE; this.updateParams(); } private toggleFullscreen(){ this.fullscreen = !this.fullscreen; this.updateParams(); } } diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-crossref/page-version-view/tln-page-version-view.component.ts b/nietzsche-beta-app/src/app/tln-edition/tln-crossref/page-version-view/tln-page-version-view.component.ts index 429c8a4..a42fed8 100644 --- a/nietzsche-beta-app/src/app/tln-edition/tln-crossref/page-version-view/tln-page-version-view.component.ts +++ b/nietzsche-beta-app/src/app/tln-edition/tln-crossref/page-version-view/tln-page-version-view.component.ts @@ -1,35 +1,35 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { TlnQueryServiceInterface} from '../../models'; -import { TlnQueryService } from '../../services'; +import { TlnCacheQueryService } from '../../services'; import { DataHandler } from '../../data_handler'; import { TlnTextGeneticOrder} from '../../datatypes/text_version'; @Component({ selector: 'tln-page-version-view', templateUrl: './tln-page-version-view.component.html', - providers: [ TlnQueryService ], + providers: [ TlnCacheQueryService], styleUrls: ['./tln-page-version-view.component.css'] }) export class TlnPageVersionViewComponent implements OnInit { /** * OPTIONAL pass a queryService with method * {@link /interfaces/TlnQueryServiceInterface.html#getData|getData} * to TlnPageViewComponent. **/ @Input() queryService: TlnQueryServiceInterface; @Output() textGenesisSelected = new EventEmitter(); @Input() current_iri: string; dataHandler: DataHandler = new DataHandler(this); geneticOrders: TlnTextGeneticOrder[] = []; - constructor(private localQueryService: TlnQueryService) { } + constructor(private localQueryService: TlnCacheQueryService) { } ngOnInit() { let tlnQueryService = (this.queryService != null) ? this.queryService : this.localQueryService; this.dataHandler.addHandler('geneticOrders', { 'handler': TlnTextGeneticOrder}); this.dataHandler.setQueryService(tlnQueryService); this.dataHandler.getData('geneticOrders', this.current_iri); } } diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-crossref/tln-crossref.component.ts b/nietzsche-beta-app/src/app/tln-edition/tln-crossref/tln-crossref.component.ts index 4b4750d..508083c 100644 --- a/nietzsche-beta-app/src/app/tln-edition/tln-crossref/tln-crossref.component.ts +++ b/nietzsche-beta-app/src/app/tln-edition/tln-crossref/tln-crossref.component.ts @@ -1,223 +1,223 @@ import { Component, EventEmitter, OnInit, OnDestroy, Output, Input } from '@angular/core'; import { Router, ActivatedRoute, Params } from '@angular/router'; import { DomSanitizer } from '@angular/platform-browser'; import { DataProcessor, TlnQueryServiceInterface, TextVersion, NavigationPage} from '../models'; import { TLN_CROSSREF_ROUTE, TLN_VIEWER_ROUTE, TLN_CONTEXT_VIEW_PARAM, TLN_FULLSCREEN_PARAM, TLN_FIND_PARAM, TLN_PAGE_PARAM, TLN_MANUSCRIPT_PARAM, TLN_SELECTED_LINES_PARAM, TLN_TEXT_GENETIC_ORDER_PARAM, TLN_VIEW_OPTION_PARAM, TLN_ZOOM_PARAM, VIEW_OPTIONS, ONTOLOTY_PREFIX } from '../constants'; import { IsReconstructedKonvolut } from '../datatypes/basic_datatype'; import { TlnNavigationPage} from '../datatypes/navigation'; import { TlnLine} from '../datatypes/line'; import { TlnWord} from '../datatypes/word'; import { ManuscriptStub } from '../datatypes/manuscript'; import { TlnPageWithTextGeneticOrder, TlnTextGeneticOrder, TlnStandaloneTextVersion} from '../datatypes/text_version'; import { Mapping } from '../route-reader'; import { RouteUpdater } from '../route-updater'; import { ComplexKeyIriMapping, DataHandler, KeyIriMapping } from '../data_handler'; -import { PageViewService, TlnQueryService } from '../services'; +import { PageViewService, TlnCacheQueryService } from '../services'; import { TlnInformationComponent, ParentInformation } from '../tln-information/tln-information.component'; import { PageInformation } from '../tln-information/page-information'; @Component({ selector: 'tln-crossref', templateUrl: './tln-crossref.component.html', - providers: [ TlnQueryService ], + providers: [ TlnCacheQueryService], styleUrls: ['./tln-crossref.component.css'] }) export class TlnCrossrefComponent extends RouteUpdater implements DataProcessor, OnDestroy { /** * OPTIONAL pass a queryService with method * {@link /interfaces/TlnQueryServiceInterface.html#getData|getData} * to TlnPageViewComponent. **/ @Input() queryService: TlnQueryServiceInterface; @Output() textGenesisSelected = new EventEmitter(); @Output() textVersionSelected = new EventEmitter(); @Output() textVersionUnselected = new EventEmitter(); tlnQueryService: TlnQueryServiceInterface; private readonly PAGE_CONTEXT_VIEW: string = TLN_PAGE_PARAM; private readonly MANUSCRIPT_CONTEXT_VIEW: string = TLN_MANUSCRIPT_PARAM; zoomFactor: number = 1; findText: string; dataHandler: DataHandler = new DataHandler(this); textVersions: TextVersion[] = []; geneticOrders: TlnTextGeneticOrder[] = []; contextView: string = this.PAGE_CONTEXT_VIEW; current_genetic_order_iri: string; current_manuscript_unity: string; current_manuscript: ManuscriptStub; current_iri: string; current_page: NavigationPage; fullscreen: boolean = false; pagesWithGeneticOrder: TlnPageWithTextGeneticOrder[] = []; max_width: number = -1; max_height: number = -1; next_page_iri: string; each_version_height: number = -1; selectedLines: string[] = []; selectedTextVersions: TextVersion[] = [] private readonly increment: number = 0.333; private readonly decrement: number = this.increment*-1; private readonly margin_width: number = 280; private readonly initialPreviewWidth : number = 400; previewWidth: number = this.initialPreviewWidth; //protected currentRoute: string = TLN_CROSSREF_ROUTE; protected mapping: Mapping = { findText: { param: TLN_FIND_PARAM, type: "string" }, fullscreen: { param: TLN_FULLSCREEN_PARAM, type: "boolean" }, contextView: { param: TLN_CONTEXT_VIEW_PARAM, type: "string" }, current_iri: { param: TLN_PAGE_PARAM, type: "string" }, current_manuscript_unity: { param: TLN_MANUSCRIPT_PARAM, type: "string" }, current_genetic_order_iri: { param: TLN_TEXT_GENETIC_ORDER_PARAM, type: "string" }, selectedLines: { param: TLN_SELECTED_LINES_PARAM, type: "string" }, zoomFactor: { param: TLN_ZOOM_PARAM, type: "number" } } routerParams: Params; selectedViewOption: string = VIEW_OPTIONS.TRANSKRIPTION updating: boolean = false; viewOptions: string[] = [ VIEW_OPTIONS.TRANSKRIPTION, VIEW_OPTIONS.FAKSIMILE ]; subscriptions: any[] = []; - constructor(private pageViewService: PageViewService, private localQueryService: TlnQueryService, protected router: Router, protected activatedRoute: ActivatedRoute ) { + constructor(private pageViewService: PageViewService, private localQueryService: TlnCacheQueryService, protected router: Router, protected activatedRoute: ActivatedRoute ) { super(router, activatedRoute); } ngOnInit() { if (screen.availWidth - this.initialPreviewWidth - this.margin_width > 1000){ this.previewWidth = screen.availWidth - this.initialPreviewWidth - 1000; } this.max_width = screen.availWidth - this.previewWidth - this.margin_width; this.max_height = screen.availHeight - 200; this.tlnQueryService = (this.queryService != null) ? this.queryService : this.localQueryService; this.dataHandler.addHandler('page_content', ['current_page', 'geneticOrders'] ); this.dataHandler.addHandler('manuscript_content', ['current_manuscript', 'pagesWithGeneticOrder'] ); this.dataHandler.addHandler('current_manuscript', { 'handler': ManuscriptStub}); this.dataHandler.addHandler('current_page', { 'handler': TlnNavigationPage }); this.dataHandler.addHandler('geneticOrders', { 'handler': TlnTextGeneticOrder}); this.dataHandler.addHandler('pagesWithGeneticOrder', { 'handler': TlnPageWithTextGeneticOrder }); this.dataHandler.addHandler('textVersions', { 'handler': TlnStandaloneTextVersion, 'process_data': this }); this.dataHandler.setQueryService(this.tlnQueryService); this.subscriptions.push(this.dataHandler.start_processing.subscribe( (started: boolean) =>{ this.updating = true; })); this.subscriptions.push(this.dataHandler.processing_finished.subscribe( (finished: boolean) =>{ this.updating = false; })); super.ngOnInit(); this.subscriptions.push(this.pageViewService.onClickedWord.subscribe( (clickedWord: TlnWord) => { this.addWordText2FindText(clickedWord) })); } ngOnDestroy() { this.subscriptions.forEach(subscription => subscription.unsubscribe()); } private addWordText2FindText(word: TlnWord){ let text = (word.edited_text != null && word.edited_text != '') ? word.edited_text : word.text; if (this.findText == null || !this.findText.includes(text)){ this.findText = (this.findText == null || this.findText == '') ? text : this.findText + ' ' + text; } else { let index = this.findText.indexOf(text) let startText = this.findText.substring(0, index) let endText = (this.findText.length > index + text.length) ? this.findText.substring(index + text.length) : ''; let findText = startText + endText; this.findText = findText.replace(' ', ' ').trim(); } this.updateParams(); } public processData(): void { this.selectedTextVersions = []; let index = 0; while (index < this.textVersions.length && this.selectedTextVersions.length < 1){ if(this.textVersions[index].textUnities != undefined && this.textVersions[index].textUnities.length > 0){ this.selectedTextVersions.push(this.textVersions[index]); } index++ } } private addOrRemove(textVersion: TextVersion) { let index = this.selectedTextVersions.indexOf(textVersion); if (index == -1){ this.textVersionSelected.emit(textVersion.id); this.selectedTextVersions.push(textVersion); } else { this.textVersionUnselected.emit(textVersion.id); this.selectedTextVersions.splice(index, 1); } this.each_version_height = (this.selectedTextVersions.length > 0) ? this.max_height/this.selectedTextVersions.length : this.max_height; } private clearFindText() { this.findText = ''; this.updateParams(); } private getButtonTitle(textVersion: TextVersion): string { return (this.selectedTextVersions.indexOf(textVersion) == -1) ? 'Textstelle anzeigen' : 'Textstelle ausblenden'; } private isNewPagePartOfOldTextVersions(): boolean { if (this.current_manuscript_unity == undefined || this.current_manuscript_unity == null){ return false } for (let textVersion of this.textVersions){ if (textVersion.manuscript != undefined && textVersion.manuscript != null && textVersion.manuscript == this.current_manuscript_unity){ for (let textUnity of textVersion.textUnities){ if (textUnity.id == this.current_iri || textUnity.belongsToPage == this.current_iri){ console.log(textUnity.id, textUnity.belongsToPage, this.current_iri); return true } } } } return false } protected readParams(params: Params){ let old_genetic_order_iri = this.current_genetic_order_iri; let old_current_iri = this.current_iri; let old_manuscript_unity = this.current_manuscript_unity; let old_context_view = this.contextView; super.readParams(params); if (this.dataHandler.ready){ if (old_current_iri != null && this.current_iri != old_current_iri && !this.isNewPagePartOfOldTextVersions()){ this.selectedTextVersions = []; this.dataHandler.resetData('textVersions') if (old_genetic_order_iri == this.current_genetic_order_iri){ this.current_genetic_order_iri = '' this.updateParams() } } if (this.contextView == this.PAGE_CONTEXT_VIEW && (this.contextView != old_context_view || (this.current_iri != null && this.current_iri != old_current_iri))){ this.dataHandler.resetData('page_content') this.dataHandler.getData('page_content', this.current_iri); } if (this.current_manuscript_unity != old_manuscript_unity){ this.dataHandler.resetData('manuscript_content') this.dataHandler.getData('manuscript_content', this.current_manuscript_unity); } if (this.contextView == this.PAGE_CONTEXT_VIEW && (this.contextView != old_context_view || (this.textVersions.length == 0 || this.current_genetic_order_iri != old_genetic_order_iri))){ this.dataHandler.resetData('textVersions') if (this.current_genetic_order_iri != null && this.current_genetic_order_iri != ''){ this.dataHandler.getData('textVersions', this.current_genetic_order_iri); } } } } private openInViewer(pageIri: string, belongsToPageIri?: string, manuscriptIri?: string, launch?: boolean){ this.current_iri = (belongsToPageIri != null) ? belongsToPageIri : pageIri; if(manuscriptIri != null){ this.current_manuscript_unity = manuscriptIri; } this.currentRoute = TLN_VIEWER_ROUTE; this.updateParams(launch); } private setZoomFactor(newZoomFactor: number){ if (newZoomFactor > 0){ this.zoomFactor = Math.round(newZoomFactor*100)/100; } else { this.zoomFactor = this.zoomFactor/2 } this.updateParams(); } } diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-edition.module.ts b/nietzsche-beta-app/src/app/tln-edition/tln-edition.module.ts index 892b383..65446ad 100644 --- a/nietzsche-beta-app/src/app/tln-edition/tln-edition.module.ts +++ b/nietzsche-beta-app/src/app/tln-edition/tln-edition.module.ts @@ -1,84 +1,103 @@ import { BrowserModule } from '@angular/platform-browser'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { HttpClientModule } from "@angular/common/http"; -import { NgModule } from '@angular/core'; -import { MatSliderModule,MatProgressSpinnerModule,MatBottomSheetModule,MatButtonModule,MatCheckboxModule,MatDialogModule,MatExpansionModule,MatFormFieldModule,MatInputModule,MatListModule,MatPaginatorModule,MatRadioModule,MatSelectModule,MatSidenavModule,MatSortModule,MatTableModule,MatToolbarModule,MatButtonToggleModule,MatCardModule,MatIconModule,MatMenuModule,MatTabsModule,MatTooltipModule +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { MatBadgeModule, MatSnackBarModule, MatSlideToggleModule, MatTreeModule, MatSliderModule,MatProgressSpinnerModule,MatBottomSheetModule,MatButtonModule,MatCheckboxModule,MatDialogModule,MatExpansionModule,MatFormFieldModule,MatInputModule,MatListModule,MatPaginatorModule,MatRadioModule,MatSelectModule,MatSidenavModule,MatSortModule,MatTableModule,MatToolbarModule,MatButtonToggleModule,MatCardModule,MatIconModule,MatMenuModule,MatTabsModule,MatTooltipModule } from '@angular/material'; +import { CodemirrorModule } from '@ctrl/ngx-codemirror'; import { NgxMatStandoffMarkupModule} from "ngx-mat-standoff-markup"; import { PageViewService } from '../page-view/page-view.service'; import { PageViewModule } from '../page-view/page-view.module'; import { TlnPageViewComponent } from './tln-page-view.component'; -import { TlnQueryService } from './tln-query.service'; +import { TlnQueryService, TlnCacheQueryService } from './tln-query.service'; import { ToolTipComponent } from './tooltip/tool-tip.component'; import { TlnViewerNavigation } from './tln-viewer-navigation/tln-viewer-navigation.component'; import { TlnInformationComponent } from './tln-information/tln-information.component'; import { TlnCrossrefComponent } from './tln-crossref/tln-crossref.component'; import { VersionViewComponent } from './tln-crossref/version-view/version-view.component'; import { PageVersionViewComponent } from './tln-crossref/page-version-view/page-version-view.component'; import { NavigationComponent } from './tln-crossref/navigation/navigation.component'; import { GeneticOrderFilterPipe} from './tln-crossref/page-version-view/filter.pipe'; import { TlnPageVersionViewComponent } from './tln-crossref/page-version-view/tln-page-version-view.component'; import { TlnManuscriptViewComponent } from './tln-manuscript-view/tln-manuscript-view.component'; import { TlnFulltextComponent } from './tln-fulltext/tln-fulltext.component'; import { FulltextNavigationComponent } from './tln-fulltext/navigation/navigation.component'; import { ResultPipePipe } from './tln-fulltext/result-pipe.pipe'; +import { PageResultFilterPipe} from './common/page-result-filter.pipe'; import { SearchComponent } from './tln-navigation-elements/search.component'; import { ToggleNavigationComponent } from './tln-navigation-elements/toggle-navigation.component'; import { ZoomComponent } from './tln-navigation-elements/zoom.component'; import { ZoomPipe } from './tln-navigation-elements/zoom.pipe'; import { OpenInViewerComponent } from './tln-navigation-elements/open-in-viewer.component'; import { DebugPipe } from './debug.pipe'; import { TlnHeightDirective } from './tln-height.directive'; -import { PageResultFilterPipe } from './tln-fulltext/page-result-filter.pipe'; +import { TlnQuantComponent } from './tln-quant/tln-quant.component'; +import { TlnTableComponent } from './tln-quant/tln-table.component'; +import { ResultPipe } from './tln-quant/result.pipe'; +import { WordPresentationComponent } from './tln-quant/word-presentation.component'; +import { ToIdsPipe } from './tln-quant/2-ids.pipe'; +import { SortByLinePipe } from './tln-quant/sort-by-line.pipe'; +import { QueryErrorComponent } from './tln-quant/query-error.component'; +import { ExportComponent } from './tln-quant/export.component'; +import { FusekiTableComponent } from './tln-quant/fuseki-table.component'; @NgModule({ - declarations: [FulltextNavigationComponent, TlnPageViewComponent, ToolTipComponent, TlnViewerNavigation, TlnInformationComponent, TlnCrossrefComponent, VersionViewComponent, PageVersionViewComponent, NavigationComponent, GeneticOrderFilterPipe, TlnPageVersionViewComponent, TlnManuscriptViewComponent, TlnFulltextComponent, ResultPipePipe, SearchComponent, ToggleNavigationComponent, ZoomComponent, ZoomPipe, OpenInViewerComponent, DebugPipe, TlnHeightDirective, PageResultFilterPipe], + declarations: [FulltextNavigationComponent, TlnPageViewComponent, ToolTipComponent, TlnViewerNavigation, TlnInformationComponent, TlnCrossrefComponent, VersionViewComponent, PageVersionViewComponent, NavigationComponent, GeneticOrderFilterPipe, TlnPageVersionViewComponent, TlnManuscriptViewComponent, TlnFulltextComponent, PageResultFilterPipe, ResultPipePipe, SearchComponent, ToggleNavigationComponent, ZoomComponent, ZoomPipe, OpenInViewerComponent, DebugPipe, TlnHeightDirective, TlnQuantComponent, TlnTableComponent, ResultPipe, WordPresentationComponent, ToIdsPipe, SortByLinePipe, QueryErrorComponent, ExportComponent, FusekiTableComponent], imports: [ + CodemirrorModule, + MatBadgeModule, + MatSnackBarModule, + MatSlideToggleModule, MatSliderModule, MatBottomSheetModule, MatButtonModule, MatButtonToggleModule, MatCardModule, MatCheckboxModule, MatDialogModule, MatExpansionModule, MatFormFieldModule, MatIconModule, MatInputModule, MatListModule, MatMenuModule, MatProgressSpinnerModule, MatPaginatorModule, MatRadioModule, MatSelectModule, MatSidenavModule, MatSortModule, MatTableModule, MatTabsModule, + MatTreeModule, MatToolbarModule, MatTooltipModule, BrowserModule, CommonModule, FormsModule, NgxMatStandoffMarkupModule, PageViewModule ], exports: [ DebugPipe, FulltextNavigationComponent, NavigationComponent, + ToggleNavigationComponent, ToolTipComponent, TlnCrossrefComponent, TlnHeightDirective, TlnManuscriptViewComponent, TlnPageViewComponent, - TlnViewerNavigation + TlnViewerNavigation, + TlnQuantComponent ], providers: [ - PageViewService + PageViewService, + //TlnQueryService ], - entryComponents: [TlnInformationComponent] + entryComponents: [TlnInformationComponent, QueryErrorComponent] }) -export class TlnEditionModule { } +export class TlnEditionModule { +} diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-fulltext/tln-fulltext.component.html b/nietzsche-beta-app/src/app/tln-edition/tln-fulltext/tln-fulltext.component.html index 9907834..b319824 100644 --- a/nietzsche-beta-app/src/app/tln-edition/tln-fulltext/tln-fulltext.component.html +++ b/nietzsche-beta-app/src/app/tln-edition/tln-fulltext/tln-fulltext.component.html @@ -1,56 +1,56 @@
-
+
{{page.title}}, {{page.number}}
- + (page)="paginatorResultStatus.showResults($event)">
diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-fulltext/tln-fulltext.component.ts b/nietzsche-beta-app/src/app/tln-edition/tln-fulltext/tln-fulltext.component.ts index 2776e04..a358459 100644 --- a/nietzsche-beta-app/src/app/tln-edition/tln-fulltext/tln-fulltext.component.ts +++ b/nietzsche-beta-app/src/app/tln-edition/tln-fulltext/tln-fulltext.component.ts @@ -1,110 +1,116 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; +import { Component, Input, OnInit, ViewChild } from '@angular/core'; import { Router, ActivatedRoute, Params } from '@angular/router'; import {PageEvent} from '@angular/material/paginator'; import { TLN_CROSSREF_ROUTE, TLN_SEARCH_ROUTE, TLN_MANUSCRIPT_ROUTE, TLN_VIEWER_ROUTE, TLN_CONTEXT_VIEW_PARAM, TLN_FULLSCREEN_PARAM, TLN_FIND_PARAM, TLN_PAGE_PARAM, TLN_MANUSCRIPT_PARAM, - TLN_RESULT_INDEX_PARAM,TLN_SELECTED_LINES_PARAM, TLN_TEXT_GENETIC_ORDER_PARAM, TLN_VIEW_OPTION_PARAM, TLN_ZOOM_PARAM, VIEW_OPTIONS, ONTOLOTY_PREFIX } from '../constants'; -import { DataHandler } from '../data_handler'; + TLN_SEARCH_QUERY_PARAM, TLN_RESULT_INDEX_PARAM,TLN_SELECTED_LINES_PARAM, TLN_TEXT_GENETIC_ORDER_PARAM, TLN_VIEW_OPTION_PARAM, TLN_ZOOM_PARAM, VIEW_OPTIONS, ONTOLOTY_PREFIX } from '../constants'; +import { Handler, DataHandler } from '../data_handler'; import { FoundPage} from '../datatypes/search'; import { Mapping } from '../route-reader'; import { RouteUpdater } from '../route-updater'; -import { TlnQueryService } from '../services'; -import { ResultRange } from './page-result-filter.pipe'; +import { TlnCacheQueryService } from '../services'; +import { ResultRange, PaginatorResultStatus } from '../common/paginator-result-status'; +import { QueryProperties, StorageHandler, TlnQueryServiceInterface} from '../models'; //TODO: german language support for paginator, see: https://github.com/ngx-translate/core @Component({ selector: 'tln-fulltext', templateUrl: './tln-fulltext.component.html', styleUrls: ['./tln-fulltext.component.css'] }) -export class TlnFulltextComponent extends RouteUpdater implements OnInit { - protected currentRoute: string = TLN_SEARCH_ROUTE; +export class TlnFulltextComponent extends RouteUpdater implements StorageHandler, OnInit { + /** + * OPTIONAL pass a queryService with method + * {@link /interfaces/TlnQueryServiceInterface.html#getData|getData} + * to TlnPageViewComponent. + **/ + @Input() queryService: TlnQueryServiceInterface; + tlnQueryService: TlnQueryServiceInterface; + //protected currentRoute: string = TLN_SEARCH_ROUTE; current_page_iri: string; current_manuscript_unity: string; dataHandler: DataHandler = new DataHandler(this); fullscreen: boolean; max_width: number = -1; max_height: number = -1; searchTerm: string; resultIndex: number = 0; - resultLength: number = 5; - resultRange: ResultRange = { start: 0, end: 4 }; + paginatorResultStatus: PaginatorResultStatus = new PaginatorResultStatus(5); resultsReceived: boolean = false; selectedViewOption: string = VIEW_OPTIONS.TRANSKRIPTION startSearch: boolean = false; protected mapping: Mapping = { current_page_iri: { param: TLN_PAGE_PARAM, type: "string" }, resultIndex: { param: TLN_RESULT_INDEX_PARAM, type: "number" }, searchTerm: { param: TLN_FIND_PARAM, type: "string" }, + queryProps: { param: TLN_SEARCH_QUERY_PARAM, type: "complex" }, current_manuscript_unity: { param: TLN_MANUSCRIPT_PARAM, type: "string" }, fullscreen: { param: TLN_FULLSCREEN_PARAM, type: "boolean" }, } pages: FoundPage[] = []; + queryProps: QueryProperties = { ignoreCase: false, selectedManuscripts: [], restrictKorpusOnContext: false } private readonly margin_width: number = 280; private readonly initialPreviewWidth : number = 300; previewWidth: number = this.initialPreviewWidth; - constructor(private tlnQueryService: TlnQueryService, protected router: Router, protected activatedRoute: ActivatedRoute ) { + constructor(private localQueryService: TlnCacheQueryService, protected router: Router, protected activatedRoute: ActivatedRoute ) { super(router, activatedRoute); } ngOnInit() { if (screen.availWidth - this.initialPreviewWidth - this.margin_width > 1000){ this.previewWidth = screen.availWidth - this.initialPreviewWidth - 1000; } + this.tlnQueryService = (this.queryService != null) ? this.queryService : this.localQueryService; this.max_width = screen.availWidth - this.previewWidth - this.margin_width; this.max_height = screen.availHeight - 200; - this.dataHandler.addHandler('pages', { 'handler': FoundPage}); + this.dataHandler.addHandler('pages', { 'handler': FoundPage, 'storage_handler': this }); this.dataHandler['pages']['service'] = this this.dataHandler.setQueryService(this.tlnQueryService); this.dataHandler.start_processing.subscribe( (started: boolean) =>{ this.resultsReceived = false; this.startSearch = true; }); this.dataHandler.processing_finished.subscribe( (finished: boolean) =>{ this.resultsReceived = true; this.startSearch = false; }); super.ngOnInit(); } private clearFindText() { this.searchTerm = ''; this.pages = []; this.resultIndex = 0; super.updateParams(); } private search(){ this.resultIndex = 0; - this.updateResultRange(); + this.paginatorResultStatus.updateResultRange(this.resultIndex); super.updateParams(); if (this.searchTerm != undefined && this.searchTerm != null && this.searchTerm != ''){ this.dataHandler.resetData('pages'); this.dataHandler.getData('pages', this.searchTerm); } } - protected readParams(params: Params){ + public getStorageDuration(handler: Handler): number { + return 28800000; + } + public getStorageKey(handler: Handler): string{ + return TLN_SEARCH_ROUTE + '_' + handler.handler.name + '_' + this.searchTerm.replace(' ', '_'); + } + protected readParams(params: Params){ let oldSearchTerm = this.searchTerm; let oldResultIndex = this.resultIndex; super.readParams(params); if (this.searchTerm != undefined && this.searchTerm != null && this.searchTerm != '' && this.searchTerm != oldSearchTerm){ this.resultIndex = 0; this.dataHandler.getData('pages', this.searchTerm); } - if(oldResultIndex != this.resultIndex){ - this.updateResultRange(); + if(this.pages.length > 0 && this.resultIndex > 0 ){ + this.paginatorResultStatus.updateResultRange(this.resultIndex); } } getSearchTerms(): string[] { return this.searchTerm.split(' '); } - showResults(event: PageEvent){ - this.resultIndex = event.pageIndex; - this.updateResultRange(); - this.updateParams() - } - private updateResultRange(){ - let newStart = this.resultIndex*this.resultLength - let newEnd = newStart+this.resultLength; - this.resultRange = { start: newStart, end: newEnd }; - } } diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-manuscript-view/tln-manuscript-view.component.ts b/nietzsche-beta-app/src/app/tln-edition/tln-manuscript-view/tln-manuscript-view.component.ts index 0239414..cfc0a6e 100644 --- a/nietzsche-beta-app/src/app/tln-edition/tln-manuscript-view/tln-manuscript-view.component.ts +++ b/nietzsche-beta-app/src/app/tln-edition/tln-manuscript-view/tln-manuscript-view.component.ts @@ -1,69 +1,69 @@ import { Component, OnInit } from '@angular/core'; import { Router, ActivatedRoute, Params } from '@angular/router'; import { TLN_CROSSREF_ROUTE, TLN_MANUSCRIPT_ROUTE, TLN_VIEWER_ROUTE, TLN_CONTEXT_VIEW_PARAM, TLN_FULLSCREEN_PARAM, TLN_FIND_PARAM, TLN_PAGE_PARAM, TLN_MANUSCRIPT_PARAM, TLN_SELECTED_LINES_PARAM, TLN_TEXT_GENETIC_ORDER_PARAM, TLN_VIEW_OPTION_PARAM, TLN_ZOOM_PARAM, VIEW_OPTIONS, ONTOLOTY_PREFIX } from '../constants'; import { DataHandler } from '../data_handler'; import { ReconstructedKonvolut, TlnExtManuscript, ManuscriptEarlierDescription, ManuscriptDescription } from '../datatypes/manuscript'; import { PageStub } from '../datatypes/page'; import { Mapping } from '../route-reader'; import { RouteUpdater } from '../route-updater'; -import { TlnQueryService } from '../services'; +import { TlnCacheQueryService } from '../services'; import { MarkupSettings, StandoffMarkup } from 'ngx-mat-standoff-markup'; @Component({ selector: 'tln-manuscript-view', templateUrl: './tln-manuscript-view.component.html', styleUrls: ['./tln-manuscript-view.component.css'] }) export class TlnManuscriptViewComponent extends RouteUpdater implements OnInit { contextView: string = TLN_MANUSCRIPT_PARAM; protected currentRoute: string = TLN_MANUSCRIPT_ROUTE; current_manuscript: TlnExtManuscript; current_manuscript_unity: string; current_page_iri: string; dataHandler: DataHandler = new DataHandler(this); earlierDescriptions: ManuscriptEarlierDescription[] = []; fullscreen: boolean; manuscriptDescription?: ManuscriptDescription; reconstructedKonvolut?: ReconstructedKonvolut; protected mapping: Mapping = { contextView: { param: TLN_CONTEXT_VIEW_PARAM, type: "string" }, current_page_iri: { param: TLN_PAGE_PARAM, type: "string" }, current_manuscript_unity: { param: TLN_MANUSCRIPT_PARAM, type: "string" }, fullscreen: { param: TLN_FULLSCREEN_PARAM, type: "boolean" }, } mySettings: MarkupSettings = new MarkupSettings(); pages: PageStub[] = []; - constructor(private tlnQueryService: TlnQueryService, protected router: Router, protected activatedRoute: ActivatedRoute ) { + constructor(private tlnQueryService: TlnCacheQueryService, protected router: Router, protected activatedRoute: ActivatedRoute ) { super(router, activatedRoute); } ngOnInit() { this.dataHandler.addHandler('manuscript_content', ['current_manuscript','manuscriptDescription', 'earlierDescriptions', 'pages', 'reconstructedKonvolut'] ); this.dataHandler.addHandler('current_manuscript', { 'handler': TlnExtManuscript }); this.dataHandler.addHandler('manuscriptDescription', { 'handler': ManuscriptDescription}); this.dataHandler.addHandler('earlierDescriptions', { 'handler': ManuscriptEarlierDescription}); this.dataHandler.addHandler('pages', { 'handler': PageStub }); this.dataHandler.addHandler('reconstructedKonvolut', { 'handler': ReconstructedKonvolut}); this.dataHandler.setQueryService(this.tlnQueryService); super.ngOnInit(); } protected readParams(params: Params){ let old_manuscript_unity = this.current_manuscript_unity; super.readParams(params); if (this.dataHandler.ready){ if (this.current_manuscript_unity != null && this.current_manuscript_unity != old_manuscript_unity){ this.dataHandler.resetData('manuscript_content') this.dataHandler.getData('manuscript_content', this.current_manuscript_unity); } } } private openInViewer(pageIri: string, launch: boolean){ this.current_page_iri = pageIri; this.currentRoute = TLN_VIEWER_ROUTE; this.fullscreen = true; this.updateParams(launch); } } diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-navigation-elements/open-in-viewer.component.html b/nietzsche-beta-app/src/app/tln-edition/tln-navigation-elements/open-in-viewer.component.html index 8d36d7f..25950f9 100644 --- a/nietzsche-beta-app/src/app/tln-edition/tln-navigation-elements/open-in-viewer.component.html +++ b/nietzsche-beta-app/src/app/tln-edition/tln-navigation-elements/open-in-viewer.component.html @@ -1,8 +1,5 @@ - - diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-navigation-elements/open-in-viewer.component.ts b/nietzsche-beta-app/src/app/tln-edition/tln-navigation-elements/open-in-viewer.component.ts index 36b9631..3ae7549 100644 --- a/nietzsche-beta-app/src/app/tln-edition/tln-navigation-elements/open-in-viewer.component.ts +++ b/nietzsche-beta-app/src/app/tln-edition/tln-navigation-elements/open-in-viewer.component.ts @@ -1,52 +1,68 @@ import { Component, Input, OnInit } from '@angular/core'; import { Router, ActivatedRoute, Params } from '@angular/router'; -import { TLN_VIEWER_ROUTE, TLN_PAGE_PARAM, TLN_MANUSCRIPT_PARAM } from '../constants'; +import { TLN_VIEWER_ROUTE, TLN_FULLSCREEN_PARAM, TLN_PAGE_PARAM, TLN_MANUSCRIPT_PARAM, TLN_SELECTED_WORDS_PARAM } from '../constants'; import { Mapping } from '../route-reader'; import { RouteUpdater } from '../route-updater'; import { NavigationPage } from '../models'; interface ViewerRouteInformation { title: string; number: string; page: string; manuscript?: string; + selectedWords?: string[]; launchExternally?: boolean; } @Component({ selector: 'open-in-viewer', templateUrl: './open-in-viewer.component.html', styleUrls: ['./open-in-viewer.component.scss'] }) export class OpenInViewerComponent extends RouteUpdater { @Input() routerInformation: ViewerRouteInformation; + @Input() title: string; + @Input() launchTitle: string; launch: boolean = false; page: NavigationPage; protected currentRoute: string = TLN_VIEWER_ROUTE; current_iri: string; current_manuscript_unity: string; + selectedWords: string[]; protected mapping: Mapping = { current_iri: { param: TLN_PAGE_PARAM, type: "string" }, + selectedWords: { param: TLN_SELECTED_WORDS_PARAM, type: "string" }, current_manuscript_unity: { param: TLN_MANUSCRIPT_PARAM, type: "string" }} constructor(protected router: Router, protected activatedRoute: ActivatedRoute ) { super(router, activatedRoute); } ngOnInit(){ super.ngOnInit(); this.page = { id: this.routerInformation.page, number: this.routerInformation.number, title: this.routerInformation.title }; this.launch = (this.routerInformation.launchExternally != undefined && this.routerInformation.launchExternally != null && this.routerInformation.launchExternally) + if (this.routerInformation.selectedWords != undefined + && this.routerInformation.selectedWords != null + && this.routerInformation.selectedWords.length > 0){ + this.selectedWords = this.routerInformation.selectedWords; + } + if (this.title == undefined || this.title == null || this.title == ''){ + this.title = this.page.title + ' ' + this.page.number + ' in Seitenansicht öffnen' + } + if (this.launchTitle == undefined || this.launchTitle == null || this.launchTitle == ''){ + this.launchTitle = this.page.title + ' ' + this.page.number + ' in neuem Tab in Seitenansicht öffnen' + } } private openInViewer(){ if (this.routerInformation != undefined && this.routerInformation != null){ this.current_iri = this.routerInformation.page; if (this.routerInformation.manuscript != undefined && this.routerInformation != null){ this.current_manuscript_unity = this.routerInformation.manuscript; } this.updateParams(this.launch); } } } diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-navigation-elements/toggle-navigation.component.ts b/nietzsche-beta-app/src/app/tln-edition/tln-navigation-elements/toggle-navigation.component.ts index 97e7007..ed62080 100644 --- a/nietzsche-beta-app/src/app/tln-edition/tln-navigation-elements/toggle-navigation.component.ts +++ b/nietzsche-beta-app/src/app/tln-edition/tln-navigation-elements/toggle-navigation.component.ts @@ -1,33 +1,33 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit} from '@angular/core'; import { Router, ActivatedRoute, Params } from '@angular/router'; import { TLN_SEARCH_ROUTE, TLN_FULLSCREEN_PARAM, TLN_NAV_BAR_OPEN_STATE_PARAM} from '../constants'; import { Mapping } from '../route-reader'; import { RouteUpdater } from '../route-updater'; @Component({ selector: 'toggle-navigation', templateUrl: './toggle-navigation.component.html', styleUrls: ['./toggle-navigation.component.css'] }) export class ToggleNavigationComponent extends RouteUpdater { fullscreen: boolean = false; navBarOpenState: boolean = false; protected mapping: Mapping = { navBarOpenState: { param: TLN_NAV_BAR_OPEN_STATE_PARAM, type: "boolean" }, fullscreen: { param: TLN_FULLSCREEN_PARAM, type: "boolean" } } constructor(protected router: Router, protected activatedRoute: ActivatedRoute ) { super(router, activatedRoute); } private toggleFullscreen(){ this.fullscreen = !this.fullscreen; this.updateParams(); } toggleNavDrawer(){ this.navBarOpenState = !this.navBarOpenState; this.updateParams(); } } diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-page-view.component.ts b/nietzsche-beta-app/src/app/tln-edition/tln-page-view.component.ts index d5cf849..55f0bee 100644 --- a/nietzsche-beta-app/src/app/tln-edition/tln-page-view.component.ts +++ b/nietzsche-beta-app/src/app/tln-edition/tln-page-view.component.ts @@ -1,244 +1,246 @@ import { Component, Input, OnInit, OnChanges, SimpleChanges } from '@angular/core'; import { Router, ActivatedRoute, Params } from '@angular/router'; import { TlnQueryServiceInterface } from './models'; -import { TlnQueryService } from './tln-query.service'; +import { TlnCacheQueryService} from './tln-query.service'; import { TlnPage} from './datatypes/page'; import { FaksimileImage } from './datatypes/faksimile_image'; import { SVGImage, SVGImageClip } from './datatypes/svg_image'; import { TlnImage } from './datatypes/image'; import { TlnLine, FaksimileLine } from './datatypes/line'; import { TlnWord, FaksimileWord } from './datatypes/word'; import { TlnTextByForeignHand, FaksimileTextByForeignHand } from './datatypes/foreign_text'; import { externalAssignClass, externalAssignStyle, Configuration, Identifier, Image, Line, Word } from './models'; import { ConfigurableComponent } from './services'; import { ComplexKeyIriMapping, DataHandler } from './data_handler'; import { DEFAULT_VIEW_OPTION, VIEW_OPTIONS, TLN_FULLSCREEN_PARAM, TLN_FIND_PARAM, TLN_PAGE_PARAM, TLN_SELECTED_LINES_PARAM, TLN_SELECTED_WORDS_PARAM, TLN_VIEW_OPTION_PARAM, TLN_MULTI_INSTANCE_ZOOM_PARAM, TLN_ZOOM_PARAM } from './constants'; import { Mapping, RouteReader } from './route-reader'; /** * Given a page IRI, this component will request all relevant information and * display the data with {@link /components/PageViewComponent.html|PageViewComponent}. **/ @Component({ selector: 'tln-page-view', templateUrl: './tln-page-view.component.html', styleUrls: ['./tln-page-view.component.css'] }) export class TlnPageViewComponent extends RouteReader implements OnInit, OnChanges { @Input() configuration: Configuration = { '*': { 'useExtendedTooltip': true }}; /** * IRI of the current page, the component will ignore page iri's from params if set. **/ @Input('page') current_iri: string; /** * the search text of words that should be highlighted as {@link /miscellaneous/enumerations.html#HIGHTLIGHT_CASES|HIGHTLIGHT_CASES.SEARCHED_WORD}. **/ @Input() findText: string; /** * whether or not to ignore the params **/ private ignoreParams: boolean = false; /** * OPTIONAL pass a queryService with method * {@link /interfaces/TlnQueryServiceInterface.html#getData|getData} * to TlnPageViewComponent. **/ @Input() queryService: TlnQueryServiceInterface; /** * global zoom factor. **/ @Input() zoomFactor: number = 1; /** * identifiers of selected words that should be highlighted. **/ @Input() selectedWords: Identifier[] = []; /** * identifiers of selected lines that should be highlighted. **/ @Input() selectedLines: Identifier[] = []; /** * the (initial) maximum height of the image. **/ @Input() max_height: number = -1; /** * the (initial) maximum width of the image, if both height and width are specified prefer width. **/ @Input() max_width: number = -1; dontShowReference: boolean = false; @Input() showAllLines: boolean = false; @Input() startLine: Identifier; @Input() endLine: Identifier; /** * should primary Url be used for image. Use secondary Url if false. **/ @Input() preferPrimaryUrl: boolean = true; /** * selected view option, i.e. one of the following * {@link /miscellaneous/enumerations.html#VIEW_OPTIONS|VIEW_OPTIONS}. * */ @Input() selectedViewOption: string = DEFAULT_VIEW_OPTION; /** * An optional function that will be passed to {@link /components/TextFieldComponent.html|TextFieldComponent} * in order to return a further highlight class * to the word rects when the internal function would return 'textfield unhighlighted'. **/ @Input('assignClass') assignClass?: externalAssignClass; /** * An optional function that will be passed to {@link /components/TextFieldComponent.html|TextFieldComponent} * and {@link /components/MarginFieldComponent.html|MarginFieldComponent} * in order to return a (svg-)style object * to the word and line rects. This function allows the user to extend the style of this component. * E.g. by returning { fill: blue } the function overwrites the default behaviour and sets * the default highlight color to blue. **/ @Input('assignStyle') assignStyle?: externalAssignStyle; /** * the data handler of this component that retrieves * data and instantiates it according to their proper * datatypes. **/ dataHandler: DataHandler = new DataHandler(this); /** * whether or not to show page view in fullscreen mode. **/ fullscreen: boolean = false; /** * texts written by foreign hand **/ foreignTexts: TlnTextByForeignHand[] = []; /** * the (first) image to be displayed by * {@link /components/PageViewComponent.html|PageViewComponent}. **/ image: Image; /** * the Array of lines of the first image that will be displayed by {@link /components/MarginFieldComponent.html|MarginFieldComponent}. **/ lines: Line[] = []; /** * texts written by foreign hand **/ second_foreignTexts: FaksimileTextByForeignHand[] = []; /** * the second image that will be displayed by {@link /components/TextFieldComponent.html|TextFieldComponent}. **/ second_image: Image; /** * the Array of lines of the second image that will be displayed by {@link /components/MarginFieldComponent.html|MarginFieldComponent}. **/ second_lines: Line[] = []; /** * the Array of words of the second image that will be displayed by {@link /components/TextFieldComponent.html|TextFieldComponent}. **/ second_words: Word[] = []; /** * the Array of words of the first image that will be displayed by {@link /components/TextFieldComponent.html|TextFieldComponent}. **/ words: Word[] = []; @Input() multiInstanceMode: boolean = false; protected mapping: Mapping = { findText: { param: TLN_FIND_PARAM, type: "string" }, fullscreen: { param: TLN_FULLSCREEN_PARAM, type: "boolean" }, selectedViewOption: { param: TLN_VIEW_OPTION_PARAM, type: "string" }, selectedLines: { param: TLN_SELECTED_LINES_PARAM, type: "string" }, selectedWords: { param: TLN_SELECTED_WORDS_PARAM, type: "string" }, current_iri: { param: TLN_PAGE_PARAM, type: "string" }, zoomFactor: { param: TLN_ZOOM_PARAM, type: "number" } } /** * @param localQueryService internal query service that will be used if no external queryService is passed * to input. **/ - constructor(private localQueryService: TlnQueryService, protected router: Router, protected activatedRoute: ActivatedRoute){ + constructor(private localQueryService: TlnCacheQueryService, protected router: Router, protected activatedRoute: ActivatedRoute){ super(router, activatedRoute); } ngOnInit() { if (this.multiInstanceMode){ this.mapping['current_iri']['ignore'] = (this.current_iri != null); this.mapping['selectedViewOption']['ignore'] = true; this.mapping['zoomFactor']['param'] = TLN_MULTI_INSTANCE_ZOOM_PARAM; this.dontShowReference = true; //this.configuration['*']['useExtendedTooltip'] = false; } super.ngOnInit(); + this.localQueryService.use_cache = true; let tlnQueryService = (this.queryService != null) ? this.queryService : this.localQueryService; + console.log(tlnQueryService['use_cache']); let queryConfiguration: Configuration = { '*': { 'tlnQueryService': tlnQueryService }} this.configuration = ConfigurableComponent.updateConfiguration(this.configuration, queryConfiguration) this.dataHandler.addHandler('image', { 'handler': SVGImage }); this.dataHandler.addHandler('words', { 'handler': TlnWord }); this.dataHandler.addHandler('foreignTexts', { 'handler': TlnTextByForeignHand}); this.dataHandler.addHandler('lines', { 'handler': TlnLine }); this.dataHandler.addHandler('second_image',{ 'handler': FaksimileImage }); this.dataHandler.addHandler('second_words',{ 'handler': FaksimileWord }); this.dataHandler.addHandler('second_lines',{ 'handler': FaksimileLine }); this.dataHandler.addHandler('second_foreignTexts', { 'handler': FaksimileTextByForeignHand}); this.dataHandler.addHandler('page_content',[ 'image', 'lines', 'words', 'foreignTexts' ]); this.dataHandler.addHandler('second_page_content', [ 'second_image', 'second_lines', 'second_words', 'second_foreignTexts' ] ); this.dataHandler.setQueryService(tlnQueryService); this.updatePageData(); } ngOnChanges(change: SimpleChanges) { if (this.dataHandler.ready && (change.current_iri != undefined && change.current_iri != null && !change.current_iri.firstChange) || (change.selectedViewOption != undefined && change.selectedViewOption != null && !change.selectedViewOption.firstChange)) { this.updatePageData(); } } protected readParams(params: Params){ let old_page_iri = this.current_iri; let old_selectedViewOption = this.selectedViewOption; let old_fullscreen = this.fullscreen super.readParams(params); if (this.dataHandler.ready && (old_page_iri != this.current_iri || old_selectedViewOption != this.selectedViewOption)){ this.updatePageData(); } if (old_fullscreen != this.fullscreen && !this.multiInstanceMode){ //this.dontShowReference = !this.fullscreen; //this.configuration['*']['useExtendedTooltip'] = this.fullscreen; } } /** * This function updates the page data by setting the handlers for the current * {@link #selectedViewOption|selectedViewOption} and by retrieving the data. **/ private updatePageData(){ this.dataHandler['image']['handler'] = (this.selectedViewOption != VIEW_OPTIONS.FAKSIMILE && this.selectedViewOption != VIEW_OPTIONS.SYNOPSIS_B) ? SVGImage : FaksimileImage ; this.dataHandler['words']['handler'] = (this.selectedViewOption != VIEW_OPTIONS.FAKSIMILE && this.selectedViewOption != VIEW_OPTIONS.SYNOPSIS_B) ? TlnWord : FaksimileWord ; this.dataHandler['lines']['handler'] = (this.selectedViewOption != VIEW_OPTIONS.FAKSIMILE && this.selectedViewOption != VIEW_OPTIONS.SYNOPSIS_B) ? TlnLine : FaksimileLine ; this.dataHandler['foreignTexts']['handler'] = (this.selectedViewOption != VIEW_OPTIONS.FAKSIMILE && this.selectedViewOption != VIEW_OPTIONS.SYNOPSIS_B) ? TlnTextByForeignHand : FaksimileTextByForeignHand ; if (this.dataHandler['page_content'][this.dataHandler['page_content'].length-1] == 'second_page_content'){ this.dataHandler['page_content'].pop(); this.second_image = null; this.second_foreignTexts = []; this.second_words = []; this.second_lines = []; } if (this.selectedViewOption == VIEW_OPTIONS.SYNOPSIS || this.selectedViewOption == VIEW_OPTIONS.SYNOPSIS_B){ this.dataHandler['second_image']['handler'] = (this.selectedViewOption == VIEW_OPTIONS.SYNOPSIS_B) ? SVGImage : FaksimileImage ; this.dataHandler['second_words']['handler'] = (this.selectedViewOption == VIEW_OPTIONS.SYNOPSIS_B) ? TlnWord : FaksimileWord ; this.dataHandler['second_lines']['handler'] = (this.selectedViewOption == VIEW_OPTIONS.SYNOPSIS_B) ? TlnLine : FaksimileLine ; this.dataHandler['second_foreignTexts']['handler'] = (this.selectedViewOption == VIEW_OPTIONS.SYNOPSIS_B) ? TlnTextByForeignHand : FaksimileTextByForeignHand ; this.dataHandler['page_content'].push('second_page_content'); } if (this.current_iri != null){ this.dataHandler.resetData('page_content'); if (this.startLine != null && this.startLine != undefined){ this.dataHandler['image']['handler'] = SVGImageClip ; let endLine = (this.endLine != null && this.endLine != undefined) ? this.endLine : this.startLine; let complex: ComplexKeyIriMapping = { idIndex: 0, mapping: [ { key: 'page', iri: this.current_iri}, { key: 'startLine', iri: this.startLine}, {key: 'endLine', iri: endLine} ] } this.dataHandler['page_content'] = this.dataHandler['page_content'].filter(key =>key != 'image') this.dataHandler.getData4Keys('image', complex); } else if (this.dataHandler['page_content'].indexOf('image') == -1){ this.dataHandler['page_content'].splice(0, 0, 'image') } this.dataHandler.getData('page_content', this.current_iri); } } } diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/2-ids.pipe.spec.ts b/nietzsche-beta-app/src/app/tln-edition/tln-quant/2-ids.pipe.spec.ts new file mode 100644 index 0000000..2212a3b --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/2-ids.pipe.spec.ts @@ -0,0 +1,8 @@ +import { 2IdsPipe } from './2-ids.pipe'; + +describe('2IdsPipe', () => { + it('create an instance', () => { + const pipe = new 2IdsPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/2-ids.pipe.ts b/nietzsche-beta-app/src/app/tln-edition/tln-quant/2-ids.pipe.ts new file mode 100644 index 0000000..2ff4af6 --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/2-ids.pipe.ts @@ -0,0 +1,13 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { BasicResultBindingElement } from '../datatypes/basic_datatype'; + +@Pipe({ + name: 'toIds' +}) +export class ToIdsPipe implements PipeTransform { + + transform(basicElements: BasicResultBindingElement[]): string[] { + return basicElements.map(element =>element.id); + } + +} diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/cache.service.spec.ts b/nietzsche-beta-app/src/app/tln-edition/tln-quant/cache.service.spec.ts new file mode 100644 index 0000000..083f0a3 --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/cache.service.spec.ts @@ -0,0 +1,12 @@ +import { TestBed } from '@angular/core/testing'; + +import { CacheService } from './cache.service'; + +describe('CacheService', () => { + beforeEach(() => TestBed.configureTestingModule({})); + + it('should be created', () => { + const service: CacheService = TestBed.get(CacheService); + expect(service).toBeTruthy(); + }); +}); diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/cache.service.ts b/nietzsche-beta-app/src/app/tln-edition/tln-quant/cache.service.ts new file mode 100644 index 0000000..fa5791f --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/cache.service.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class CacheService { + cache = {}; + + constructor() { } + + public getItem(key: string): any { + try { + return JSON.parse(localStorage.getItem(key)); + } catch(e) { + try { + return this.cache[key]; + } catch(e){ + return null; + } + } + } + public removeItem(key: string){ + try { delete this.cache[key]; } catch(e){} + try { localStorage.removeItem(key); } catch(e){} + } + public setItem(key: string, data: any){ + this.cache[key] = data; + try { + localStorage.setItem(key, JSON.stringify(data)); + } catch(e) { + localStorage.clear(); + } + } +} diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/export.component.css b/nietzsche-beta-app/src/app/tln-edition/tln-quant/export.component.css new file mode 100644 index 0000000..e69de29 diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/export.component.html b/nietzsche-beta-app/src/app/tln-edition/tln-quant/export.component.html new file mode 100644 index 0000000..afd9b6c --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/export.component.html @@ -0,0 +1,9 @@ + + Resultate als + + + {{exportFormat}} + + + + diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/export.component.spec.ts b/nietzsche-beta-app/src/app/tln-edition/tln-quant/export.component.spec.ts new file mode 100644 index 0000000..ad71a7c --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/export.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DownloadComponent } from './download.component'; + +describe('DownloadComponent', () => { + let component: DownloadComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ DownloadComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DownloadComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/export.component.ts b/nietzsche-beta-app/src/app/tln-edition/tln-quant/export.component.ts new file mode 100644 index 0000000..a0cdf76 --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/export.component.ts @@ -0,0 +1,96 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { ngxCsv } from 'ngx-csv/ngx-csv'; +import { SelectableWordProperty } from '../datatypes/quant'; +import { FusekiResults} from '../datatypes/basic_datatype'; + + +@Component({ + selector: 'export', + templateUrl: './export.component.html', + styleUrls: ['./export.component.css'] +}) +export class ExportComponent implements OnInit { + @Input() displayedColumns: string[]; + @Input() exportData: any[]; + @Input() fusekiResults: FusekiResults; + @Input() replaceId: string; + @Input() selectedWordProperties: SelectableWordProperty[] = []; + @Input() csv_title: string = 'SPN Data from ' + location.hostname; + @Input() csv_file_name: string = "SPN_Data"; + readonly availableExportFormats: string[] = [ 'CSV', 'JSON' ] + selectedExportFormat: string = 'CSV' + + constructor() { } + + ngOnInit() { + if(this.selectedWordProperties.length > 0){ + this.csv_title = 'Wörter mit Eigenschaften:,' + this.selectedWordProperties.map(property =>property.label).join(','); + } + } + // EXPORT function from: https://github.com/nie-ine/inseri/blob/devel/src/app/app-engine/apps/data-list-view/data-list-view-table/data-list-view-table.component.ts + + public export() { + switch (this.selectedExportFormat) { + case 'CSV': { + this.exportToCsv(); + break; + } + case 'JSON': this.exportToJson(); + } + } + + public exportToCsv() { + var options = { + fieldSeparator: ',', + quoteStrings: '"', + decimalseparator: '.', + showLabels: true, + showTitle: true, + title: this.csv_title, + useBom: true, + noDownload: false, + headers: this.displayedColumns.map(column =>{ + if (column == 'id' && this.replaceId != undefined && this.replaceId != null){ + return this.replaceId; + } else { + return column; + } + }) + }; + + //let exportData = this.flatten(); + new ngxCsv(this.flattenData(), this.csv_file_name, options); + } + private flattenData(): any[] { + let data = []; + this.exportData.forEach(entry =>{ + let element = {}; + this.displayedColumns.forEach(key =>{ + element[key] = (entry[key].hasOwnProperty('value')) ? entry[key].value : entry[key]; + }); + data.push(element); + }); + return data; + } + exportToJson() { + const data = (this.fusekiResults != undefined && this.fusekiResults != null) ? this.fusekiResults : this.exportData; + const dataStr = JSON.stringify(data, null, 2); + const file = new Blob([dataStr], {type: 'text/json'}); + this.download(file, 'export.json'); + } + + download(blob, filename) { + if (window.navigator.msSaveOrOpenBlob) { window.navigator.msSaveOrOpenBlob(blob, filename); } else { + let elem = document.createElement('a'), url = URL.createObjectURL(blob); + elem.href = url; + elem.download = filename; + document.body.appendChild(elem); + elem.click(); + setTimeout(function() { + document.body.removeChild(elem); + window.URL.revokeObjectURL(url); + },0); + } + } + +} diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/fuseki-table.component.css b/nietzsche-beta-app/src/app/tln-edition/tln-quant/fuseki-table.component.css new file mode 100644 index 0000000..4426171 --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/fuseki-table.component.css @@ -0,0 +1,6 @@ +table { + width: 100%; +} +export { + float: right; +} diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/fuseki-table.component.html b/nietzsche-beta-app/src/app/tln-edition/tln-quant/fuseki-table.component.html new file mode 100644 index 0000000..f75a34f --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/fuseki-table.component.html @@ -0,0 +1,26 @@ + + + Fuseki Resultate + + + + + + + + + + +
{{column}} {{element[column] != null ? element[column].value : ''}}
+ + + +
+
diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/fuseki-table.component.spec.ts b/nietzsche-beta-app/src/app/tln-edition/tln-quant/fuseki-table.component.spec.ts new file mode 100644 index 0000000..3823de3 --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/fuseki-table.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FusekiTableComponent } from './fuseki-table.component'; + +describe('FusekiTableComponent', () => { + let component: FusekiTableComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FusekiTableComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FusekiTableComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/fuseki-table.component.ts b/nietzsche-beta-app/src/app/tln-edition/tln-quant/fuseki-table.component.ts new file mode 100644 index 0000000..3c4f419 --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/fuseki-table.component.ts @@ -0,0 +1,37 @@ +import { Component, EventEmitter, Input, Output, OnInit, ViewChild } from '@angular/core'; +import {MatPaginator} from '@angular/material/paginator'; +import {Sort} from '@angular/material/sort'; +import { PaginatorResultStatus } from '../common/paginator-result-status'; +import { FusekiResults, FusekiBindings } from '../datatypes/basic_datatype'; +import { MySort} from './sort'; +import { QuantQuery, PageIndexUpdater } from '../models'; + +@Component({ + selector: 'fuseki-table', + templateUrl: './fuseki-table.component.html', + styleUrls: ['./fuseki-table.component.css'] +}) +export class FusekiTableComponent implements OnInit, PageIndexUpdater { + @Input() fusekiResults: FusekiResults; + @Input() resultIndicesIndex: number = 0; + @Input() quantQuery: QuantQuery; + @Output() quantQueryChanged = new EventEmitter(); + @Output() change = new EventEmitter(); + @ViewChild(MatPaginator,{static:false}) paginator: MatPaginator; + paginatorResultStatus: PaginatorResultStatus = new PaginatorResultStatus(10, this); + sort: MySort = new MySort(); + + constructor() {} + + ngOnInit() { + this.paginatorResultStatus.updateResultRange(this.quantQuery.resultIndices[this.resultIndicesIndex]); + } + private sortData(sort: Sort){ + this.paginator.pageIndex = 0; + this.paginatorResultStatus.updateResultRange(0); + this.quantQuery.sortArray[this.resultIndicesIndex] = sort; + this.quantQuery.resultIndices[this.resultIndicesIndex] = 0; + this.quantQueryChanged.emit(this.quantQuery); + } + +} diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/query-error.component.css b/nietzsche-beta-app/src/app/tln-edition/tln-quant/query-error.component.css new file mode 100644 index 0000000..8170b4a --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/query-error.component.css @@ -0,0 +1,6 @@ +.error { + /* margin-top: 20px; + color: red;*/ + background-color: black; + color: white; +} diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/query-error.component.html b/nietzsche-beta-app/src/app/tln-edition/tln-quant/query-error.component.html new file mode 100644 index 0000000..710e867 --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/query-error.component.html @@ -0,0 +1 @@ + diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/query-error.component.spec.ts b/nietzsche-beta-app/src/app/tln-edition/tln-quant/query-error.component.spec.ts new file mode 100644 index 0000000..da120a6 --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/query-error.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { QueryErrorComponent } from './query-error.component'; + +describe('QueryErrorComponent', () => { + let component: QueryErrorComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ QueryErrorComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(QueryErrorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/query-error.component.ts b/nietzsche-beta-app/src/app/tln-edition/tln-quant/query-error.component.ts new file mode 100644 index 0000000..11cd93b --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/query-error.component.ts @@ -0,0 +1,12 @@ +import { Component, Inject} from '@angular/core'; +import {MatDialog, MAT_DIALOG_DATA} from '@angular/material/dialog'; +import { MAT_BOTTOM_SHEET_DATA, MatBottomSheetRef} from '@angular/material/bottom-sheet'; + +@Component({ + selector: 'app-query-error', + templateUrl: './query-error.component.html', + styleUrls: ['./query-error.component.css'] +}) +export class QueryErrorComponent { + constructor(@Inject(MAT_BOTTOM_SHEET_DATA) public data: any) {} +} diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/result.pipe.spec.ts b/nietzsche-beta-app/src/app/tln-edition/tln-quant/result.pipe.spec.ts new file mode 100644 index 0000000..39a0c53 --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/result.pipe.spec.ts @@ -0,0 +1,8 @@ +import { ResultPipe } from './result.pipe'; + +describe('ResultPipe', () => { + it('create an instance', () => { + const pipe = new ResultPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/result.pipe.ts b/nietzsche-beta-app/src/app/tln-edition/tln-quant/result.pipe.ts new file mode 100644 index 0000000..db3cf8e --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/result.pipe.ts @@ -0,0 +1,33 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { NumericResultRow } from '../datatypes/quant'; +import { QuantQuery } from '../models'; + +@Pipe({ + name: 'quantResultPipe' +}) +export class ResultPipe implements PipeTransform { + + transform(numResultRows: NumericResultRow[], quantQuery: QuantQuery): string { + const total = (numResultRows.length > 0) ? numResultRows[0].total : 0; + let numWordWithPropertiesTotal = 0 + numResultRows.forEach(result =>{ numWordWithPropertiesTotal += result.numProperties}); + let wordPropString = (numWordWithPropertiesTotal > 1) ? 'Wörter' : 'Wort'; + if (quantQuery.altQuery == null && quantQuery.selectedWordProperties.length > 0) { + if (quantQuery.selectedWordProperties.length > 1) { + const propStrings = quantQuery.selectedWordProperties.map(prop =>'"' + prop.label.replace('.', '') + '"'); + wordPropString = wordPropString + ' haben die Eigenschaften ' + propStrings.join(', ') + } else { + wordPropString = wordPropString + ' haben die Eigenschaft "' + quantQuery.selectedWordProperties[0].label.replace('.', '') + '"'; + } + } + let filterString = '' + if (quantQuery.altQuery == null + && ((quantQuery.text != null && quantQuery.text != '') + || (quantQuery.filterValue != null && quantQuery.filterValue != ''))){ + filterString = (quantQuery.text != null && quantQuery.text != '') ? ' und den Worttext "' + quantQuery.text + '"' : ' und den Filtertext "' + quantQuery.filterValue +'"'; + } + //const filterString = (quantQuery.altQuery == null && quantQuery.text != null && quantQuery.text != '') ? ' und den Worttext "' + quantQuery.text + '"': ''; + return numWordWithPropertiesTotal + '/' + total + ' ' + wordPropString + filterString; + } + +} diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/select-by-query.pipe.spec.ts b/nietzsche-beta-app/src/app/tln-edition/tln-quant/select-by-query.pipe.spec.ts new file mode 100644 index 0000000..c5d263b --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/select-by-query.pipe.spec.ts @@ -0,0 +1,8 @@ +import { SelectByQueryPipe } from './select-by-query.pipe'; + +describe('SelectByQueryPipe', () => { + it('create an instance', () => { + const pipe = new SelectByQueryPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/select-by-query.pipe.ts b/nietzsche-beta-app/src/app/tln-edition/tln-quant/select-by-query.pipe.ts new file mode 100644 index 0000000..d6bcbc3 --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/select-by-query.pipe.ts @@ -0,0 +1,12 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'selectByQuery' +}) +export class SelectByQueryPipe implements PipeTransform { + + transform(value: any, ...args: any[]): any { + return null; + } + +} diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/sort-by-line.pipe.spec.ts b/nietzsche-beta-app/src/app/tln-edition/tln-quant/sort-by-line.pipe.spec.ts new file mode 100644 index 0000000..d9d6376 --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/sort-by-line.pipe.spec.ts @@ -0,0 +1,8 @@ +import { SortByLinePipe } from './sort-by-line.pipe'; + +describe('SortByLinePipe', () => { + it('create an instance', () => { + const pipe = new SortByLinePipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/sort-by-line.pipe.ts b/nietzsche-beta-app/src/app/tln-edition/tln-quant/sort-by-line.pipe.ts new file mode 100644 index 0000000..a1375f1 --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/sort-by-line.pipe.ts @@ -0,0 +1,13 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { PresentationWord } from '../datatypes/word_presentation'; + +@Pipe({ + name: 'sortByLine' +}) +export class SortByLinePipe implements PipeTransform { + + transform(words: PresentationWord[]): PresentationWord[] { + return words.sort((n1,n2) => n1.line_number - n2.line_number); + } + +} diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/sort.ts b/nietzsche-beta-app/src/app/tln-edition/tln-quant/sort.ts new file mode 100644 index 0000000..c83caed --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/sort.ts @@ -0,0 +1,19 @@ +import {Sort} from '@angular/material/sort'; +import { FusekiResults } from '../datatypes/basic_datatype'; + +export class MySort { + public sortFusekiData(sort: Sort, fusekiResults: FusekiResults){ + let bindings = fusekiResults.results.bindings; + let data = bindings.slice(); + if (!sort.active || sort.direction === '') { + fusekiResults.results.bindings = <[]>data; + return; + } + fusekiResults.results.bindings = <[]>data.sort((a, b) =>{ + return this.compare(a[sort.active]['value'], b[sort.active]['value'], sort.direction === 'asc'); + }); + } + public compare(a: number | string, b: number | string, isAsc: boolean): number { + return (a < b ? -1 : 1) * (isAsc ? 1: -1); + } +} diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-quant.component.css b/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-quant.component.css new file mode 100644 index 0000000..5d15716 --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-quant.component.css @@ -0,0 +1,27 @@ +.text-quality { + margin-left: 15px; + margin-top: 10px; + display: block; +} + +mat-selection-list { + width: 300px; +} +.headers-align .mat-expansion-panel-header-title, +.headers-align .mat-expansion-panel-header-description { + flex-basis: 0; +} + +.headers-align .mat-expansion-panel-header-description { + justify-content: space-between; + align-items: center; +} + +.headers-align .mat-form-field + .mat-form-field { + margin-left: 8px; +} +.form { + margin-top: 10px; + margin-left: 15px; + margin-right: 15px; +} diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-quant.component.html b/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-quant.component.html new file mode 100644 index 0000000..05ddee7 --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-quant.component.html @@ -0,0 +1,116 @@ + + + Quantitative Abfrage + + + + + + Worteigenschaften + + Eigenschaften für Anfrage auswählen + + + + + {{wordProperty.label}} + + + + Wort hat Text (regex) + + + + + Groß- und Kleinschreibung ignorieren + +
+ + Text ohne Satzzeichen + + + + Text mit Satzzeichen + + Edierter Text bevorzugen + +
+ +
+ + + Korpus + + Anfrage {{ quantQuery.selectedManuscripts.length == 1 ? + 'ist auf ' + quantQuery.selectedManuscripts[0].title + ' beschränkt' + : 'auf ausgewählte Manuskripte beschränken' }} + + + + + {{manuscript.title}} + + + + + + + SPARQL-Query + Query bearbeiten + + +
+ + + + + + +
+
+
+
+ + +
+ + + + + + + + + + diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-quant.component.spec.ts b/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-quant.component.spec.ts new file mode 100644 index 0000000..2416d97 --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-quant.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TlnQuantComponent } from './tln-quant.component'; + +describe('TlnQuantComponent', () => { + let component: TlnQuantComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ TlnQuantComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TlnQuantComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-quant.component.ts b/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-quant.component.ts new file mode 100644 index 0000000..a581095 --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-quant.component.ts @@ -0,0 +1,275 @@ +import { Component, OnInit, Input, ViewChild } from '@angular/core'; +import { Router, ActivatedRoute, Params } from '@angular/router'; +import { HttpErrorResponse} from '@angular/common/http'; +import {Sort} from '@angular/material/sort'; +import {MatBottomSheet, MatBottomSheetRef} from '@angular/material/bottom-sheet'; +import {MatTabChangeEvent} from '@angular/material/tabs'; +import {QueryErrorComponent} from './query-error.component'; +import { CodemirrorComponent} from '@ctrl/ngx-codemirror'; +import { DataHandler } from '../data_handler'; +import { QuantitativeDataHandler } from '../quant_data_handler'; +import { Manuscript4Selection} from '../datatypes/manuscript'; +import { FusekiResults } from '../datatypes/basic_datatype'; +import { NumericResultRow, QueryJson, SelectableWordProperty} from '../datatypes/quant'; +import { AccordionStatus, DataProcessor, QuantQuery, TlnQueryServiceInterface, TextQuality} from '../models'; +import { TlnCacheQueryService } from '../services'; +import { TLN_QUANT_ROUTE, TLN_MANUSCRIPT_ROUTE, TLN_VIEWER_ROUTE, TLN_CONTEXT_VIEW_PARAM, TLN_FULLSCREEN_PARAM, TLN_FIND_PARAM, TLN_PAGE_PARAM, TLN_MANUSCRIPT_PARAM, TLN_QUANT_QUERY_PARAM, + TLN_QUERY_PARAM, TLN_RESULT_INDEX_PARAM,TLN_SELECTED_LINES_PARAM, TLN_TEXT_GENETIC_ORDER_PARAM, TLN_VIEW_OPTION_PARAM, TLN_ZOOM_PARAM, VIEW_OPTIONS, ONTOLOTY_PREFIX } from '../constants'; +import { Mapping } from '../route-reader'; +import { RouteUpdater } from '../route-updater'; +import { CacheService } from './cache.service'; +import { CompareMapping, SelectFromArray } from '../common/select-array'; + +@Component({ + selector: 'tln-quant', + templateUrl: './tln-quant.component.html', + providers: [ TlnCacheQueryService], + styleUrls: ['./tln-quant.component.css'] +}) +export class TlnQuantComponent extends RouteUpdater implements DataProcessor, OnInit { + /** + * OPTIONAL pass a queryService with method + * {@link /interfaces/TlnQueryServiceInterface.html#getData|getData} + * to TlnPageViewComponent. + **/ + @Input() queryService: TlnQueryServiceInterface; + @Input() restrictKorpusOnContext: boolean = false; + @ViewChild(CodemirrorComponent,{static:false}) codemirror: CodemirrorComponent; + tlnQueryService: TlnQueryServiceInterface; + dataHandler: DataHandler = new DataHandler(this); + fusekiResults: FusekiResults; + current_manuscript_iri: string; + select_manuscript_iri: string; + displayedColumns: string[] = [ 'id', 'numProperties', 'numPropertyTextPercent', 'numPropertiesPercent', 'numPropertiesIncludeMulti', 'numText', 'numTextPercent' ]; + errorLine: number = -1; + quantDataHandler: QuantitativeDataHandler = new QuantitativeDataHandler(this); + isLoadingResults: boolean = false; + manuscriptPages: Manuscript4Selection[] = []; + numResultRows: NumericResultRow[] = []; + resultIndex: number = 0; + quantAccordionStatus: AccordionStatus = { + wordProperties: { expanded: true, disabled: false }, + scopus: { expanded: false, disabled: false }, + query: { expanded: false, disabled: false } + }; + query: string = ''; + queryInput: string = ''; + queryHasSyntaxError: boolean = false; + curlQuery: string = ''; + shareQuery: boolean = false; + wordProperties: SelectableWordProperty[] = []; + quantQuery: QuantQuery = { + currentTable: 0, resultIndices: [ 0, 0 ], sortArray: [ { active: null, direction: '' }, { active: null, direction: '' }], + ignoreCase: false, text: '', selectedManuscripts: [], selectedWordProperties: [], textQuality: { clean: true, preferEditedText: true }, + restrictKorpusOnContext: false, filterValue: '' + } + protected mapping: Mapping = { + current_manuscript_iri: { param: TLN_MANUSCRIPT_PARAM, type: "string" }, + quantQuery: { param: TLN_QUANT_QUERY_PARAM, type: "complex" } } + + constructor(private cacheService: CacheService, private localQueryService: TlnCacheQueryService, + protected router: Router, protected activatedRoute: ActivatedRoute, private _dialog: MatBottomSheet) { + super(router, activatedRoute); + } + + ngOnInit() { + this.tlnQueryService = (this.queryService != null) ? this.queryService : this.localQueryService; + this.dataHandler.addHandler('wordProperties', { 'handler': SelectableWordProperty}); + this.dataHandler.addHandler('manuscriptPages', { 'handler': Manuscript4Selection}); + this.quantDataHandler.addHandler('numResultRows', { 'handler': NumericResultRow, 'process_data': this }); + this.dataHandler.setQueryService(this.tlnQueryService); + this.quantDataHandler.setQueryService(this.tlnQueryService); + this.quantDataHandler.start_processing.subscribe( + (started: boolean) =>{ + this.isLoadingResults = true; + }); + this.quantDataHandler.processing_finished.subscribe( + (started: boolean) =>{ + this.isLoadingResults = false; + }); + this.dataHandler.getData('wordProperties'); + this.dataHandler.getData('manuscriptPages'); + this.quantQuery.restrictKorpusOnContext = this.restrictKorpusOnContext + if (this.restrictKorpusOnContext){ + this.dataHandler['manuscriptPages']['process_data'] = new SelectFromArray(this, { + compareValueKey: 'select_manuscript_iri', + sourceArrayKey: 'manuscriptPages', + targetArrayKey: ['quantQuery', 'selectedManuscripts'], + commonPropertyKey: 'id' + }) + } + super.ngOnInit(); + } + isManuscriptSelected(manuscript: Manuscript4Selection): boolean { + return this.restrictKorpusOnContext && this.current_manuscript_iri == manuscript.id + } + isSelected = (o1: any, o2: any): boolean => { + return o1.id == o2.id; + } + normalLineNumberFormatter = (line: number): string =>{ + return String(line); + } + private markError(error: string){ + if (this.quantQuery != null + && this.quantQuery.dataKey != undefined + && this.quantQuery.dataKey != null){ + this.cacheService.removeItem(this.quantQuery.dataKey) + this.quantQuery['dataKey'] = null; + this.updateParams(); + } + const matches = error.match(/(.*line\s)([0-9]+)(:.*)/s) + if (matches != null && matches.length == 4){ + this.errorLine = Number(matches[2]); + this.codemirror.codeMirror.setOption('lineNumberFormatter' as any, (line:number): string =>{ + if (line == this.errorLine){ + return 'E>'; + } + return String(line) + }); + this.quantAccordionStatus.query.expanded = true; + } + } + private send() { + const error = QueryJson.getSyntaxError(this.query); + //console.log('error', error); + if (error != ''){ + this._dialog.open(QueryErrorComponent, { data: {error: error }}); + this.markError(error); + } else { + this.numResultRows = []; + Object.keys(this.quantAccordionStatus).forEach(key =>{this.quantAccordionStatus[key].expanded = false}); + this.quantDataHandler.getData('numResultRows', this.query, 'fusekiResults'); + } + } + private cancel(){ + this.quantDataHandler.stop_processing.emit(true); + this.isLoadingResults = false; + } + private copyToClipboard(curlQueryInput){ + curlQueryInput.select(); + curlQueryInput.setSelectionRange(0, 99999); /* For mobile devices */ + + /* Copy the text inside the text field */ + document.execCommand("copy"); + } + protected readParams(params: Params){ + const old_current_manuscript_iri = this.current_manuscript_iri + super.readParams(params); + if (this.current_manuscript_iri != null + && old_current_manuscript_iri != this.current_manuscript_iri + && this.quantQuery.restrictKorpusOnContext){ + this.select_manuscript_iri = this.current_manuscript_iri; + this.quantAccordionStatus.scopus.expanded = true; + } + if (this.quantQuery != null){ + if (this.quantQuery.selectedManuscripts.length > 0){ + this.select_manuscript_iri = null; + } + if (this.quantQuery.altQuery != null){ + this.query = this.quantQuery.altQuery; + this.curlQuery = 'curl ' + this.tlnQueryService.baseUrl + ' -X POST --data \'query=' + encodeURI(this.query) + '\'' + this.quantAccordionStatus.wordProperties = { expanded: false, disabled: true }; + this.quantAccordionStatus.query.expanded = true; + } + if (this.quantQuery.dataKey != undefined && this.quantQuery.dataKey != null){ + const results = this.cacheService.getItem(this.quantQuery.dataKey); + if (results != null){ + this.fusekiResults = results; + this.numResultRows = NumericResultRow.convertData(this.fusekiResults); + if (this.quantQuery.sortArray[this.quantQuery.currentTable].active != null){ + const sort = this.quantQuery.sortArray[this.quantQuery.currentTable] + if (this.quantQuery.currentTable == 0){ + this.numResultRows = this.numResultRows.sort((a, b) => { + return this.compare(a[sort.active], b[sort.active], sort.direction === 'asc') + }); + } else { + this.fusekiResults.results.bindings = this.fusekiResults.results.bindings.sort((a, b) => { + return this.compare((a[sort.active]['value']), (b[sort.active]['value']), sort.direction === 'asc') + }); + } + } + if (this.quantQuery.filterValue != undefined && this.quantQuery.filterValue != null && this.quantQuery.filterValue != ''){ + this.numResultRows = this.numResultRows.filter(result =>result.id.startsWith(this.quantQuery.filterValue)); + } + } + } + } + if (this.query == ''){ + this.updateQuery(); + } + } + private compare(a: number | string, b: number | string, isAsc: boolean): number { + return (a < b ? -1 : 1) * (isAsc ? 1: -1); + } + private toggleEnableSelectionList(updateQuery: boolean) { + if (this.errorLine > -1){ + this.errorLine = -1; + this.codemirror.codeMirror.setOption('lineNumberFormatter' as any, (line:number): string =>{return String(line)}) + } + if (updateQuery) { + this.updateQuery(); + this.queryHasSyntaxError = false; + this.quantAccordionStatus.wordProperties.disabled = false; + } else { + const queryChanged = this.query != this.getQuery(); + this.queryHasSyntaxError = QueryJson.hasSyntaxError(this.query); + this.quantAccordionStatus.wordProperties.disabled = queryChanged; + this.updateQuery4Ext(queryChanged); + if (this.quantAccordionStatus.wordProperties.disabled) { + this.quantAccordionStatus.wordProperties.expanded = false; + } + } + } + private getQuery(): string { + let selectedManuscripts = (this.quantQuery.selectedManuscripts.length == this.manuscriptPages.length) ? [] : this.quantQuery.selectedManuscripts; + return this.quantDataHandler['numResultRows'].handler.getSelectableQuery( + this.quantQuery.selectedWordProperties, + selectedManuscripts, + this.quantQuery.textQuality, + this.quantQuery.text, this.quantQuery.ignoreCase); + } + public processData(){ + if (this.quantQuery != null + && this.quantQuery.dataKey != undefined + && this.quantQuery.dataKey != null){ + this.cacheService.removeItem(this.quantQuery.dataKey); + } + const dataKey = TLN_QUANT_ROUTE + Date.now().toString(); + this.cacheService.setItem(dataKey, this.fusekiResults); + this.quantQuery['dataKey'] = dataKey; + this.updateParams(); + } + private tabChanged(tabChange: MatTabChangeEvent){ + this.quantQuery.currentTable = tabChange.index; + this.quantQuery.resultIndex = 0; + this.updateParams(); + } + private updateTextQuality(clean: boolean, preferEditedText?: boolean){ + this.quantQuery.textQuality = { clean: clean, preferEditedText: preferEditedText }; + this.updateQuery(); + } + private updatePageIndex(pageIndex: number){ + if (typeof pageIndex == 'number'){ + this.quantQuery.resultIndices[this.quantQuery.currentTable] = pageIndex; + this.updateParams(); + } + } + private updateQuantQuery(quantQuery: QuantQuery){ + console.log(quantQuery); + this.quantQuery = quantQuery; + this.updateParams(); + } + private updateQuery(source?: string) { + this.query = this.getQuery(); + this.updateQuery4Ext(false); + } + private updateQuery4Ext(setAltQuery: boolean) { + this.curlQuery = 'curl ' + this.tlnQueryService.baseUrl + ' -X POST --data \'query=' + encodeURI(this.query) + '\'' + if (setAltQuery) { + this.quantQuery.altQuery = this.query + } else { + this.quantQuery.altQuery = null; + } + this.updateParams(); + } +} diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-table.component.css b/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-table.component.css new file mode 100644 index 0000000..2f5338f --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-table.component.css @@ -0,0 +1,58 @@ +table { + width: 100%; +} +export { + float: right; +} +mat-form-field { + margin-left: 25px; + margin-top: 30px; +} +tr.example-detail-row { + height: 0; +} +tr.example-element-row { + cursor: pointer; +} +tr.example-element-row:not(.example-expanded-row):hover { + background: whitesmoke; +} + +tr.example-element-row:not(.example-expanded-row):active { + background: #efefef; +} + +.example-element-row td { + border-bottom-width: 0; +} + +.example-element-detail { + overflow: hidden; + /*display: flex;*/ +} + +.example-element-diagram { + min-width: 80px; + border: 2px solid black; + padding: 8px; + font-weight: lighter; + margin: 8px 0; + height: 104px; +} + +.example-element-symbol { + font-weight: bold; + font-size: 40px; + line-height: normal; +} + +.example-element-description { + padding: 16px; +} + +.example-element-description-attribution { + opacity: 0.5; +} +.test { + display: block; +} diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-table.component.html b/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-table.component.html new file mode 100644 index 0000000..b55311d --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-table.component.html @@ -0,0 +1,66 @@ + + + {{results.length}} Resultate ({{results | quantResultPipe:resultQuery}}) + + + + Filter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Anzahl mit Eigenschaften (einfach) {{element.numProperties}} % Wortanteil {{element.numPropertyTextPercent + ' %'}} % aller Wörter {{element.numPropertiesPercent + ' %'}} Anzahl mit Eigenschaften (mehrfach) {{element.numPropertiesIncludeMulti}} Anzahl Total {{element.numText}} % aller Wörter {{element.numTextPercent + ' %'}} Wort {{element.id}} +
+ +
+
+ + + +
+
diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-table.component.spec.ts b/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-table.component.spec.ts new file mode 100644 index 0000000..7f468ad --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-table.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TlnTableComponent } from './tln-table.component'; + +describe('TlnTableComponent', () => { + let component: TlnTableComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ TlnTableComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TlnTableComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-table.component.ts b/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-table.component.ts new file mode 100644 index 0000000..aae9e3e --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-table.component.ts @@ -0,0 +1,56 @@ +import { Component, EventEmitter, Input, Output, OnInit, ViewChild } from '@angular/core'; +import {animate, state, style, transition, trigger} from '@angular/animations'; +import {MatPaginator} from '@angular/material/paginator'; +import {Sort} from '@angular/material/sort'; +import { NumericResultRow} from '../datatypes/quant'; +import { PaginatorResultStatus } from '../common/paginator-result-status'; +import { QuantQuery, PageIndexUpdater } from '../models'; +import { MySort } from './sort'; + +@Component({ + selector: 'tln-table', + templateUrl: './tln-table.component.html', + animations: [ + trigger('detailExpand', [ + state('collapsed', style({height: '0px', minHeight: '0'})), + state('expanded', style({height: '*'})), + transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')), + ]), + ], + styleUrls: ['./tln-table.component.css'] +}) +export class TlnTableComponent implements OnInit, PageIndexUpdater { + @Input() results: NumericResultRow[]; + @Input() resultIndicesIndex: number = 0; + @Input() quantQuery: QuantQuery; + @Output() quantQueryChanged = new EventEmitter(); + @Output() change = new EventEmitter(); + expandedElement: NumericResultRow | null; + resultQuery: QuantQuery; + paginatorResultStatus: PaginatorResultStatus = new PaginatorResultStatus(10, this); + sort: MySort = new MySort(); + unfilteredResults: NumericResultRow[] = []; + @Input() displayedColumns: string[]; + @ViewChild(MatPaginator,{static:false}) paginator: MatPaginator; + + constructor() { } + + ngOnInit() { + this.resultQuery = this.quantQuery; + this.paginatorResultStatus.updateResultRange(this.resultQuery.resultIndices[this.resultIndicesIndex]); + } + private addFilter(event: Event) { + const filterValue = (event.target as HTMLInputElement).value; + this.quantQuery.filterValue = filterValue; + this.quantQuery.resultIndices[this.resultIndicesIndex] = 0; + this.paginatorResultStatus.updateResultRange(0); + this.quantQueryChanged.emit(this.quantQuery); + } + private sortData(sort: Sort){ + this.paginator.pageIndex = 0; + this.paginatorResultStatus.updateResultRange(0); + this.quantQuery.sortArray[this.resultIndicesIndex] = sort; + this.quantQuery.resultIndices[this.resultIndicesIndex] = 0; + this.quantQueryChanged.emit(this.quantQuery); + } +} diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/word-presentation.component.css b/nietzsche-beta-app/src/app/tln-edition/tln-quant/word-presentation.component.css new file mode 100644 index 0000000..60b9e24 --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/word-presentation.component.css @@ -0,0 +1,3 @@ +.word { + margin-left: 10px; +} diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/word-presentation.component.html b/nietzsche-beta-app/src/app/tln-edition/tln-quant/word-presentation.component.html new file mode 100644 index 0000000..b4f42d9 --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/word-presentation.component.html @@ -0,0 +1,32 @@ + + + + +
{{manuscript.title}}
+ +
+ {{page.number}} + + + + : + + Z {{word.line_number}} + + + + + +
+
+
+
+
diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/word-presentation.component.spec.ts b/nietzsche-beta-app/src/app/tln-edition/tln-quant/word-presentation.component.spec.ts new file mode 100644 index 0000000..c3daccc --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/word-presentation.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WordPresentationComponent } from './word-presentation.component'; + +describe('WordPresentationComponent', () => { + let component: WordPresentationComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ WordPresentationComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WordPresentationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/word-presentation.component.ts b/nietzsche-beta-app/src/app/tln-edition/tln-quant/word-presentation.component.ts new file mode 100644 index 0000000..cecb9cf --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/word-presentation.component.ts @@ -0,0 +1,49 @@ +import { Component, OnInit, OnChanges, SimpleChanges,Input } from '@angular/core'; +import { DataHandler } from '../data_handler'; +import { QuantitativeDataHandler } from '../quant_data_handler'; +import { TlnQueryServiceInterface} from '../models'; +import { TlnCacheQueryService } from '../services'; +import { ManuscriptPageWords } from '../datatypes/word_presentation'; + +@Component({ + selector: 'word-presentation', + templateUrl: './word-presentation.component.html', + styleUrls: ['./word-presentation.component.css'] +}) +export class WordPresentationComponent implements OnInit, OnChanges { + /** + * OPTIONAL pass a queryService with method + * {@link /interfaces/TlnQueryServiceInterface.html#getData|getData} + * to TlnPageViewComponent. + **/ + @Input() queryService: TlnQueryServiceInterface; + @Input() wordIds: string[]; + @Input() expanded: boolean = false; + isLoadingResults: boolean = false; + dataHandler: QuantitativeDataHandler = new QuantitativeDataHandler(this); + private readonly displayedColumns: string[] = [ 'text', 'editedText', 'cleanText', 'cleanEditedText', 'link']; + manuscriptPageWords: ManuscriptPageWords[] = []; + tlnQueryService: TlnQueryServiceInterface; + + constructor(private localQueryService: TlnCacheQueryService) {} + + ngOnInit() { + this.tlnQueryService = (this.queryService != null) ? this.queryService : this.localQueryService; + this.dataHandler.addHandler('manuscriptPageWords', { 'handler': ManuscriptPageWords}); + this.dataHandler.setQueryService(this.tlnQueryService); + this.dataHandler.start_processing.subscribe( + (started: boolean) =>{ + this.isLoadingResults = true; + }); + this.dataHandler.processing_finished.subscribe( + (started: boolean) =>{ + this.isLoadingResults = false; + }); + } + ngOnChanges(changes: SimpleChanges){ + if (this.expanded && this.manuscriptPageWords.length == 0 && this.wordIds.length > 0){ + const query = ManuscriptPageWords.getParameterizedQuery(this.wordIds) + this.dataHandler.getData('manuscriptPageWords', query); + } + } +} diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-query.service.ts b/nietzsche-beta-app/src/app/tln-edition/tln-query.service.ts index 662881e..83a64c6 100644 --- a/nietzsche-beta-app/src/app/tln-edition/tln-query.service.ts +++ b/nietzsche-beta-app/src/app/tln-edition/tln-query.service.ts @@ -1,52 +1,76 @@ import { Injectable, EventEmitter, OnInit } from '@angular/core'; import {HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http'; -import { Observable, throwError } from 'rxjs'; -import { catchError } from 'rxjs/operators'; +import { EMPTY, Observable, throwError } from 'rxjs'; +import { catchError, shareReplay } from 'rxjs/operators'; import { TlnQueryServiceInterface } from './models'; +import { MatSnackBar, MatSnackBarHorizontalPosition, MatSnackBarVerticalPosition, } from '@angular/material/snack-bar'; +import {MatDialog, MatDialogConfig} from '@angular/material/dialog'; /** * This is the internal query service * that communicates with the SPARQL-endpoint. * */ @Injectable({ providedIn: 'root'}) export class TlnQueryService implements TlnQueryServiceInterface { - //baseUrl = 'http://localhost:3030/nietzsche/query'; - baseUrl = 'https://nietzsche.fuseki.services.dasch.swiss/nietzsche' + //baseUrl = 'http://localhost:3030/nietzsche/query'; + baseUrl = 'https://nietzsche.fuseki.services.dasch.swiss/nietzsche' reset_data = new EventEmitter(); + error_emitter = new EventEmitter(); + use_cache: boolean = false; + cache = {}; - constructor(private http: HttpClient) { } + constructor(private http: HttpClient, private _snackBar: MatSnackBar) { } public resetData(key: string){ this.reset_data.emit(key); } /** * Gets the data from an endpoint via http post * * @param query: The query to run. * @returns response */ public getData(query: string): Observable { let httpOptions = { headers: new HttpHeaders( { 'Content-Type': 'application/sparql-query', 'Accept': 'application/sparql-results+json; charset=UTF-8'} ) }; - return this.http.post(this.baseUrl, query, httpOptions).pipe(catchError(this.handleError)); + if (this.use_cache && this.cache[query]){ + console.log('Returning cached value!') + return this.cache[query]; + } + if (!this.use_cache){ + return this.http.post(this.baseUrl, query, httpOptions).pipe(catchError(this.handleError)); + } + this.cache[query] = this.http.post(this.baseUrl, query, httpOptions).pipe( + shareReplay(1), + catchError(error =>{ + delete this.cache[query]; + this.handleError(error); + return EMPTY + }) + ); + return this.cache[query] } private handleError(error: HttpErrorResponse) { - if (error.status === 0) { - // A client-side or network error occurred. Handle it accordingly. - console.error('The backend data server is offline:', error.error); - } else { - // The backend returned an unsuccessful response code. - // The response body may contain clues as to what went wrong. - console.error( - `Backend returned code ${error.status}, ` + - `body was: ${error.error}`); + this.error_emitter.emit(error); + switch (error.status){ + case 0: + // A client-side or network error occurred. Handle it accordingly. + console.error('The backend data server is offline:', error.error); + this._snackBar.open('The backend data server ' + this.baseUrl + ' is offline!'); + break; + default: + console.error( `Backend returned code ${error.status}, ` + `body was: ${error.error}`); } - return throwError( 'Something bad happened; please try again later.'); + return throwError( 'Something bad happened; please try again later.'); } } +@Injectable({ providedIn: 'root'}) +export class TlnCacheQueryService extends TlnQueryService { + use_cache: boolean = true; +} diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-viewer-navigation/tln-viewer-navigation.component.ts b/nietzsche-beta-app/src/app/tln-edition/tln-viewer-navigation/tln-viewer-navigation.component.ts index 05e5b86..5c6436b 100644 --- a/nietzsche-beta-app/src/app/tln-edition/tln-viewer-navigation/tln-viewer-navigation.component.ts +++ b/nietzsche-beta-app/src/app/tln-edition/tln-viewer-navigation/tln-viewer-navigation.component.ts @@ -1,165 +1,165 @@ import { Component, OnInit, OnDestroy, HostListener, Input, ElementRef } from '@angular/core'; import { Router, ActivatedRoute, Params } from '@angular/router'; import {MatBottomSheet, MatBottomSheetRef} from '@angular/material/bottom-sheet'; import { TlnQueryServiceInterface, Reference, ManuscriptUnity, NavigationPage } from '../models'; import { TlnManuscriptUnity, TlnNavigationPage} from '../datatypes/navigation'; import { DEFAULT_VIEW_OPTION, TLN_VIEWER_ROUTE, TLN_FULLSCREEN_PARAM, TLN_FIND_PARAM, TLN_NAV_BAR_OPEN_STATE_PARAM, TLN_PAGE_PARAM, TLN_MANUSCRIPT_PARAM, TLN_SELECTED_LINES_PARAM, TLN_SELECTED_WORDS_PARAM, TLN_VIEW_OPTION_PARAM, TLN_ZOOM_PARAM, VIEW_OPTIONS, ONTOLOTY_PREFIX } from '../constants'; import { IsReconstructedKonvolut } from '../datatypes/basic_datatype'; import { TlnLine} from '../datatypes/line'; import { TlnWord} from '../datatypes/word'; import { TlnTextGeneticOrder} from '../datatypes/text_version'; import { Mapping } from '../route-reader'; import { RouteUpdater } from '../route-updater'; import { ComplexKeyIriMapping, DataHandler, KeyIriMapping } from '../data_handler'; -import { PageViewService, TlnQueryService } from '../services'; +import { PageViewService, TlnCacheQueryService } from '../services'; import { TlnInformationComponent, ParentInformation } from '../tln-information/tln-information.component'; import { PageInformation } from '../tln-information/page-information'; @Component({ selector: 'tln-viewer-navigation', templateUrl: './tln-viewer-navigation.component.html', - providers: [ TlnQueryService], + providers: [ TlnCacheQueryService ], styleUrls: ['./tln-viewer-navigation.component.css'] }) export class TlnViewerNavigation extends RouteUpdater implements OnDestroy { /** * OPTIONAL pass a queryService with method * {@link /interfaces/TlnQueryServiceInterface.html#getData|getData} * to TlnPageViewComponent. **/ @Input() queryService: TlnQueryServiceInterface; /** * whether or not to show page view in fullscreen mode. **/ fullscreen: boolean = false; findText: string; current_iri: string; current_manuscript_iri: string; manuscript_unity: ManuscriptUnity; current_page: NavigationPage; pageInformation: PageInformation; previous_page: NavigationPage; next_page: NavigationPage; navBarOpenState: boolean; showArchivalManuscriptUnity: boolean = false; dataHandler: DataHandler = new DataHandler(this); geneticOrders: TlnTextGeneticOrder[] = []; selectedLines: string[] = []; selectedWords: string[] = []; private readonly increment: number = 0.333; private readonly decrement: number = this.increment*-1; //protected currentRoute: string = TLN_VIEWER_ROUTE; protected mapping: Mapping = { findText: { param: TLN_FIND_PARAM, type: "string" }, current_iri: { param: TLN_PAGE_PARAM, type: "string" }, navBarOpenState: { param: TLN_NAV_BAR_OPEN_STATE_PARAM, type: "boolean" }, current_manuscript_iri: { param: TLN_MANUSCRIPT_PARAM, type: "string" }, fullscreen: { param: TLN_FULLSCREEN_PARAM, type: "boolean" }, selectedViewOption: { param: TLN_VIEW_OPTION_PARAM, type: "string" }, selectedWords: { param: TLN_SELECTED_WORDS_PARAM, type: "string" }, selectedLines: { param: TLN_SELECTED_LINES_PARAM, type: "string" } } routerParams: Params; selectedViewOption: string = DEFAULT_VIEW_OPTION updating: boolean = false; viewOptions: string[] = [ VIEW_OPTIONS.TRANSKRIPTION, VIEW_OPTIONS.FAKSIMILE, VIEW_OPTIONS.SYNOPSIS, VIEW_OPTIONS.SYNOPSIS_B ]; subscriptions: any[] = []; - constructor(el: ElementRef, private bottomSheet: MatBottomSheet, private pageViewService: PageViewService, private localQueryService: TlnQueryService, protected router: Router, protected activatedRoute: ActivatedRoute ) { + constructor(el: ElementRef, private bottomSheet: MatBottomSheet, private pageViewService: PageViewService, private localQueryService: TlnCacheQueryService, protected router: Router, protected activatedRoute: ActivatedRoute ) { super(router, activatedRoute); } ngOnInit() { let tlnQueryService = (this.queryService != null) ? this.queryService : this.localQueryService; this.dataHandler.addHandler('manuscript_unity', { 'handler': TlnManuscriptUnity, 'next_key': 'navigation_page'}); this.dataHandler.addHandler('navigation_page', ['current_page', 'geneticOrders'] ); this.dataHandler.addHandler('current_page', { 'handler': TlnNavigationPage }); this.dataHandler.addHandler('geneticOrders', { 'handler': TlnTextGeneticOrder}); this.dataHandler.setQueryService(tlnQueryService); this.subscriptions.push(this.dataHandler.start_processing.subscribe( (started: boolean) =>{ this.updating = true; })); this.subscriptions.push(this.dataHandler.processing_finished.subscribe( (finished: boolean) =>{ this.updating = false; })); super.ngOnInit(); this.subscriptions.push(this.pageViewService.reference.subscribe( (newReference: Reference) => { this.updatePageToReference(newReference) })); this.subscriptions.push(this.pageViewService.onClickedLine.subscribe( (clickedLine: TlnLine) => { let index = this.selectedLines.indexOf(clickedLine.id) if (index > -1){ this.selectedLines.splice(index, 1); } else { this.selectedLines.push(clickedLine.id); } this.updateParams(); })); this.subscriptions.push(this.pageViewService.onClickedWord.subscribe( (clickedWord: TlnWord) => { this.clickWord(clickedWord) })); } ngOnDestroy() { this.subscriptions.forEach(subscription => subscription.unsubscribe()); } private clearFindText() { this.findText = ''; this.updateParams(); } private clickWord(clickedWord: TlnWord){ let wordIndex = this.selectedWords.indexOf(clickedWord.id) let lineIndex = this.selectedLines.indexOf(clickedWord.line) if (wordIndex > -1){ this.selectedWords.splice(wordIndex, 1); } else if (lineIndex > -1){ this.selectedLines.splice(lineIndex, 1); } else { this.selectedWords.push(clickedWord.id); } this.updateParams(); } private getPageTitle(page?: NavigationPage, numPages?: number): string { if (page == null){ return ''; } let indexPrefix = (numPages != null) ? page.index + '/' + numPages : page.index; return indexPrefix + ': ' + page.title + ' ' + page.number; } protected readParams(params: Params){ super.readParams(params); if (this.dataHandler.ready && (this.current_page == null || this.current_page.id != this.current_iri)){ this.dataHandler.resetData('navigation_page') if(this.current_manuscript_iri != null){ //this.dataHandler.debug = true; /*this.dataHandler.conditionalAddHandler(IsReconstructedKonvolut.getQuery(this.current_manuscript_iri), 'current_page',{ handler: TlnNavigationPage}, { handler: TlnNavigationPage});*/ this.dataHandler.getData('manuscript_unity', this.current_manuscript_iri, this.current_iri); } else { this.dataHandler.getData('current_page', this.current_iri); } } } private setCurrentIri(pageIri: string, manuscriptIir?: string){ this.dataHandler.stop_processing.emit(true); this.current_iri = pageIri; this.updateParams(); } private showInformation() { let parentData: ParentInformation = { geneticOrders: this.geneticOrders, page: this.current_page, manuscript_iri: this.current_manuscript_iri, parentActivatedRoute: this.activatedRoute.parent } this.bottomSheet.open(TlnInformationComponent, { data: parentData }); } private updatePageToReference(reference: Reference){ this.current_iri = reference.page.id; this.selectedLines = [ reference.line.id ] this.updateParams(); } } diff --git a/nietzsche-beta-app/src/app/tln-edition/tooltip/tool-tip.component.ts b/nietzsche-beta-app/src/app/tln-edition/tooltip/tool-tip.component.ts index 61b3ae0..0904fd1 100644 --- a/nietzsche-beta-app/src/app/tln-edition/tooltip/tool-tip.component.ts +++ b/nietzsche-beta-app/src/app/tln-edition/tooltip/tool-tip.component.ts @@ -1,219 +1,219 @@ import { Component, Input, OnInit, OnChanges, ElementRef, ViewChild} from '@angular/core'; import { MarkupSettings, StandoffMarkup } from 'ngx-mat-standoff-markup'; -import { ConfigurableComponent, PageViewService, TlnQueryService } from '../services'; +import { ConfigurableComponent, PageViewService, TlnCacheQueryService } from '../services'; import { Configuration, Continuation, Copyright, Point, Position, PositionalObject, Reference, TextByForeignHand, TlnQueryServiceInterface, Word } from '../models'; import { TlnEarlierVersionStub, TlnOverwrittenStub } from '../datatypes/earlier_version'; import { TlnLineReference } from '../datatypes/line_reference'; import { TlnPreviewWord } from '../datatypes/preview_data'; import { TlnPositionalStyleMarkup, TlnFilteredPositionalStyleMarkup } from '../datatypes/positional-markup'; import { DataHandler } from '../data_handler'; /** * This component can be used to show extended tooltips, i.e. with deleted words and distinguished * words if they are by foreign hand. **/ @Component({ selector: 'tool-tip', templateUrl: './tool-tip.component.html', - providers: [ TlnQueryService ], + providers: [ TlnCacheQueryService ], styleUrls: ['./tool-tip.component.css'] }) export class ToolTipComponent extends ConfigurableComponent implements OnInit, OnChanges { @Input('fullscreen') fullscreen: boolean = false; @Input() hasMenuIssues: boolean = false; @Input() menuOffsetPosition: Position; @Input() container: HTMLElement; //fullscreen: boolean = false; /** * content keys **/ private readonly contentKeys: string[] = [ 'word', 'foreignHand', 'copyright', 'continuation' ] /** * copyright information to be shown in the tooltip. **/ copyright: Copyright; /** * current key of contentKeys **/ currentKey: string; debug: boolean = false; position: number = 0; /** * the data handler of this component that retrieves * data and instantiates it according to their proper * datatypes. **/ dataHandler: DataHandler = new DataHandler(this); /** * text by foreign hand to be shown in tooltip **/ foreignHand: TextByForeignHand; /** * earlier version of word **/ earlier_version: TlnEarlierVersionStub; /** * overwritten word **/ overwrittenWord: TlnOverwrittenStub; /** * a line continuation **/ continuation: Continuation; /** * a list of positional style markups. **/ positionalStyleMarkups: StandoffMarkup[] = []; continuation_words: TlnPreviewWord[] = []; mySettings: MarkupSettings = new MarkupSettings(); width: number = 300; /** * line reference from **/ sourceLineReference: TlnLineReference; /** * actual tooltip position **/ tooltipPosition: Point = { visible: false, clientX: -1, clientY: -1, layerX: -1, layerY: -1 }; /** * whether or not to show extended tooltips **/ useExtendedTooltip: boolean = true; /** * word to be shown in tooltip **/ word: Word; /** * y offset for tooltip position relative to mouse position **/ private readonly yOffset: number = 25; private readonly menuIssueOffset: number = 80; private readonly menuIssueXOffset: number = 55; topOffset: number = 0; leftOffset: number = 0; - constructor(protected pageViewService: PageViewService, private tlnQueryService: TlnQueryService) { + constructor(protected pageViewService: PageViewService, private tlnQueryService: TlnCacheQueryService) { super() } ngOnChanges(){ super.ngOnChanges(); /*if((this.fullscreen && this.fullscreenString != 'true') || (!this.fullscreen && this.fullscreenString == 'true')){ this.fullscreen = (this.fullscreenString == 'true') }*/ if (this.tlnQueryService != null && !this.dataHandler.ready){ this.dataHandler.addHandler('earlier_version', { 'handler': TlnEarlierVersionStub }); this.dataHandler.addHandler('overwrittenWord', { 'handler': TlnOverwrittenStub}); this.dataHandler.addHandler('positionalStyleMarkups', { 'handler': TlnFilteredPositionalStyleMarkup }); this.dataHandler.addHandler('sourceLineReference', { 'handler': TlnLineReference}); this.dataHandler.addHandler('continuation_words', { 'handler': TlnPreviewWord}); this.dataHandler.addHandler('wordStubs', ['earlier_version', 'overwrittenWord', 'positionalStyleMarkups' ]); this.dataHandler.setQueryService(this.tlnQueryService); this.tlnQueryService.reset_data.subscribe( (data_key: string) =>{ if (data_key == 'page_content'){ this.resetData() } }); } } /** * listen on pageViewService **/ ngOnInit() { this.pageViewService.mousePosition.subscribe( (newPoint: Point) =>{ this.tooltipPosition = newPoint; //console.log(this.tooltipPosition); }); this.pageViewService.onHoveredWord.subscribe( (newWord: Word) => { this.setContent('word', newWord);this.updateEarlierVersion() } ); this.pageViewService.offHoveredWord.subscribe( (newWord: Word) => this.word = null ); this.pageViewService.onHoveredTextByForeignHand.subscribe( (newTextByForeignHand: TextByForeignHand) => { this.setContent('foreignHand',newTextByForeignHand) } ); this.pageViewService.offHoveredTextByForeignHand.subscribe( (newTextByForeignHand: TextByForeignHand) => this.foreignHand = null ); this.pageViewService.copyright.subscribe( (copyright: Copyright) =>{ this.setContent('copyright', (this.copyright == null) ? copyright : null) }); this.pageViewService.onHoveredContinuation.subscribe( (newContinuation: Continuation) => { if(this.continuation == null){ this.updateLineContinuation(newContinuation) } }); this.pageViewService.offHoveredContinuation.subscribe( (newContinuation: Continuation) => { this.dataHandler.stop_processing.emit(true);this.continuation = null;this.updateLineContinuation(); } ); } /** * reset data **/ private resetData(){ this.dataHandler.resetData('wordStubs') this.contentKeys.forEach(key=>this[key] = null); } /** * update earlier version of word **/ private updateEarlierVersion(){ if (this.word != null && this.dataHandler.ready) { this.dataHandler.resetData('wordStubs') this.dataHandler.getData('wordStubs', this.word.id); } } private updateLineContinuation(continuation?: Continuation){ this.setContent('continuation', continuation); if(continuation != null && this.dataHandler.ready){ this.dataHandler.resetData('continuation_words') if (continuation.source.page != null){ this.dataHandler.getData('continuation_words', this.continuation.reference.line.id) } } } private getTop(key: string): number { if (this.menuOffsetPosition != undefined && this.menuOffsetPosition != null && this.container != null && this.container != undefined){ let containerRect: DOMRect = this.container.getBoundingClientRect(); this.topOffset = containerRect.top; if (!this.fullscreen){ this.topOffset = this.topOffset - this.menuOffsetPosition.y; } } return this.tooltipPosition.clientY - this.topOffset + this.yOffset; } private getLeft(width: number): number { let containerRect: DOMRect = (this.container != null && this.container != undefined) ? this.container.getBoundingClientRect() : null; this.leftOffset = (this.menuOffsetPosition != undefined && this.menuOffsetPosition != null && containerRect != null) ? containerRect.left : 0; let left = this.tooltipPosition.clientX - this.leftOffset; if (this.menuOffsetPosition != undefined && this.menuOffsetPosition != null){ left += this.menuOffsetPosition.x; } if (!this.tooltipPosition.visible || this.continuation == null || this.continuation.show == null || this.continuation.show != 'to'){ return left; } return (left + width <= containerRect.right) ? left : left-(left+width-containerRect.right)-5; } /** * Set tooltip's content and remove prior content. * @param key key of content * @param content new content **/ private setContent(key: string, content: Continuation|Copyright|PositionalObject){ this.contentKeys.forEach(key=>this[key] = null); this[key] = content; this.currentKey = key; } /** * whether tooltip has any content (i.e. any content of {@link /components/ToolTipComponent.html#contentKeys|contentKeys}) **/ private hasAnyContent(): boolean { return this.contentKeys.filter(key =>this[key] != null).length > 0 } } diff --git a/nietzsche-beta-app/src/konsole-server b/nietzsche-beta-app/src/konsole-server new file mode 100644 index 0000000..7513a40 --- /dev/null +++ b/nietzsche-beta-app/src/konsole-server @@ -0,0 +1 @@ +profile: ng diff --git a/nietzsche-beta-app/src/main.ts b/nietzsche-beta-app/src/main.ts index c7b673c..22627a1 100644 --- a/nietzsche-beta-app/src/main.ts +++ b/nietzsche-beta-app/src/main.ts @@ -1,12 +1,14 @@ +import 'codemirror/mode/javascript/javascript'; +import 'codemirror/mode/sparql/sparql'; import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; if (environment.production) { enableProdMode(); } platformBrowserDynamic().bootstrapModule(AppModule) .catch(err => console.error(err)); diff --git a/nietzsche-beta-app/src/styles.scss b/nietzsche-beta-app/src/styles.scss index 0ba298c..fb99a2b 100644 --- a/nietzsche-beta-app/src/styles.scss +++ b/nietzsche-beta-app/src/styles.scss @@ -1,45 +1,47 @@ // Custom Theming for Angular Material // For more information: https://material.angular.io/guide/theming @import '~@angular/material/theming'; // Plus imports for other components in your app. +@import '~codemirror/lib/codemirror'; +@import '~codemirror/theme/material'; // Include the common styles for Angular Material. We include this here so that you only // have to load a single css file for Angular Material in your app. // Be sure that you only ever include this mixin once! @include mat-core(); // importing the themes from our theme files @import "./themes/standard-theme"; @import "./themes/green-theme"; // Include theme styles for core and each component used in your app. // Alternatively, you can import and @include the theme mixins for each component // that you are using. // for own components, i.e. non material component we like to use the same style/palettes .standard-theme { @include angular-material-theme($standard-theme); h1 { color: mat-color($standard-theme-primary)} } .green-theme { @include angular-material-theme($green-theme); h1 { color: mat-color($green-theme-primary)} } /* You can add global styles to this file, and also import other style files */ html, body { height: 100%; } body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } .flex-spacer { flex: 1 1 auto; } diff --git a/nietzsche-beta-app/tsconfig.json b/nietzsche-beta-app/tsconfig.json index 30956ae..62fde20 100644 --- a/nietzsche-beta-app/tsconfig.json +++ b/nietzsche-beta-app/tsconfig.json @@ -1,26 +1,27 @@ { "compileOnSave": false, "compilerOptions": { "baseUrl": "./", "outDir": "./dist/out-tsc", "sourceMap": true, + "skipLibCheck": true, "declaration": false, "downlevelIteration": true, "experimentalDecorators": true, "module": "esnext", "moduleResolution": "node", "importHelpers": true, "target": "es2015", "typeRoots": [ "node_modules/@types" ], "lib": [ "es2018", "dom" ] }, "angularCompilerOptions": { "fullTemplateTypeCheck": true, "strictInjectionParameters": true } }