From 6de83b801b0414311112e13e90298308c6c6df79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Kilchenmann?= Date: Thu, 4 Mar 2021 07:21:20 +0100 Subject: [PATCH] refactor(ontology): improve ontology editor design (DSP-1376) (#401) * style(ontology): update position of ontology header * style(ontology): start to use card for res class * style(ontology): other icon for external url * refactor(ontology): using mat-card for res class * style(ontology): update res class card * style(ontology): update ontology viewer * feat(ontology): display class type in class card * test(ontology): new test for translateSubClassOf pipe * feat(ontology): add default icons to default class types * chore(ontology): display icons from default classes * chore(ontology): init own res class component * feat(ontology): own res class info component * refactor(eslint): eslint is not yet implemented * refactor(ontology): new res class info setup * test(ontology): init res class info test * chore(ontology): expanded variable as @Input * test(ontology): add test to res class info * chore(ontology): update res class info * feat(ontology): sort ontologies by label * refactor(ontology): replace translate-subclass-of.pipe the pipe was too slow * chore(ontology): clean up commented code * chore(ontology): delete console.log * refactor(ontology): clean up empty lines * docs(ontology): add jsdocs to method --- .eslintignore | 10 - .eslintrc.json | 335 ------------------ src/app/app.module.ts | 4 +- .../default-data/default-properties.ts | 2 +- .../default-data/default-resource-classes.ts | 132 ++++++- .../ontology-form.component.spec.ts | 1 - .../ontology-form/ontology-form.component.ts | 2 - .../project/ontology/ontology.component.html | 53 +-- .../project/ontology/ontology.component.scss | 39 +- .../project/ontology/ontology.component.ts | 21 +- .../property-form/property-form.component.ts | 3 +- .../property-info.component.scss | 1 - .../property-info/property-info.component.ts | 3 - .../resource-class-form.component.ts | 2 - .../resource-class-form.service.ts | 1 - .../resource-class-info.component.html | 57 +++ .../resource-class-info.component.scss | 45 +++ .../resource-class-info.component.spec.ts | 133 +++++++ .../resource-class-info.component.ts | 90 +++++ 19 files changed, 478 insertions(+), 456 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc.json create mode 100644 src/app/project/ontology/resource-class-info/resource-class-info.component.html create mode 100644 src/app/project/ontology/resource-class-info/resource-class-info.component.scss create mode 100644 src/app/project/ontology/resource-class-info/resource-class-info.component.spec.ts create mode 100644 src/app/project/ontology/resource-class-info/resource-class-info.component.ts diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 8372844224..0000000000 --- a/.eslintignore +++ /dev/null @@ -1,10 +0,0 @@ -# copied from @angular/material -# Ignore miscellaneous folders -.github/ -.idea/ -node_modules/ -dist/ -tmp/ - -# Ignore certain project files -docs/assets/js/ \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 17e748d646..0000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,335 +0,0 @@ -{ - "extends": "eslint:recommended", - "parserOptions": { - "ecmaVersion": 8 - }, - "rules": { - "accessor-pairs": "error", - "array-bracket-newline": "off", - "array-bracket-spacing": [ - "error", - "never" - ], - "array-callback-return": "error", - "array-element-newline": "off", - "arrow-body-style": "error", - "arrow-parens": [ - "error", - "as-needed" - ], - "arrow-spacing": "error", - "block-scoped-var": "off", - "block-spacing": "off", - "brace-style": "off", - "callback-return": "off", - "camelcase": "off", - "capitalized-comments": "off", - "class-methods-use-this": "error", - "comma-dangle": "off", - "comma-spacing": "off", - "comma-style": [ - "error", - "last" - ], - "complexity": "off", - "computed-property-spacing": "off", - "consistent-return": "off", - "consistent-this": "off", - "curly": "off", - "default-case": "off", - "dot-location": "off", - "dot-notation": "off", - "eol-last": "off", - "eqeqeq": "off", - "for-direction": "error", - "func-call-spacing": "off", - "func-name-matching": "off", - "func-names": "off", - "func-style": "off", - "generator-star-spacing": "error", - "getter-return": "error", - "global-require": "off", - "guard-for-in": "off", - "handle-callback-err": "error", - "id-blacklist": "error", - "id-length": "off", - "id-match": "error", - "indent": "off", - "indent-legacy": "off", - "init-declarations": "off", - "jsx-quotes": "error", - "key-spacing": "off", - "keyword-spacing": [ - "error", - { - "before": true, - "after": true - } - ], - "line-comment-position": "off", - "linebreak-style": "off", - "lines-around-comment": "off", - "lines-around-directive": "off", - "max-depth": "error", - "max-len": "off", - "max-lines": "off", - "max-nested-callbacks": "error", - "max-params": "off", - "max-statements": "off", - "max-statements-per-line": "off", - "multiline-ternary": "off", - "new-parens": "error", - "newline-after-var": "off", - "newline-before-return": "off", - "newline-per-chained-call": "off", - "no-alert": "off", - "no-array-constructor": "error", - "no-await-in-loop": "error", - "no-bitwise": "off", - "no-buffer-constructor": "error", - "no-caller": "error", - "no-catch-shadow": "error", - "no-confusing-arrow": "error", - "no-console": [ - "off" - ], - "no-constant-condition": [ - "error", - { - "checkLoops": false - } - ], - "no-continue": "off", - "no-div-regex": "off", - "no-duplicate-imports": "error", - "no-else-return": "off", - "no-empty-function": "off", - "no-eq-null": "off", - "no-eval": "error", - "no-extend-native": "off", - "no-extra-bind": "off", - "no-extra-label": "error", - "no-extra-parens": "off", - "no-floating-decimal": "error", - "no-implicit-coercion": [ - "error", - { - "boolean": false, - "number": false, - "string": false - } - ], - "no-implicit-globals": "off", - "no-implied-eval": "error", - "no-inline-comments": "off", - "no-inner-declarations": [ - "error", - "functions" - ], - "no-invalid-this": "off", - "no-iterator": "error", - "no-label-var": "error", - "no-labels": "error", - "no-lone-blocks": "error", - "no-lonely-if": "off", - "no-loop-func": "error", - "no-magic-numbers": "off", - "no-mixed-operators": "off", - "no-mixed-requires": "error", - "no-multi-assign": "off", - "no-multi-spaces": "off", - "no-multi-str": "off", - "no-multiple-empty-lines": "off", - "no-native-reassign": "error", - "no-negated-condition": "off", - "no-negated-in-lhs": "error", - "no-nested-ternary": "off", - "no-new": "error", - "no-new-func": "error", - "no-new-object": "error", - "no-new-require": "error", - "no-new-wrappers": "error", - "no-octal-escape": "error", - "no-param-reassign": "off", - "no-path-concat": "error", - "no-plusplus": "off", - "no-process-env": "off", - "no-process-exit": "error", - "no-proto": "error", - "no-prototype-builtins": "off", - "no-restricted-globals": "error", - "no-restricted-imports": "error", - "no-restricted-modules": "error", - "no-restricted-properties": "error", - "no-restricted-syntax": "error", - "no-return-assign": "off", - "no-return-await": "error", - "no-script-url": "error", - "no-self-compare": "error", - "no-sequences": "off", - "no-shadow": "off", - "no-shadow-restricted-names": "error", - "no-spaced-func": "off", - "no-sync": "off", - "no-tabs": "off", - "no-template-curly-in-string": "error", - "no-ternary": "off", - "no-throw-literal": "off", - "no-trailing-spaces": [ - "error" - ], - "no-undef-init": "error", - "no-undefined": "off", - "no-underscore-dangle": "off", - "no-unmodified-loop-condition": "error", - "no-unneeded-ternary": "off", - "no-unused-expressions": "off", - "no-use-before-define": "off", - "no-useless-call": "error", - "no-useless-computed-key": "error", - "no-useless-concat": "off", - "no-useless-constructor": "error", - "no-useless-rename": "error", - "no-useless-return": "error", - "no-var": "off", - "no-void": "off", - "no-warning-comments": "off", - "no-whitespace-before-property": "error", - "no-with": "error", - "nonblock-statement-body-position": [ - "error", - "any" - ], - "object-curly-newline": "off", - "object-curly-spacing": "off", - "object-property-newline": "off", - "object-shorthand": "off", - "one-var": "off", - "one-var-declaration-per-line": "off", - "operator-assignment": "off", - "operator-linebreak": "off", - "padded-blocks": "off", - "padding-line-between-statements": "error", - "prefer-arrow-callback": "off", - "prefer-const": "error", - "prefer-destructuring": "off", - "prefer-numeric-literals": "error", - "prefer-promise-reject-errors": "error", - "prefer-reflect": "off", - "prefer-rest-params": "off", - "prefer-spread": "off", - "prefer-template": "off", - "quote-props": "off", - "quotes": "off", - "radix": "off", - "require-await": "error", - "require-jsdoc": "off", - "rest-spread-spacing": "error", - "semi": "off", - "semi-spacing": "off", - "semi-style": "off", - "sort-imports": "error", - "sort-keys": "off", - "sort-vars": "off", - "space-before-blocks": "off", - "space-before-function-paren": "off", - "space-in-parens": [ - "error", - "never" - ], - "space-infix-ops": "off", - "space-unary-ops": "off", - "spaced-comment": [ - "error", - "always", - { - "exceptions": [ - "*" - ] - } - ], - "strict": "off", - "switch-colon-spacing": "off", - "symbol-description": "error", - "template-curly-spacing": "error", - "template-tag-spacing": "error", - "unicode-bom": [ - "error", - "never" - ], - "valid-jsdoc": "off", - "vars-on-top": "off", - "wrap-iife": "off", - "wrap-regex": "off", - "yield-star-spacing": "error", - "yoda": "off", - "no-unused-vars": "off", - "no-cond-assign": "off", - "no-unexpected-multiline": "off" - }, - "env": { - "node": true - }, - "globals": { - "angular": true, - "moment": true - }, - "overrides": [ - { - "files": [ - "docs/app/js/**/*", - "src/**/*" - ], - "parserOptions": { - "ecmaVersion": 5 - }, - "env": { - "browser": true - }, - "rules": { - "arrow-parens": "error", - "global-require": "error", - "no-console": [ - "error" - ], - "no-process-env": "error", - "no-sync": "error" - }, - "globals": { - "angular": true, - "CryptoJS": true, - "hljs": true - } - }, - { - "files": [ - "scripts/**/*" - ], - "rules": { - "no-process-env": "off" - } - }, - { - "files": [ - "**/*.spec.js" - ], - "env": { - "jasmine": true - }, - "rules": { - "no-native-reassign": "off", - "no-global-assign": "off" - }, - "globals": { - "module": true, - "inject": true, - "disableAnimations": true, - "createMockStyleSheet": true, - "$mdUtil": false, - "$timeout": false, - "$animate": false, - "$material": false - } - } - ] -} \ No newline at end of file diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 5437945c29..0a5f5604f8 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -91,6 +91,7 @@ import { AddressTemplateComponent } from './project/board/address-template/addre import { OrganisationTemplateComponent } from './project/board/organisation-template/organisation-template.component'; import { EditListItemComponent } from './project/list/list-item-form/edit-list-item/edit-list-item.component'; import { PropertyInfoComponent } from './project/ontology/property-info/property-info.component'; +import { ResourceClassInfoComponent } from './project/ontology/resource-class-info/resource-class-info.component'; // translate: AoT requires an exported function for factories export function HttpLoaderFactory(httpClient: HttpClient) { @@ -164,7 +165,8 @@ export function HttpLoaderFactory(httpClient: HttpClient) { AddressTemplateComponent, OrganisationTemplateComponent, EditListItemComponent, - PropertyInfoComponent + PropertyInfoComponent, + ResourceClassInfoComponent ], imports: [ AppRoutingModule, diff --git a/src/app/project/ontology/default-data/default-properties.ts b/src/app/project/ontology/default-data/default-properties.ts index ad81a63276..301ff1ba93 100644 --- a/src/app/project/ontology/default-data/default-properties.ts +++ b/src/app/project/ontology/default-data/default-properties.ts @@ -160,7 +160,7 @@ export class DefaultProperties { // group: 'Link' // }, { - icon: 'http', + icon: 'language', label: 'External URL', subPropOf: Constants.HasValue, objectType: Constants.UriValue, diff --git a/src/app/project/ontology/default-data/default-resource-classes.ts b/src/app/project/ontology/default-data/default-resource-classes.ts index 3963d9e078..704bc7e5c2 100644 --- a/src/app/project/ontology/default-data/default-resource-classes.ts +++ b/src/app/project/ontology/default-data/default-resource-classes.ts @@ -1,41 +1,153 @@ import { Constants } from '@dasch-swiss/dsp-js'; -export interface DefaultClass { +export interface DefaultInfo { iri: string; label: string; - icons?: string[]; + icons?: string[]; // icons can be used to be selected in the resource class form } export class DefaultResourceClasses { - public static data: DefaultClass[] = [ + public static data: DefaultInfo[] = [ { iri: Constants.Resource, - label: 'Object without file representation (metadata only)' + label: 'Object without file representation (metadata only)', + icons: [ + 'person', + 'person_outline', + 'group', + 'groups', + 'people_alt', + 'note', + 'notes', + 'text_snippet', + 'short_text', + 'comment', + 'event_note', + 'emoji_symbols', + 'calculate', + 'functions', + 'house', + 'location_city', + 'science', + 'school', + 'emoji_transportation', + 'local_bar', + 'fastfood', + 'restaurant', + 'collections', + 'portrait', + 'auto_stories', + 'book', + 'import_contacts', + 'menu_book', + 'commute', + 'map', + 'satellite', + 'public', + 'language', + 'devices', + 'devices_other', + 'important_devices', + 'source' + ] }, { iri: Constants.KnoraApiV2 + Constants.HashDelimiter + 'StillImageRepresentation', - label: 'Still Image' + label: 'Still Image', + icons: [ + 'photo', + 'panorama', + 'photo_library', + 'camera_roll', + 'camera', + 'camera_alt', + 'camera_enhance', + 'portrait', + 'auto_stories', + 'book', + 'import_contacts', + 'menu_book', + 'note', + 'sticky_note_2', + 'account_balance', + 'museum', + 'theater_comedy', + 'landscape', + 'nature_people', + 'screenshot', + 'wallpaper' + ] }, { iri: Constants.KnoraApiV2 + Constants.HashDelimiter + 'MovingImageRepresentation', - label: 'Moving Image' + label: 'Moving Image', + icons: [ + 'movie', + 'theaters', + 'slideshow', + 'live_tv', + 'animation', + 'music_video', + 'play_circle_filled', + 'play_circle_outline', + 'videocam', + 'video_library', + 'duo', + 'subtitles' + ] }, { iri: Constants.KnoraApiV2 + Constants.HashDelimiter + 'AudioRepresentation', - label: 'Audio' + label: 'Audio', + icons: [ + 'audiotrack', + 'music_note', + 'graphiq_eq', + 'headphones', + 'volume_up', + 'mic', + 'equalizer', + 'speaker', + 'album', + 'voicemail', + 'music_video', + 'library_music', + 'radio' + ] }, { iri: Constants.KnoraApiV2 + Constants.HashDelimiter + 'TextRepresentation', - label: 'Text' + label: 'Text', + icons: [ + 'rtt', + 'notes', + 'subject', + 'chrome_reader_mode' + ] }, { iri: Constants.KnoraApiV2 + Constants.HashDelimiter + 'DocumentRepresentation', - label: 'Document (Word, PDF, etc.)' + label: 'Document (Word, PDF, etc.)', + icons: [ + 'description', + 'article', + 'text_snippet', + 'picture_as_pdf', + 'mark_as_unread', + 'history_edu', + 'mail', + 'drafts', + 'library_books' + ] }, { iri: Constants.KnoraApiV2 + Constants.HashDelimiter + 'DDDRepresentation', - label: 'RTI Image' + label: 'RTI Image', + icons: [ + 'view_in_ar', + 'layers' + ] } ]; diff --git a/src/app/project/ontology/ontology-form/ontology-form.component.spec.ts b/src/app/project/ontology/ontology-form/ontology-form.component.spec.ts index 21f901f728..37ea33a733 100644 --- a/src/app/project/ontology/ontology-form/ontology-form.component.spec.ts +++ b/src/app/project/ontology/ontology-form/ontology-form.component.spec.ts @@ -70,7 +70,6 @@ describe('OntologyFormComponent', () => { label: null }); - fixture.detectChanges(); }); diff --git a/src/app/project/ontology/ontology-form/ontology-form.component.ts b/src/app/project/ontology/ontology-form/ontology-form.component.ts index f01c2fd448..5a2266deb6 100644 --- a/src/app/project/ontology/ontology-form/ontology-form.component.ts +++ b/src/app/project/ontology/ontology-form/ontology-form.component.ts @@ -211,6 +211,4 @@ export class OntologyFormComponent implements OnInit { } - - } diff --git a/src/app/project/ontology/ontology.component.html b/src/app/project/ontology/ontology.component.html index 62042189e9..b1aa9a2b16 100644 --- a/src/app/project/ontology/ontology.component.html +++ b/src/app/project/ontology/ontology.component.html @@ -104,7 +104,7 @@

{{ontology?.label @@ -129,9 +129,9 @@

{{ontology?.label Add class - @@ -145,46 +145,12 @@

{{ontology?.label
-
- - -

- {{resClass.label | dspTruncate: 24}}

- - - - - - - - - -
-
- - - - - - - - -
+ +
@@ -214,7 +180,6 @@

-
diff --git a/src/app/project/ontology/ontology.component.scss b/src/app/project/ontology/ontology.component.scss index b385f4c099..a3f7f20337 100644 --- a/src/app/project/ontology/ontology.component.scss +++ b/src/app/project/ontology/ontology.component.scss @@ -29,6 +29,12 @@ $width: 340px; border: 1px dotted $primary_200; position: relative; margin: 0 auto 60px auto; + z-index: 0; + + .ontology-editor-header { + z-index: 2; + top: 121px; + } } .ontology-viewer { width: 98vw; @@ -69,39 +75,6 @@ $width: 340px; } } -.resource-class { - min-height: 120px; - height: auto; - position: relative; - @include mat-elevation-transition; - @include mat-elevation(2); - padding: 12px; - margin: 6px; - background-color: #fff; - - .resource-class-header { - margin: -12px -12px 0 -12px; - width: calc(100% + 24px); - min-height: 48px !important; - height: 48px; - cursor: move; - } - - .resource-class-properties { - li.property-info { - list-style-type: none; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - margin-left: -24px; - list-style-position: inside; - } - } - - &:active { - @include mat-elevation(8); - } -} .ontology-editor-header { border-bottom: 1px solid rgba(0, 0, 0, 0.12); position: sticky !important; diff --git a/src/app/project/ontology/ontology.component.ts b/src/app/project/ontology/ontology.component.ts index 6f325f6b0b..55b2e6502d 100644 --- a/src/app/project/ontology/ontology.component.ts +++ b/src/app/project/ontology/ontology.component.ts @@ -25,7 +25,7 @@ import { DspApiConnectionToken, Session, SessionService, SortingService } from ' import { CacheService } from 'src/app/main/cache/cache.service'; import { DialogComponent } from 'src/app/main/dialog/dialog.component'; import { ErrorHandlerService } from 'src/app/main/error/error-handler.service'; -import { DefaultClass, DefaultResourceClasses } from './default-data/default-resource-classes'; +import { DefaultInfo, DefaultResourceClasses } from './default-data/default-resource-classes'; import { ResourceClassFormService } from './resource-class-form/resource-class-form.service'; export interface OntologyInfo { @@ -90,7 +90,7 @@ export class OntologyComponent implements OnInit { /** * list of all default resource classes (sub class of) */ - resourceClass: DefaultClass[] = DefaultResourceClasses.data; + defaultClasses: DefaultInfo[] = DefaultResourceClasses.data; @ViewChild('ontologyEditor', { read: ViewContainerRef }) ontologyEditor: ViewContainerRef; @@ -202,7 +202,7 @@ export class OntologyComponent implements OnInit { this.ontologyIri = response.ontologies[0].id; } - response.ontologies.forEach((ontoMeta, index, array) => { + response.ontologies.forEach(ontoMeta => { // set list of already existing ontology names // it will be used in ontology form // because ontology name has to be unique @@ -212,6 +212,7 @@ export class OntologyComponent implements OnInit { // get each ontology this._dspApiConnection.v2.onto.getOntology(ontoMeta.id, true).subscribe( (readOnto: ReadOntology) => { + this.ontologies.push(readOnto); if (ontoMeta.id === this.ontologyIri) { @@ -261,6 +262,7 @@ export class OntologyComponent implements OnInit { this.loadOntology = false; } if (response.ontologies.length === this.ontologies.length) { + this.ontologies = this._sortingService.keySortByAlphabetical(this.ontologies, 'label'); this._cache.set('currentProjectOntologies', this.ontologies); this.setCache(); } @@ -357,7 +359,7 @@ export class OntologyComponent implements OnInit { * @param mode * @param resClassInfo (could be subClassOf (create mode) or resource class itself (edit mode)) */ - openResourceClassForm(mode: 'createResourceClass' | 'editResourceClass', resClassInfo: DefaultClass): void { + openResourceClassForm(mode: 'createResourceClass' | 'editResourceClass', resClassInfo: DefaultInfo): void { const dialogConfig: MatDialogConfig = { disableClose: true, @@ -380,7 +382,6 @@ export class OntologyComponent implements OnInit { }); } - /** * Updates cardinality * @param subClassOf resource class @@ -409,20 +410,20 @@ export class OntologyComponent implements OnInit { } /** - * Delete either ontology or sourcetype + * Delete either ontology or resource class * - * @param id * @param mode Can be 'Ontology' or 'ResourceClass' + * @param id * @param title */ - delete(id: string, mode: 'Ontology' | 'ResourceClass', title: string) { + delete(mode: 'Ontology' | 'ResourceClass', info: DefaultInfo) { const dialogConfig: MatDialogConfig = { width: '560px', maxHeight: '80vh', position: { top: '112px' }, - data: { mode: 'delete' + mode, title: title } + data: { mode: 'delete' + mode, title: info.label } }; const dialogRef = this._dialog.open( @@ -462,7 +463,7 @@ export class OntologyComponent implements OnInit { // delete resource class and refresh the view this.loadOntology = true; const resClass: DeleteResourceClass = new DeleteResourceClass(); - resClass.id = id; + resClass.id = info.iri; resClass.lastModificationDate = this.ontology.lastModificationDate; this._dspApiConnection.v2.onto.deleteResourceClass(resClass).subscribe( (response: OntologyMetadata) => { diff --git a/src/app/project/ontology/property-form/property-form.component.ts b/src/app/project/ontology/property-form/property-form.component.ts index c00a32099d..49dbefa972 100644 --- a/src/app/project/ontology/property-form/property-form.component.ts +++ b/src/app/project/ontology/property-form/property-form.component.ts @@ -18,8 +18,7 @@ import { Observable } from 'rxjs'; import { map, startWith } from 'rxjs/operators'; import { CacheService } from 'src/app/main/cache/cache.service'; import { ErrorHandlerService } from 'src/app/main/error/error-handler.service'; -import { DefaultProperties, Category, PropertyType } from '../default-data/default-properties'; - +import { Category, DefaultProperties, PropertyType } from '../default-data/default-properties'; // TODO: should be removed and replaced by AutocompleteItem from dsp-ui /** diff --git a/src/app/project/ontology/property-info/property-info.component.scss b/src/app/project/ontology/property-info/property-info.component.scss index 160e1defdd..2edc608115 100644 --- a/src/app/project/ontology/property-info/property-info.component.scss +++ b/src/app/project/ontology/property-info/property-info.component.scss @@ -27,7 +27,6 @@ } } - .mat-line.info { font-size: small; diff --git a/src/app/project/ontology/property-info/property-info.component.ts b/src/app/project/ontology/property-info/property-info.component.ts index 3af7aac457..ee8ff5b447 100644 --- a/src/app/project/ontology/property-info/property-info.component.ts +++ b/src/app/project/ontology/property-info/property-info.component.ts @@ -152,9 +152,6 @@ export class PropertyInfoComponent implements OnInit, AfterContentInit { } } ); - - - } } diff --git a/src/app/project/ontology/resource-class-form/resource-class-form.component.ts b/src/app/project/ontology/resource-class-form/resource-class-form.component.ts index 62308fcb2f..d02d80e011 100644 --- a/src/app/project/ontology/resource-class-form/resource-class-form.component.ts +++ b/src/app/project/ontology/resource-class-form/resource-class-form.component.ts @@ -145,7 +145,6 @@ export class ResourceClassFormComponent implements OnInit, OnDestroy, AfterViewC selectedLanguage = 'en'; languages: StringLiteral[] = AppGlobal.languagesList; - constructor( @Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection, private _cache: CacheService, @@ -426,7 +425,6 @@ export class ResourceClassFormComponent implements OnInit, OnDestroy, AfterViewC } ); - } else { // edit mode: res class cardinality // submit properties and set cardinality diff --git a/src/app/project/ontology/resource-class-form/resource-class-form.service.ts b/src/app/project/ontology/resource-class-form/resource-class-form.service.ts index 0a60739ed5..0423b9aacd 100644 --- a/src/app/project/ontology/resource-class-form/resource-class-form.service.ts +++ b/src/app/project/ontology/resource-class-form/resource-class-form.service.ts @@ -178,7 +178,6 @@ export class ResourceClassFormService { } - /** * add new property line */ diff --git a/src/app/project/ontology/resource-class-info/resource-class-info.component.html b/src/app/project/ontology/resource-class-info/resource-class-info.component.html new file mode 100644 index 0000000000..33943ae9be --- /dev/null +++ b/src/app/project/ontology/resource-class-info/resource-class-info.component.html @@ -0,0 +1,57 @@ + + + + + + + {{resourceClass.label | dspTruncate: 24}} + + + + {{subClassOfLabel}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/project/ontology/resource-class-info/resource-class-info.component.scss b/src/app/project/ontology/resource-class-info/resource-class-info.component.scss new file mode 100644 index 0000000000..1478799fb2 --- /dev/null +++ b/src/app/project/ontology/resource-class-info/resource-class-info.component.scss @@ -0,0 +1,45 @@ +@import "~@angular/material/theming"; +@import "../../../../assets/style/config"; + +.resource-class { + min-height: 120px; + height: auto; + position: relative; + @include mat-elevation-transition; + @include mat-elevation(2); + padding: 12px; + margin: 6px; + background-color: #fff; + + .resource-class-header { + cursor: move; + margin-left: -12px; + + .resource-class-header-action { + position: absolute; + right: 0; + top: 0; + } + } + + .resource-class-footer { + position: absolute; + bottom: 14px; + right: 14px; + } + + .resource-class-properties { + li.property-info { + list-style-type: none; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-left: -24px; + list-style-position: inside; + } + } + + &:active { + @include mat-elevation(8); + } +} diff --git a/src/app/project/ontology/resource-class-info/resource-class-info.component.spec.ts b/src/app/project/ontology/resource-class-info/resource-class-info.component.spec.ts new file mode 100644 index 0000000000..b92524ea03 --- /dev/null +++ b/src/app/project/ontology/resource-class-info/resource-class-info.component.spec.ts @@ -0,0 +1,133 @@ +import { Component, DebugElement, OnInit, ViewChild } from '@angular/core'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatCardModule } from '@angular/material/card'; +import { MatIconModule } from '@angular/material/icon'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { By } from '@angular/platform-browser'; +import { ClassDefinition, Constants, MockOntology, ReadOntology } from '@dasch-swiss/dsp-js'; +import { DspActionModule, SortingService } from '@dasch-swiss/dsp-ui'; +import { of } from 'rxjs'; +import { CacheService } from 'src/app/main/cache/cache.service'; +import { ResourceClassInfoComponent } from './resource-class-info.component'; + +/** + * test host component to simulate parent component + * Property is of type simple text + */ +@Component({ + template: `` +}) +class HostComponent implements OnInit { + + @ViewChild('resClassInfo') resourceClassInfoComponent: ResourceClassInfoComponent; + + // get ontology from DSP-JS-Lib test data + ontology: ReadOntology; + + resourceClass: ClassDefinition; + + constructor( + private _cache: CacheService, + private _sortingService: SortingService + ) { + + } + + ngOnInit() { + + this._cache.get('currentOntology').subscribe( + (response: ReadOntology) => { + this.ontology = response; + + const allOntoClasses = response.getAllClassDefinitions(); + // reset the ontology classes + let classesToDisplay = []; + + // display only the classes which are not a subClass of Standoff + allOntoClasses.forEach(resClass => { + if (resClass.subClassOf.length) { + const splittedSubClass = resClass.subClassOf[0].split('#'); + if (!splittedSubClass[0].includes(Constants.StandoffOntology) && !splittedSubClass[1].includes('Standoff')) { + classesToDisplay.push(resClass); + } + } + }); + classesToDisplay = this._sortingService.keySortByAlphabetical(classesToDisplay, 'label'); + this.resourceClass = classesToDisplay[0]; + } + ); + + } + +} + +describe('ResourceClassInfoComponent', () => { + let hostComponent: HostComponent; + let hostFixture: ComponentFixture; + + beforeEach(async(() => { + const dspConnSpy = { + v2: { + onto: jasmine.createSpyObj('onto', ['getOntology']), + } + }; + + const cacheServiceSpy = jasmine.createSpyObj('CacheService', ['get']); + + TestBed.configureTestingModule({ + declarations: [ + HostComponent, + ResourceClassInfoComponent + ], + imports: [ + DspActionModule, + MatCardModule, + MatIconModule, + MatMenuModule, + MatTooltipModule + ], + providers: [ + { + provide: CacheService, + useValue: cacheServiceSpy + } + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + // mock cache service for currentOntology + const cacheSpy = TestBed.inject(CacheService); + + (cacheSpy as jasmine.SpyObj).get.and.callFake( + () => { + const response: ReadOntology = MockOntology.mockReadOntology('http://0.0.0.0:3333/ontology/0001/anything/v2'); + return of(response); + } + ); + + hostFixture = TestBed.createComponent(HostComponent); + hostComponent = hostFixture.componentInstance; + hostFixture.detectChanges(); + + expect(hostComponent).toBeTruthy(); + + }); + + it('expect title to be "Blue thing" and subclass of "Thing"', () => { + expect(hostComponent.resourceClassInfoComponent).toBeTruthy(); + expect(hostComponent.resourceClassInfoComponent.resourceClass).toBeDefined(); + + const hostCompDe = hostFixture.debugElement; + + const title: DebugElement = hostCompDe.query(By.css('mat-card-title')); + + expect(title.nativeElement.innerText).toEqual('Blue thing'); + + const subtitle: DebugElement = hostCompDe.query(By.css('mat-card-subtitle')); + + expect(subtitle.nativeElement.innerText).toEqual('Thing'); + }); +}); diff --git a/src/app/project/ontology/resource-class-info/resource-class-info.component.ts b/src/app/project/ontology/resource-class-info/resource-class-info.component.ts new file mode 100644 index 0000000000..4cdc2bf47e --- /dev/null +++ b/src/app/project/ontology/resource-class-info/resource-class-info.component.ts @@ -0,0 +1,90 @@ +import { EventEmitter, Input, Output } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; +import { ApiResponseError, ClassDefinition, ReadOntology } from '@dasch-swiss/dsp-js'; +import { CacheService } from 'src/app/main/cache/cache.service'; +import { ErrorHandlerService } from 'src/app/main/error/error-handler.service'; +import { DefaultInfo, DefaultResourceClasses } from '../default-data/default-resource-classes'; + +@Component({ + selector: 'app-resource-class-info', + templateUrl: './resource-class-info.component.html', + styleUrls: ['./resource-class-info.component.scss'] +}) +export class ResourceClassInfoComponent implements OnInit { + + // open / close res class card + @Input() expanded = false; + + @Input() resourceClass: ClassDefinition; + + @Output() editResourceClass: EventEmitter = new EventEmitter(); + @Output() updateCardinality: EventEmitter = new EventEmitter(); + @Output() deleteResourceClass: EventEmitter = new EventEmitter(); + + ontology: ReadOntology; + + subClassOfLabel = ''; + + // list of default classes + defaultClasses: DefaultInfo[] = DefaultResourceClasses.data; + + constructor( + private _cache: CacheService, + private _errorHandler: ErrorHandlerService, + ) { } + + ngOnInit(): void { + this._cache.get('currentOntology').subscribe( + (response: ReadOntology) => { + this.ontology = response; + this.translateSubClassOfIri(this.resourceClass.subClassOf); + }, + (error: ApiResponseError) => { + this._errorHandler.showMessage(error); + } + ); + } + + + /** + * translates iri from "sub class of" array + * - display label from default resource classes (as part of Knora System Project) + * - in case the class is a subclass of another class in the same ontology: display this class label + * - in none of those cases display the name from the class IRI + * + * @param classIris + */ + translateSubClassOfIri(classIris: string[]) { + + classIris.forEach((iri, index) => { + // get ontology iri from class iri + const splittedIri = iri.split('#'); + const ontologyIri = splittedIri[0]; + const className = splittedIri[1]; + + this.subClassOfLabel += (index > 0 ? ', ' : ''); + + // find default class for the current class iri + const defaultClass = this.defaultClasses.find(i => i.iri === iri); + if (defaultClass) { + this.subClassOfLabel += defaultClass.label; + } else if (this.ontology.id === ontologyIri) { + // the class is not defined in the default classes + // but defined in the current ontology + // get class label from there + this.subClassOfLabel += this.ontology.classes[iri].label; + } else { + // the ontology iri of the upper class couldn't be found + // display the class name + if (className) { + this.subClassOfLabel += className; + } else { + // iri is not kind of [ontologyIri]#[className] + this.subClassOfLabel += iri.split('/').filter(e => e).slice(-1); + } + } + }); + + } + +}