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 db1c2d2..6feb92b 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,12 +1,14 @@
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_VIEWER_ROUTE } from '../tln-edition/constants';
+import { TLN_CROSSREF_ROUTE, TLN_MANUSCRIPT_ROUTE, TLN_SEARCH_ROUTE, TLN_VIEWER_ROUTE } from '../tln-edition/constants';
+import { TlnFulltextComponent } from '../tln-edition/tln-fulltext/tln-fulltext.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: '', redirectTo: TLN_VIEWER_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 5ed9cac..308df06 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,86 +1,90 @@
import {AfterViewInit, Component, OnInit} from '@angular/core';
import {ActivatedRoute, Params, Router} from '@angular/router';
import {Subscription} from "rxjs/index";
import {NavigationServiceService} from "../services/navigation-service.service";
-import { TLN_CROSSREF_ROUTE, TLN_MANUSCRIPT_ROUTE, TLN_VIEWER_ROUTE } from '../tln-edition/constants';
+import { TLN_CROSSREF_ROUTE, TLN_MANUSCRIPT_ROUTE, TLN_SEARCH_ROUTE, TLN_VIEWER_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, AfterViewInit {
// 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;
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
}, {
label: 'Seitenansicht',
link: TLN_VIEWER_ROUTE,
index: 1
}, {
label: 'Querverweise',
link: TLN_CROSSREF_ROUTE,
index: 2
+ },{
+ label: 'Suche',
+ link: TLN_SEARCH_ROUTE,
+ index: 3
},
];
}
ngOnInit() {
// this.mesurePerformance();
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
this.queryParamSubscription = this.activatedRoute.queryParams.subscribe( (queryParams: Params ) => {
console.log('new qParams in contentview', queryParams);
this.queryParams = queryParams;
this.navBarOpenState = JSON.parse(queryParams.navBarOpenState.toLowerCase());
this.fullscreen = JSON.parse(queryParams.fullscreen.toLowerCase());
});
}
ngAfterViewInit() {
this.navBarOpenState = JSON.parse(this.activatedRoute.snapshot.queryParamMap.get('navBarOpenState'));
}
mesurePerformance() {
const perfData = window.performance.timing;
const pageLoadTime = perfData.loadEventEnd - perfData.navigationStart;
console.log('content load performance is: ', pageLoadTime);
}
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') {
// console.log('setQParamsOnInit', this.activatedRoute.snapshot.queryParamMap.get('navBarOpenState'))
this.navBarOpenState = true;
qParams.navBarOpenState = 'true'; }
// viewMode
if (!this.activatedRoute.snapshot.queryParamMap.get('viewMode')) {
qParams.viewMode = 'Transkription/Faksimile';
}
this.naviService.updateRoute(qParams);
}
}
diff --git a/nietzsche-beta-app/src/app/page-view-wrapper-component/page-view-wrapper.component.html b/nietzsche-beta-app/src/app/page-view-wrapper-component/page-view-wrapper.component.html
index 3792bb8..82e8f18 100644
--- a/nietzsche-beta-app/src/app/page-view-wrapper-component/page-view-wrapper.component.html
+++ b/nietzsche-beta-app/src/app/page-view-wrapper-component/page-view-wrapper.component.html
@@ -1,9 +1,8 @@
-
+
-
+
-
+
-
diff --git a/nietzsche-beta-app/src/app/page-view-wrapper-component/page-view-wrapper.component.ts b/nietzsche-beta-app/src/app/page-view-wrapper-component/page-view-wrapper.component.ts
index d45ea58..6c2241d 100644
--- a/nietzsche-beta-app/src/app/page-view-wrapper-component/page-view-wrapper.component.ts
+++ b/nietzsche-beta-app/src/app/page-view-wrapper-component/page-view-wrapper.component.ts
@@ -1,32 +1,33 @@
import {Component, OnInit} from '@angular/core';
import {ActivatedRoute, Params, Router} from '@angular/router';
import { TlnQueryParams} from '../models/models';
@Component({
selector: 'app-page-view-wrapper-component',
templateUrl: './page-view-wrapper.component.html',
styleUrls: ['./page-view-wrapper.component.scss']
})
export class PageViewWrapperComponent implements OnInit {
queryParams: TlnQueryParams;
constructor(private router: Router,
private activatedRoute: ActivatedRoute) { }
ngOnInit() {
+
this.activatedRoute.queryParams.subscribe( (queryParams: Params ) => {
this.queryParams = new TlnQueryParams(
queryParams.navBarOpenState,
queryParams.NavContext,
queryParams.manuscript,
queryParams.page,
queryParams.selectedLines,
queryParams.selectedWords,
queryParams.viewMode,
queryParams.fullscreen,
queryParams.zoom
);
});
}
}
diff --git a/nietzsche-beta-app/src/app/page-view/margin-field/margin-field.component.ts b/nietzsche-beta-app/src/app/page-view/margin-field/margin-field.component.ts
index 472dd20..3b538d2 100644
--- a/nietzsche-beta-app/src/app/page-view/margin-field/margin-field.component.ts
+++ b/nietzsche-beta-app/src/app/page-view/margin-field/margin-field.component.ts
@@ -1,194 +1,198 @@
import { Component, Input, OnInit, OnChanges, SimpleChanges } from '@angular/core';
import { externalAssignStyle, Continuation, Identifier, Line, LineStub, TextField, Word} from '../models';
import { PageViewService } from '../page-view.service';
import { HIGHTLIGHT_CASES } from '../highlight_status';
/**
* This component displays an Array of lines.
**/
@Component({
selector: 'margin-field',
templateUrl: './margin-field.component.html',
styleUrls: ['./margin-field.component.css']
})
export class MarginFieldComponent implements OnInit, OnChanges {
/**
* scrollable HTML-container of this textfield
**/
@Input() container: HTMLElement;
/**
* the hovered status for a line
**/
HOVERED_STATUS: string = HIGHTLIGHT_CASES.LINE_HOVERED
SELECTED_STATUS: string = HIGHTLIGHT_CASES.SELECTED_LINE
/**
* the currently hovered line
* */
hoveredLine?: Line;
/**
* the currently hovered reference line
* */
hoveredReferenceLine?: LineStub;
/**
* the currently hovered word
* */
hoveredWord?: Word;
/**
* an Array of lines that will be displayed.
**/
@Input() lines: Line[];
/**
* the height of a line rect.
**/
line_height: number = 8;
/**
* the length of the line rect.
**/
line_length: number = 10;
/**
* x coordinate of the line rect.
**/
line_x: number = 5;
/**
* the height of the margin field.
**/
margin_height: number = 973.91998;
/**
* geometrical top position of the margin field.
**/
margin_top: number = 0;
/**
* the width of the margin field.
**/
margin_width: number = 30;
/**
* specifies reference type that should be displayed
**/
@Input() showReference: string = "to"
/**
* The area of the image that will be displayed by {@link /components/TextFieldComponent.html|TextFieldComponent}.
* The height of the text_field determines {@link #margin_height|margin_height}, while its top position
* determines {@link #margin_top|margin_top}.
**/
@Input() text_field: TextField;
/**
* The viewbox of this svg component.
**/
viewBox: string = '';
/**
* initial maximum height of margin field.
**/
@Input() max_height: number = -1;
/**
* initial maximum width of margin field.
**/
@Input() max_width: number = -1;
/**
* identifiers of selected lines that should be highlighted.
**/
@Input() selectedLines: Identifier[] = [];
/**
* global zoom factor
**/
@Input() zoomFactor: number = 1;
/**
* An optional function that can be passed to this component in order to return a (svg-)style object
* to the 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') extAssignStyle?: externalAssignStyle;
/**
* local zoom factor
**/
local_zoom: number = 1;
/**
* @param lineservice an information source about (un-)hovered and clicked Lines/Words.
**/
constructor( private lineservice: PageViewService) { }
- /**
+ /**
* Initialize geometrical information and subscribe to {@link /injectables/PageViewService.html|PageViewService}.
**/
- ngOnInit() {
+ ngOnInit() {
if (this.max_height == -1 && this.max_width == -1){
this.max_height = screen.availHeight;
}
this.viewBox = 0 + ' ' + this.margin_top + ' ' + this.margin_width + ' ' + this.margin_height;
if (this.text_field != null) {
this.updateViewBox()
}
this.lineservice.onHoveredLine.subscribe(
(changedLine: Line) => { this.hoveredLine = changedLine;}
);
this.lineservice.onHoveredContinuation.subscribe(
(changedContinuation: Continuation) => { this.hoveredReferenceLine = changedContinuation.reference.line;}
);
this.lineservice.offHoveredContinuation.subscribe(
(changedContinuation: Continuation) => { this.hoveredReferenceLine = null}
);
this.lineservice.offHoveredLine.subscribe(
(changedLine: Line) => { this.hoveredLine = null; }
);
this.lineservice.onHoveredWord.subscribe(
(changedWord: Word) => { this.hoveredWord = changedWord;}
);
this.lineservice.offHoveredWord.subscribe(
(changedWord: Word) => { this.hoveredWord = null; }
);
- }
- /**
+ }
+ /**
* Update viewBox if there is a change.
- **/
- ngOnChanges(changes: SimpleChanges) {
+ **/
+ ngOnChanges(changes: SimpleChanges) {
if (this.text_field != null) {
this.updateViewBox()
}
- }
- /**
+ }
+ /**
* Update viewBox: set
* {@link #margin_height|margin_height},
* {@link #margin_top|margin_top},
* {@link #viewBox|viewBox}
* and {@link #local_zoom|local_zoom} according to
* {@link #text_field|text_field}.
- **/
- private updateViewBox(){
+ **/
+ private updateViewBox(){
this.lines.forEach(line =>line.datatype = "Line");
if (this.showReference == 'to'){
this.margin_width = (this.lines.some(line =>line.continuesTo != null || line.continuesTo != undefined)) ? this.line_length*2+30 : 30;
} else {
let hasReference = this.lines.some(line =>line.continuesFrom != null || line.continuesFrom != undefined)
this.margin_width = (this.showReference == 'from' && hasReference) ? this.line_length*2+30 : 30;
this.line_x = (this.showReference == 'from' && hasReference) ? 2*this.line_length : 5;
}
this.margin_height = this.text_field.height;
this.margin_top = this.text_field.top;
this.viewBox = 0 + ' ' + this.margin_top + ' ' + this.margin_width + ' ' + this.margin_height;
this.local_zoom = (this.max_height != -1 && this.max_width == -1)
? this.max_height/this.text_field.height : this.max_width/this.text_field.width;
- }
- /**
+ if (this.max_width == -1 && this.text_field.height < this.text_field.width){
+ this.local_zoom = (window.innerWidth/2-100)/this.text_field.width;
+ }
+
+ }
+ /**
* Get the hover status of a line, i.e. whether it is hovered
* ({@link /miscellaneous/enumerations.html#HIGHTLIGHT_CASES|HIGHTLIGHT_CASES.LINE_HOVERED})
* or not ({@link /miscellaneous/enumerations.html#HIGHTLIGHT_CASES|HIGHTLIGHT_CASES.DEFAULT}).
**/
- private getHoverStatus(line: Line): string {
+ private getHoverStatus(line: Line): string {
if ( (this.hoveredLine != undefined && this.hoveredLine != null && line.id == this.hoveredLine.id)
|| (this.hoveredReferenceLine != undefined && this.hoveredReferenceLine != null && line.id == this.hoveredReferenceLine.id)
|| (this.hoveredWord != undefined && this.hoveredWord != null && line.id == this.hoveredWord.line)) {
return HIGHTLIGHT_CASES.LINE_HOVERED;
} else if (this.selectedLines.length > 0 && this.selectedLines.indexOf(line.id) > -1) {
return HIGHTLIGHT_CASES.SELECTED_LINE
} else {
return HIGHTLIGHT_CASES.DEFAULT;
}
- }
+ }
/**
* Assign a style to the rects of a line.
**/
private assignStyle(line: Line, hoveredWord: Word, hoveredLine: Line, hoverStatus: string): Object {
return (this.extAssignStyle != null) ? this.extAssignStyle(line, hoveredWord, hoveredLine, hoverStatus) : {};
}
}
diff --git a/nietzsche-beta-app/src/app/page-view/page-view.component.html b/nietzsche-beta-app/src/app/page-view/page-view.component.html
index 1bdcd5c..0beeafb 100644
--- a/nietzsche-beta-app/src/app/page-view/page-view.component.html
+++ b/nietzsche-beta-app/src/app/page-view/page-view.component.html
@@ -1,50 +1,50 @@
diff --git a/nietzsche-beta-app/src/app/page-view/page-view.component.ts b/nietzsche-beta-app/src/app/page-view/page-view.component.ts
index 1b02618..9639e50 100644
--- a/nietzsche-beta-app/src/app/page-view/page-view.component.ts
+++ b/nietzsche-beta-app/src/app/page-view/page-view.component.ts
@@ -1,174 +1,173 @@
import { Component, Input, OnInit, OnChanges} from '@angular/core';
import { externalAssignClass, externalAssignStyle, Configuration, Identifier, Image, Line, TextField, TextByForeignHand, Word} from './models';
/**
* This component displays one or two {@link /components/TextFieldComponent.html|TextFieldComponent(s)}
* and its or their {@link /components/MarginFieldComponent.html|MarginFieldComponent(s)}.
**/
@Component({
selector: 'page-view',
templateUrl: './page-view.component.html',
styleUrls: ['./page-view.component.css']
})
export class PageViewComponent implements OnInit, OnChanges {
@Input() configuration: Configuration;
/**
* the search text of words that should be highlighted as {@link /miscellaneous/enumerations.html#HIGHTLIGHT_CASES|HIGHTLIGHT_CASES.SEARCHED_WORD}.
**/
@Input() findText: string;
/**
* first texts written by foreign hand
**/
@Input() first_foreign_texts: TextByForeignHand[] = [];
/**
* the first image that will be displayed by {@link /components/TextFieldComponent.html|TextFieldComponent}.
**/
@Input() first_image: Image;
/**
* the Array of lines of the first image that will be displayed by {@link /components/MarginFieldComponent.html|MarginFieldComponent}.
**/
@Input() first_lines: Line[];
/**
* Identification of first textfield.
**/
first_textfield_id: string = 'first textfield'
/**
* the Array of words of the first image that will be displayed by {@link /components/TextFieldComponent.html|TextFieldComponent}.
**/
@Input() first_words: Word[];
/**
* the (initial) maximum height of the image(s).
**/
@Input() max_height: number = -1;
/**
* the (initial) maximum width of the image(s).
**/
@Input() max_width: number = -1;
/**
* should primary Url be used for image. Use secondary Url if false.
**/
@Input() preferPrimaryUrl: boolean = true;
/**
* second texts written by foreign hand
**/
@Input() second_foreign_texts: TextByForeignHand[] = [];
/**
* the second image that will be displayed by {@link /components/TextFieldComponent.html|TextFieldComponent}.
**/
@Input() second_image: Image;
/**
* the Array of lines of the second image that will be displayed by {@link /components/MarginFieldComponent.html|MarginFieldComponent}.
**/
@Input() second_lines: Line[];
/**
* Identification of second textfield.
**/
second_textfield_id: string = 'second textfield'
/**
* the Array of words of the second image that will be displayed by {@link /components/TextFieldComponent.html|TextFieldComponent}.
**/
@Input() second_words: Word[];
/**
* 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;
/**
* 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[] = [];
@Input('startLine') startLineId: Identifier;
@Input('endLine') endLineId: Identifier;
@Input() dontShowReference: boolean;
showReferenceLeft: string = 'from';
showReferenceRight: string = 'to';
constructor() {}
/**
* sets {@link /components/PageViewComponent.html#max_height|max_height} if it is unset.
**/
ngOnInit() {
- if (this.max_height == -1 && this.max_width == -1){
- this.max_height = screen.availHeight - 40;
- console.log(this.max_height);
- }
+ if (this.max_height == -1 && this.max_width == -1){
+ this.max_height = screen.availHeight;
+ }
this.checkImages();
}
ngOnChanges(){
if (this.dontShowReference != undefined && this.dontShowReference != null && this.dontShowReference){
this.showReferenceLeft = '';
this.showReferenceRight = '';
} else {
this.showReferenceLeft = 'from';
this.showReferenceRight = 'to';
}
this.checkImages();
if (this.first_image != null && this.first_image != undefined && this.first_image.transform != null){
this.updateLines(this.first_words, this.first_lines)
}
if (this.second_image != null && this.second_image != undefined && this.second_image.transform != null){
this.updateLines(this.second_words, this.second_lines)
}
}
private checkImages(){
if (this.first_image != null && this.first_image != undefined && this.startLineId != null && this.startLineId != undefined){
if(this.first_lines != null && this.first_lines != undefined && this.first_lines.length > 0){
this.first_image = this.updateTextField(this.first_image, this.first_lines);
}
if(this.second_lines != null && this.second_lines != undefined && this.second_lines.length > 0){
this.second_image = this.updateTextField(this.second_image, this.second_lines);
}
}
}
private updateLines(words: Word[], lines: Line[]) {
for (var i = 0; i < lines.length; i++){
if (words.filter(word =>word.line == lines[i].id).length > 0){
lines[i].top = words.filter(word =>word.line == lines[i].id).map(word =>Number(word.top)).sort(function(a,b){ return a-b; })[0]
lines[i].bottom = words.filter(word =>word.line == lines[i].id).map(word =>Number(word.top)+Number(word.height)).sort(function(a,b){ return b-a; })[0]
}
}
}
private updateTextField(image: Image, lines: Line[]): Image {
let endLineId = (this.endLineId != null && this.endLineId != undefined) ? this.endLineId : this.startLineId;
let startLines = lines.filter(line =>line.id == this.startLineId)
let endLines = lines.filter(line =>line.id == endLineId)
if (startLines.length > 0 && endLines.length > 0){
let top = (startLines[0].top > 10) ? startLines[0].top-10 : startLines[0].top;
let height = (endLines[0].bottom-top)+10;
let text_field: TextField = { top: top, left: image.text_field.left, width: image.text_field.width, height: height }
return { x: image.x, y: image.y, width: image.width, height: image.height, filename: image.filename,
URL: image.URL, secondaryURL: image.secondaryURL, text_field: text_field, transform: image.transform,
copyright: image.copyright }
}
return image;
}
/**
* Returns whether the two images can be displayed as columns.
**/
private hasColumnStyle(): boolean {
if (this.zoomFactor <= 1 || this.first_image == null || this.second_image == null){
return true
}
let newLeftWidth = this.max_height/this.first_image.text_field.height*this.zoomFactor*this.first_image.text_field.width;
let newRightWidth = this.max_height/this.second_image.text_field.height*this.zoomFactor*this.second_image.text_field.width;
return newLeftWidth + newRightWidth < screen.availWidth;
}
}
diff --git a/nietzsche-beta-app/src/app/page-view/page-view.module.ts b/nietzsche-beta-app/src/app/page-view/page-view.module.ts
index bfa1335..e13e24d 100644
--- a/nietzsche-beta-app/src/app/page-view/page-view.module.ts
+++ b/nietzsche-beta-app/src/app/page-view/page-view.module.ts
@@ -1,37 +1,61 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
+import { 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 { NgxMatStandoffMarkupModule } from '../lib/ngx-mat-standoff-markup.module';
import { MarginFieldComponent } from './margin-field/margin-field.component';
import { TextFieldComponent} from './textfield-component/textfield.component';
import { InteractedDirective } from './interacted.directive';
import { PageViewComponent } from './page-view.component';
import { PageViewService } from './page-view.service';
import { CopyrightComponent } from './copyright/copyright.component';
import { LineReferenceComponent } from './margin-field/line-reference/line-reference.component';
import { CopyrightSheetComponent } from './copyright/copyright-sheet/copyright-sheet.component';
@NgModule({
declarations: [
InteractedDirective,
MarginFieldComponent,
TextFieldComponent,
PageViewComponent,
CopyrightComponent,
LineReferenceComponent,
CopyrightSheetComponent
],
imports: [
+ MatBottomSheetModule,
+ MatButtonModule,
+ MatButtonToggleModule,
+ MatCardModule,
+ MatCheckboxModule,
+ MatDialogModule,
+ MatExpansionModule,
+ MatFormFieldModule,
+ MatIconModule,
+ MatInputModule,
+ MatListModule,
+ MatMenuModule,
+ MatPaginatorModule,
+ MatRadioModule,
+ MatSelectModule,
+ MatSidenavModule,
+ MatSortModule,
+ MatTableModule,
+ MatTabsModule,
+ MatToolbarModule,
+ MatTooltipModule,
CommonModule
],
providers: [
PageViewService
],
exports: [
MarginFieldComponent,
TextFieldComponent,
PageViewComponent,
CopyrightComponent
],
entryComponents: [CopyrightSheetComponent]
})
export class PageViewModule { }
diff --git a/nietzsche-beta-app/src/app/page-view/textfield-component/textfield.component.ts b/nietzsche-beta-app/src/app/page-view/textfield-component/textfield.component.ts
index 4524a70..d091a4a 100644
--- a/nietzsche-beta-app/src/app/page-view/textfield-component/textfield.component.ts
+++ b/nietzsche-beta-app/src/app/page-view/textfield-component/textfield.component.ts
@@ -1,344 +1,348 @@
-import { Component, ElementRef, Input, OnInit, OnChanges, SimpleChanges } from '@angular/core';
+import { Component, ElementRef, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core';
import { externalAssignClass, externalAssignStyle, Configuration, Continuation, Identifier, Image, Line, Position, PositionalObject, TextByForeignHand, Word, USE_EXTERNAL_TOOLTIP} from '../models';
import { PageViewService } from '../page-view.service';
import { HIGHTLIGHT_CASES } from '../highlight_status';
import { ConfigurableComponent } from '../configurable-component';
import { Matrix } from './matrix';
/**
* This component displays an image with word hovers.
**/
@Component({
selector: 'text-field',
templateUrl: './textfield.component.html',
styleUrls: ['./textfield.component.css']
})
export class TextFieldComponent extends ConfigurableComponent implements OnInit, OnChanges {
/**
* scrollable HTML-container of this textfield
**/
@Input() container: HTMLElement;
/**
* the currently clicked word
* */
clickedWord?: Word;
/**
* Debug mode.
**/
debug: boolean = false;
/**
* the search text of words that should be highlighted as {@link /miscellaneous/enumerations.html#HIGHTLIGHT_CASES|HIGHTLIGHT_CASES.SEARCHED_WORD}.
**/
@Input() findText: string;
/**
* texts written by foreign hand
**/
@Input() foreign_texts: TextByForeignHand[] = [];
/**
* the currently hovered line
* */
hoveredLine?: Line;
/**
* the currently hovered text by foreign hand
* */
hoveredTextByForeignHand?: TextByForeignHand;
/**
* the currently hovered word
* */
hoveredWord?: Word;
/**
* the image that will be displayed.
**/
@Input() image: Image;
/**
* textfield's identity.
**/
@Input() identity: string = 'first textfield';
/**
* The (unzoomed) height of the root svg.
*
* (The actual height is 'image_height*local_zoom*zoomFactor'
* */
image_height: number = 400;
/**
* image properties for the svg-image.
* */
imageSpec = { x: 0, y: 0, height: 973.91998, width: 2038.5601, URL: null, secondaryURL: null, transform: 'matrix(1 0 0 1 0 0)' };
/**
* The (unzoomed) width of the root svg.
*
* (The actual width is 'image_width*local_zoom*zoomFactor'
* */
image_width: number = 300;
/**
* the viewBox of the root svg specifying the area of the svg that will be shown.
* */
viewBox: string = '';
/**
* the (initial) maximum height of the image.
**/
@Input() max_height: number = -1;
/**
* the (initial) maximum width of the image.
**/
@Input() max_width: number = -1;
/**
* should primary Url be used for image. Use secondary Url if false.
**/
@Input() preferPrimaryUrl: boolean = true;
/**
* Use extended tooltip.
**/
@Input() useExtendedTooltip: boolean = false;
/**
* the words that will be displayed as rects on the image.
**/
@Input() words: Word[];
/**
* global zoom factor.
**/
@Input() zoomFactor: number = 1;
/**
* local zoom factor that sets the height and width of the image according to {@link #max_height|max_height}.
* */
local_zoom: number = 1;
/**
* An optional function that can be passed to this component in order to return a further highlight class
* to the word rects when the internal function would return 'textfield unhighlighted'.
**/
@Input('assignClass') externalAssignClassAfter?: externalAssignClass;
/**
* An optional function that can be passed to this component in order to return a (svg-)style object
* to the word 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') extAssignStyle?: externalAssignStyle;
/**
* identifiers of selected words that should be highlighted.
**/
@Input() selectedWords: Identifier[] = [];
/**
* identifiers of selected lines that should be highlighted.
**/
@Input() selectedLines: Identifier[] = [];
/**
* @param pageViewService an information source about (un-)hovered and clicked Lines/Words.
* */
constructor( protected pageViewService: PageViewService) {
super()
}
ngOnInit() {
if (this.max_height == -1 && this.max_width == -1){
this.max_height = screen.availHeight;
}
if (this.image.text_field != null) {
this.updateImageProperties();
} else if (this.imageSpec != null) {
this.image_width = this.imageSpec.width;
this.image_height = this.imageSpec.height;
this.viewBox = '0 0 ' + this.image_width + ' ' + this.image_height;
}
this.pageViewService.onClickedWord.subscribe(
(changedWord: Word ) => this.clickedWord = changedWord
);
this.pageViewService.onHoveredWord.subscribe(
(changedWord: Word) => this.hoveredWord = changedWord
);
this.pageViewService.offHoveredWord.subscribe(
(changedWord: Word) => { this.hoveredWord = null; }
);
this.pageViewService.onHoveredLine.subscribe(
(changedLine: Line) => { this.hoveredLine = changedLine}
);
this.pageViewService.offHoveredLine.subscribe(
(changedLine: Line) => { this.hoveredLine = null; }
);
this.pageViewService.onHoveredTextByForeignHand.subscribe(
(changedForeignText: TextByForeignHand) => { this.hoveredTextByForeignHand = changedForeignText;}
);
this.pageViewService.offHoveredTextByForeignHand.subscribe(
(changedForeignText: TextByForeignHand) => { this.hoveredTextByForeignHand = null; }
);
}
ngOnChanges() {
super.ngOnChanges()
if (this.image.text_field != null) {
this.updateImageProperties();
}
if(this.debug && this.findText != null && this.findText != ''){
let words = this.words.filter(word =>word.text == this.findText)
if (words.length > 0){
this.pageViewService.onHoverService(words[0], {visible: true, clientX: 100, clientY: 100, layerX: -1, layerY: -1 })
}
}
}
/**
* Update image properties: use textfield in order to specify the area of the image that will be shown.
*
* @param URL set alternative image url. This will be used on image load error (see Template)
**/
private updateImageProperties(URL?: string){
let previous_word: Word = null;
for (var i = 0; i < this.words.length; i++){
this.words[i].datatype = "Word";
if (previous_word == null || previous_word.id != this.words[i].id){
previous_word = this.words[i]
previous_word.is_top_object = true;
} else if (previous_word.top > this.words[i].top){
previous_word.is_top_object = false;
previous_word = this.words[i]
previous_word.is_top_object = true;
} else {
this.words[i].is_top_object = false;
}
}
this.foreign_texts.forEach(foreignText =>foreignText.datatype = "TextByForeignHand");
let image_left = this.image.text_field.left;
let image_top = this.image.text_field.top;
this.image_width = this.image.text_field.width;
this.image_height = this.image.text_field.height;
this.local_zoom = (this.max_height != -1 && this.max_width == -1)
? this.max_height/this.image.text_field.height : this.max_width/this.image.text_field.width;
+ if (this.max_width == -1 && this.image_height < this.image_width){
+ this.local_zoom = (window.innerWidth/2-100)/this.image.text_field.width;
+ }
this.imageSpec.x = this.image.x;
this.imageSpec.y = this.image.y;
this.imageSpec.height = this.image.height;
this.imageSpec.width = this.image.width;
this.imageSpec.URL = (this.preferPrimaryUrl) ? this.image.URL : this.image.secondaryURL;
this.imageSpec.secondaryURL = (this.preferPrimaryUrl) ? this.image.URL : this.image.URL;
if (URL != null){
this.imageSpec.secondaryURL = this.imageSpec.URL
this.imageSpec.URL = URL;
}
if(this.image.transform != null){
this.local_zoom = this.max_height/this.image.text_field.width;
let matrix = new Matrix(this.image.transform, this.local_zoom*this.zoomFactor);
this.imageSpec.transform = matrix.toString()
}
this.viewBox = image_left + ' ' + image_top + ' ' + this.image_width + ' ' + this.image_height;
}
/**
* Return the position (i.e. '{ x: x, y: y }') for the copyright symbol.
* @param dimension dimension of the copyright symbol.
**/
private getCopyrightPosition(dimension: number): Object {
if (this.image.text_field != null && this.image.text_field != undefined){
let positions: Position[] = [
{ x: Number(this.image.text_field.left) +10/this.zoomFactor,
y: Number(this.image.text_field.top) +10/this.zoomFactor },
{ x: Number(this.image.text_field.left),
y: Number(this.image.text_field.top)},
{ x: Number(this.image.text_field.width) + Number(this.image.text_field.left) -15/this.zoomFactor - dimension,
y: Number(this.image.text_field.height) + Number(this.image.text_field.top) -15/this.zoomFactor - dimension },
{ x: Number(this.image.text_field.width) + Number(this.image.text_field.left) - dimension,
y: Number(this.image.text_field.height) + Number(this.image.text_field.top) - dimension },
{ x: Number(this.image.text_field.left) +10/this.zoomFactor,
y: Number(this.image.text_field.height) + Number(this.image.text_field.top) -10/this.zoomFactor - dimension },
{ x: Number(this.image.text_field.width) + Number(this.image.text_field.left) -10/this.zoomFactor - dimension,
y: Number(this.image.text_field.top) +10/this.zoomFactor},
{ x: Number(this.image.text_field.width) + Number(this.image.text_field.left) - dimension,
y: Number(this.image.text_field.top)}
]
let default_index = 1
let index = 0;
let position_found = false;
while (!position_found && index < positions.length){
let left = positions[index].x
let top = positions[index].y
if(!this.doesPositionConflict(left, top, dimension, this.words)
&& !this.doesPositionConflict(left, top, dimension, this.foreign_texts)){
position_found = true;
} else {
index++
}
}
let left = (index < positions.length) ? positions[index].x : positions[default_index].x;
let top = (index < positions.length) ? positions[index].y : positions[default_index].y;
return { x: `${left}px`, y: `${top}px` }
} else {
return { x: '0px', y: '0px' }
}
}
/**
* Return whether position specified by left, top and dimension does conflict with one of the positional objects' position.
*
* @param left left of position
* @param top top of position
* @param dimension dimension of position
* @param positionalObjects Array of positions
**/
private doesPositionConflict(left: number, top: number, dimension: number, positionalObjects: PositionalObject[]): boolean {
let conflicts = positionalObjects.filter(positionalObject =>
!(Number(positionalObject.left) + Number(positionalObject.width) < left || Number(positionalObject.left) > left + dimension
|| Number(positionalObject.top) > top + dimension || Number(positionalObject.top) + Number(positionalObject.height) < top)
)
return conflicts.length > 0
}
/**
* Get the hover status of the word as one of the {@link /miscellaneous/enumerations.html#HIGHTLIGHT_CASES|HIGHTLIGHT_CASES}.
**/
private getHoverStatus(word: Word, skipFindText: boolean = false): string {
if (this.selectedWords.indexOf(word.id) > -1
|| this.selectedLines.indexOf(word.line) > -1){
return HIGHTLIGHT_CASES.SELECTED_WORD;
}
if (!skipFindText && this.findText != null && this.findText != ''){
- return (word.text.match(this.findText)
- || (word.edited_text != null && word.edited_text.match(this.findText))
+ let findRegex = '^[^\\w]*(' + this.findText.split(' ').join('|') + ')'
+ return (word.text.match(findRegex)
+ || (word.edited_text != null && word.edited_text.match(findRegex))
) ? HIGHTLIGHT_CASES.SEARCHED_WORD : this.getHoverStatus(word, true);
}
if (typeof this.hoveredLine !== 'undefined' && this.hoveredLine !== null) {
return (this.hoveredLine.id == word.line
|| (this.hoveredLine.continuesTo != undefined && this.hoveredLine.continuesTo != null && this.hoveredLine.continuesTo.line.id == word.line)
|| (this.hoveredLine.continuesFrom != undefined && this.hoveredLine.continuesFrom != null && this.hoveredLine.continuesFrom.line.id == word.line))
? HIGHTLIGHT_CASES.LINE_HOVERED : HIGHTLIGHT_CASES.DEFAULT;
} else if (typeof this.hoveredWord !== 'undefined' && this.hoveredWord !== null){
return (this.hoveredWord.id == word.id) ? HIGHTLIGHT_CASES.WORD_HOVERED : HIGHTLIGHT_CASES.DEFAULT;
}
return HIGHTLIGHT_CASES.DEFAULT;
}
/**
* Return a css class for word that will be used with [ngClass] in order to (un-)highlight the word's rect.
*
* If a function has been passed to Input {@link #assignClass|assignClass},
* this function will call it if {@link #getHoverStatus|getHoverStatus(word)} == {@link /miscellaneous/enumerations.html#HIGHTLIGHT_CASES|HIGHTLIGHT_CASES.DEFAULT}.
**/
private assignClass(positionalObject: PositionalObject, elementName?: string): string {
if (positionalObject.datatype == 'TextByForeignHand'){
return (this.hoveredTextByForeignHand != null && this.hoveredTextByForeignHand.id == positionalObject.id) ?
'text_field highlight_foreign_text' : 'text_field unhighlighted'
}
let word = positionalObject;
if (elementName != null) {
return (this.getHoverStatus(word) == HIGHTLIGHT_CASES.DEFAULT) ? `text_field unhighlighted_${elementName}` : `text_field highlight_${elementName}`;
}
switch(this.getHoverStatus(word)) {
case HIGHTLIGHT_CASES.SELECTED_WORD: {
return 'textfield highlight_magenta';
}
case HIGHTLIGHT_CASES.SEARCHED_WORD: {
return 'textfield highlight_red';
}
case HIGHTLIGHT_CASES.LINE_HOVERED: {
return (word.deleted) ? 'textfield deleted' : 'textfield highlight_yellow';
}
case HIGHTLIGHT_CASES.WORD_HOVERED: {
return (word.deleted) ? 'textfield deleted' : 'textfield highlight_yellow';
}
case HIGHTLIGHT_CASES.DEFAULT: {
return (this.externalAssignClassAfter != null) ? this.externalAssignClassAfter(word, this.hoveredWord, this.hoveredLine) : 'textfield unhighlighted';
}
}
}
/**
* Assign a style to the rects of a line.
**/
private assignStyle(word: Word, hoveredWord: Word, hoveredLine: Line, hoverStatus: string): Object {
return (this.extAssignStyle != null) ? this.extAssignStyle(word, hoveredWord, hoveredLine, hoverStatus) : {};
}
private msg(URL: string){
if(this.preferPrimaryUrl){
console.log(URL + ' TODO: show smaller image during loading');
}
}
}
diff --git a/nietzsche-beta-app/src/app/rhizome-view-component/rhizome-view-component.component.html b/nietzsche-beta-app/src/app/rhizome-view-component/rhizome-view-component.component.html
index 8629103..33e8c1b 100644
--- a/nietzsche-beta-app/src/app/rhizome-view-component/rhizome-view-component.component.html
+++ b/nietzsche-beta-app/src/app/rhizome-view-component/rhizome-view-component.component.html
@@ -1,5 +1,5 @@
-
+
diff --git a/nietzsche-beta-app/src/app/tln-edition/constants.ts b/nietzsche-beta-app/src/app/tln-edition/constants.ts
index fdc95ae..75d02b6 100644
--- a/nietzsche-beta-app/src/app/tln-edition/constants.ts
+++ b/nietzsche-beta-app/src/app/tln-edition/constants.ts
@@ -1,66 +1,70 @@
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 TlnFulltextComponent
+ **/
+export const TLN_SEARCH_ROUTE: string = 'tln-search';
/**
* Route for TlnManuscriptViewComponent
**/
export const TLN_MANUSCRIPT_ROUTE: string = 'tln-manuscript';
/**
* 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 page iri.
**/
export const TLN_PAGE_PARAM: string = 'page';
/**
* 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 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 zoom.
**/
export const TLN_ZOOM_PARAM: string = 'zoom';
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 c3a6b88..19f16cc 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,217 +1,218 @@
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 {
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;
}
/**
* 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;
}
/**
* 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)
- if (parsedQuery.where[0].patterns != undefined){
- for (var j = 0; j < parsedQuery.where[0].patterns.length; j++){
- for (var i = 0; i < parsedQuery.where[0].patterns[j].triples.length; i++){
- if(parsedQuery.where[0].patterns[j].triples[i]['subject']['value'] == key){
- parsedQuery.where[0].patterns[j].triples[i]['subject'] = { termType: "NamedNode", value: id };
- } else if(parsedQuery.where[0].patterns[j].triples[i]['object']['value'] == key){
- parsedQuery.where[0].patterns[j].triples[i]['object'] = { termType: "NamedNode", value: id };
- } else if(parsedQuery.where[0].patterns[j].triples[i]['predicate']['value'] == key){
- parsedQuery.where[0].patterns[j].triples[i]['predicate'] = { termType: "NamedNode", value: id };
+ 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);
- } else if (parsedQuery.where[0].triples == undefined){
- console.log('ERROR', parsedQuery);
- return ''
- }
- 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);
}
}
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/manuscript.ts b/nietzsche-beta-app/src/app/tln-edition/datatypes/manuscript.ts
index fa6224e..bc5f1cf 100644
--- a/nietzsche-beta-app/src/app/tln-edition/datatypes/manuscript.ts
+++ b/nietzsche-beta-app/src/app/tln-edition/datatypes/manuscript.ts
@@ -1,235 +1,237 @@
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:hasArchivalicSignature ?archivalicSignature;
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 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 ?cssStyleTag ?startIndex ?endIndex WHERE {
?manuscript tln:hasDescription ?id.
?id tln:textHasContent ?text.
OPTIONAL { ?id tln:textHasMarkup ?description_markup.
?description_markup stoff:hasCSS ?cssStyleTag;
stoff:standoffMarkupHasStartIndex ?startIndex;
stoff:standoffMarkupHasEndIndex ?endIndex.}
}`;
/**
* 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 ?cssStyleTag ?startIndex ?endIndex 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 ?cssStyleTag;
stoff:standoffMarkupHasStartIndex ?startIndex;
stoff:standoffMarkupHasEndIndex ?endIndex.}
}`;
/**
* 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/search.ts b/nietzsche-beta-app/src/app/tln-edition/datatypes/search.ts
new file mode 100644
index 0000000..9210e46
--- /dev/null
+++ b/nietzsche-beta-app/src/app/tln-edition/datatypes/search.ts
@@ -0,0 +1,130 @@
+import { BasicResultBindingElement, FusekiResults} from './basic_datatype';
+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 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/debug.pipe.spec.ts b/nietzsche-beta-app/src/app/tln-edition/debug.pipe.spec.ts
new file mode 100644
index 0000000..15747c5
--- /dev/null
+++ b/nietzsche-beta-app/src/app/tln-edition/debug.pipe.spec.ts
@@ -0,0 +1,8 @@
+import { DebugPipe } from './debug.pipe';
+
+describe('DebugPipe', () => {
+ it('create an instance', () => {
+ const pipe = new DebugPipe();
+ expect(pipe).toBeTruthy();
+ });
+});
diff --git a/nietzsche-beta-app/src/app/tln-edition/debug.pipe.ts b/nietzsche-beta-app/src/app/tln-edition/debug.pipe.ts
new file mode 100644
index 0000000..9e0c555
--- /dev/null
+++ b/nietzsche-beta-app/src/app/tln-edition/debug.pipe.ts
@@ -0,0 +1,13 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({
+ name: 'debug'
+})
+export class DebugPipe implements PipeTransform {
+
+ transform(value: any): any {
+ console.log(value)
+ return value;
+ }
+
+}
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 c55f009..859514c 100644
--- a/nietzsche-beta-app/src/app/tln-edition/route-updater.ts
+++ b/nietzsche-beta-app/src/app/tln-edition/route-updater.ts
@@ -1,50 +1,54 @@
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;
+ }
}
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){
newRouterParam[paramsKey] = JSON.stringify(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 });
window.open(link.toString(), '_blank')
} else {
this.router.navigate([ parentPath + '/' + this.currentRoute], { queryParams: newRouterParam });
}
});
} else {
if (launch != undefined && launch){
let link = this.router.createUrlTree([ this.currentRoute], { queryParams: newRouterParam });
window.open(link.toString(), '_blank')
} else {
this.router.navigate([ this.currentRoute], { queryParams: newRouterParam });
}
}
}
}
diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-crossref/navigation/navigation.component.css b/nietzsche-beta-app/src/app/tln-edition/tln-crossref/navigation/navigation.component.css
index 90d18aa..9f0f9dd 100644
--- a/nietzsche-beta-app/src/app/tln-edition/tln-crossref/navigation/navigation.component.css
+++ b/nietzsche-beta-app/src/app/tln-edition/tln-crossref/navigation/navigation.component.css
@@ -1,26 +1,18 @@
#navi {
width: 100%;
height: 50px;
margin: 0;
padding: 0;
white-space: nowrap;
}
.search {
margin-left: 8px;
}
-.zoom {
- max-width: 20px;
-}
-.zoom-in {
- cursor: zoom-in;
-}
-.zoom-out {
- cursor: zoom-out;
-}
+
.mat-button.min-width {
min-width: 120px;
max-width: 120px;
}
.default-mouse {
cursor: default;
}
diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-crossref/navigation/navigation.component.html b/nietzsche-beta-app/src/app/tln-edition/tln-crossref/navigation/navigation.component.html
index a1af008..2fc987f 100644
--- a/nietzsche-beta-app/src/app/tln-edition/tln-crossref/navigation/navigation.component.html
+++ b/nietzsche-beta-app/src/app/tln-edition/tln-crossref/navigation/navigation.component.html
@@ -1,30 +1,18 @@