diff --git a/CHANGELOG.md b/CHANGELOG.md index bfd439cfc..e01349798 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated to Angular 13 ### Added +- Text annotator component visualization - Text/images connection in page change - Support for styleDefDecl as default style of renditional information - Apparatus entry inline visualization diff --git a/package.json b/package.json index 32670820e..df2357b1e 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "angular-gridster2": "^13.2.0", "bootstrap": "^5.1.3", "dexie": "^3.2.1", + "evt-text-annotator": "^1.1.1", "jquery": "^3.6.0", "ng-dynamic-component": "~10.1.0", "ng2-handy-syntax-highlighter": "^1.0.12", diff --git a/src/app/app.component.html b/src/app/app.component.html index f1722b8a7..c290169e1 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -8,4 +8,5 @@ - \ No newline at end of file + + diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 8a29002df..c7e648ea1 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -183,8 +183,18 @@ export interface EditionConfig { defaultImageZoomLevel: number; showSubstitutionMarker: boolean; multiPageEngineForCriticalEdition: boolean; + annotatorColors: AnnotatorColors; + annotationTextType: AnnotationTextType; } +export interface AnnotatorColors { + note: string; + highlights: string[]; +} + +export type AnnotationTextType = 'annotate' | 'highlight'; + + export type EditionImagesSources = 'manifest' | 'graphics'; export interface FileConfig { diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 91593257e..f12630762 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -131,6 +131,7 @@ import { SubstitutionComponent } from './components/substitution/substitution.co import { SuppliedComponent } from './components/supplied/supplied.component'; import { SurplusComponent } from './components/surplus/surplus.component'; import { TagsDeclComponent } from './components/tags-decl/tags-decl.component'; +import { TextAnnotatorComponent } from './components/annotator/text-annotator/text-annotator.component'; import { TextComponent } from './components/text/text.component'; import { TextPanelComponent } from './panels/text-panel/text-panel.component'; import { TextSourcesComponent } from './view-modes/text-sources/text-sources.component'; @@ -269,6 +270,11 @@ const DynamicComponents = [ SourcesComponent, SourcesPanelComponent, StartsWithPipe, + SuppliedComponent, + SurplusComponent, + TagsDeclComponent, + TextAnnotatorComponent, + TextComponent, StyledBiblioEntryComponent, SubstitutionComponent, TextPanelComponent, diff --git a/src/app/components/annotator/text-annotator/text-annotator.component.html b/src/app/components/annotator/text-annotator/text-annotator.component.html new file mode 100644 index 000000000..52d2504ec --- /dev/null +++ b/src/app/components/annotator/text-annotator/text-annotator.component.html @@ -0,0 +1,35 @@ +
+
+
+ {{'annotate' | translate}} +
+
+ {{'highlight' | translate}} +
+ +
+ +
+ + +
+
+
+
+ + +
+
+ {{'annotatorNote' | translate}} + +
+
+
+
+
+ +
+
diff --git a/src/app/components/annotator/text-annotator/text-annotator.component.scss b/src/app/components/annotator/text-annotator/text-annotator.component.scss new file mode 100644 index 000000000..3ddf5c5bb --- /dev/null +++ b/src/app/components/annotator/text-annotator/text-annotator.component.scss @@ -0,0 +1,127 @@ +@import "../../../../assets/scss/colors"; + + +$buttonColor: get-anno-color(buttonColors); +$noteColor: get-anno-color(noteColor); + + +.evt-adder-annotation { + font-family: Junicode, Times, serif; + display: block; + position: absolute; + z-index: 999; + height: auto; + border-radius: 5px; +} + +.evt-annotate { + border-right: 1px solid #ccc; +} + +.evt-annotator-option { + background: $buttonColor; + padding: 8px 5px; + cursor: pointer; +} + +.evt-annotator-option span { + margin: 0; +} + +.evt-adder-toolbar { + all: initial; + z-index: 1; + display: flex; + flex-direction: row; + margin-top: 15px; + box-shadow: 0px 0px 9px 0 rgb(0 0 0 / 33%); + border-radius: 5px; + border: 1px solid #ccc; +} + +.evt-adder-toolbar::after { + content: ""; + position: absolute; + width: 0; + height: 0; + margin-left: -0.5em; + bottom: 25px; + left: 15%; + box-sizing: border-box; + border: 5px solid #ccc; + border-color: transparent transparent #fff #fff; + transform-origin: 0 0; + transform: rotate(135deg); + box-shadow: -3px 3px 3px 0 rgb(0 0 0 / 14%); +} + +.evt-highlight-colors div { + width: 20px; + height: 20px; + float: left; +} + + +.evt-annotate-creator { + width: 280px; + height: auto; +} + +[contentEditable=true]:empty:not(:focus):before{ + content:attr(data-placeholder); + color:grey; + font-style:italic; +} + +.evt-annotate-creator { + margin-top: 15px; + background: $buttonColor; + overflow: hidden; + box-shadow: 0px 0px 9px 0 rgb(0 0 0 / 33%); +} + +.evt-annotate-creator::after { + content: ""; + position: absolute; + width: 0; + height: 0; + margin-left: -0.5em; + bottom: 92%; + left: 15%; + box-sizing: border-box; + border: 5px solid #ccc; + border-color: transparent transparent #E4EBF6 #E4EBF6; + transform-origin: 0 0; + transform: rotate(135deg); + box-shadow: -3px 3px 3px 0 rgb(0 0 0 / 14%); +} + +.evt-note-header { + background-color: $noteColor; + padding: 10px; +} + +.evt-note.evt-note-body { + padding: 20px; +} + +.note-title, +.note-body { + border: 1px solid #ccc; + padding: 5px; + margin-top: 10px; +} + +.note-title:focus, +.note-body:focus { + outline: none; + border: 1px solid #000; +} + +.note-body { + height: 100px; +} + +.evt-note-footer { + padding: 0 20px 20px 0; +} diff --git a/src/app/components/annotator/text-annotator/text-annotator.component.spec.ts b/src/app/components/annotator/text-annotator/text-annotator.component.spec.ts new file mode 100644 index 000000000..aef2da0f6 --- /dev/null +++ b/src/app/components/annotator/text-annotator/text-annotator.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TextAnnotatorComponent } from './text-annotator.component'; + +describe('TextAnnotatorComponent', () => { + let component: TextAnnotatorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ TextAnnotatorComponent ], + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TextAnnotatorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/annotator/text-annotator/text-annotator.component.ts b/src/app/components/annotator/text-annotator/text-annotator.component.ts new file mode 100644 index 000000000..cf6496337 --- /dev/null +++ b/src/app/components/annotator/text-annotator/text-annotator.component.ts @@ -0,0 +1,83 @@ +import { Component, OnDestroy } from '@angular/core'; +import { Subscription } from 'rxjs'; +import { AnnotationTextType, AnnotatorColors, AppConfig } from 'src/app/app.config'; +import { AnnotatorService } from 'src/app/services/annotator/annotator.service'; + +interface TextAnnotation { + options: { + position: AnnotatorPosition; + highlightColors: AnnotatorColors; + showAdder: Boolean; + updateMode: Boolean; + } + values: { + range: Range; + type: AnnotationTextType; + selectedText: string; + } +} +interface AnnotatorPosition { + x: number; + y: number; +} +@Component({ + selector: 'evt-text-annotator', + templateUrl: './text-annotator.component.html', + styleUrls: ['./text-annotator.component.scss'], +}) +export class TextAnnotatorComponent implements OnDestroy { + private subscriptions: Subscription[] = []; + public annotator: TextAnnotation = { + options: { + position: { x: 0, y: 0 }, + highlightColors: AppConfig.evtSettings.edition.annotatorColors, + showAdder: false, + updateMode: true, + }, + values: { + range: null, + type: null, + selectedText: '', + }, + } + + constructor( + private annotatorService: AnnotatorService, + ) { + const { values } = this.annotator; + + this.subscriptions.push(this.annotatorService.textSelection$ + .subscribe((selection) => { + values.selectedText = selection.toString(); + if (/\S/.test(values.selectedText)) { + this.openAdder(selection); + } else { + this.closeAdder(); + } + })); + } + + openAdder(selection: Selection) { + const { options, values } = this.annotator; + values.range = selection.getRangeAt(0); + options.showAdder = true + const rect = values.range.getBoundingClientRect(); + options.position = { y: rect.bottom, x: rect.left }; + } + + closeAdder() { + const { options, values } = this.annotator; + options.showAdder = false; + values.type = null; + } + + openCreation(choice: string) { + const { options, values } = this.annotator; + values.type = choice as AnnotationTextType; + options.showAdder = choice !== 'annotate'; + } + + ngOnDestroy() { + this.subscriptions.forEach((subscription) => subscription.unsubscribe()); + } +} diff --git a/src/app/ui-components/button/button.component.scss b/src/app/ui-components/button/button.component.scss index e6752100b..acd65e27b 100644 --- a/src/app/ui-components/button/button.component.scss +++ b/src/app/ui-components/button/button.component.scss @@ -76,4 +76,22 @@ width: 40px; } } + &.btn-annotator { + @include themify($themes) { + color: themed("toolsColor"); + background-color: themed("toolsBackgroundDarker"); + border-color: themed("toolsBackgroundDarker"); + width: 80px; + margin-left: 10px; + } + } + &.btn-annotator-light { + @include themify($themes) { + color: themed("toolsColor"); + background-color: themed("annotatorColor"); + border-color: themed("annotatorColor"); + width: 80px; + margin-left: 10px; + } + } } diff --git a/src/app/ui-components/icon/icon.component.scss b/src/app/ui-components/icon/icon.component.scss index a3fbc503b..ce021eba6 100644 --- a/src/app/ui-components/icon/icon.component.scss +++ b/src/app/ui-components/icon/icon.component.scss @@ -28,53 +28,41 @@ .evt-icon-files-empty:before { content: "\e93c"; -} - -.evt-icon-equalizer:before { + } + .evt-icon-equalizer:before { content: "\e93d"; -} - -.evt-icon-square-o:before { + } + .evt-icon-square-o:before { content: "\e93e"; -} - -.evt-icon-copy:before { + } + .evt-icon-copy:before { content: "\e93f"; -} - -.evt-icon-clone:before { + } + .evt-icon-clone:before { content: "\e940"; -} - -.evt-icon-quote-right:before { + } + .evt-icon-quote-right:before { content: "\e941"; -} - -.evt-icon-fork:before { + } + .evt-icon-fork:before { content: "\e942"; -} - -.evt-icon-quote-left:before { + } + .evt-icon-quote-left:before { content: "\e943"; -} - -.evt-icon-versions:before { + } + .evt-icon-versions:before { content: "\e944"; -} - -.evt-icon-srcTxt:before { + } + .evt-icon-srcTxt:before { content: "\e945"; -} - -.evt-icon-bookreader:before { + } + .evt-icon-bookreader:before { content: "\e904"; -} - -.evt-icon-collation:before { + } + .evt-icon-collation:before { content: "\e908"; -} - -.evt-icon-imgTxt:before { + } + .evt-icon-imgTxt:before { content: "\e91c"; } @@ -88,204 +76,166 @@ .evt-icon-txt:before { content: "\e92e"; -} - -.evt-icon-txtTxt:before { + } + .evt-icon-txtTxt:before { content: "\e92f"; -} - -.evt-icon-add:before { + } + .evt-icon-add:before { content: "\e900"; -} - -.evt-icon-book:before { + } + .evt-icon-book:before { content: "\e901"; -} - -.evt-icon-bookmark-alt:before { + } + .evt-icon-bookmark-alt:before { content: "\e902"; -} - -.evt-icon-bookmark:before { + } + .evt-icon-bookmark:before { content: "\e903"; -} - -.evt-icon-books:before { + } + .evt-icon-books:before { content: "\e905"; -} - -.evt-icon-close:before { + } + .evt-icon-close:before { content: "\e906"; -} - -.evt-icon-code:before { + } + .evt-icon-code:before { content: "\e907"; -} - -.evt-icon-color-legend:before { + } + .evt-icon-color-legend:before { content: "\e909"; -} - -.evt-icon-color-lens:before { + } + .evt-icon-color-lens:before { content: "\e90a"; -} - -.evt-icon-colors-fill:before { + } + .evt-icon-colors-fill:before { content: "\e90b"; -} - -.evt-icon-copyright:before { + } + .evt-icon-copyright:before { content: "\e90c"; -} - -.evt-icon-drop-down:before { + } + .evt-icon-drop-down:before { content: "\e90d"; -} - -.evt-icon-drop-up:before { + } + .evt-icon-drop-up:before { content: "\e90e"; -} - -.evt-icon-filter:before { + } + .evt-icon-filter:before { content: "\e90f"; -} - -.evt-icon-find-in-page:before { + } + .evt-icon-find-in-page:before { content: "\e910"; -} - -.evt-icon-font-size-minu-alt:before { + } + .evt-icon-font-size-minu-alt:before { content: "\e911"; -} - -.evt-icon-font-size-minus:before { + } + .evt-icon-font-size-minus:before { content: "\e912"; -} - -.evt-icon-font-size-plus-alt:before { + } + .evt-icon-font-size-plus-alt:before { content: "\e913"; -} - -.evt-icon-font-size-plus:before { + } + .evt-icon-font-size-plus:before { content: "\e914"; -} - -.evt-icon-font-size-reset:before { + } + .evt-icon-font-size-reset:before { content: "\e915"; -} - -.evt-icon-fragment:before { + } + .evt-icon-fragment:before { content: "\e916"; -} - -.evt-icon-heatmap-alt:before { + } + .evt-icon-heatmap-alt:before { content: "\e917"; -} - -.evt-icon-heatmap:before { + } + .evt-icon-heatmap:before { content: "\e918"; -} - -.evt-icon-help-alt:before { + } + .evt-icon-help-alt:before { content: "\e919"; -} - -.evt-icon-help:before { + } + .evt-icon-help:before { content: "\e91a"; -} - -.evt-icon-hotspot:before { + } + .evt-icon-hotspot:before { content: "\e91b"; -} - -.evt-icon-info-alt-r:before { + } + .evt-icon-info-alt-r:before { content: "\e91d"; -} - -.evt-icon-info-alt-sq:before { + } + .evt-icon-info-alt-sq:before { content: "\e91e"; -} - -.evt-icon-info-alt:before { + } + .evt-icon-info-alt:before { content: "\e91f"; -} - -.evt-icon-info:before { + } + .evt-icon-info:before { content: "\e920"; -} - -.evt-icon-link:before { + } + .evt-icon-link:before { content: "\e921"; -} - -.evt-icon-list-alt:before { + } + .evt-icon-list-alt:before { content: "\e922"; -} - -.evt-icon-list:before { + } + .evt-icon-list:before { content: "\e923"; -} - -.evt-icon-mail:before { + } + .evt-icon-mail:before { content: "\e924"; -} - -.evt-icon-menu:before { + } + .evt-icon-menu:before { content: "\e925"; -} - -.evt-icon-paragraph:before { + } + .evt-icon-paragraph:before { content: "\e929"; -} - -.evt-icon-search:before { + } + .evt-icon-search:before { content: "\e92d"; -} - -.evt-icon-zoom-fit:before { + } + .evt-icon-zoom-fit:before { content: "\e930"; -} - -.evt-icon-zoom-in:before { + } + .evt-icon-zoom-in:before { content: "\e931"; -} - -.evt-icon-zoom-one:before { + } + .evt-icon-zoom-one:before { content: "\e932"; -} - -.evt-icon-zoom-out:before { + } + .evt-icon-zoom-out:before { content: "\e933"; -} - -.evt-icon-zoom:before { + } + .evt-icon-zoom:before { content: "\e934"; -} - -.evt-icon-align:before { + } + .evt-icon-align:before { content: "\e935"; -} - -.evt-icon-bookmark-alt2:before { + } + .evt-icon-bookmark-alt2:before { content: "\e936"; -} - -.evt-icon-bookreader-alt:before { + } + .evt-icon-bookreader-alt:before { content: "\e937"; -} - -.evt-icon-font-size-minus-alt:before { + } + .evt-icon-font-size-minus-alt:before { content: "\e938"; -} - -.evt-icon-font-size-reset-alt:before { + } + .evt-icon-font-size-reset-alt:before { content: "\e939"; -} - -.evt-icon-font-size:before { + } + .evt-icon-font-size:before { content: "\e93a"; -} - -.evt-icon-thumbnails:before { + } + .evt-icon-thumbnails:before { content: "\e93b"; -} \ No newline at end of file + } + .evt-icon-note:before { + content: "\e926"; + } + .evt-icon-annotate:before { + content: "\e927"; + } + .evt-icon-dots:before { + content: "\e928"; + } + .evt-icon-highlight:before { + content: "\e92a"; + } \ No newline at end of file diff --git a/src/assets/config/edition_config.json b/src/assets/config/edition_config.json index 7be4141d9..b8ac165d0 100644 --- a/src/assets/config/edition_config.json +++ b/src/assets/config/edition_config.json @@ -170,5 +170,9 @@ } }, "defaultImageZoomLevel": 0.8, - "multiPageEngineForCriticalEdition": false + "multiPageEngineForCriticalEdition": false, + "annotatorColors": { + "note": "blue", + "highlights": ["#F2CB05", "#F29F05", "#4FC756"] + } } \ No newline at end of file diff --git a/src/assets/fonts/evt-icons-project.json b/src/assets/fonts/evt-icons-project.json old mode 100755 new mode 100644 diff --git a/src/assets/fonts/evt-icons.eot b/src/assets/fonts/evt-icons.eot old mode 100755 new mode 100644 index dfdd1dde6..8fd50418a Binary files a/src/assets/fonts/evt-icons.eot and b/src/assets/fonts/evt-icons.eot differ diff --git a/src/assets/fonts/evt-icons.svg b/src/assets/fonts/evt-icons.svg old mode 100755 new mode 100644 index 1ccbf0e1a..c2ef04622 --- a/src/assets/fonts/evt-icons.svg +++ b/src/assets/fonts/evt-icons.svg @@ -61,7 +61,11 @@ + + + + diff --git a/src/assets/fonts/evt-icons.ttf b/src/assets/fonts/evt-icons.ttf old mode 100755 new mode 100644 index 5be59060b..cbee95c85 Binary files a/src/assets/fonts/evt-icons.ttf and b/src/assets/fonts/evt-icons.ttf differ diff --git a/src/assets/fonts/evt-icons.woff b/src/assets/fonts/evt-icons.woff old mode 100755 new mode 100644 index e880bb07c..745c51716 Binary files a/src/assets/fonts/evt-icons.woff and b/src/assets/fonts/evt-icons.woff differ diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 9670994aa..371a98da2 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -202,5 +202,12 @@ "corrSeq": "Correction sequence", "criticalNote": "Critical note", "showsDeletions": "Show deletions", - "hidesDeletions": "Hide deletions" + "hidesDeletions": "Hide deletions", + "annotate": "Annotation", + "highlight": "Highlight", + "annotatorNote": "Note", + "noteTitle": "Title", + "noteBody": "Enter the note", + "noteCancel": "Cancel", + "noteSave": "Save" } \ No newline at end of file diff --git a/src/assets/i18n/it.json b/src/assets/i18n/it.json index 782d94914..9bff794c5 100644 --- a/src/assets/i18n/it.json +++ b/src/assets/i18n/it.json @@ -201,5 +201,12 @@ "corrSeq": "Sequenza correzioni", "criticalNote": "Nota critica", "showsDeletions": "Mostra cancellazioni", - "hidesDeletions": "Nascondi cancellazioni" + "hidesDeletions": "Nascondi cancellazioni", + "annotate": "Annotazione", + "highlight": "Evidenziazione", + "annotatorNote":"Nota", + "noteTitle": "Titlo", + "noteBody": "Iserisci la nota", + "noteCancel": "Cancella", + "noteSave": "Salva" } \ No newline at end of file diff --git a/src/assets/scss/_colors.scss b/src/assets/scss/_colors.scss index d273742ee..a7d8be439 100644 --- a/src/assets/scss/_colors.scss +++ b/src/assets/scss/_colors.scss @@ -26,7 +26,11 @@ $editionColors: ( versesBackground: #800000, versesColor: #ffffff, versesBorder: #000000, - highlightColor: #ffffcc + highlightColor: #ffffcc, + annotations: ( + buttonColors: #FFFFFF, + noteColor: #E4EBF6 + ) ); @function get-color($key) { @@ -35,4 +39,8 @@ $editionColors: ( @function get-ne-color($key) { @return get-color(namedEntities $key); +} + +@function get-anno-color($key){ + @return get-color(annotations $key); } \ No newline at end of file diff --git a/src/assets/scss/_themes.scss b/src/assets/scss/_themes.scss index da1fde7b3..739ae9ce5 100644 --- a/src/assets/scss/_themes.scss +++ b/src/assets/scss/_themes.scss @@ -1,5 +1,6 @@ $themes: ( neutral: ( + annotatorColor: #8dc6ff, baseColorDark: #000, baseColorLight: #fff, baseBorder: rgba(0, 0, 0, 0.125), @@ -16,6 +17,7 @@ $themes: ( appEntryBoxActiveTabBg: #e7e7e7, ), modern: ( + annotatorColor: #8dc6ff, baseColorDark: #263238, baseColorLight: #ECEFF1, baseBorder: rgba(0, 0, 0, 0.125), @@ -31,7 +33,8 @@ $themes: ( appEntryBoxBackground: #f1f4f5, appEntryBoxActiveTabBg: #e2e4e4b0, ), - classic: ( + classic: ( + annotatorColor: #8dc6ff, baseColorDark: rgb(54, 45, 40), baseColorLight: rgb(245, 234, 212), baseBorder: rgba(0, 0, 0, 0.125),