Page MenuHomec4science

textfield.component.ts
No OneTemporary

File Metadata

Created
Mon, May 6, 13:10

textfield.component.ts

import { Component, ElementRef, Input, 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;
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 != ''){
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 = <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');
}
}
}

Event Timeline