Browse Source

Manage components to allow image-viewer with local files

Federica 2 years ago
parent
commit
bc091ee5a8
100 changed files with 1863 additions and 22781 deletions
  1. 23 0
      CHANGELOG.md
  2. 1 22663
      package-lock.json
  3. 1 0
      package.json
  4. 8 2
      src/app/app.component.html
  5. 54 5
      src/app/app.component.scss
  6. 15 1
      src/app/app.component.ts
  7. 15 1
      src/app/app.config.ts
  8. 44 1
      src/app/app.module.ts
  9. 0 4
      src/app/components/additional/additional.component.scss
  10. 104 0
      src/app/components/apparatus-entry/apparatus-entry-detail/apparatus-entry-detail.component.html
  11. 96 0
      src/app/components/apparatus-entry/apparatus-entry-detail/apparatus-entry-detail.component.scss
  12. 25 0
      src/app/components/apparatus-entry/apparatus-entry-detail/apparatus-entry-detail.component.spec.ts
  13. 70 0
      src/app/components/apparatus-entry/apparatus-entry-detail/apparatus-entry-detail.component.ts
  14. 25 0
      src/app/components/apparatus-entry/apparatus-entry-readings/apparatus-entry-readings.component.html
  15. 5 0
      src/app/components/apparatus-entry/apparatus-entry-readings/apparatus-entry-readings.component.scss
  16. 24 0
      src/app/components/apparatus-entry/apparatus-entry-readings/apparatus-entry-readings.component.spec.ts
  17. 41 0
      src/app/components/apparatus-entry/apparatus-entry-readings/apparatus-entry-readings.component.ts
  18. 8 3
      src/app/components/apparatus-entry/apparatus-entry.component.html
  19. 27 0
      src/app/components/apparatus-entry/apparatus-entry.component.scss
  20. 25 6
      src/app/components/apparatus-entry/apparatus-entry.component.ts
  21. 18 0
      src/app/components/edition-stmt/edition-stmt.component.html
  22. 5 0
      src/app/components/edition-stmt/edition-stmt.component.scss
  23. 25 0
      src/app/components/edition-stmt/edition-stmt.component.spec.ts
  24. 14 0
      src/app/components/edition-stmt/edition-stmt.component.ts
  25. 24 0
      src/app/components/editorial-decl/editorial-decl.component.html
  26. 7 0
      src/app/components/editorial-decl/editorial-decl.component.scss
  27. 24 0
      src/app/components/editorial-decl/editorial-decl.component.spec.ts
  28. 13 0
      src/app/components/editorial-decl/editorial-decl.component.ts
  29. 28 0
      src/app/components/encoding-desc/encoding-desc.component.html
  30. 5 0
      src/app/components/encoding-desc/encoding-desc.component.scss
  31. 24 0
      src/app/components/encoding-desc/encoding-desc.component.spec.ts
  32. 13 0
      src/app/components/encoding-desc/encoding-desc.component.ts
  33. 5 0
      src/app/components/extent/extent.component.html
  34. 0 0
      src/app/components/extent/extent.component.scss
  35. 25 0
      src/app/components/extent/extent.component.spec.ts
  36. 14 0
      src/app/components/extent/extent.component.ts
  37. 20 0
      src/app/components/file-desc/file-desc.component.html
  38. 19 0
      src/app/components/file-desc/file-desc.component.scss
  39. 25 0
      src/app/components/file-desc/file-desc.component.spec.ts
  40. 13 0
      src/app/components/file-desc/file-desc.component.ts
  41. 0 4
      src/app/components/history/history.component.scss
  42. 4 4
      src/app/components/manuscript-thumbnails-viewer/manuscript-thumbnails-viewer.component.html
  43. 31 22
      src/app/components/manuscript-thumbnails-viewer/manuscript-thumbnails-viewer.component.scss
  44. 30 7
      src/app/components/manuscript-thumbnails-viewer/manuscript-thumbnails-viewer.component.ts
  45. 11 0
      src/app/components/ms-desc-selector/ms-desc-selector.component.html
  46. 0 0
      src/app/components/ms-desc-selector/ms-desc-selector.component.scss
  47. 25 0
      src/app/components/ms-desc-selector/ms-desc-selector.component.spec.ts
  48. 39 0
      src/app/components/ms-desc-selector/ms-desc-selector.component.ts
  49. 0 4
      src/app/components/ms-desc/ms-desc.component.scss
  50. 0 4
      src/app/components/ms-frag/ms-frag.component.scss
  51. 2 2
      src/app/components/ms-item/ms-item.component.html
  52. 15 0
      src/app/components/namespace/namespace.component.html
  53. 0 0
      src/app/components/namespace/namespace.component.scss
  54. 24 0
      src/app/components/namespace/namespace.component.spec.ts
  55. 13 0
      src/app/components/namespace/namespace.component.ts
  56. 11 0
      src/app/components/notes-stmt/notes-stmt.component.html
  57. 5 0
      src/app/components/notes-stmt/notes-stmt.component.scss
  58. 25 0
      src/app/components/notes-stmt/notes-stmt.component.spec.ts
  59. 14 0
      src/app/components/notes-stmt/notes-stmt.component.ts
  60. 17 35
      src/app/components/osd/osd.component.ts
  61. 1 1
      src/app/components/page/page.component.html
  62. 0 4
      src/app/components/phys-desc/phys-desc.component.scss
  63. 3 0
      src/app/components/project-desc/project-desc.component.html
  64. 0 0
      src/app/components/project-desc/project-desc.component.scss
  65. 24 0
      src/app/components/project-desc/project-desc.component.spec.ts
  66. 13 0
      src/app/components/project-desc/project-desc.component.ts
  67. 25 0
      src/app/components/project-info/project-info.component.html
  68. 17 0
      src/app/components/project-info/project-info.component.scss
  69. 25 0
      src/app/components/project-info/project-info.component.spec.ts
  70. 26 0
      src/app/components/project-info/project-info.component.ts
  71. 24 0
      src/app/components/publication-stmt/publication-stmt.component.html
  72. 5 0
      src/app/components/publication-stmt/publication-stmt.component.scss
  73. 25 0
      src/app/components/publication-stmt/publication-stmt.component.spec.ts
  74. 14 0
      src/app/components/publication-stmt/publication-stmt.component.ts
  75. 28 0
      src/app/components/rendition/rendition.component.html
  76. 22 0
      src/app/components/rendition/rendition.component.scss
  77. 24 0
      src/app/components/rendition/rendition.component.spec.ts
  78. 33 0
      src/app/components/rendition/rendition.component.ts
  79. 14 0
      src/app/components/resp-stmt/resp-stmt.component.html
  80. 6 0
      src/app/components/resp-stmt/resp-stmt.component.scss
  81. 25 0
      src/app/components/resp-stmt/resp-stmt.component.spec.ts
  82. 19 0
      src/app/components/resp-stmt/resp-stmt.component.ts
  83. 3 0
      src/app/components/sampling-decl/sampling-decl.component.html
  84. 0 0
      src/app/components/sampling-decl/sampling-decl.component.scss
  85. 24 0
      src/app/components/sampling-decl/sampling-decl.component.spec.ts
  86. 13 0
      src/app/components/sampling-decl/sampling-decl.component.ts
  87. 24 0
      src/app/components/series-stmt/series-stmt.component.html
  88. 13 0
      src/app/components/series-stmt/series-stmt.component.scss
  89. 25 0
      src/app/components/series-stmt/series-stmt.component.spec.ts
  90. 13 0
      src/app/components/series-stmt/series-stmt.component.ts
  91. 12 0
      src/app/components/tags-decl/tags-decl.component.html
  92. 0 0
      src/app/components/tags-decl/tags-decl.component.scss
  93. 24 0
      src/app/components/tags-decl/tags-decl.component.spec.ts
  94. 13 0
      src/app/components/tags-decl/tags-decl.component.ts
  95. 23 0
      src/app/components/title-stmt/title-stmt.component.html
  96. 21 0
      src/app/components/title-stmt/title-stmt.component.scss
  97. 25 0
      src/app/components/title-stmt/title-stmt.component.spec.ts
  98. 14 0
      src/app/components/title-stmt/title-stmt.component.ts
  99. 10 2
      src/app/main-menu/main-menu.component.ts
  100. 35 6
      src/app/models/evt-models.ts

+ 23 - 0
CHANGELOG.md

@@ -10,6 +10,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Updated to Angular 9
 
 ### Added
+- Link between text and images
+- Apparatus entry inline visualization
+- Support for viewer information extracted from xml
+- Tags declaration visualization
+- Namespace declaration visualization
+- Rendition declaration visualization
+- Editorial declaration visualization
+- Sampling declaration visualization
+- Project description visualization
+- Encoding description visualization
+- Critical edition navigation
+- Project Info modal
+- Notes statement visualization
+- Series statement visualization
+- Extent visualization
+- Edition statement visualization
+- Publication statement visualization
+- Resp statement visualization
+- Title statement visualization
+- Header section visualization
+- File description visualization
+- Navigation toolbar
+- Manuscript description header button
 - Manuscript description visualization
 - Manuscript part visualization
 - Manuscript fragment visualization

File diff suppressed because it is too large
+ 1 - 22663
package-lock.json


+ 1 - 0
package.json

@@ -15,6 +15,7 @@
   },
   "private": true,
   "dependencies": {
+    "@angular-slider/ngx-slider": "^2.0.3",
     "@angular/animations": "^11.0.4",
     "@angular/cdk": "^9.1.0",
     "@angular/cdk-experimental": "^9.1.0",

+ 8 - 2
src/app/app.component.html

@@ -1,5 +1,11 @@
 <evt-main-header></evt-main-header>
-<div class="temp-panel">
+<div class="temp-panel" [ngClass]="{'has-navbar': navbarOpened$ | async}">
   <router-outlet></router-outlet>
 </div>
-<ngx-spinner #mainSpinner bdColor="rgba(51,51,51,0.8)"></ngx-spinner>
+<ng-container *ngIf="hasNavBar">
+  <span class="navbar-toggler" [ngClass]="{opened: navbarOpened$ | async}" (click)="toggleToolbar()" [title]="'toggleToolbar' | translate">
+    <evt-icon [iconInfo]="navbarTogglerIcon$ | async"></evt-icon>
+  </span>
+  <evt-nav-bar [ngClass]="{opened: navbarOpened$ | async}"></evt-nav-bar>
+</ng-container>
+<ngx-spinner #mainSpinner bdColor="rgba(51,51,51,0.8)"></ngx-spinner>

+ 54 - 5
src/app/app.component.scss

@@ -1,9 +1,58 @@
-@import '../assets/scss/themes';
-@import '../assets/scss/mixins';
-@import '../assets/scss/variables';
+@import "../assets/scss/themes";
+@import "../assets/scss/mixins";
+@import "../assets/scss/variables";
 
 // TEMP
 .temp-panel {
     height: 100%;
-    @include calc('height', '100% - #{$base-header-height}')
-}
+    @include calc("height", "100% - #{$base-header-height}");
+}
+
+.has-navbar {
+    @include calc("height", "100% - #{$base-header-height} - #{$base-navbar-height}");
+}
+
+.navbar-toggler {
+    position: absolute;
+    bottom: 0;
+    height: 30px;
+    width: 30px;
+    right: 2px;
+    z-index: 9;
+    opacity: 0.5;
+
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 0.25rem 0.25rem 0 0;
+
+    transition: bottom 0.6s;
+
+    &:hover {
+        opacity: 1;
+        cursor: pointer;
+    }
+
+    @include themify($themes) {
+        color: themed("toolsBackground");
+        background-color: rgba(themed("toolsColor"), 0.7);
+        border-color: themed("toolsBackground");
+    }
+
+    &.opened {
+        bottom: $base-navbar-height;
+        transition: bottom 0.5s;
+    }
+}
+
+::ng-deep evt-nav-bar {
+    .nav-bar {
+        position: fixed;
+        bottom: -$base-navbar-height;
+        transition: bottom 1s;
+    }
+    &.opened .nav-bar {
+        bottom: 0;
+        transition: bottom 0.5s;
+    }
+}

+ 15 - 1
src/app/app.component.ts

@@ -1,9 +1,12 @@
 import { Component, ElementRef, HostBinding, HostListener, OnDestroy, ViewChild } from '@angular/core';
 import { NavigationCancel, NavigationEnd, NavigationError, NavigationStart, Router } from '@angular/router';
 import { NgxSpinnerService } from 'ngx-spinner';
-import { Subscription } from 'rxjs';
+import { BehaviorSubject, Observable, Subscription } from 'rxjs';
+import { map } from 'rxjs/operators';
+import { AppConfig } from './app.config';
 import { ThemesService } from './services/themes.service';
 import { ShortcutsService } from './shortcuts/shortcuts.service';
+import { EvtIconInfo } from './ui-components/icon/icon.component';
 
 @Component({
   selector: 'evt-root',
@@ -13,6 +16,12 @@ import { ShortcutsService } from './shortcuts/shortcuts.service';
 export class AppComponent implements OnDestroy {
   @ViewChild('mainSpinner') mainSpinner: ElementRef;
   private subscriptions: Subscription[] = [];
+  public hasNavBar = AppConfig.evtSettings.ui.enableNavBar;
+  public navbarOpened$ = new BehaviorSubject(this.hasNavBar && AppConfig.evtSettings.ui.initNavBarOpened);
+
+  public navbarTogglerIcon$: Observable<EvtIconInfo> = this.navbarOpened$.pipe(
+    map((opened: boolean) => opened ? { icon: 'caret-down', iconSet: 'fas' } : { icon: 'caret-up', iconSet: 'fas' }),
+  );
 
   constructor(
     private router: Router,
@@ -38,6 +47,11 @@ export class AppComponent implements OnDestroy {
 
   @HostBinding('attr.data-theme') get dataTheme() { return this.themes.getCurrentTheme().value; }
 
+  toggleToolbar() {
+    this.navbarOpened$.next(!this.navbarOpened$.getValue());
+    window.dispatchEvent(new Event('resize')); // Needed to tell Gridster to resize
+  }
+
   ngOnDestroy() {
     this.subscriptions.forEach(subscription => subscription.unsubscribe());
   }

+ 15 - 1
src/app/app.config.ts

@@ -67,6 +67,10 @@ export interface UiConfig {
         label: string;
         enabled: boolean;
     }>;
+    enableNavBar: boolean;
+    initNavBarOpened: boolean;
+    thumbnailsButton: boolean;
+    viscollButton: boolean;
 }
 
 export interface EditionConfig {
@@ -92,10 +96,20 @@ export interface EditionConfig {
     verseNumberPrinter: number;
 }
 
+export type EditionImagesSources = 'manifest' | 'graphics';
+
 export interface FileConfig {
     editionUrls: string[];
-    manifestURL: string;
+    editionImagesSource: {
+        [T in EditionImagesSources]: EditionImagesConfig;
+    };
     logoUrl?: string;
+    imagesFolderUrl?: string;
+}
+
+export interface EditionImagesConfig {
+    value: string;
+    enabled: boolean;
 }
 
 export interface NamedEntitiesListsConfig {

+ 44 - 1
src/app/app.module.ts

@@ -1,3 +1,4 @@
+import { NgxSliderModule } from '@angular-slider/ngx-slider';
 import { ScrollingModule as ExperimentalScrollingModule } from '@angular/cdk-experimental/scrolling';
 import { ScrollingModule } from '@angular/cdk/scrolling';
 import { HttpClientModule } from '@angular/common/http';
@@ -23,6 +24,8 @@ import { AppConfig } from './app.config';
 
 import { AdditionComponent } from './components/addition/addition.component';
 import { AdditionalComponent } from './components/additional/additional.component';
+import { ApparatusEntryDetailComponent } from './components/apparatus-entry/apparatus-entry-detail/apparatus-entry-detail.component';
+import { ApparatusEntryReadingsComponent } from './components/apparatus-entry/apparatus-entry-readings/apparatus-entry-readings.component';
 import { ApparatusEntryComponent } from './components/apparatus-entry/apparatus-entry.component';
 import { CharComponent } from './components/char/char.component';
 import { ChoiceComponent } from './components/choice/choice.component';
@@ -30,7 +33,12 @@ import { ContentViewerComponent } from './components/content-viewer/content-view
 import { DamageComponent } from './components/damage/damage.component';
 import { DeletionComponent } from './components/deletion/deletion.component';
 import { EditionLevelSelectorComponent } from './components/edition-level-selector/edition-level-selector.component';
+import { EditionStmtComponent } from './components/edition-stmt/edition-stmt.component';
+import { EditorialDeclComponent } from './components/editorial-decl/editorial-decl.component';
+import { EncodingDescComponent } from './components/encoding-desc/encoding-desc.component';
 import { EntitiesSelectComponent } from './components/entities-select/entities-select.component';
+import { ExtentComponent } from './components/extent/extent.component';
+import { FileDescComponent } from './components/file-desc/file-desc.component';
 import { GComponent } from './components/g/g.component';
 import { GapComponent } from './components/gap/gap.component';
 import { GenericElementComponent } from './components/generic-element/generic-element.component';
@@ -40,6 +48,7 @@ import { IdentifierComponent } from './components/identifier/identifier.componen
 import { LbComponent } from './components/lb/lb.component';
 import { ManuscriptThumbnailsViewerComponent } from './components/manuscript-thumbnails-viewer/manuscript-thumbnails-viewer.component';
 import { MsContentsComponent } from './components/ms-contents/ms-contents.component';
+import { MsDescSelectorComponent } from './components/ms-desc-selector/ms-desc-selector.component';
 import { MsDescComponent } from './components/ms-desc/ms-desc.component';
 import { MsFragComponent } from './components/ms-frag/ms-frag.component';
 import { MsIdentifierComponent } from './components/ms-identifier/ms-identifier.component';
@@ -51,18 +60,29 @@ import { NamedEntityRelationComponent } from './components/named-entity-relation
 import { NamedEntityDetailComponent } from './components/named-entity/named-entity-detail/named-entity-detail.component';
 import { NamedEntityOccurrenceComponent } from './components/named-entity/named-entity-occurrence/named-entity-occurrence.component';
 import { NamedEntityComponent } from './components/named-entity/named-entity.component';
+import { NamespaceComponent } from './components/namespace/namespace.component';
 import { NoteComponent } from './components/note/note.component';
+import { NotesStmtComponent } from './components/notes-stmt/notes-stmt.component';
 import { OriginalEncodingViewerComponent } from './components/original-encoding-viewer/original-encoding-viewer.component';
 import { OsdComponent } from './components/osd/osd.component';
 import { PageSelectorComponent } from './components/page-selector/page-selector.component';
 import { PageComponent } from './components/page/page.component';
 import { ParagraphComponent } from './components/paragraph/paragraph.component';
 import { PhysDescComponent } from './components/phys-desc/phys-desc.component';
+import { ProjectDescComponent } from './components/project-desc/project-desc.component';
+import { ProjectInfoComponent } from './components/project-info/project-info.component';
+import { PublicationStmtComponent } from './components/publication-stmt/publication-stmt.component';
 import { ReadingComponent } from './components/reading/reading.component';
+import { RenditionComponent } from './components/rendition/rendition.component';
+import { RespStmtComponent } from './components/resp-stmt/resp-stmt.component';
+import { SamplingDeclComponent } from './components/sampling-decl/sampling-decl.component';
+import { SeriesStmtComponent } from './components/series-stmt/series-stmt.component';
 import { SicComponent } from './components/sic/sic.component';
 import { SuppliedComponent } from './components/supplied/supplied.component';
 import { SurplusComponent } from './components/surplus/surplus.component';
+import { TagsDeclComponent } from './components/tags-decl/tags-decl.component';
 import { TextComponent } from './components/text/text.component';
+import { TitleStmtComponent } from './components/title-stmt/title-stmt.component';
 import { VerseComponent } from './components/verse/verse.component';
 import { VersesGroupComponent } from './components/verses-group/verses-group.component';
 import { WordComponent } from './components/word/word.component';
@@ -73,6 +93,7 @@ import { HtmlAttributesDirective } from './directives/html-attributes.directive'
 import { EvtInfoComponent } from './evt-info/evt-info.component';
 import { MainHeaderComponent } from './main-header/main-header.component';
 import { MainMenuComponent } from './main-menu/main-menu.component';
+import { NavBarComponent } from './nav-bar/nav-bar.component';
 import { ImagePanelComponent } from './panels/image-panel/image-panel.component';
 import { PinboardPanelComponent } from './panels/pinboard-panel/pinboard-panel.component';
 import { SourcesPanelComponent } from './panels/sources-panel/sources-panel.component';
@@ -112,6 +133,8 @@ export function initializeApp(appConfig: AppConfig) {
     AdditionalComponent,
     AnnotatorDirective,
     ApparatusEntryComponent,
+    ApparatusEntryDetailComponent,
+    ApparatusEntryReadingsComponent,
     AppComponent,
     CharComponent,
     ChoiceComponent,
@@ -120,9 +143,14 @@ export function initializeApp(appConfig: AppConfig) {
     DamageComponent,
     DeletionComponent,
     EditionLevelSelectorComponent,
+    EditionStmtComponent,
     EditorialConventionLayoutDirective,
+    EditorialDeclComponent,
+    EncodingDescComponent,
     EntitiesSelectComponent,
     EvtInfoComponent,
+    ExtentComponent,
+    FileDescComponent,
     FilterPipe,
     GapComponent,
     GComponent,
@@ -140,9 +168,10 @@ export function initializeApp(appConfig: AppConfig) {
     MainMenuComponent,
     ManuscriptThumbnailsViewerComponent,
     MsContentsComponent,
+    MsDescComponent,
     MsDescSectionComponent,
+    MsDescSelectorComponent,
     MsFragComponent,
-    MsDescComponent,
     MsFragComponent,
     MsIdentifierComponent,
     MsItemComponent,
@@ -153,7 +182,10 @@ export function initializeApp(appConfig: AppConfig) {
     NamedEntityOccurrenceComponent,
     NamedEntityRefComponent,
     NamedEntityRelationComponent,
+    NamespaceComponent,
+    NavBarComponent,
     NoteComponent,
+    NotesStmtComponent,
     OriginalEncodingViewerComponent,
     OsdComponent,
     PageComponent,
@@ -163,19 +195,28 @@ export function initializeApp(appConfig: AppConfig) {
     PinboardComponent,
     PinboardPanelComponent,
     PinnerComponent,
+    ProjectDescComponent,
+    ProjectInfoComponent,
+    PublicationStmtComponent,
     ReadingComponent,
     ReadingTextComponent,
+    RenditionComponent,
+    RespStmtComponent,
+    SamplingDeclComponent,
+    SeriesStmtComponent,
     ShortcutsComponent,
     SicComponent,
     SourcesPanelComponent,
     StartsWithPipe,
     SuppliedComponent,
     SurplusComponent,
+    TagsDeclComponent,
     TextComponent,
     TextPanelComponent,
     TextSourcesComponent,
     TextTextComponent,
     TextVersionsComponent,
+    TitleStmtComponent,
     VerseComponent,
     VersesGroupComponent,
     VersionPanelComponent,
@@ -197,6 +238,7 @@ export function initializeApp(appConfig: AppConfig) {
     Ng2HandySyntaxHighlighterModule,
     NgbModule,
     NgbPopoverModule,
+    NgxSliderModule,
     NgxSpinnerModule,
     RouterModule.forRoot(routes, { useHash: true, relativeLinkResolution: 'legacy' }),
     ScrollingModule,
@@ -228,6 +270,7 @@ export function initializeApp(appConfig: AppConfig) {
     NamedEntityDetailComponent,
     NamedEntityRefComponent,
     NamedEntityRelationComponent,
+    ProjectInfoComponent,
     ShortcutsComponent,
   ],
 })

+ 0 - 4
src/app/components/additional/additional.component.scss

@@ -21,10 +21,6 @@
     margin-bottom: 0.7rem;
 }
 
-::ng-deep p {
-    margin-bottom: 0;
-}
-
 @media (min-width: 1400px) { 
     .flex-item-label {
         flex-basis: 20%;

+ 104 - 0
src/app/components/apparatus-entry/apparatus-entry-detail/apparatus-entry-detail.component.html

@@ -0,0 +1,104 @@
+<div class="card app-detail-container">
+    <div class="card-body app-detail-content">
+        <span>
+            <evt-apparatus-entry-readings class="app-detail-readings" [data]="data" [template]="nestedAppsReadingsTemplate" [rdgHasCounter]="true"></evt-apparatus-entry-readings>
+            <div *ngIf="data.nestedAppsIDs.length > 0" class="app-detail-nested-readings-container">
+                <span *ngFor="let nesApp of nestedApps; let i = index" class="app-detail-nested-readings">
+                    <sup>{{ i + 1 }} </sup>
+                    <evt-apparatus-entry-readings [data]="nesApp" [template]="nestedAppsReadingsTemplate" [rdgHasCounter]="false"></evt-apparatus-entry-readings>
+                </span>
+            </div>
+        </span>
+        <evt-pinner [item]="data" renderer="ApparatusEntryReadings" pinType="ApparatusEntry"></evt-pinner>
+    </div>
+    <div class="card-footer app-detail-tabs">
+        <ul ngbNav #appEntryTab="ngbNav" class="nav-pills">
+            <li ngbNavItem="criticalNotes" *ngIf="data.notes.length > 0">
+                <a class="app-detail-btn" ngbNavLink>{{'criticalNotes' | translate}}</a>
+                <ng-template ngbNavContent>
+                    <evt-note *ngFor="let note of data.notes" [data]="note"></evt-note>
+                </ng-template>
+            </li>
+            <li ngbNavItem="notSignificantRdg" *ngIf="notSignificantRdg.length > 0">
+                <a class="app-detail-btn" ngbNavLink>{{'ortographicVariants' | translate}}</a>
+                <ng-template ngbNavContent>
+                    <span class="d-block" *ngFor="let el of notSignificantRdg">
+                        <evt-reading [data]="el"></evt-reading>
+                        <span *ngFor="let witID of el.witIDs" class="font-italic"> {{ witID }} </span>
+                    </span>
+                </ng-template>
+            </li>
+            <li ngbNavItem="info">
+                <a class="app-detail-btn" ngbNavLink>{{'info' | translate}}</a>
+                <ng-template ngbNavContent>
+                    <div class="info-lemma-wrapper" *ngIf="data.lemma">
+                        <span>{{'metadataForLemma' | translate}} </span>
+                        <evt-reading *ngIf="data.nestedAppsIDs.length === 0" class="info-rdg" [data]="data.lemma"></evt-reading>
+                        <span *ngIf="data.nestedAppsIDs.length > 0" class="info-rdg">
+                            <ng-container *ngTemplateOutlet="nestedAppsReadingsTemplate; context: { rdgHasCounter: rdgHasCounter }"></ng-container>
+                        </span>
+                        <span class="d-block ml-2" *ngFor="let metadata of rdgMetadata | keyvalue">
+                            <span class="info-label" *ngIf="metadata.key !== 'id'">{{ metadata.key }}:</span> {{ metadata.value }}
+                        </span>
+                        <span class="d-block ml-2" *ngIf="(rdgMetadata | keyvalue).length === 0">
+                            <span class="font-italic">{{'noDataAvailable' | translate}}</span>
+                        </span>
+                    </div>
+                    <div>
+                        <span class="more-info-label">{{'moreInfoAboutApp' | translate}}</span>
+                        <ng-container *ngFor="let rdg of readings">
+                            <div *ngIf="(rdg.attributes | keyvalue).length !== 0" class="mb-2">
+                                <span>{{'metadataFor' | translate}} </span>
+                                <ng-container *ngIf="rdg.content.length !== 0">
+                                    <evt-reading *ngIf="data.nestedAppsIDs.length === 0" class="info-rdg" [data]="rdg"></evt-reading>
+                                    <ng-container *ngIf="data.nestedAppsIDs.length > 0">
+                                        <ng-container *ngFor="let c of rdg.content">
+                                            <evt-content-viewer class="info-rdg" *ngIf="!isAppEntry(c)" [content]="c"></evt-content-viewer>
+                                            <evt-reading class="info-rdg" *ngIf="isAppEntry(c)" [data]="c.lemma"></evt-reading>
+                                        </ng-container>
+                                    </ng-container>
+                                </ng-container>
+                                <span *ngIf="rdg.content.length === 0" class="font-italic info-rdg">{{'omit' | translate}}</span>
+                                <span *ngIf="rdg.attributes.wit" class="d-block ml-2">
+                                    <span class="info-label">{{'wit' | translate}}</span> {{rdg.attributes.wit}}
+                                </span>
+                                <ng-container *ngFor="let metadata of rdgMetadata | keyvalue">
+                                    <span class="d-block ml-2" *ngIf="metadata.key !== 'wit'">
+                                        <span class="info-label">{{ metadata.key }}:</span> {{ metadata.value }}
+                                    </span>
+                                </ng-container>
+                            </div>
+                        </ng-container>
+                    </div>
+                </ng-template>
+            </li>
+            <li ngbNavItem="xml">
+                <a class="app-detail-btn" ngbNavLink>{{'xml' | translate}}</a>
+                <ng-template ngbNavContent>
+                    <pre>{{ data.originalEncoding | xmlBeautify }}</pre>
+                </ng-template>
+            </li>
+        </ul>
+        <div [ngbNavOutlet]="appEntryTab" class="tab-content"></div>
+    </div>
+</div>
+
+<ng-template #nestedAppsReadingsTemplate let-rdgHasCounter="rdgHasCounter">
+    <ng-container *ngFor="let c of data.lemma.content">
+        <evt-content-viewer *ngIf="!isAppEntry(c)" class="{{ data.class }}" [content]="c"></evt-content-viewer>
+
+        <ng-container *ngIf="isAppEntry(c)">
+            <!-- Handle reading of nested app with no further nesting -->
+            <evt-reading [data]="c.lemma"></evt-reading>
+            <sup *ngIf="rdgHasCounter">{{ getNestedAppPos(c.id) + 1 }} </sup>
+
+            <!-- Handle reading of nested app with further nesting -->
+            <ng-container *ngIf="c.nestedAppsIDs.length > 0">
+                <ng-container *ngFor="let nesID of c.nestedAppsIDs; let i = index">
+                    <evt-reading [data]="getNestedAppLemma(nesID)"></evt-reading>
+                    <sup *ngIf="rdgHasCounter">{{ getNestedAppPos(nesID) + 1 }}</sup>
+                </ng-container>
+            </ng-container>
+        </ng-container>
+    </ng-container>
+</ng-template>

+ 96 - 0
src/app/components/apparatus-entry/apparatus-entry-detail/apparatus-entry-detail.component.scss

@@ -0,0 +1,96 @@
+@import "../../../../assets/scss/themes";
+
+.app-detail-container {
+    top: -0.063rem;
+    z-index: 0;
+    border-radius: 0;
+    cursor: auto;
+    @include themify($themes) {
+        background-color: themed("appEntryBoxBackground");
+    }
+}
+
+.app-detail-content,
+.app-detail-tabs {
+    background-color: transparent;
+    font-size: 1.063rem;
+}
+
+.app-detail-content {
+    display: flex;
+    justify-content: space-between;
+    padding: 0.313rem;
+}
+
+.app-detail-readings {
+    display: block;
+}
+
+.app-detail-nested-readings {
+    display: block;
+    margin-left: 0.938rem;
+    font-size: 1rem;
+}
+
+.app-detail-tabs {
+    font-size: 1rem;
+    margin: 0 0.313rem 0 0.313rem;
+    padding: 0.313rem 0 0 0;
+    .nav-link {
+        background-color: transparent;
+        color: #000000;
+        line-height: 1;
+        padding: 0.25rem 0.375rem;
+        cursor: pointer;
+        border-radius: 0;
+        &.active {
+            color: #000000;
+        }
+    }
+    .nav-pills,
+    .tab-content {
+        margin-right: -0.313rem;
+        margin-left: -0.313rem;
+    }
+    .nav-link.active,
+    .tab-content {
+        @include themify($themes) {
+            background-color: themed("appEntryBoxActiveTabBg");
+        }
+    }
+    .tab-content {
+        padding: 0.625rem 0.813rem;
+        max-height: 12.5rem;
+        overflow: auto;
+        .info-lemma-wrapper {
+            @include themify($themes) {
+                border-bottom: 1px solid themed("baseBorder");
+            }
+            padding-bottom: 0.438rem;
+            margin-bottom: 0.625rem;
+        }
+        .info-rdg {
+            font-style: italic;
+            font-weight: 600;
+            font-size: 1.063rem;
+        }
+        .info-label {
+            font-size: 0.813rem;
+            text-transform: uppercase;
+            font-weight: 600;
+        }
+        .more-info-label {
+            display: block;
+            font-size: 0.813rem;
+            font-weight: 600;
+            text-transform: uppercase;
+            margin-bottom: 0.25rem;
+        }
+        pre {
+            white-space: pre-wrap;
+            font-size: 75%;
+            margin-bottom: 0;
+            margin-top: -1rem;
+        }
+    }
+}

+ 25 - 0
src/app/components/apparatus-entry/apparatus-entry-detail/apparatus-entry-detail.component.spec.ts

@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ApparatusEntryDetailComponent } from './apparatus-entry-detail.component';
+
+describe('ApparatusEntryDetailComponent', () => {
+  let component: ApparatusEntryDetailComponent;
+  let fixture: ComponentFixture<ApparatusEntryDetailComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ ApparatusEntryDetailComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(ApparatusEntryDetailComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 70 - 0
src/app/components/apparatus-entry/apparatus-entry-detail/apparatus-entry-detail.component.ts

@@ -0,0 +1,70 @@
+import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
+import { ApparatusEntry, GenericElement, Reading } from '../../../models/evt-models';
+import { register } from '../../../services/component-register.service';
+import { EVTModelService } from '../../../services/evt-model.service';
+@Component({
+  selector: 'evt-apparatus-entry-detail',
+  templateUrl: './apparatus-entry-detail.component.html',
+  styleUrls: ['./apparatus-entry-detail.component.scss'],
+  changeDetection: ChangeDetectionStrategy.OnPush,
+})
+
+@register(ApparatusEntryDetailComponent)
+export class ApparatusEntryDetailComponent implements OnInit {
+  @Input() data: ApparatusEntry;
+  nestedApps: ApparatusEntry[] = [];
+  rdgHasCounter = false;
+
+  get significantRdg(): Reading[] {
+    return this.data.readings.filter((rdg) => rdg.significant);
+  }
+
+  get notSignificantRdg(): Reading[] {
+    return this.data.readings.filter((rdg) => !rdg.significant);
+  }
+
+  get readings(): Reading[] {
+    return [this.data.lemma, ...this.significantRdg, ...this.notSignificantRdg]
+  }
+
+  get rdgMetadata() {
+    return Object.keys(this.data.attributes).filter((key) => key !== 'id')
+      .reduce((obj, key) => ({
+        ...obj,
+        [key]: this.data.attributes[key],
+      }),     {});
+  }
+
+  constructor(
+    public evtModelService: EVTModelService,
+  ) {
+  }
+
+  ngOnInit() {
+    if (this.data.nestedAppsIDs.length > 0) {
+      this.recoverNestedApps(this.data);
+    }
+  }
+
+  recoverNestedApps(app: ApparatusEntry) {
+    const nesApps = app.lemma.content.filter((c: ApparatusEntry | GenericElement) => c.type === ApparatusEntry);
+    nesApps.forEach((nesApp: ApparatusEntry) => {
+      this.nestedApps = this.nestedApps.concat(nesApp);
+      if (nesApp.nestedAppsIDs.length > 0) {
+        this.recoverNestedApps(nesApp);
+      }
+    });
+  }
+
+  isAppEntry(item: GenericElement | ApparatusEntry): boolean {
+    return item.type === ApparatusEntry;
+  }
+
+  getNestedAppLemma(appId: string): Reading {
+    return this.nestedApps.find((c) => c.id === appId).lemma;
+  }
+
+  getNestedAppPos(appId: string): number {
+    return this.nestedApps.findIndex((nesApp) => nesApp.id === appId);
+  }
+}

+ 25 - 0
src/app/components/apparatus-entry/apparatus-entry-readings/apparatus-entry-readings.component.html

@@ -0,0 +1,25 @@
+<span class="app-entry-reading">
+    <!--
+        It's necessary to handle both readings with and without nested apps, because nested apps inside app-entry-detail have different behaviour,
+        so we can't use apparatus-entry-component to render them.
+    -->
+    <evt-reading *ngIf="data.nestedAppsIDs.length === 0" [data]="data.lemma"></evt-reading>
+
+    <ng-container *ngIf="data.nestedAppsIDs.length > 0">
+        <ng-container *ngTemplateOutlet="template context: { rdgHasCounter: rdgHasCounter }"></ng-container>
+    </ng-container>
+
+    <ng-container *ngIf="data.lemma.content.length > 0">
+        <span *ngFor="let witID of data.lemma.witIDs"> {{ witID }}</span>
+    </ng-container>
+    <span>] </span>
+</span>
+
+<span class="app-entry-reading" *ngFor="let el of significantRdg">
+    <evt-reading *ngIf="el.content.length !== 0" [data]="el"></evt-reading>
+    <span class="font-italic" *ngIf="el.content.length === 0">omit.</span>
+    <!-- TODO: handle lacunastart and lacunaend -->
+    <ng-container *ngFor="let witID of el.witIDs">
+        <span *ngFor="let wit of getWits$(witID) | async" class="font-italic"> {{ wit }} </span>
+    </ng-container>
+</span>

+ 5 - 0
src/app/components/apparatus-entry/apparatus-entry-readings/apparatus-entry-readings.component.scss

@@ -0,0 +1,5 @@
+.app-entry-reading {
+    display: inline-block;
+    padding-right: 0.5rem;
+    line-height: 1;
+}

+ 24 - 0
src/app/components/apparatus-entry/apparatus-entry-readings/apparatus-entry-readings.component.spec.ts

@@ -0,0 +1,24 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ApparatusEntryReadingsComponent } from './apparatus-entry-readings.component';
+
+describe('ApparatusEntryReadingsComponent', () => {
+  let component: ApparatusEntryReadingsComponent;
+  let fixture: ComponentFixture<ApparatusEntryReadingsComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ ApparatusEntryReadingsComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(ApparatusEntryReadingsComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 41 - 0
src/app/components/apparatus-entry/apparatus-entry-readings/apparatus-entry-readings.component.ts

@@ -0,0 +1,41 @@
+import { ChangeDetectionStrategy, Component, Input, TemplateRef } from '@angular/core';
+import { Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+import { ApparatusEntry, Reading } from 'src/app/models/evt-models';
+import { register } from 'src/app/services/component-register.service';
+import { EVTModelService } from 'src/app/services/evt-model.service';
+
+@Component({
+  selector: 'evt-apparatus-entry-readings',
+  templateUrl: './apparatus-entry-readings.component.html',
+  styleUrls: ['./apparatus-entry-readings.component.scss'],
+  changeDetection: ChangeDetectionStrategy.OnPush,
+})
+
+@register(ApparatusEntryReadingsComponent)
+export class ApparatusEntryReadingsComponent {
+  @Input() data: ApparatusEntry;
+  @Input() rdgHasCounter: boolean;
+  // tslint:disable-next-line: no-any
+  @Input() template: TemplateRef<any>;
+
+  groups$ = this.evtModelService.groups$;
+
+  constructor(
+    public evtModelService: EVTModelService,
+  ) {
+  }
+
+  get significantRdg(): Reading[] {
+    return this.data.readings.filter((rdg) => rdg.significant);
+  }
+
+  getWits$(witID: string): Observable<string[]> {
+    return this.groups$.pipe(
+      map((groups) => {
+        return groups.filter((g) => g.id === witID).map((g) => g.witnesses).reduce((x, y) => ([ ...x, ...y ]), []);
+      }),
+      map((groupWits) => groupWits.length > 0 ? groupWits : [witID]),
+    );
+  }
+}

+ 8 - 3
src/app/components/apparatus-entry/apparatus-entry.component.html

@@ -1,3 +1,8 @@
-<span class="evt-app-entry" [attr.data-variance]="variance$ | async">
-   <evt-content-viewer *ngIf="data.lemma" [content]="data.lemma" [editionLevel]="editionLevel"></evt-content-viewer>
-</span>
+<!--
+   Check if the app entry is inside an app detail (nested app). In the app detail it's not necessary to render the entire app but only the lemma.
+-->
+<span *ngIf="!isInsideAppDetail" class="app-entry" [attr.data-variance]="variance$ | async" [ngClass]="{ 'app-detail-opened': opened }">
+   <evt-reading class="app-entry-lem" *ngIf="data?.lemma" [data]="data.lemma" (click)="toggleAppEntryBox($event);"></evt-reading>
+   <!-- The click event in "evt-apparatus-entry-detail" is used to manage nested apps -->
+   <evt-apparatus-entry-detail *ngIf="data?.lemma && opened" [data]="data" (click)="stopPropagation($event)"></evt-apparatus-entry-detail>
+</span>

+ 27 - 0
src/app/components/apparatus-entry/apparatus-entry.component.scss

@@ -0,0 +1,27 @@
+@import '../../../assets/scss/themes';
+
+.app-entry {
+    &.app-detail-opened {
+        > .app-entry-lem {
+            position: relative;
+            z-index: 1;
+            padding-bottom: 0.313rem;
+            @include themify($themes) {
+                border: 1px solid themed('baseBorder');
+                border-bottom: 0;
+            }
+        }
+    }
+}
+
+.app-entry-lem {
+    // TODO: Change background color when variance is handled
+    @include themify($themes) {
+        background-color: #ccc;
+        //background-color: themed('appEntryBoxBackground');
+    }
+    cursor: pointer;
+    .app-entry-lem {
+        background-color: gray;
+    }
+}

+ 25 - 6
src/app/components/apparatus-entry/apparatus-entry.component.ts

@@ -1,26 +1,45 @@
-import { Component, Input } from '@angular/core';
-import { map } from 'rxjs/operators';
+import { ChangeDetectionStrategy, Component, Input, Optional } from '@angular/core';
+import { map, shareReplay } from 'rxjs/operators';
 import { ApparatusEntry } from '../../models/evt-models';
 import { register } from '../../services/component-register.service';
 import { EVTModelService } from '../../services/evt-model.service';
-import { EditionlevelSusceptible } from '../components-mixins';
-
+import { ApparatusEntryDetailComponent } from './apparatus-entry-detail/apparatus-entry-detail.component';
 @Component({
   selector: 'evt-apparatus-entry',
   templateUrl: './apparatus-entry.component.html',
   styleUrls: ['./apparatus-entry.component.scss'],
+  changeDetection: ChangeDetectionStrategy.OnPush,
 })
 @register(ApparatusEntry)
-export class ApparatusEntryComponent extends EditionlevelSusceptible {
+export class ApparatusEntryComponent {
   @Input() data: ApparatusEntry;
 
+  public opened = false;
+  public isInsideAppDetail: boolean;
+  public nestedApps: ApparatusEntry[] = [];
+
   variance$ = this.evtModelService.appVariance$.pipe(
     map((variances) => variances[this.data.id]),
+    shareReplay(1),
   );
 
   constructor(
     private evtModelService: EVTModelService,
+    @Optional() private parentDetailComponent?: ApparatusEntryDetailComponent,
   ) {
-    super();
+    this.isInsideAppDetail = this.parentDetailComponent ? true : false;
+  }
+
+  toggleAppEntryBox(e: MouseEvent) {
+    e.stopPropagation();
+    this.opened = !this.opened;
+  }
+
+  closeAppEntryBox() {
+    this.opened = false;
+  }
+
+  stopPropagation(e: MouseEvent) {
+    e.stopPropagation();
   }
 }

+ 18 - 0
src/app/components/edition-stmt/edition-stmt.component.html

@@ -0,0 +1,18 @@
+<ng-container [ngSwitch]="data?.structuredData">
+    <ng-container *ngSwitchCase="true">
+        <ng-container *ngTemplateOutlet="section; context: { label: 'edition', items: data?.edition, class: 'edition' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'responsibles', items: data?.respStmt, class: 'principal' }"></ng-container>
+    </ng-container>
+    <ng-container *ngSwitchDefault>
+        <evt-content-viewer *ngFor="let element of data?.content" [content]="element"></evt-content-viewer>
+    </ng-container>
+</ng-container>
+
+
+<ng-template #section let-label="label" let-items="items" let-class="class">
+    <evt-header-section *ngIf="items?.length > 0" [label]="label" [inlineLabel]="true" [additionalClass]="class">
+        <div content>
+            <evt-content-viewer *ngFor="let item of items" [content]="item" ></evt-content-viewer>
+        </div>
+    </evt-header-section>
+</ng-template>

+ 5 - 0
src/app/components/edition-stmt/edition-stmt.component.scss

@@ -0,0 +1,5 @@
+@import '../../../assets/scss/mixins';
+
+.main-section-title {
+    @include headerSectionTitle()
+}

+ 25 - 0
src/app/components/edition-stmt/edition-stmt.component.spec.ts

@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { EditionStmtComponent } from './edition-stmt.component';
+
+describe('EditionStmtComponent', () => {
+  let component: EditionStmtComponent;
+  let fixture: ComponentFixture<EditionStmtComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ EditionStmtComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(EditionStmtComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 14 - 0
src/app/components/edition-stmt/edition-stmt.component.ts

@@ -0,0 +1,14 @@
+import { Component, Input } from '@angular/core';
+
+import { EditionStmt } from '../../models/evt-models';
+import { register } from '../../services/component-register.service';
+
+@Component({
+  selector: 'evt-edition-stmt',
+  templateUrl: './edition-stmt.component.html',
+  styleUrls: ['./edition-stmt.component.scss'],
+})
+@register(EditionStmt)
+export class EditionStmtComponent {
+  @Input() data: EditionStmt;
+}

+ 24 - 0
src/app/components/editorial-decl/editorial-decl.component.html

@@ -0,0 +1,24 @@
+<ng-container [ngSwitch]="data?.structuredData">
+    <ng-container *ngSwitchCase="true">
+        <ng-container *ngTemplateOutlet="section; context: { label: 'correction', items: data?.correction, class: 'correction' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'normalization', items: data?.normalization, class: 'normalization' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'punctuation', items: data?.punctuation, class: 'punctuation' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'quotation', items: data?.quotation, class: 'quotation' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'hyphenation', items: data?.hyphenation, class: 'hyphenation' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'segmentation', items: data?.segmentation, class: 'segmentation' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'stdVals', items: data?.stdVals, class: 'stdVals' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'interpretation', items: data?.interpretation, class: 'interpretation' }"></ng-container>
+    </ng-container>
+    <ng-container *ngSwitchDefault>
+        <evt-content-viewer *ngFor="let element of data?.content" [content]="element"></evt-content-viewer>
+    </ng-container>
+</ng-container>
+
+
+<ng-template #section let-label="label" let-items="items" let-class="class">
+    <evt-header-section *ngIf="items?.length > 0" [label]="label" [additionalClass]="class" [inlineLabel]="true">
+        <div content>
+            <evt-content-viewer *ngFor="let item of items" [content]="item" ></evt-content-viewer>
+        </div>
+    </evt-header-section>
+</ng-template>

+ 7 - 0
src/app/components/editorial-decl/editorial-decl.component.scss

@@ -0,0 +1,7 @@
+.editorialDecl {
+    ::ng-deep {
+        .stdvals {
+            display: inline-block;
+        }
+    }
+}

+ 24 - 0
src/app/components/editorial-decl/editorial-decl.component.spec.ts

@@ -0,0 +1,24 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { EditorialDeclComponent } from './editorial-decl.component';
+
+describe('EditorialDeclComponent', () => {
+  let component: EditorialDeclComponent;
+  let fixture: ComponentFixture<EditorialDeclComponent>;
+
+  beforeEach(async() => {
+    await TestBed.configureTestingModule({
+      declarations: [ EditorialDeclComponent ],
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(EditorialDeclComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 13 - 0
src/app/components/editorial-decl/editorial-decl.component.ts

@@ -0,0 +1,13 @@
+import { Component, Input } from '@angular/core';
+import { EditorialDecl } from '../../models/evt-models';
+import { register } from '../../services/component-register.service';
+
+@Component({
+  selector: 'evt-editorial-decl',
+  templateUrl: './editorial-decl.component.html',
+  styleUrls: ['./editorial-decl.component.scss'],
+})
+@register(EditorialDecl)
+export class EditorialDeclComponent {
+  @Input() data: EditorialDecl;
+}

+ 28 - 0
src/app/components/encoding-desc/encoding-desc.component.html

@@ -0,0 +1,28 @@
+<ng-container [ngSwitch]="data?.structuredData">
+    <ng-container *ngSwitchCase="true">
+        <ng-container *ngTemplateOutlet="section; context: { label: 'projectDesc', items: data?.projectDesc, class: 'projectDesc' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'samplingDecl', items: data?.samplingDecl, class: 'samplingDecl' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'editorialDecl', items: data?.editorialDecl, class: 'editorialDecl' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'tagsDecl', items: data?.tagsDecl, class: 'tagsDecl' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'refsDecl', items: data?.refsDecl, class: 'refsDecl' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'classDecl', items: data?.classDecl, class: 'classDecl' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'geoDecl', items: data?.geoDecl, class: 'geoDecl' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'unitDecl', items: data?.unitDecl, class: 'unitDecl' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'schemaSpec', items: data?.schemaSpec, class: 'schemaSpec' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'schemaRef', items: data?.schemaRef, class: 'schemaRef' }"></ng-container>
+    </ng-container>
+    <ng-container *ngSwitchDefault>
+        <evt-content-viewer *ngFor="let element of data?.content" [content]="element"></evt-content-viewer>
+    </ng-container>
+</ng-container>
+
+<ng-template #section let-label="label" let-items="items" let-class="class">
+    <ng-container *ngIf="items?.length > 0" >
+        <h4 *ngIf="label" class="main-section-title">{{ label | translate }}</h4>
+        <evt-header-section [additionalClass]="class">
+            <div content>
+                <evt-content-viewer *ngFor="let item of items" [content]="item"></evt-content-viewer> 
+            </div>
+        </evt-header-section>
+    </ng-container>
+</ng-template>

+ 5 - 0
src/app/components/encoding-desc/encoding-desc.component.scss

@@ -0,0 +1,5 @@
+@import "../../../assets/scss/mixins";
+
+.main-section-title {
+    @include headerSectionTitle();
+}

+ 24 - 0
src/app/components/encoding-desc/encoding-desc.component.spec.ts

@@ -0,0 +1,24 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { EncodingDescComponent } from './encoding-desc.component';
+
+describe('EncodingDescComponent', () => {
+  let component: EncodingDescComponent;
+  let fixture: ComponentFixture<EncodingDescComponent>;
+
+  beforeEach(async() => {
+    await TestBed.configureTestingModule({
+      declarations: [ EncodingDescComponent ],
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(EncodingDescComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 13 - 0
src/app/components/encoding-desc/encoding-desc.component.ts

@@ -0,0 +1,13 @@
+import { Component, Input } from '@angular/core';
+import { EncodingDesc } from '../../models/evt-models';
+import { register } from '../../services/component-register.service';
+
+@Component({
+  selector: 'evt-encoding-desc',
+  templateUrl: './encoding-desc.component.html',
+  styleUrls: ['./encoding-desc.component.scss'],
+})
+@register(EncodingDesc)
+export class EncodingDescComponent {
+  @Input() data: EncodingDesc;
+}

+ 5 - 0
src/app/components/extent/extent.component.html

@@ -0,0 +1,5 @@
+<evt-header-section *ngIf="data?.content?.length > 0" [label]="'extent'" [additionalClass]="'extent'" [inlineLabel]="true">
+    <div content>
+        <evt-content-viewer *ngFor="let el of data.content" [content]="el"></evt-content-viewer> 
+    </div>
+</evt-header-section>

+ 0 - 0
src/app/components/extent/extent.component.scss


+ 25 - 0
src/app/components/extent/extent.component.spec.ts

@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ExtentComponent } from './extent.component';
+
+describe('ExtentComponent', () => {
+  let component: ExtentComponent;
+  let fixture: ComponentFixture<ExtentComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ ExtentComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(ExtentComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 14 - 0
src/app/components/extent/extent.component.ts

@@ -0,0 +1,14 @@
+import { Component, Input } from '@angular/core';
+
+import { Extent } from '../../models/evt-models';
+import { register } from '../../services/component-register.service';
+
+@Component({
+  selector: 'evt-extent',
+  templateUrl: './extent.component.html',
+  styleUrls: ['./extent.component.scss'],
+})
+@register(Extent)
+export class ExtentComponent {
+  @Input() data: Extent;
+}

+ 20 - 0
src/app/components/file-desc/file-desc.component.html

@@ -0,0 +1,20 @@
+<div class="fileDesc">
+    <ng-container *ngTemplateOutlet="section; context: { label: '', content: data?.titleStmt, class: 'titleStmt' }"></ng-container>
+    <ng-container *ngTemplateOutlet="section; context: { label: 'editionStatement', content: data?.editionStmt, class: 'editionStmt' }"></ng-container>
+    <ng-container *ngTemplateOutlet="section; context: { label: 'extent', content: data?.extent, class: 'extent' }"></ng-container>
+    <ng-container *ngTemplateOutlet="section; context: { label: 'publicationStatement', content: data?.publicationStmt, class: 'publicationStmt' }"></ng-container>
+    <ng-container *ngTemplateOutlet="section; context: { label: 'seriesStatement', content: data?.seriesStmt, class: 'seriesStmt' }"></ng-container>
+    <ng-container *ngTemplateOutlet="section; context: { label: 'notesStatement', content: data?.notesStmt, class: 'notesStmt' }"></ng-container>
+    <ng-container *ngTemplateOutlet="section; context: { label: 'sourceDesc', content: data?.sourceDesc, class: 'sourceDesc' }"></ng-container>
+</div>
+
+<ng-template #section let-label="label" let-content="content" let-class="class">
+    <ng-container *ngIf="content">
+        <h4 *ngIf="label" class="main-section-title {{class}}-title">{{ label | translate }}</h4>
+        <evt-header-section [additionalClass]="class">
+            <div content>
+                <evt-content-viewer [content]="content"></evt-content-viewer> 
+            </div>
+        </evt-header-section>
+    </ng-container>
+</ng-template>

+ 19 - 0
src/app/components/file-desc/file-desc.component.scss

@@ -0,0 +1,19 @@
+@import "../../../assets/scss/mixins";
+
+.main-section-title {
+    @include headerSectionTitle();
+}
+
+::ng-deep .fileDesc {
+    .sourceDesc-title {
+        @include headerSectionTitle();
+    }
+    
+    .sourceDesc .section.underline-dotted {
+        border-bottom: none !important;
+    }
+    
+    .extent .section-label {
+        display: none !important;
+    }
+}

+ 25 - 0
src/app/components/file-desc/file-desc.component.spec.ts

@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { FileDescComponent } from './file-desc.component';
+
+describe('FileDescComponent', () => {
+  let component: FileDescComponent;
+  let fixture: ComponentFixture<FileDescComponent>;
+
+  beforeEach(async() => {
+    await TestBed.configureTestingModule({
+      declarations: [ FileDescComponent ],
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(FileDescComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 13 - 0
src/app/components/file-desc/file-desc.component.ts

@@ -0,0 +1,13 @@
+import { Component, Input } from '@angular/core';
+import { FileDesc } from 'src/app/models/evt-models';
+import { register } from 'src/app/services/component-register.service';
+
+@Component({
+  selector: 'evt-file-desc',
+  templateUrl: './file-desc.component.html',
+  styleUrls: ['./file-desc.component.scss'],
+})
+@register(FileDesc)
+export class FileDescComponent {
+  @Input() data: FileDesc;
+}

+ 0 - 4
src/app/components/history/history.component.scss

@@ -21,10 +21,6 @@
     margin-bottom: 0.7rem;
 }
 
-::ng-deep p {
-    margin-bottom: 0;
-}
-
 @media (min-width: 612px) { 
     .flex-item-label {
         flex-basis: 20%;

+ 4 - 4
src/app/components/manuscript-thumbnails-viewer/manuscript-thumbnails-viewer.component.html

@@ -1,20 +1,20 @@
 <div class="thumbnails-view">
-  <ng-container fullGrid></ng-container>
+  <ng-container *ngTemplateOutlet="fullGrid"></ng-container>
 </div>
 
 <ng-template #fullGrid >
   <ng-container *ngFor="let page of grid; let i = index">
     <div id="page_{{i+1}}" class="page" [ngClass]="{'active': indexPage === i }">
       <div class="d-flex row flex-row bd-highlight mb-3" *ngFor="let row of page">
-        <div class="p-2 bd-highlight item-content " *ngFor="let item of row" (click)="clickedItem(item)">
-            <img class="evt-img-page" src="{{item.url}}" alt="page" [style.width.px]="100" [ngClass]="{'clicked-item': item.active }"/>
+        <div class="p-2 bd-highlight item-content " *ngFor="let item of row" (click)="goToThumbPage(item)">
+            <img class="evt-img-page" src="{{item.url}}" alt="page" [ngClass]="{'clicked-item': (currentItem$ | async) === item }"/>
             <p class="item-page-index"[ngClass]="{'clicked-item-par': item.active }">{{item.name}}</p>
         </div>
       </div>
     </div>
   </ng-container>
   <ng-container>
-    <div class="change-page-container" [hidden] = "!(grid.length >= 1)">
+    <div class="change-page-container" [hidden]="grid.length <= 1">
       <evt-button [iconRight]="{icon: 'caret-left'}" (btnClick)="goToPrevPage()"></evt-button>
       <p class="current-page">{{indexPage+1}}/{{grid.length}}</p>
       <evt-button [iconLeft]="{icon: 'caret-right'}" (btnClick)="goToNextPage()"></evt-button>

+ 31 - 22
src/app/components/manuscript-thumbnails-viewer/manuscript-thumbnails-viewer.component.scss

@@ -1,77 +1,86 @@
 @import "../../../assets/scss/themes";
 @import "../../../assets/scss/mixins";
+@import "../../../assets/scss/variables";
 
-.thumbnails-view{
+.thumbnails-view {
     height: 100%;
     @include themify($themes) {
-        background-color: themed('secondaryColorDark'); 
+        background-color: themed("secondaryColorDark");
     }
-    overflow: scroll;
+    overflow: auto;
     position: relative;
 }
 .change-page-container {
     padding: 10px;
     @include themify($themes) {
-        background-color: themed('baseColorDark'); 
+        background-color: themed("baseColorDark");
     }
     position: absolute;
     bottom: 0;
     width: 100%;
 }
-.page{
+.page {
     display: none;
     padding: 0 20px;
 }
 .active {
     display: block;
 }
-.item-content{
+.item-content {
     position: relative;
 }
 p.item-page-index {
     position: absolute;
     top: 70%;
-    @include calc('width', '100% - 16px');
+    @include calc("width", "100% - 16px");
     text-align: center;
     @include themify($themes) {
-        background-color: themed('baseColorLight'); 
-        color: themed('secondaryColorDark')
+        background-color: themed("baseColorLight");
+        color: themed("secondaryColorDark");
     }
 }
-.item-content:hover p.item-page-index{
+.item-content:hover p.item-page-index:not(.clicked-item-par) {
     @include themify($themes) {
-        background: themed('secondaryColorDark');
-        color: themed('baseColorLight')
+        background: themed("secondaryColorDark");
+        color: themed("baseColorLight");
     }
 }
-.pages-controller{
+.pages-controller {
     width: 20px;
     @include themify($themes) {
-        color: themed('baseColorLight')
+        color: themed("baseColorLight");
     }
     margin: 5px;
     @include themify($themes) {
-        border: 3px solid themed('baseColorLight');
+        border: 3px solid themed("baseColorLight");
     }
     border-radius: 3px;
 }
 
-.clicked-item{
+.clicked-item {
     @include themify($themes) {
-        border: 2px solid  themed('baseColorLight');
+        border: 2px solid themed("baseColorLight");
     }
 }
 
-.clicked-item-par{
+.clicked-item-par {
     @include themify($themes) {
-        background-color: themed('baseColorLight') !important;
-        color: themed('secondaryColorDark');
+        background-color: themed("baseColorLight");
+        color: themed("secondaryColorDark");
     }
 }
-.current-page{
+.current-page {
     @include themify($themes) {
-        color: themed('baseColorLight');
+        color: themed("baseColorLight");
     }
     display: inline;
     padding: 0 10px;
+}
+
+.evt-img-page {
+    min-height: $thumbnail-height;
+    min-width: $thumbnail-width;
+    max-height: $thumbnail-height;
+    max-width: $thumbnail-width;
+    cursor: pointer;
 }

+ 30 - 7
src/app/components/manuscript-thumbnails-viewer/manuscript-thumbnails-viewer.component.ts

@@ -1,5 +1,7 @@
-import { Component, Input, OnInit } from '@angular/core';
-import { GridItem } from '../../models/evt-models';
+import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
+import { map } from 'rxjs/operators';
+import { GridItem, Page } from '../../models/evt-models';
+import { EVTStatusService } from '../../services/evt-status.service';
 
 @Component({
   selector: 'evt-manuscript-thumbnails',
@@ -7,9 +9,10 @@ import { GridItem } from '../../models/evt-models';
   styleUrls: ['./manuscript-thumbnails-viewer.component.scss'],
 })
 
-export class ManuscriptThumbnailsViewerComponent implements OnInit {
+export class ManuscriptThumbnailsViewerComponent implements OnInit, OnChanges {
+  @Output() clickedItem = new EventEmitter<GridItem>();
 
-  @Input() urls = [];
+  @Input() pages: Page[] = [];
   @Input() col = 1;
   @Input() row = 1;
 
@@ -17,8 +20,27 @@ export class ManuscriptThumbnailsViewerComponent implements OnInit {
   private items: GridItem[];
   public grid: GridItem[][][] = [];
 
+  public currentItem$ = this.evtStatusService.currentPage$.pipe(
+    map(p => this.items.find(i => i.id === p.id)),
+  );
+
+  constructor(
+    private evtStatusService: EVTStatusService,
+  ) {
+  }
+
   ngOnInit() {
-    this.items = this.urls.map((url, i) => ({ url, name: 'page_' + i, active: false }));
+    this._setup();
+  }
+
+  ngOnChanges(changes: SimpleChanges) {
+    if (Object.keys(changes).some(k => changes[k].currentValue !== changes[k].previousValue)) {
+      this._setup();
+    }
+  }
+
+  private _setup() {
+    this.items = this.pages.map((page) => ({ url: page.url, name: page.label, id: page.id }));
     this.col = this.isValid(this.col) ? this.col : 1;
     this.row = this.isValid(this.row) ? this.row : 1;
     const gridSize = this.col * this.row;
@@ -40,7 +62,8 @@ export class ManuscriptThumbnailsViewerComponent implements OnInit {
     this.indexPage = Math.min(this.indexPage + 1, this.grid.length - 1);
   }
 
-  clickedItem(item) {
-    this.items.forEach(el => el.active = el === item);
+  goToThumbPage(item) {
+    this.evtStatusService.updatePage$.next(this.pages.find(p => p.id === item.id));
+    this.clickedItem.emit(item);
   }
 }

+ 11 - 0
src/app/components/ms-desc-selector/ms-desc-selector.component.html

@@ -0,0 +1,11 @@
+<ng-select
+    class="mr-1"
+    [closeOnSelect]="true" 
+    [searchable]="false"
+    bindLabel="label" 
+    bindValue="id"
+    [items]="msDesc$ | async" #ngSelectComponent
+    (clear)="resetMsDesc();"
+    [placeholder]="'selectMsDesc' | translate" 
+    (change)="msDescID = $event?.id; openMsDescContent()">     
+</ng-select>

+ 0 - 0
src/app/components/ms-desc-selector/ms-desc-selector.component.scss


+ 25 - 0
src/app/components/ms-desc-selector/ms-desc-selector.component.spec.ts

@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MsDescSelectorComponent } from './ms-desc-selector.component';
+
+describe('MsDescSelectorComponent', () => {
+  let component: MsDescSelectorComponent;
+  let fixture: ComponentFixture<MsDescSelectorComponent>;
+
+  beforeEach(async() => {
+    await TestBed.configureTestingModule({
+      declarations: [ MsDescSelectorComponent ],
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(MsDescSelectorComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 39 - 0
src/app/components/ms-desc-selector/ms-desc-selector.component.ts

@@ -0,0 +1,39 @@
+import { EventEmitter, Output, ViewChild } from '@angular/core';
+import { Component, Input } from '@angular/core';
+import { NgSelectComponent } from '@ng-select/ng-select';
+import { EVTModelService } from 'src/app/services/evt-model.service';
+
+@Component({
+  selector: 'evt-ms-desc-selector',
+  templateUrl: './ms-desc-selector.component.html',
+  styleUrls: ['./ms-desc-selector.component.scss'],
+})
+export class MsDescSelectorComponent {
+  public msDesc$ = this.evtModelService.msDesc$;
+
+  @Output() selectionChange: EventEmitter<string> = new EventEmitter<string>();
+  @Output() msDescOpen: EventEmitter<boolean> = new EventEmitter<boolean>();
+  @ViewChild('ngSelectComponent') ngSelectComponent: NgSelectComponent;
+
+  // tslint:disable-next-line: variable-name
+  private _msDescID: string;
+  @Input() set msDescID(p: string) {
+    this._msDescID = p;
+  }
+
+  get msDescID() { return this._msDescID; }
+
+  constructor(
+    public evtModelService: EVTModelService,
+  ) {
+  }
+
+  openMsDescContent() {
+    this.selectionChange.emit(this.msDescID);
+    this.msDescOpen.emit(true);
+  }
+
+  resetMsDesc() {
+    this.msDescOpen.emit(false);
+  }
+}

+ 0 - 4
src/app/components/ms-desc/ms-desc.component.scss

@@ -13,10 +13,6 @@
     margin-bottom: 0.7rem;
 }
 
-::ng-deep p {
-    margin-bottom: 0;
-}
-
 @media (min-width: 500px) { 
     .flex-item-label {
         flex-basis: 20%;

+ 0 - 4
src/app/components/ms-frag/ms-frag.component.scss

@@ -17,10 +17,6 @@
     margin-bottom: 0.7rem;
 }
 
-::ng-deep p {
-    margin-bottom: 0;
-}
-
 @media (min-width: 500px) { 
     .flex-item-label {
         flex-basis: 20%;

+ 2 - 2
src/app/components/ms-item/ms-item.component.html

@@ -1,6 +1,6 @@
 <div class="flex-container section-msItem" [class.has-nested1]="nested1" [class.has-nested2]="nested2">   
-    <div class="flex-item-locus">   
-        <span *ngIf="data?.locus" class="locus">
+    <div class="flex-item-locus" *ngIf="data?.locus">   
+        <span class="locus">
             <evt-content-viewer [content]="data.locus"></evt-content-viewer>
             <span>:</span>
         </span>

+ 15 - 0
src/app/components/namespace/namespace.component.html

@@ -0,0 +1,15 @@
+<div class="namespace">
+    <div class="namespace-name">{{data.name}}</div>
+    <ul class="namespace-tagUsage">
+        <li *ngFor="let usage of data.tagUsage">
+            <strong>{{usage.gi}}</strong>
+            <ng-container *ngIf="usage.occurs || usage.withId">
+                (<ng-container *ngIf="usage.occurs">{{usage.occurs}} {{ usage.occurs === 1 ? 'occurence' : 'occurences' | translate }}</ng-container>
+                <ng-container *ngIf="usage.withId"> {{ 'withId' | translate }} {{usage.withId}}</ng-container>)
+            </ng-container>
+            <div>
+                <evt-content-viewer *ngFor="let element of usage.content" [content]="element"></evt-content-viewer>
+            </div>
+        </li>
+    </ul>
+</div>

+ 0 - 0
src/app/components/namespace/namespace.component.scss


+ 24 - 0
src/app/components/namespace/namespace.component.spec.ts

@@ -0,0 +1,24 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { NamespaceComponent } from './namespace.component';
+
+describe('NamespaceComponent', () => {
+  let component: NamespaceComponent;
+  let fixture: ComponentFixture<NamespaceComponent>;
+
+  beforeEach(async() => {
+    await TestBed.configureTestingModule({
+      declarations: [ NamespaceComponent ],
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(NamespaceComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 13 - 0
src/app/components/namespace/namespace.component.ts

@@ -0,0 +1,13 @@
+import { Component, Input } from '@angular/core';
+import { Namespace } from '../../models/evt-models';
+import { register } from '../../services/component-register.service';
+
+@Component({
+  selector: 'evt-namespace',
+  templateUrl: './namespace.component.html',
+  styleUrls: ['./namespace.component.scss'],
+})
+@register(Namespace)
+export class NamespaceComponent {
+  @Input() data: Namespace;
+}

+ 11 - 0
src/app/components/notes-stmt/notes-stmt.component.html

@@ -0,0 +1,11 @@
+<div *ngFor="let note of data?.notes">
+    <evt-content-viewer [content]="note"></evt-content-viewer>
+</div>
+
+<evt-header-section *ngIf="data?.relatedItems?.length > 0" [label]="'relatedItems'" [additionalClass]="'relatedItems'" [inlineLabel]="false">
+    <div content>
+        <div *ngFor="let item of data?.relatedItems">
+            <evt-content-viewer [content]="item"></evt-content-viewer>
+        </div>
+    </div>
+</evt-header-section>

+ 5 - 0
src/app/components/notes-stmt/notes-stmt.component.scss

@@ -0,0 +1,5 @@
+@import '../../../assets/scss/mixins';
+
+.main-section-title {
+    @include headerSectionTitle()
+}

+ 25 - 0
src/app/components/notes-stmt/notes-stmt.component.spec.ts

@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { NotesStmtComponent } from './notes-stmt.component';
+
+describe('NotesStmtComponent', () => {
+  let component: NotesStmtComponent;
+  let fixture: ComponentFixture<NotesStmtComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ NotesStmtComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(NotesStmtComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 14 - 0
src/app/components/notes-stmt/notes-stmt.component.ts

@@ -0,0 +1,14 @@
+import { Component, Input } from '@angular/core';
+import { NotesStmt } from '../../models/evt-models';
+import { register } from '../../services/component-register.service';
+
+@Component({
+  selector: 'evt-notes-stmt',
+  templateUrl: './notes-stmt.component.html',
+  styleUrls: ['./notes-stmt.component.scss'],
+})
+@register(NotesStmt)
+export class NotesStmtComponent {
+  @Input() data: NotesStmt;
+
+}

+ 17 - 35
src/app/components/osd/osd.component.ts

@@ -4,7 +4,9 @@ import { HttpClient } from '@angular/common/http';
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
 
 import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs';
-import { distinctUntilChanged, filter, map, switchMap } from 'rxjs/operators';
+import { distinctUntilChanged } from 'rxjs/operators';
+import { ViewerDataType } from '../../models/evt-models';
+import { OsdTileSource, ViewerDataInput, ViewerSource } from '../../models/evt-polymorphic-models';
 import { uuid } from '../../utils/js-utils';
 
 declare var OpenSeadragon;
@@ -39,10 +41,8 @@ interface OsdViewerAPI {
   gestureSettingsMouse;
   raiseEvent: (evtName: string) => void;
 }
-
 /*
-From:
-{
+Observable<OsdTileSource[]>
   "@id": "https://www.e-codices.unifr.ch:443/loris/bge/bge-gr0044/bge-gr0044_e001.jp2/full/full/0/default.jpg",
   "@type": "dctypes:Image",
   "format": "image/jpeg",
@@ -65,16 +65,6 @@ To:
   'width': 5472,
 }
 */
-function manifestResourcetoTileSource(manifestResource) {
-  return {
-    '@context': manifestResource.service['@context'],
-    '@id': manifestResource.service['@id'],
-    profile: [manifestResource.service['@profile']],
-    protocol: 'http://iiif.io/api/image',
-    height: manifestResource.height,
-    width: manifestResource.width,
-  };
-}
 
 @Component({
   selector: 'evt-osd',
@@ -96,16 +86,14 @@ export class OsdComponent implements AfterViewInit, OnDestroy {
   get options() { return this._options; }
   optionsChange = new BehaviorSubject({});
 
-  // tslint:disable-next-line: variable-name
-  private _manifestURL: string;
-  @Input() set manifestURL(v: string) {
-    if (v !== this._manifestURL) {
-      this._manifestURL = v;
-      this.manifestURLChange.next(this._manifestURL);
-    }
+  private _viewerDataType: string; // tslint:disable-line: variable-name
+  public _viewerSource: ViewerDataInput; // tslint:disable-line: variable-name
+  @Input() set viewerData(v: ViewerDataType) {
+    this._viewerDataType = v.type;
+    this._viewerSource = ViewerSource.getSource(v, v.type);
+    this.sourceChange.next(this._viewerSource);
   }
-  get manifestURL() { return this._manifestURL; }
-  manifestURLChange = new BehaviorSubject(undefined);
+  sourceChange = new BehaviorSubject<ViewerDataInput>([]);
 
   // tslint:disable-next-line: variable-name
   private _page: number;
@@ -115,29 +103,21 @@ export class OsdComponent implements AfterViewInit, OnDestroy {
       this.pageChange.next(this._page);
     }
   }
+
   get page() { return this._page; }
+
   @Output() pageChange = new EventEmitter<number>();
 
   @Input() text: string;
 
-  tileSources: Observable<Array<{}>> = this.manifestURLChange
-    .pipe(
-      filter((url) => !!url),
-      distinctUntilChanged(),
-      switchMap((url) => this.http.get<{ sequences: Partial<Array<{ canvases }>> }>(url)),
-      map((manifest) => manifest // get the resource fields in the manifest json structure
-        .sequences.map((seq) => seq.canvases.map((canv) => canv.images).reduce((x, y) => x.concat(y), []))
-        .reduce((x, y) => x.concat(y), []).map((res) => res.resource)
-        .map(manifestResourcetoTileSource),
-      ),
-    );
-
   viewer: Partial<OsdViewerAPI>;
   viewerId: string;
   annotationsHandle: OsdAnnotationAPI;
 
   private subscriptions: Subscription[] = [];
 
+  tileSources: Observable<OsdTileSource[]>;
+
   constructor(
     private http: HttpClient,
   ) {
@@ -154,6 +134,8 @@ export class OsdComponent implements AfterViewInit, OnDestroy {
     this.viewerId = uuid('openseadragon');
     this.div.nativeElement.id = this.viewerId;
 
+    this.tileSources = ViewerSource.getTileSource(this.sourceChange, this._viewerDataType, this.http);
+
     const commonOptions = {
       visibilityRatio: 0.1,
       minZoomLevel: 0.5,

+ 1 - 1
src/app/components/page/page.component.html

@@ -1,5 +1,5 @@
 <evt-content-viewer
-    *ngFor="let element of  (pageDataChange | async)?.parsedContent || []"
+    *ngFor="let element of (pageDataChange | async)?.parsedContent || []"
     [content]="element" [editionLevel]="editionLevel?.id" [itemsToHighlight]="itemsToHighlight" [textFlow]="textFlow">
 </evt-content-viewer>
 <ngx-spinner

+ 0 - 4
src/app/components/phys-desc/phys-desc.component.scss

@@ -17,10 +17,6 @@
     margin-bottom: 0.7rem;
 }
 
-::ng-deep p {
-    margin-bottom: 0;
-}
-
 @media (min-width: 594px) { 
     .flex-item-label {
         flex-basis: 20%;

+ 3 - 0
src/app/components/project-desc/project-desc.component.html

@@ -0,0 +1,3 @@
+<div class="projectDesc">
+    <evt-content-viewer *ngFor="let content of data?.content" [content]="content"></evt-content-viewer>
+</div>

+ 0 - 0
src/app/components/project-desc/project-desc.component.scss


+ 24 - 0
src/app/components/project-desc/project-desc.component.spec.ts

@@ -0,0 +1,24 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ProjectDescComponent } from './project-desc.component';
+
+describe('ProjectDescComponent', () => {
+  let component: ProjectDescComponent;
+  let fixture: ComponentFixture<ProjectDescComponent>;
+
+  beforeEach(async() => {
+    await TestBed.configureTestingModule({
+      declarations: [ ProjectDescComponent ],
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(ProjectDescComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 13 - 0
src/app/components/project-desc/project-desc.component.ts

@@ -0,0 +1,13 @@
+import { Component, Input } from '@angular/core';
+import { ProjectDesc } from '../../models/evt-models';
+import { register } from '../../services/component-register.service';
+
+@Component({
+  selector: 'evt-project-desc',
+  templateUrl: './project-desc.component.html',
+  styleUrls: ['./project-desc.component.scss'],
+})
+@register(ProjectDesc)
+export class ProjectDescComponent {
+  @Input() data: ProjectDesc;
+}

+ 25 - 0
src/app/components/project-info/project-info.component.html

@@ -0,0 +1,25 @@
+<div class="project-info-container row h-100 m-0">
+    <div class="project-info-tabs bg-light col-3 p-0 h-100 border-right">
+        <ng-container *ngIf="projectInfo$ | async as projectInfo">
+            <ng-template [ngTemplateOutlet]="sectionTemplate" [ngTemplateOutletContext]="{ key: 'fileDesc', data: projectInfo.fileDesc }"></ng-template>
+            <ng-template [ngTemplateOutlet]="sectionTemplate" [ngTemplateOutletContext]="{ key: 'encodingDesc', data: projectInfo.encodingDesc }"></ng-template>
+            <ng-template [ngTemplateOutlet]="sectionTemplate" [ngTemplateOutletContext]="{ key: 'profileDesc', data: projectInfo.profileDesc }"></ng-template>
+            <ng-template [ngTemplateOutlet]="sectionTemplate" [ngTemplateOutletContext]="{ key: 'revisionDesc', data: projectInfo.revisionDesc }"></ng-template>
+        </ng-container>
+    </div>
+    <div class="project-info-content col-9 p-3 h-100">
+        <evt-content-viewer [content]="selectedSection?.content"></evt-content-viewer>        
+    </div>
+</div>
+
+<ng-template #sectionTemplate let-key="key" let-data="data">
+    <div class="section-item border-bottom" (click)="openSection(key, data)" [ngClass]="{
+        hidden: !data || data.content?.length === 0,
+        'bg-dark': selectedSection?.key === key,
+        'text-light': selectedSection?.key === key
+    }">
+        <span class="p-2 w-100 d-block edition-font">
+            {{key | translate}}
+        </span>
+    </div>
+</ng-template>

+ 17 - 0
src/app/components/project-info/project-info.component.scss

@@ -0,0 +1,17 @@
+.project-info-container {
+    overflow: hidden;
+}
+
+.project-info-tabs,
+.project-info-content {
+    overflow: auto;
+}
+
+.section-item {
+    cursor: pointer;
+
+    &:not(.active):hover {
+        cursor: pointer;
+        background: rgba(0, 0, 0, 0.1);
+    }
+}

+ 25 - 0
src/app/components/project-info/project-info.component.spec.ts

@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ProjectInfoComponent } from './project-info.component';
+
+describe('ProjectInfoComponent', () => {
+  let component: ProjectInfoComponent;
+  let fixture: ComponentFixture<ProjectInfoComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ ProjectInfoComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(ProjectInfoComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 26 - 0
src/app/components/project-info/project-info.component.ts

@@ -0,0 +1,26 @@
+import { Component } from '@angular/core';
+import { first, tap } from 'rxjs/operators';
+import { GenericElement } from 'src/app/models/evt-models';
+import { EVTModelService } from '../../services/evt-model.service';
+
+@Component({
+  selector: 'evt-project-info',
+  templateUrl: './project-info.component.html',
+  styleUrls: ['./project-info.component.scss'],
+})
+export class ProjectInfoComponent {
+  public projectInfo$ = this.evtModelService.projectInfo$.pipe(
+    first(),
+    tap((info) => this.openSection('fileDesc', info.fileDesc)),
+  );
+
+  public selectedSection: { key: string; content: GenericElement };
+
+  constructor(
+    private evtModelService: EVTModelService,
+  ) { }
+
+  openSection(key: string, content: GenericElement) {
+    this.selectedSection = { key, content };
+  }
+}

+ 24 - 0
src/app/components/publication-stmt/publication-stmt.component.html

@@ -0,0 +1,24 @@
+<ng-container [ngSwitch]="data?.structuredData">
+    <ng-container *ngSwitchCase="true">
+        <ng-container *ngTemplateOutlet="section; context: { label: 'publisher', items: data?.publisher, class: 'publisher' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'distributor', items: data?.distributor, class: 'distributor' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'authority', items: data?.authority, class: 'authority' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'publicationPlace', items: data?.pubPlace, class: 'pubPlace' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'address', items: data?.address, class: 'address' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'idno', items: data?.idno, class: 'idno' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'availability', items: data?.availability, class: 'availability' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'date', items: data?.date, class: 'date' }"></ng-container>
+        <ng-container *ngTemplateOutlet="section; context: { label: 'licence', items: data?.licence, class: 'licence' }"></ng-container>
+    </ng-container>
+    <ng-container *ngSwitchDefault>
+        <evt-content-viewer *ngFor="let element of data?.content" [content]="element"></evt-content-viewer>
+    </ng-container>
+</ng-container>
+
+<ng-template #section let-label="label" let-items="items" let-class="class">
+    <evt-header-section *ngIf="items?.length > 0" [label]="label" [additionalClass]="class" [inlineLabel]="true">
+        <div content>
+            <evt-content-viewer *ngFor="let item of items" [content]="item"></evt-content-viewer> 
+        </div>
+    </evt-header-section>
+</ng-template>

+ 5 - 0
src/app/components/publication-stmt/publication-stmt.component.scss

@@ -0,0 +1,5 @@
+@import '../../../assets/scss/mixins';
+
+.main-section-title {
+    @include headerSectionTitle()
+}

+ 25 - 0
src/app/components/publication-stmt/publication-stmt.component.spec.ts

@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PublicationStmtComponent } from './publication-stmt.component';
+
+describe('PublicationStmtComponent', () => {
+  let component: PublicationStmtComponent;
+  let fixture: ComponentFixture<PublicationStmtComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ PublicationStmtComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(PublicationStmtComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 14 - 0
src/app/components/publication-stmt/publication-stmt.component.ts

@@ -0,0 +1,14 @@
+import { Component, Input } from '@angular/core';
+import { PublicationStmt } from '../../models/evt-models';
+import { register } from '../../services/component-register.service';
+
+@Component({
+  selector: 'evt-publication-stmt',
+  templateUrl: './publication-stmt.component.html',
+  styleUrls: ['./publication-stmt.component.scss'],
+})
+@register(PublicationStmt)
+export class PublicationStmtComponent {
+  @Input() data: PublicationStmt;
+
+}

+ 28 - 0
src/app/components/rendition/rendition.component.html

@@ -0,0 +1,28 @@
+<div class="rendition">
+    <ng-container *ngIf="data.scope || data.selector || data.scheme">
+        <div class="rendition-section rendition-scope" *ngIf="data.scope">
+            <label>{{'scope' | translate}}: </label>
+            <span>{{data.scope}} {{ scopeDescription$ | async }}</span>
+        </div>
+        <div class="rendition-section rendition-selector" *ngIf="data.selector">
+            <label>{{'selector' | translate}}: </label><span>{{data.selector}}</span>
+        </div>
+        <div class="rendition-section rendition-scheme" *ngIf="data.scheme">
+            <label>{{'scheme' | translate}}: </label><span>{{data.scheme}} {{data.schemeVersion}}</span>
+        </div>
+        <div class="rendition-section rendition-content">
+            <label>{{'rules' | translate}}: </label>
+            <span>
+                <evt-content-viewer *ngFor="let element of data.content" [content]="element"></evt-content-viewer>
+            </span>
+        </div>
+    </ng-container>
+    <ng-container *ngIf="!data.scope && !data.selector && !data.scheme && data.id">
+        <div class="rendition-section rendition-content">
+            <label>{{data.id}}:</label>
+            <span>
+                <evt-content-viewer *ngFor="let element of data.content" [content]="element"></evt-content-viewer>
+            </span>
+        </div>
+    </ng-container>
+</div>

+ 22 - 0
src/app/components/rendition/rendition.component.scss

@@ -0,0 +1,22 @@
+.rendition {
+    margin-bottom: 10px;
+
+    &-section {
+        display: flex;
+        align-items: flex-start;
+        justify-content: flex-start;
+    }
+
+    label {
+        margin-bottom: 0;
+        margin-right: 5px;
+        font-weight: bolder;
+        font-variant: all-small-caps;
+    }
+}
+
+::ng-deep .rendition-content span * {
+    font-family: monospace;
+    font-size: 0.9em;
+    font-weight: lighter;
+}

+ 24 - 0
src/app/components/rendition/rendition.component.spec.ts

@@ -0,0 +1,24 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { RenditionComponent } from './rendition.component';
+
+describe('RenditionComponent', () => {
+  let component: RenditionComponent;
+  let fixture: ComponentFixture<RenditionComponent>;
+
+  beforeEach(async() => {
+    await TestBed.configureTestingModule({
+      declarations: [RenditionComponent],
+    })
+      .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(RenditionComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 33 - 0
src/app/components/rendition/rendition.component.ts

@@ -0,0 +1,33 @@
+import { Component, Input } from '@angular/core';
+import { TranslateService } from '@ngx-translate/core';
+import { of } from 'rxjs';
+import { map } from 'rxjs/operators';
+import { Rendition } from '../../models/evt-models';
+import { register } from '../../services/component-register.service';
+import { snakeToCamelCased } from '../../utils/js-utils';
+
+@Component({
+  selector: 'evt-rendition',
+  templateUrl: './rendition.component.html',
+  styleUrls: ['./rendition.component.scss'],
+})
+@register(Rendition)
+export class RenditionComponent {
+  @Input() data: Rendition;
+
+  get scopeDescription$() {
+    if (this.data.scope) {
+      const descKey = snakeToCamelCased(`rendition-${this.data.scope}-desc`);
+
+      return this.translateService.get(descKey).pipe(
+        map(translation => translation === descKey ? '' : `(${translation})`),
+      );
+    }
+
+    return of('');
+  }
+
+  constructor(
+    private translateService: TranslateService,
+  ) { }
+}

+ 14 - 0
src/app/components/resp-stmt/resp-stmt.component.html

@@ -0,0 +1,14 @@
+<div class="resp-stmt">
+    <ng-container *ngIf="data.responsibility">
+        <span class="responsibility" (click)="openNormalizedResp(data.responsibility)" [class.has-link]="data.responsibility.normalizedResp">
+            <ng-container *ngIf="data.responsibility.date">({{data.responsibility.date}}) </ng-container>
+            <evt-content-viewer *ngFor="let el of data.responsibility.content" [content]="el"></evt-content-viewer>
+            {{ ' ' }}
+        </span>
+    </ng-container>
+    <span *ngFor="let subj of data?.people; let i = index">
+        <evt-content-viewer [content]="subj"></evt-content-viewer>
+        <ng-container>{{ i < data?.people.length - 1 ? ', ' : '' }}</ng-container>
+    </span>
+    <evt-content-viewer *ngFor="let note of data?.notes" [content]="note"></evt-content-viewer>
+</div>

+ 6 - 0
src/app/components/resp-stmt/resp-stmt.component.scss

@@ -0,0 +1,6 @@
+.responsibility {
+    font-weight: bold;
+    &.has-link {
+        cursor: pointer;
+    }
+}

+ 25 - 0
src/app/components/resp-stmt/resp-stmt.component.spec.ts

@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { RespStmtComponent } from './resp-stmt.component';
+
+describe('RespStmtComponent', () => {
+  let component: RespStmtComponent;
+  let fixture: ComponentFixture<RespStmtComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ RespStmtComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(RespStmtComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 19 - 0
src/app/components/resp-stmt/resp-stmt.component.ts

@@ -0,0 +1,19 @@
+import { Component, Input } from '@angular/core';
+import { Resp, RespStmt } from '../../models/evt-models';
+import { register } from '../../services/component-register.service';
+
+@Component({
+  selector: 'evt-resp-stmt',
+  templateUrl: './resp-stmt.component.html',
+  styleUrls: ['./resp-stmt.component.scss'],
+})
+@register(RespStmt)
+export class RespStmtComponent {
+  @Input() data: RespStmt;
+
+  openNormalizedResp(resp: Resp) {
+    if (resp.normalizedResp) {
+      window.open(resp.normalizedResp, '_blank');
+    }
+  }
+}

+ 3 - 0
src/app/components/sampling-decl/sampling-decl.component.html

@@ -0,0 +1,3 @@
+<div class="samplingDecl">
+    <evt-content-viewer *ngFor="let content of data?.content" [content]="content"></evt-content-viewer>
+</div>

+ 0 - 0
src/app/components/sampling-decl/sampling-decl.component.scss


+ 24 - 0
src/app/components/sampling-decl/sampling-decl.component.spec.ts

@@ -0,0 +1,24 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { SamplingDeclComponent } from './sampling-decl.component';
+
+describe('SamplingDeclComponent', () => {
+  let component: SamplingDeclComponent;
+  let fixture: ComponentFixture<SamplingDeclComponent>;
+
+  beforeEach(async() => {
+    await TestBed.configureTestingModule({
+      declarations: [ SamplingDeclComponent ],
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(SamplingDeclComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 13 - 0
src/app/components/sampling-decl/sampling-decl.component.ts

@@ -0,0 +1,13 @@
+import { Component, Input } from '@angular/core';
+import { SamplingDecl } from '../../models/evt-models';
+import { register } from '../../services/component-register.service';
+
+@Component({
+  selector: 'evt-sampling-decl',
+  templateUrl: './sampling-decl.component.html',
+  styleUrls: ['./sampling-decl.component.scss'],
+})
+@register(SamplingDecl)
+export class SamplingDeclComponent {
+  @Input() data: SamplingDecl;
+}

+ 24 - 0
src/app/components/series-stmt/series-stmt.component.html

@@ -0,0 +1,24 @@
+<div class="seriesStatement">
+    <ng-container [ngSwitch]="data?.structuredData">
+        <ng-container *ngSwitchCase="true">
+            <ng-container *ngTemplateOutlet="section; context: { label: '', items: data?.title, class: 'title' }"></ng-container>
+            <ng-container *ngTemplateOutlet="section; context: { label: '', items: data?.biblScope, class: 'biblScope' }"></ng-container>
+            <ng-container *ngTemplateOutlet="section; context: { label: '', items: data?.editor, class: 'editor' }"></ng-container>
+            <ng-container *ngTemplateOutlet="section; context: { label: 'idno', items: data?.idno, class: 'idno' }"></ng-container>
+            <ng-container *ngTemplateOutlet="section; context: { label: 'responsibles', items: data?.respStmt, class: 'respStmt', itemsSeparator: ', ' }"></ng-container>
+        </ng-container>
+        <ng-container *ngSwitchDefault>
+            <evt-content-viewer *ngFor="let element of data?.content" [content]="element"></evt-content-viewer>
+        </ng-container>
+    </ng-container>
+</div>
+
+<ng-template #section let-label="label" let-items="items" let-class="class" let-itemsSeparator="itemsSeparator">
+    <evt-header-section *ngIf="items?.length > 0" [label]="label" [additionalClass]="class" [inlineLabel]="true">
+        <div content>
+            <ng-container *ngFor="let item of items; let i = index">
+                <evt-content-viewer [content]="item"></evt-content-viewer><ng-container *ngIf="itemsSeparator && i < items.length - 1">{{ itemsSeparator }}</ng-container> 
+            </ng-container>
+        </div>
+    </evt-header-section>
+</ng-template>

+ 13 - 0
src/app/components/series-stmt/series-stmt.component.scss

@@ -0,0 +1,13 @@
+@import "../../../assets/scss/mixins";
+
+.main-section-title {
+    @include headerSectionTitle();
+}
+
+.seriesStatement {
+    ::ng-deep {
+        .resp-stmt {
+            display: inline-block;
+        }
+    }
+}

+ 25 - 0
src/app/components/series-stmt/series-stmt.component.spec.ts

@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SeriesStmtComponent } from './series-stmt.component';
+
+describe('SeriesStmtComponent', () => {
+  let component: SeriesStmtComponent;
+  let fixture: ComponentFixture<SeriesStmtComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ SeriesStmtComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(SeriesStmtComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 13 - 0
src/app/components/series-stmt/series-stmt.component.ts

@@ -0,0 +1,13 @@
+import { Component, Input } from '@angular/core';
+import { SeriesStmt } from '../../models/evt-models';
+import { register } from '../../services/component-register.service';
+
+@Component({
+  selector: 'evt-series-stmt',
+  templateUrl: './series-stmt.component.html',
+  styleUrls: ['./series-stmt.component.scss'],
+})
+@register(SeriesStmt)
+export class SeriesStmtComponent {
+  @Input() data: SeriesStmt;
+}

+ 12 - 0
src/app/components/tags-decl/tags-decl.component.html

@@ -0,0 +1,12 @@
+<div class="tagsDecl">
+    <ng-container *ngTemplateOutlet="section; context: { label: 'rendition', items: data?.rendition, class: 'rendition' }"></ng-container>
+    <ng-container *ngTemplateOutlet="section; context: { label: 'namespace', items: data?.namespace, class: 'namespace' }"></ng-container>
+</div>
+
+<ng-template #section let-label="label" let-items="items" let-class="class">
+    <evt-header-section *ngIf="items?.length > 0" [label]="label" [additionalClass]="class" [inlineLabel]="true">
+        <div content>
+            <evt-content-viewer *ngFor="let item of items" [content]="item" ></evt-content-viewer>
+        </div>
+    </evt-header-section>
+</ng-template>

+ 0 - 0
src/app/components/tags-decl/tags-decl.component.scss


+ 24 - 0
src/app/components/tags-decl/tags-decl.component.spec.ts

@@ -0,0 +1,24 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { TagsDeclComponent } from './tags-decl.component';
+
+describe('TagsDeclComponent', () => {
+  let component: TagsDeclComponent;
+  let fixture: ComponentFixture<TagsDeclComponent>;
+
+  beforeEach(async() => {
+    await TestBed.configureTestingModule({
+      declarations: [ TagsDeclComponent ],
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(TagsDeclComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 13 - 0
src/app/components/tags-decl/tags-decl.component.ts

@@ -0,0 +1,13 @@
+import { Component, Input } from '@angular/core';
+import { TagsDecl } from '../../models/evt-models';
+import { register } from '../../services/component-register.service';
+
+@Component({
+  selector: 'evt-tags-decl',
+  templateUrl: './tags-decl.component.html',
+  styleUrls: ['./tags-decl.component.scss'],
+})
+@register(TagsDecl)
+export class TagsDeclComponent {
+  @Input() data: TagsDecl;
+}

+ 23 - 0
src/app/components/title-stmt/title-stmt.component.html

@@ -0,0 +1,23 @@
+<div class="main-title" *ngFor="let el of data?.titles" [evtHtmlAttributes]="el?.attributes">
+    <evt-content-viewer [content]="el"></evt-content-viewer>
+</div>
+<div class="sub-title" *ngFor="let el of data?.subtitles" [evtHtmlAttributes]="el?.attributes">
+    <evt-content-viewer [content]="el"></evt-content-viewer>
+</div>
+<div class="author" *ngFor="let author of data?.authors">
+    <evt-content-viewer [content]="author"></evt-content-viewer>
+</div>
+
+<ng-container *ngTemplateOutlet="section; context: { label: 'editors', items: data?.editors, class: 'editor' }"></ng-container>
+<ng-container *ngTemplateOutlet="section; context: { label: 'sponsors', items: data?.sponsors, class: 'sponsor' }"></ng-container>
+<ng-container *ngTemplateOutlet="section; context: { label: 'funders', items: data?.funders, class: 'funder' }"></ng-container>
+<ng-container *ngTemplateOutlet="section; context: { label: 'principals', items: data?.principals, class: 'principal' }"></ng-container>
+<ng-container *ngTemplateOutlet="section; context: { label: 'responsibles', items: data?.respStmts, class: 'principal' }"></ng-container>
+
+<ng-template #section let-label="label" let-items="items" let-class="class">
+    <evt-header-section *ngIf="items?.length > 0" [label]="label" [additionalClass]="class">
+        <div content>
+            <evt-content-viewer *ngFor="let item of items" [content]="item" ></evt-content-viewer>
+        </div>
+    </evt-header-section>
+</ng-template>

+ 21 - 0
src/app/components/title-stmt/title-stmt.component.scss

@@ -0,0 +1,21 @@
+.main-title,
+.sub-title,
+.author {
+    display: block;
+    text-align: center;
+    margin-bottom: 10px;
+}
+.main-title {
+    font-weight: 700;
+    font-size: 1.5em;
+}
+
+.sub-title {
+    font-weight: 700;
+    font-size: 1.2em;
+}
+
+.author {
+    font-size: 1em;
+    font-style: italic;
+}

+ 25 - 0
src/app/components/title-stmt/title-stmt.component.spec.ts

@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TitleStmtComponent } from './title-stmt.component';
+
+describe('TitleStmtComponent', () => {
+  let component: TitleStmtComponent;
+  let fixture: ComponentFixture<TitleStmtComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ TitleStmtComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(TitleStmtComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 14 - 0
src/app/components/title-stmt/title-stmt.component.ts

@@ -0,0 +1,14 @@
+import { Component, Input } from '@angular/core';
+
+import { TitleStmt } from '../../models/evt-models';
+import { register } from '../../services/component-register.service';
+
+@Component({
+  selector: 'evt-title-stmt',
+  templateUrl: './title-stmt.component.html',
+  styleUrls: ['./title-stmt.component.scss'],
+})
+@register(TitleStmt)
+export class TitleStmtComponent {
+  @Input() data: TitleStmt;
+}

+ 10 - 2
src/app/main-menu/main-menu.component.ts

@@ -7,6 +7,7 @@ import { map } from 'rxjs/operators';
 
 import { AppConfig } from '../app.config';
 import { GlobalListsComponent } from '../components/global-lists/global-lists.component';
+import { ProjectInfoComponent } from '../components/project-info/project-info.component';
 import { EvtInfoComponent } from '../evt-info/evt-info.component';
 import { EVTModelService } from '../services/evt-model.service';
 import { ColorTheme, ThemesService } from '../services/themes.service';
@@ -104,9 +105,16 @@ export class MainMenuComponent implements OnInit, OnDestroy {
   }
 
   private openGlobalDialogInfo() {
-    // TODO openGlobalDialogInfo
-    console.log('openGlobalDialogInfo');
     this.itemClicked.emit('globalInfo');
+    const modalRef = this.modalService.open(ModalComponent, { id: 'project-info', animation: false });
+    const modalComp = modalRef.componentInstance as ModalComponent;
+    modalComp.fixedHeight = true;
+    modalComp.wider = true;
+    modalComp.modalId = 'project-info';
+    modalComp.title = 'projectInfo';
+    modalComp.bodyContentClass = 'p-0 h-100';
+    modalComp.headerIcon = { icon: 'info', iconSet: 'fas', additionalClasses: 'mr-3' };
+    modalComp.bodyComponent = ProjectInfoComponent;
   }
 
   private openGlobalDialogLists() {

+ 35 - 6
src/app/models/evt-models.ts

@@ -1,7 +1,6 @@
 import { Type } from '@angular/core';
 import { EditionLevelType } from '../app.config';
 import { ParseResult } from '../services/xml-parsers/parser-models';
-import { Map } from '../utils/js-utils';
 
 export interface EditorialConvention {
     element: string;
@@ -50,8 +49,11 @@ export interface ViewMode {
 export interface Page {
     id: string;
     label: string;
+    facs: string;
     originalContent: OriginalEncodingNodeType[];
     parsedContent: Array<ParseResult<GenericElement>>;
+    url: string;
+    facsUrl: string;
 }
 
 export interface NamedEntities {
@@ -139,8 +141,8 @@ export class NamedEntityRef extends GenericElement {
 }
 
 export interface Witnesses {
-    witnesses: Map<Witness>;
-    groups: Map<WitnessGroup>;
+    witnesses: Witness[];
+    groups: WitnessGroup[];
 }
 
 export interface Witness {
@@ -158,12 +160,14 @@ export interface WitnessGroup {
     witnesses: string[];
     groupId: string;
 }
+
 export class ApparatusEntry extends GenericElement {
     id: string;
     lemma: Reading;
     readings: Reading[];
     notes: Note[];
     originalEncoding: string;
+    nestedAppsIDs: string[];
 }
 
 export class Reading extends GenericElement {
@@ -173,9 +177,9 @@ export class Reading extends GenericElement {
 }
 
 export interface GridItem {
+    id: string;
     url: string;
     name: string;
-    active: boolean;
 }
 
 export type HTML = GenericElement & {
@@ -348,6 +352,8 @@ export class MsPart extends MsFrag {
 
 export class MsDesc extends MsPart {
     id: string;
+    n: string;
+    label: string;
     msFrags: MsFrag[];
 }
 
@@ -848,7 +854,7 @@ export class NotesStmt extends GenericElement {
 
 export class SourceDesc extends GenericElement {
     structuredData: boolean;
-    msDesc: MsDesc;
+    msDescs: MsDesc[];
     bibl: Array<ParseResult<GenericElement>>; // TODO: Add specific type when bibl is handled
     biblFull: Array<ParseResult<GenericElement>>; // TODO: Add specific type when biblFull is handled
     biblStruct: Array<ParseResult<GenericElement>>; // TODO: Add specific type when biblStruct is handled
@@ -864,7 +870,7 @@ export class EncodingDesc extends GenericElement {
     samplingDecl: SamplingDecl[];
     editorialDecl: EditorialDecl[];
     tagsDecl: TagsDecl[];
-    styleDefDecl: Array<ParseResult<GenericElement>>; // TODO: Add specific type when styleDefDecl is handled
+    styleDefDecl: ParseResult<GenericElement>; // TODO: Add specific type when styleDefDecl is handled
     refsDecl: RefsDecl[];
     classDecl: Array<ParseResult<GenericElement>>; // TODO: Add specific type when classDecl is handled
     geoDecl: Array<ParseResult<GenericElement>>; // TODO: Add specific type when geoDecl is handled
@@ -1181,3 +1187,26 @@ export class RevisionDesc extends GenericElement {
     content: Array<ListChange | Change>;
     status?: Status | string;
 }
+
+export class ProjectInfo {
+    fileDesc: FileDesc;
+    encodingDesc: EncodingDesc;
+    profileDesc: ProfileDesc;
+    revisionDesc: RevisionDesc;
+}
+
+export interface ViewerDataValue {
+    manifestURL?: string;
+    xmlImages?: XMLImagesValues[];
+}
+
+export interface XMLImagesValues {
+    url: string;
+    width?: number;
+    height?: number;
+}
+
+export interface ViewerDataType {
+    type: string;
+    value: ViewerDataValue;
+}

Some files were not shown because too many files changed in this diff