diff --git a/src/app/app.component.html b/src/app/app.component.html index c09ce18..c7792c4 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,10 +1,12 @@ - - +
+ diff --git a/src/app/app.module.ts b/src/app/app.module.ts index e5e31fe..30185a8 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,81 +1,90 @@ import { AppComponent } from './app.component'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { CdkTableModule } from '@angular/cdk/table'; import { DragDropModule } from '@angular/cdk/drag-drop'; import { FormsModule, ReactiveFormsModule} from '@angular/forms'; import { NgModule } from '@angular/core'; import { MatDialogModule, MatToolbarModule} from '@angular/material'; import { MatExpansionModule } from '@angular/material/expansion'; import {MatIconModule} from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatListModule } from '@angular/material/list'; import {MatMenuModule} from '@angular/material/menu'; -import { MatPaginatorModule, MatSortModule, MatSidenavModule, MatCheckboxModule } from '@angular/material'; +import { MatPaginatorModule, MatSortModule, MatSidenavModule, MatCheckboxModule, MatRadioModule } from '@angular/material'; import { MatSelectModule } from '@angular/material/select'; import { MatTableModule } from '@angular/material/table'; import {MatTabsModule} from '@angular/material/tabs'; import { DataListViewComponent } from './data-list-view/data-list-view.component'; import { DataListViewTableComponent, HighlightPipe } from './data-list-view/data-list-view-table/data-list-view-table.component'; import { DialogComponent } from './dialog-component/dialog.component'; import { InfoBoxComponent } from './info-box-component/info-box.component'; import { TextFieldComponent} from './textfield-component/textfield.component'; import { WordPositionDirective } from './textfield-component/word-position.directive'; import { TextfieldOptionsComponentComponent } from './textfield-options-component/textfield-options-component.component'; import { CommonModule } from '@angular/common'; import { HttpClientModule } from '@angular/common/http'; import { RdfDataBrowserComponentComponent } from './rdf-data-browser-component/rdf-data-browser-component.component'; import { QueryService } from './services/query.service'; import {AnnotationService, TextStyleService} from './services/annotation.service'; import { DisplayedCollumnsService } from './data-list-view/data-list-view-services/table-data.service'; import {DataListViewSettings} from './data-list-view/data-list-view-settings/data-list-view-settings.service'; import { MarkupTextComponentComponent } from './markup-text-component/markup-text-component.component'; import { BergwerkeComponent } from './bergwerke/bergwerke.component'; +import { SubmenuComponentComponent } from './markup-text-component/submenu-component/submenu-component.component'; +import { MarkupTextButtonsComponentComponent } from './markup-text-component/markup-text-buttons-component/markup-text-buttons-component.component'; +import { OptionsComponent } from './options-component/options-component.component'; +import { MarkupHyperlinkComponentComponent } from './markup-text-component/markup-hyperlink-component/markup-hyperlink-component.component'; @NgModule({ declarations: [ AppComponent, TextFieldComponent, WordPositionDirective, InfoBoxComponent, TextfieldOptionsComponentComponent, DataListViewComponent, DataListViewTableComponent, DialogComponent, HighlightPipe, RdfDataBrowserComponentComponent, MarkupTextComponentComponent, - BergwerkeComponent + BergwerkeComponent, + SubmenuComponentComponent, + MarkupTextButtonsComponentComponent, + OptionsComponent, + MarkupHyperlinkComponentComponent ], imports: [ BrowserModule, BrowserAnimationsModule, CdkTableModule, CommonModule, DragDropModule, HttpClientModule, MatCheckboxModule, MatDialogModule, MatExpansionModule, MatIconModule, MatInputModule, MatListModule, MatMenuModule, MatPaginatorModule, + MatRadioModule, MatSelectModule, MatSidenavModule, MatSortModule, MatTableModule, MatTabsModule, MatToolbarModule, FormsModule, ReactiveFormsModule ], providers: [ QueryService, DisplayedCollumnsService, DataListViewSettings, TextStyleService, AnnotationService ], bootstrap: [AppComponent], entryComponents: [DialogComponent] }) export class AppModule { } diff --git a/src/app/bergwerke/bergwerke.component.html b/src/app/bergwerke/bergwerke.component.html index eda2cf8..d85a029 100644 --- a/src/app/bergwerke/bergwerke.component.html +++ b/src/app/bergwerke/bergwerke.component.html @@ -1,4 +1,5 @@

This is a Text app styled with markup component

Please take your time to also read this gorgeous text. It is one of the most beautiful texts of all time: The "Unverhofftes Wiedersehen" of Johann Peter Hebel

+

Unverhofftes Wiedersehen

diff --git a/src/app/bergwerke/bergwerke.component.ts b/src/app/bergwerke/bergwerke.component.ts index 6db34f9..7673dc8 100644 --- a/src/app/bergwerke/bergwerke.component.ts +++ b/src/app/bergwerke/bergwerke.component.ts @@ -1,49 +1,50 @@ import { Component, OnInit } from '@angular/core'; +import markupDefaultSettings from '../markup-text-component/markup-text-settings.json'; +import {MarkupOptionsService, MarkupSetting} from '../markup-text-component/markup-options.service'; @Component({ selector: 'app-bergwerke', templateUrl: './bergwerke.component.html', styleUrls: ['./bergwerke.component.scss'] }) export class BergwerkeComponent implements OnInit { myText = 'In Falun in Schweden küßte vor guten fünfzig Jahren und mehr ein junger Bergmann seine junge hübsche Braut und sagte zu ihr: »Auf Sankt Luciä wird unsere Liebe von des Priesters Hand gesegnet. Dann sind wir Mann und Weib, und bauen uns ein eigenes Nestlein.« – »Und Friede und Liebe soll darin wohnen«, sagte die schöne Braut mit holdem Lächeln, »denn du bist mein einziges und alles, und ohne dich möchte ich lieber im Grab sein, als an einem andern Ort.« Als sie aber vor St. Luciä der Pfarrer zum zweitenmal in der Kirche ausgerufen hatte: »So nun jemand Hindernis wüßte anzuzeigen, warum diese Personen nicht möchten ehelich zusammenkommen« – da meldete sich der Tod. Denn als der Jüngling den andern Morgen in seiner schwarzen Bergmannskleidung an ihrem Haus vorbeiging, der Bergmann hat sein Totenkleid immer an, da klopfte er zwar noch einmal an ihrem Fenster, und sagte ihr guten Morgen, aber keinen guten Abend mehr. Er kam nimmer aus dem Bergwerk zurück, und sie saumte vergeblich selbigen Morgen ein schwarzes Halstuch mit rotem Rand für ihn zum Hochzeittag, sondern als er nimmer kam, legte sie es weg, und weinte um ihn und vergaß ihn nie. Unterdessen wurde die Stadt Lissabon in Portugal durch ein Erdbeben zerstört, und der Siebenjährige Krieg ging vorüber, und Kaiser Franz der Erste starb, und der Jesuitenorden wurde aufgehoben und Polen geteilt, und die Kaiserin Maria Theresia starb, und der Struensee wurde hingerichtet, Amerika wurde frei, und die vereinigte französische und spanische Macht konnte Gibraltar nicht erobern. Die Türken schlossen den General Stein in der Veteraner Höhle in Ungarn ein, und der Kaiser Joseph starb auch. Der König Gustav von Schweden eroberte russisch Finnland, und die Französische Revolution und der lange Krieg fing an, und der Kaiser Leopold der Zweite ging auch ins Grab. Napoleon eroberte Preußen, und die Engländer bombardierten Kopenhagen, und die Ackerleute säeten und schnitten. Der Müller mahlte, und die Schmiede hämmerten, und die Bergleute gruben nach den Metalladern in ihrer unterirdischen Werkstatt. Als aber die Bergleute in Falun im Jahr 1809 etwas vor oder nach Johannis zwischen zwei Schachten eine Öffnung durchgraben wollten, gute dreihundert Ellen tief unter dem Boden gruben sie aus dem Schutt und Vitriolwasser den Leichnam eines Jünglings heraus, der ganz mit Eisenvitriol durchdrungen, sonst aber unverwest und unverändert war; also daß man seine Gesichtszüge und sein Alter noch völlig erkennen konnte, als wenn er erst vor einer Stunde gestorben, oder ein wenig eingeschlafen wäre, an der Arbeit. Als man ihn aber zu Tag ausgefördert hatte, Vater und Mutter, Gefreundte und Bekannte waren schon lange tot, kein Mensch wollte den schlafenden Jüngling kennen oder etwas von seinem Unglück wissen, bis die ehemalige Verlobte des Bergmanns kam, der eines Tages auf die Schicht gegangen war und nimmer zurückkehrte. Grau und zusammengeschrumpft kam sie an einer Krücke an den Platz und erkannte ihren Bräutigam; und mehr mit freudigem Entzücken als mit Schmerz sank sie auf die geliebte Leiche nieder, und erst als sie sich von einer langen heftigen Bewegung des Gemüts erholt hatte, »es ist mein Verlobter«, sagte sie endlich, »um den ich fünfzig Jahre lang getrauert hatte, und den mich Gott noch einmal sehen läßt vor meinem Ende. Acht Tage vor der Hochzeit ist er unter die Erde gegangen und nimmer heraufgekommen.« Da wurden die Gemüter aller Umstehenden von Wehmut und Tränen ergriffen, als sie sahen die ehemalige Braut jetzt in der Gestalt des hingewelkten kraftlosen Alters und den Bräutigam noch in seiner jugendlichen Schöne, und wie in ihrer Brust nach 50 Jahren die Flamme der jugendlichen Liebe noch einmal erwachte; aber er öffnete den Mund nimmer zum Lächeln oder die Augen zum Wiedererkennen; und wie sie ihn endlich von den Bergleuten in ihr Stüblein tragen ließ, als die einzige, die ihm angehöre, und ein Recht an ihn habe, bis sein Grab gerüstet sei auf dem Kirchhof. Den andern Tag, als das Grab gerüstet war auf dem Kirchhof und ihn die Bergleute holten, schloß sie ein Kästlein auf, legte sie ihm das schwarzseidene Halstuch mit roten Streifen um, und begleitete ihn alsdann in ihrem Sonntagsgewand, als wenn es ihr Hochzeittag und nicht der Tag seiner Beerdigung wäre. Denn als man ihn auf dem Kirchhof ins Grab legte, sagte sie: »Schlafe nun wohl, noch einen Tag oder zehen im kühlen Hochzeitbett, und laß dir die Zeit nicht lange werden. Ich habe nur noch wenig zu tun, und komme bald, und bald wird\'s wieder Tag. – Was die Erde einmal wiedergegeben hat, wird sie zum zweitenmal auch nicht behalten«, sagte sie, als sie fortging, und noch einmal umschaute.'; + defaultSettings = JSON.parse(JSON.stringify(markupDefaultSettings)); + myStandoffData: Array> = [ [ "stoff:SemanticAnnotation", 3, 8, 'humgeo:City', "https://www.wikidata.org/wiki/Q26509"], [ "stoff:SemanticAnnotation", 12, 19, 'humgeo:State', "https://www.wikidata.org/wiki/Q34"], [ "stoff:SemanticAnnotation", 1662, 1666, 'human:Organization', "https://de.wikipedia.org/wiki/K%C3%B6nig"], [ "stoff:SemanticAnnotation", 1679, 1686, 'humgeo:State', "https://de.wikipedia.org/wiki/Schweden"], [ "stoff:SemanticAnnotation", 1662, 1686, 'human:Person', "https://de.wikipedia.org/wiki/Karl_X._Gustav"], [ "stoff:CssStyle", 125, 301, "font-style: italic;"], [ "stoff:CssStyle", 346, 457, "font-style: italic;"], [ "stoff:CssStyle", 544, 645, "font-style: italic;"], [ "stoff:CssStyle", 3163, 3186, "font-style: italic;"], [ "stoff:CssStyle", 3207, 3398, "font-style: italic;"], [ "stoff:CssStyle", 4334, 4605, "font-style: italic;"], [ "stoff:CssStyle", 2097, 2102, "font-weight: bold;"], [ "stoff:CssStyle", 2097, 2102, "color: blue;"], [ "stoff:CssStyle", 2097, 2102, "text-decoration: underline;"], [ "stoff:CssStyle", 1182, 1190, "text-decoration: underline;"], [ "stoff:CssStyle", 1182, 1190, "font-weight: bold;"], [ "stoff:CssStyle", 1182, 1190, "color: blue;"], [ "stoff:CssStyle", 1443, 1450, "text-decoration: underline;"], [ "stoff:CssStyle", 1443, 1450, "font-weight: bold;"], [ "stoff:CssStyle", 1443, 1450, "color: blue;"], [ "stoff:CssStyle", 1285, 1300, "text-decoration: underline;"], [ "stoff:CssStyle", 1285, 1300, "font-weight: bold;"], [ "stoff:CssStyle", 1285, 1300, "color: red;"], [ "stoff:CssStyle", 1316, 1329, "text-decoration: underline;"], [ "stoff:CssStyle", 1316, 1329, "color: green;"], [ "stoff:CssStyle", 1383, 1397, "text-decoration: underline;"], [ "stoff:CssStyle", 1383, 1397, "font-weight: bold;"], [ "stoff:CssStyle", 1383, 1397, "color: red;"], [ "stoff:CssStyle", 1639, 1645, "text-decoration: underline;"], [ "stoff:CssStyle", 1639, 1645, "font-weight: bold;"], [ "stoff:CssStyle", 1639, 1645, "color: red;"] ]; - constructor() { } - ngOnInit() { } - } diff --git a/src/app/markup-text-component/markup-hyperlink-component/markup-hyperlink-component.component.html b/src/app/markup-text-component/markup-hyperlink-component/markup-hyperlink-component.component.html new file mode 100644 index 0000000..738d596 --- /dev/null +++ b/src/app/markup-text-component/markup-hyperlink-component/markup-hyperlink-component.component.html @@ -0,0 +1,17 @@ +{{segment[1]}} + + + + {{segment[1]}} + + + +
+ {{item.oClass}}: {{item.linkText}} +
+
+ diff --git a/src/app/markup-text-component/markup-hyperlink-component/markup-hyperlink-component.component.scss b/src/app/markup-text-component/markup-hyperlink-component/markup-hyperlink-component.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/markup-text-component/markup-hyperlink-component/markup-hyperlink-component.component.ts b/src/app/markup-text-component/markup-hyperlink-component/markup-hyperlink-component.component.ts new file mode 100644 index 0000000..be98a69 --- /dev/null +++ b/src/app/markup-text-component/markup-hyperlink-component/markup-hyperlink-component.component.ts @@ -0,0 +1,16 @@ +import {Component, Input, OnInit} from '@angular/core'; + +@Component({ + selector: 'app-markup-hyperlink-component', + templateUrl: './markup-hyperlink-component.component.html', + styleUrls: ['../markup-text-component.component.scss'] +}) +export class MarkupHyperlinkComponentComponent implements OnInit { + @Input() segment: any; + @Input() action: string; + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/app/markup-text-component/markup-options.service.ts b/src/app/markup-text-component/markup-options.service.ts new file mode 100644 index 0000000..bd7cd8f --- /dev/null +++ b/src/app/markup-text-component/markup-options.service.ts @@ -0,0 +1,30 @@ +import {EventEmitter, Injectable} from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class MarkupOptionsService { + initialSettings: MarkupSetting; + optionsChange: EventEmitter = new EventEmitter(); + + constructor() { + this.initialSettings = new MarkupSetting(); + } + + public loadDefaultSettings(defaultSettings) { + for (const settingGroup of defaultSettings.settings) { + for (const entry of settingGroup.entries) { + this.initialSettings[settingGroup.label + '.' + entry.label] = entry.value; + } + } + return this.initialSettings; + } + + public setSettings(newSettings: MarkupSetting) { + this.optionsChange.emit(newSettings); + } +} + +export class MarkupSetting { + [prop: string]: string; +} diff --git a/src/app/markup-text-component/markup-text-buttons-component/markup-text-buttons-component.component.html b/src/app/markup-text-component/markup-text-buttons-component/markup-text-buttons-component.component.html new file mode 100644 index 0000000..1fab462 --- /dev/null +++ b/src/app/markup-text-component/markup-text-buttons-component/markup-text-buttons-component.component.html @@ -0,0 +1,29 @@ + + + + + + +
open resource in data browser
+
edit text
+
edit semantic annotation "{{segment[3][0].cssClass}}"
+
delete semantic annotation "{{segment[3][0].cssClass}}"
+
+ + + + diff --git a/src/app/markup-text-component/markup-text-buttons-component/markup-text-buttons-component.component.scss b/src/app/markup-text-component/markup-text-buttons-component/markup-text-buttons-component.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/markup-text-component/markup-text-buttons-component/markup-text-buttons-component.component.ts b/src/app/markup-text-component/markup-text-buttons-component/markup-text-buttons-component.component.ts new file mode 100644 index 0000000..a35decb --- /dev/null +++ b/src/app/markup-text-component/markup-text-buttons-component/markup-text-buttons-component.component.ts @@ -0,0 +1,16 @@ +import {Component, Input, OnInit} from '@angular/core'; + +@Component({ + selector: 'app-markup-text-buttons-component', + templateUrl: './markup-text-buttons-component.component.html', + styleUrls: ['../markup-text-component.component.scss'] +}) +export class MarkupTextButtonsComponentComponent implements OnInit { + @Input() segment: any; + @Input() action: string; + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/app/markup-text-component/markup-text-component.component.html b/src/app/markup-text-component/markup-text-component.component.html index 6ebe05a..c29513f 100644 --- a/src/app/markup-text-component/markup-text-component.component.html +++ b/src/app/markup-text-component/markup-text-component.component.html @@ -1,22 +1,18 @@
+ {{segment[1]}} + {{segment[1]}} - - - {{segment[1]}} - + + + - - {{segment[1]}} - - -
- {{i}}) {{item.oClass}}: {{item.linkText}} -
-
+ +
diff --git a/src/app/markup-text-component/markup-text-component.component.scss b/src/app/markup-text-component/markup-text-component.component.scss index d1d0b9f..70bdfd4 100644 --- a/src/app/markup-text-component/markup-text-component.component.scss +++ b/src/app/markup-text-component/markup-text-component.component.scss @@ -1,48 +1,62 @@ body { } a { color: inherit; /* no colors for links. Color will be defined by semantic classes */ text-decoration: inherit; /* no underline */ } ::ng-deep .poly-menu { width: 400px; } +.annotation-button { + padding: 0; + margin-right: 0.25em; // needed as a text with white spaces within a button does not make the button have white spaces around ... + border: 2px; + border-color: transparent; + background-color: transparent; + white-space: normal; +} + +.annotation-button:hover { + background-color: rgba(10, 104, 24, 0.21); + border-color: rgba(10, 104, 24, 0.21); +} + .style-markup { } .semantic-markup { } .semantic-markup.polysemantic { text-decoration: underline; cursor: pointer; color: #0c5593; font-weight: bold; } .semantic-markup.humgeo { font-weight: bold; } .humgeo.city { color: blue; } .humgeo.state { color: #a4a4a4; } .semantic-markup.human { } .human.organization { color: #fa1cbc; } .human.person{ color: #ffbb5c; } diff --git a/src/app/markup-text-component/markup-text-component.component.ts b/src/app/markup-text-component/markup-text-component.component.ts index 324f272..1efd59b 100644 --- a/src/app/markup-text-component/markup-text-component.component.ts +++ b/src/app/markup-text-component/markup-text-component.component.ts @@ -1,153 +1,168 @@ import {Component, Input, OnChanges} from '@angular/core'; -import {RESOURCE_CACHE_PROVIDER} from '@angular/platform-browser-dynamic'; +import {MarkupOptionsService, MarkupSetting} from './markup-options.service'; +import markupDefaultSettings from './markup-text-settings.json'; @Component({ selector: 'app-markup-text-component', templateUrl: './markup-text-component.component.html', styleUrls: ['./markup-text-component.component.scss'] // TODO: implement dynamic css from files/assets/server or somewhere }) /** TODO: change documentation * Simple and leightweight component marking up/styling a text input textToStyle with standoff markupData. * @param StandoffData: markup data. * @param textToStyle: the word for which the markup information should be applied as styles in the template. * @param startIndices: the startindices of all final text segments to style * @param styleSegments: the resulting style segments with their css styles which are then applied by ngStyle in the template */ export class MarkupTextComponentComponent implements OnChanges { @Input() standoffData: Array>; // [[rdfs:type, startIndex:number, endIndex:number, cssStyle:string], [...], ...] @Input() textToStyle: string; + @Input() markupSettings?: MarkupSetting; + defaultSettings = JSON.parse(JSON.stringify(markupDefaultSettings)); + // temporary mockup data markupData: Array> = [ ["stoff:CssStyle", 0, 1, "font-weight: bold;"], ["stoff:CssStyle", 1, 4, "font-style: italic;"], ["stoff:CssStyle", 0, 6, "text-decoration: underline;"], ["stoff:CssStyle", 4, 4, "color: blue;"], ["stoff:CssStyle", 4, 5, "text-decoration: line-through;"] ]; - polyMenuOpen = false; // the status of the menu for polysemantics - startIndices: Array = []; // the startindices of all style segments templateSegments: Array = []; // the resulting style segments with their css styles - - constructor() { + constructor( private optionService: MarkupOptionsService) { + if (!this.markupSettings ) { + this.markupSettings = this.optionService.loadDefaultSettings(this.defaultSettings); + } } ngOnChanges() { + this.optionService.optionsChange.subscribe( + options => { this.markupSettings = options; console.log('new markup settings coming in: ' + this.markupSettings); } + ); // TODO: maybe go for data stream as an observable/promise if (this.standoffData && this.standoffData.length > 0 ) { this.markupData = this.standoffData; } this.templateSegments = []; // needed reset, if not ngOnChanges appends the new spans to the old ones in the template instead of replacing! this.startIndices = this.getStartIndices(this.markupData); this.createSegments(); } /** * Creates the final text segments and styles (this.templateSegments) which will * be *ngFored as and styled with *ngStyle in the template. * Iterates through all startindices defined, pushes the corresponding substring * of our text to style together with its matching styles to this.templateSegments * */ private createSegments() { let c = 1; // counter for getting the end index of the substring() via this.startIndices[c] for (const startIndex of this.startIndices) { const textSegment = this.textToStyle.substring(startIndex, this.startIndices[c]); const markupDef = this.getMarkupDef(startIndex); if (markupDef) { this.templateSegments.push([markupDef[0], textSegment, markupDef[1], markupDef[2]]); } c += 1; } console.log('this.templateSegments: ', this.templateSegments); } /** * Gets the styles that apply for a passed start index. Hence the styles of a segment * apply to every character within that segment equally, we only need to get the styles * for the startindex, so all those styles apply to the whole segment correctly. * Iterates through all defined styles/ranges ranges of this.markupData. * For every range covering the startIndex, its style is added as property to the styles object. * * @param startIndex: The startindex of a new range to style * @return styles: An object with all matching css styles as properties */ private getMarkupDef(startIndex) { const markup = {}; const resource = []; // more than one linked resource possible at overlaps let markuptype = ''; for (const entry of this.markupData) { // if our startIndex is within a given range, the style of that range will be added to styles. if (entry[1] <= startIndex && startIndex <= entry[2]) { if (entry[0] === 'stoff:CssStyle') { markuptype = entry[0].split(':')[1]; // CssStyle // Adds css property/value as an object: Splits the style string at ':', // removes leading and ending spaces, deletes ";" and assigns it as an object as css property:"value" markup[entry[3].split(':')[0].trim()] = entry[3].split(':')[1].trim().replace(';', ''); } if (entry[0] === 'stoff:SemanticAnnotation') { markuptype = entry[0].split(':')[1]; // SemanticAnnotation // adds css class markup[(entry[3].trim().replace(':', ' ' ).toLowerCase())] = true; resource.push( { linkText: this.textToStyle.substring(entry[1], entry[2] + 1), link: entry[4], cssClass: entry[3].trim().replace(':', ' ' ).toLowerCase(), oClass: entry[3].trim().split(':')[1] }); // links } } } if (Object.keys(markup).length === 0 && markup.constructor === Object) { // If there is no range applying, e.g. no style at all defined for a startindex we simply define font-style: normal. return ['CssStyle', {'font-style': 'normal'}, '']; } else { return [markuptype, markup, resource ]; } } /** * Creates an array of startindices defining the final text/style segments. * Hence every end index can be described as a startindex minus one (an end * index equals a start index of sth. new -1), end indices are also covered implicitly. * So every endIndex +1 is also pushed to the startIndices array if not yet existing. * If a defined range is ending at the very last character, there is obviously not a new * style starting and an additive startIndex is wrong. Therefore the last startIndex * is simply popped from the array if its value exceeds textToStyle.length. * * @return startIndices: Array of all distinct startIndices of the final text segments. */ private getStartIndices(standoffData) { const startIndices: Array = []; // Push every startindex to startIndices if not yet there (distinct). // Hence every endIndex is itself equals a (startIndex -1), we simply add also every endIndex +1 to // the Array of startIndices if there is not yet a start defined for that character. standoffData.forEach(range => { if (startIndices.indexOf(range[1]) === -1) { startIndices.push(range[1]); } if (startIndices.indexOf(range[2] + 1) === -1) { startIndices.push(range[2] + 1); } }); // if there is no startindex of 0, we have to add it to define the first text segment. if (startIndices.indexOf(0) === -1) { startIndices.push(0); } // sort it startIndices.sort((n1, n2) => n1 - n2); // If the last endIndex is the very last character of the string to style, we must not generate a startIndex at endIndex+1. // In any other case we have to start a new style. // If the last generated startIndex is bigger than this.textToStyle.length // we have to pop that last startIndex (from the right) from our startIndices. // If the textToStyle.length is bigger or equals the last startIndex, that last startIndex simply means the end of the style // one character before. In theese cases a last style range with font style undefined (style:normal) has to start, // so the last end ist also defined. if (this.textToStyle.length < startIndices[startIndices.length - 1]) { console.log('popping last startIndex: ', startIndices[startIndices.length - 1]); startIndices.pop(); } return startIndices; } + // runs some action + // TODO: Add actions + private getHRef(action, target) { + console.log('action = ', action); + if (action === 'external') { + return target; + } + } } diff --git a/src/app/markup-text-component/markup-text-settings.json b/src/app/markup-text-component/markup-text-settings.json new file mode 100644 index 0000000..37ead40 --- /dev/null +++ b/src/app/markup-text-component/markup-text-settings.json @@ -0,0 +1,85 @@ +{ + "settings":[ + { + "label":"semanticMarkup", + "description":"display semantic annotations as", + "entries":[ + { + "label":"styleType", + "description":"render semantic annotations as", + "options":[ + { + "id":"button", + "description":"buttons" + }, + { + "id":"icon", + "description":"icons" + }, + { + "id":"hyperlink", + "description":"text with hyperlinks" + }, + { + "id":"markup", + "description":"styled text/markup only" + }, + { + "id":"none", + "description":"plane unstyled text" + } + ], + "value":"button", + "type":"select" + }, + { + "label":"position", + "description":"position them", + "options":[ + { + "id":"before", + "description":"before the text" + }, + { + "id":"after", + "description":"after the text" + } + ], + "value":"after", + "type":"radio", + "condition": [{"option":"semanticMarkup.styleType", "value":"icon"}] + }, + { + "label":"actions", + "description":"action to take place on click at a semantic annotation", + "options":[ + { + "id":"external", + "description":"open external link" + }, + { + "id":"pannel", + "description":"display resource in pannel" + }, + { + "id":"menu", + "description":"open menu" + } + ], + "value":"menu", + "type":"select", + "condition": [ + {"option": "semanticMarkup.styleType", "value": "icon"}, + {"option": "semanticMarkup.styleType", "value": "button"}, + {"option": "semanticMarkup.styleType", "value": "hyperlink"} + ] + }, + { + "label":"tooltip", + "value":true, + "type":"checkbox" + } + ] + } + ] +} diff --git a/src/app/markup-text-component/submenu-component/submenu-component.component.html b/src/app/markup-text-component/submenu-component/submenu-component.component.html new file mode 100644 index 0000000..75fadeb --- /dev/null +++ b/src/app/markup-text-component/submenu-component/submenu-component.component.html @@ -0,0 +1,10 @@ + +
+ {{item.oClass}}: {{item.linkText}} +
+ +
open resource in data browser
+
edit text
+
edit semantic annotation
+
delete semantic annotation
+
diff --git a/src/app/markup-text-component/submenu-component/submenu-component.component.scss b/src/app/markup-text-component/submenu-component/submenu-component.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/markup-text-component/submenu-component/submenu-component.component.ts b/src/app/markup-text-component/submenu-component/submenu-component.component.ts new file mode 100644 index 0000000..50c1d6d --- /dev/null +++ b/src/app/markup-text-component/submenu-component/submenu-component.component.ts @@ -0,0 +1,16 @@ +import {Component, Input, OnInit} from '@angular/core'; + +@Component({ + selector: 'app-submenu-component', + templateUrl: './submenu-component.component.html', + styleUrls: ['../markup-text-component.component.scss'] +}) +export class SubmenuComponentComponent implements OnInit { + + constructor() { } + @Input() item: any; + + ngOnInit() { + } + +} diff --git a/src/app/options-component/options-component.component.html b/src/app/options-component/options-component.component.html new file mode 100644 index 0000000..0e844bc --- /dev/null +++ b/src/app/options-component/options-component.component.html @@ -0,0 +1,33 @@ +

Options

+ + + + + {{settingsGroup.label}} + + +
+ +
+
{{setting.description}}
+ + + {{option.description}} + + +
+ +
+
{{setting.description}}
+ +
+ {{option.description}} +
+
+
+
+
+
+ diff --git a/src/app/options-component/options-component.component.scss b/src/app/options-component/options-component.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/options-component/options-component.component.ts b/src/app/options-component/options-component.component.ts new file mode 100644 index 0000000..aeafbd1 --- /dev/null +++ b/src/app/options-component/options-component.component.ts @@ -0,0 +1,40 @@ +import {Component, Input, OnInit} from '@angular/core'; +import {MarkupOptionsService, MarkupSetting} from '../markup-text-component/markup-options.service'; + +@Component({ + selector: 'app-options-component', + templateUrl: './options-component.component.html', + styleUrls: ['./options-component.component.scss'] +}) +export class OptionsComponent implements OnInit { + @Input() initialSettings?: any; + actualSettings: MarkupSetting; + + constructor(private optionService: MarkupOptionsService) { } + + ngOnInit() { + if (this.initialSettings) { + this.actualSettings = this.optionService.loadDefaultSettings(this.initialSettings); + console.log('this.actualSettings loaded: ', this.actualSettings); + } + } + + onSelectValueChange(settingGroup, changedSetting) { + console.log('new selection => ', changedSetting.label , ': ', changedSetting.value); + this.actualSettings[settingGroup + '.' + changedSetting.label] = changedSetting.value; + + this.optionService.setSettings(this.actualSettings); + console.log('new displaySettings: ', this.actualSettings); + } + + private evaluateDisplayOption(conditions) { + if (conditions) { + let evaluator = false; + for (const condition of conditions) { + if (this.actualSettings[condition.option] === condition.value) { + evaluator = true; + } + } + return evaluator; } else {return true; } + } +} diff --git a/src/app/services/annotation.service.ts b/src/app/services/annotation.service.ts index 32b6d4e..80ee4e7 100644 --- a/src/app/services/annotation.service.ts +++ b/src/app/services/annotation.service.ts @@ -1,88 +1,87 @@ import {Annotation, Word} from '../models/models'; import {Parser, Writer, StreamParser } from 'n3'; import { QueryService } from '../services/query.service'; import {Injectable} from '@angular/core'; -import {ThemePalette} from '@angular/material'; import {Observable} from 'rxjs'; @Injectable() export class AnnotationService { annotationQuery; markupData; constructor( private queryService: QueryService ) {} /** * Gets the Markup definitions of a word by redefining a query and running it. * * @param word: the word for which the markup information should be queried. * @returns Observable of the resulting json response containing all markups. */ public getAnnotationMarkup(word: Word) { // get the pre made query this.queryService.getQueryfromFilename('annotationMarkup_SELECT.rq' ) .subscribe(query => { // parse it to sparqljs object. const parsedQuery = this.queryService.parser.parse(query); // reset the subject iri to the word's iri we like to query for // TODO: Change static to the passed word.iri parsedQuery.where[0].patterns[0].triples[0].subject.id = decodeURI('http://rdfh.ch/resources#word'); // generate the new query string const generatedQuery = this.queryService.sparqlGenerator.stringify(parsedQuery); console.log('generatedQuery: ', generatedQuery); // get and return the data from the endpoint this.markupData = this.queryService.getData(generatedQuery, 'SELECT'); }); console.log(typeof this.markupData, ' is: ', this.markupData); return this.markupData; } public getAnnotationMarkup2(word: Word) { // get the pre made query this.generateAnnotationQuery(word); return this.queryService.getData(this.annotationQuery, 'SELECT'); // get and return the data from the endpoint } public generateAnnotationQuery(word: Word) { this.queryService.getQueryfromFilename('annotationMarkup.rq' ) .subscribe(query => { // parse it to sparqljs object. const parsedQuery = this.queryService.parser.parse(query); // reset the subject iri to the word's iri we like to query for // TODO: Change static to the passed word.iri parsedQuery.where[0].patterns[0].triples[0].subject.id = decodeURI('http://rdfh.ch/resources#word'); // generate the new query string and return it this.annotationQuery = this.queryService.sparqlGenerator.stringify(parsedQuery); console.log('this.annotationQuery: ', this.annotationQuery); }); } } export class TextStyleService { public setStyles(annotation?: Annotation) { const style = { 'font-style': 'italic', // 'font-weight': 'bold' }; return style; } public parseStyles( rdfString: string ) { const parser = new Parser({ format: 'application/turtle' }); parser.parse( rdfString, (error, quad, prefixes) => { if (quad) { console.log('my quad' , quad); } else { console.log('prefixes: ', prefixes); } }); } } diff --git a/src/app/services/options.service.ts b/src/app/services/options.service.ts index 49f85be..03633ee 100644 --- a/src/app/services/options.service.ts +++ b/src/app/services/options.service.ts @@ -1,17 +1,18 @@ import {Injectable} from '@angular/core'; import { EventEmitter } from '@angular/core'; @Injectable() export class OptionService { fadeOutchange: EventEmitter = new EventEmitter(); markupAll: EventEmitter = new EventEmitter(); + // TODO: simplify and generalize for every opption and setting e.g. via: obj.property === obj["property"] public change_fadeout_option(fadeout: boolean) { this.fadeOutchange.emit(fadeout); } public change_markup_all(showMarkup: boolean) { this.markupAll.emit(showMarkup); } }