From 2f4531665d3cc8ab10d43d0e9d6175d935465477 Mon Sep 17 00:00:00 2001 From: Irmantas Kaukas Date: Wed, 10 Apr 2024 09:13:37 +0200 Subject: [PATCH] feat: ux-improvements (DEV-) (#1542) --- .github/workflows/publish-from-branch.yml | 6 +- .../e2e/logged-out-user/dashboard.cy.ts | 5 +- .../cypress/e2e/system-admin/dashboard.cy.ts | 6 +- apps/dsp-app/src/app/app.module.ts | 2 + .../main/directive/base-value.directive.ts | 44 ++-- .../project-tile/project-tile.component.html | 41 +--- .../project-tile/project-tile.component.scss | 107 +++------ .../app/user/overview/overview.component.html | 3 - .../app/user/overview/overview.component.scss | 119 +++++----- .../app/user/overview/overview.component.ts | 5 - .../display-edit/display-edit.component.html | 28 +-- .../display-edit/display-edit.component.scss | 10 + .../display-edit/display-edit.component.ts | 40 ---- .../properties/properties.component.html | 175 +++----------- .../properties/properties.component.scss | 85 ++++--- .../properties/properties.component.ts | 203 +--------------- .../resource-toolbar/resource-toolbar.html | 148 ++++++++++++ .../resource-toolbar/resource-toolbar.scss | 12 + .../resource-toolbar/resource-toolbar.ts | 217 ++++++++++++++++++ .../still-image/still-image.component.html | 91 ++++---- .../still-image/still-image.component.scss | 11 + .../resource/resource.component.html | 60 ++++- .../resource/resource.component.scss | 73 +++++- .../workspace/resource/resource.component.ts | 157 +++++++++++-- .../boolean-value.component.html | 2 +- .../boolean-value/boolean-value.component.ts | 8 +- .../color-value/color-value.component.html | 2 +- .../color-value/color-value.component.ts | 8 +- .../date-value/date-value.component.html | 2 +- .../values/date-value/date-value.component.ts | 8 +- .../decimal-value.component.html | 2 +- .../decimal-value/decimal-value.component.ts | 8 +- .../geoname-value.component.html | 2 +- .../geoname-value/geoname-value.component.ts | 4 +- .../values/int-value/int-value.component.html | 2 +- .../values/int-value/int-value.component.ts | 8 +- .../interval-value.component.html | 2 +- .../interval-value.component.ts | 8 +- .../link-value/link-value.component.html | 7 +- .../values/link-value/link-value.component.ts | 10 +- .../list-value/list-value.component.html | 2 +- .../values/list-value/list-value.component.ts | 4 +- .../text-value-as-html.component.html | 4 +- .../text-value-as-string.component.html | 10 +- .../text-value-as-string.component.spec.ts | 1 - .../text-value-as-string.component.ts | 8 +- .../text-value-as-xml.component.html | 24 +- .../text-value-as-xml.component.ts | 5 +- .../time-value/time-value.component.html | 2 +- .../values/time-value/time-value.component.ts | 12 +- .../values/uri-value/uri-value.component.html | 2 +- .../values/uri-value/uri-value.component.ts | 8 +- apps/dsp-app/src/assets/i18n/de.json | 3 + apps/dsp-app/src/assets/i18n/en.json | 3 + apps/dsp-app/src/styles/_elements.scss | 10 +- apps/dsp-app/src/styles/_layout.scss | 7 +- apps/dsp-app/src/styles/_viewer.scss | 21 ++ .../advanced-search-store.service.ts | 7 +- .../src/lib/resource/resource.actions.ts | 8 + .../src/lib/resource/resource.selectors.ts | 10 + .../src/lib/resource/resource.state-model.ts | 2 + .../src/lib/resource/resource.state.ts | 19 +- package.json | 2 +- 63 files changed, 1127 insertions(+), 778 deletions(-) create mode 100644 apps/dsp-app/src/app/workspace/resource/properties/resource-toolbar/resource-toolbar.html create mode 100644 apps/dsp-app/src/app/workspace/resource/properties/resource-toolbar/resource-toolbar.scss create mode 100644 apps/dsp-app/src/app/workspace/resource/properties/resource-toolbar/resource-toolbar.ts diff --git a/.github/workflows/publish-from-branch.yml b/.github/workflows/publish-from-branch.yml index 24f2fe974d..4cb20e1534 100644 --- a/.github/workflows/publish-from-branch.yml +++ b/.github/workflows/publish-from-branch.yml @@ -1,7 +1,6 @@ name: Publish from branch -on: - workflow_dispatch +on: workflow_dispatch jobs: publish-from-branch: name: Publish from branch @@ -18,4 +17,7 @@ jobs: with: username: ${{ secrets.DOCKER_USER }} password: ${{ secrets.DOCKER_HUB_TOKEN }} + - run: | + echo "//npm.pkg.github.com/:_authToken=${{ secrets.ORG_GITHUB_PACKAGES_PAT }}" > ~/.npmrc + echo "@dasch-swiss:registry=https://npm.pkg.github.com/dasch-swiss" >> ~/.npmrc - run: make docker-publish diff --git a/apps/dsp-app/cypress/e2e/logged-out-user/dashboard.cy.ts b/apps/dsp-app/cypress/e2e/logged-out-user/dashboard.cy.ts index 397343f57f..721dac4436 100644 --- a/apps/dsp-app/cypress/e2e/logged-out-user/dashboard.cy.ts +++ b/apps/dsp-app/cypress/e2e/logged-out-user/dashboard.cy.ts @@ -2,11 +2,10 @@ describe('Visual Check for Projects on Home Page', () => { it('should load clickable projects on the home page', () => { cy.visit('/'); - cy.get('[data-cy=tile]') + cy.get('[data-cy=navigate-to-project-button]') .should('be.visible') .should('have.length.greaterThan', 0) - .first() - .find('[data-cy=navigate-to-project-button]') + .eq(Math.floor(Math.random() * 5)) .click(); cy.url().should('include', '/project'); // Update with the expected URL diff --git a/apps/dsp-app/cypress/e2e/system-admin/dashboard.cy.ts b/apps/dsp-app/cypress/e2e/system-admin/dashboard.cy.ts index 60a16d1017..a94665e53c 100644 --- a/apps/dsp-app/cypress/e2e/system-admin/dashboard.cy.ts +++ b/apps/dsp-app/cypress/e2e/system-admin/dashboard.cy.ts @@ -1,11 +1,13 @@ describe('Dashboard', () => { it('should load clickable projects on the home page', () => { cy.visit('/'); - const projectTileSelector = '[data-cy=tile]'; + const projectTileSelector = '[data-cy=navigate-to-project-button]'; cy.get(projectTileSelector).should('be.visible').should('have.length.greaterThan', 0); - cy.get(projectTileSelector).first().find('[data-cy=navigate-to-project-button]').click(); + cy.get(projectTileSelector) + .eq(Math.floor(Math.random() * 5)) + .click(); cy.url().should('include', '/project'); // Update with the expected URL }); diff --git a/apps/dsp-app/src/app/app.module.ts b/apps/dsp-app/src/app/app.module.ts index 38ee307b9d..c880d8b3de 100644 --- a/apps/dsp-app/src/app/app.module.ts +++ b/apps/dsp-app/src/app/app.module.ts @@ -137,6 +137,7 @@ import { CreateLinkResourceComponent } from './workspace/resource/operations/cre import { DisplayEditComponent } from './workspace/resource/operations/display-edit/display-edit.component'; import { PermissionInfoComponent } from './workspace/resource/permission-info/permission-info.component'; import { PropertiesComponent } from './workspace/resource/properties/properties.component'; +import { ResourceToolbarComponent } from './workspace/resource/properties/resource-toolbar/resource-toolbar'; import { AddRegionFormComponent } from './workspace/resource/representation/add-region-form/add-region-form.component'; import { ArchiveComponent } from './workspace/resource/representation/archive/archive.component'; import { AudioComponent } from './workspace/resource/representation/audio/audio.component'; @@ -285,6 +286,7 @@ export function httpLoaderFactory(httpClient: HttpClient) { ResourceInstanceFormComponent, ResourceLinkFormComponent, ResourceListComponent, + ResourceToolbarComponent, ResultsComponent, SearchPanelComponent, SelectedResourcesComponent, diff --git a/apps/dsp-app/src/app/main/directive/base-value.directive.ts b/apps/dsp-app/src/app/main/directive/base-value.directive.ts index b66919c819..70c1991722 100644 --- a/apps/dsp-app/src/app/main/directive/base-value.directive.ts +++ b/apps/dsp-app/src/app/main/directive/base-value.directive.ts @@ -1,13 +1,18 @@ import { Directive, Input, OnDestroy, OnInit } from '@angular/core'; import { AbstractControl, FormBuilder, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms'; import { CreateValue, ReadValue, UpdateValue } from '@dasch-swiss/dsp-js'; -import { Subscription } from 'rxjs'; +import { ResourceSelectors } from '@dasch-swiss/vre/shared/app-state'; +import { Select, Store } from '@ngxs/store'; +import { Observable, Subject, Subscription } from 'rxjs'; +import { map, takeUntil } from 'rxjs/operators'; // https://stackoverflow.com/questions/45661010/dynamic-nested-reactive-form-expressionchangedafterithasbeencheckederror const resolvedPromise = Promise.resolve(null); @Directive() export abstract class BaseValueDirective implements OnInit, OnDestroy { + private ngUnsubscribe: Subject = new Subject(); + /** * sets the mode of the component. */ @@ -33,8 +38,6 @@ export abstract class BaseValueDirective implements OnInit, OnDestroy { */ @Input() commentDisabled? = false; - shouldShowComment = false; - /** * subscription of comment changes. */ @@ -59,8 +62,8 @@ export abstract class BaseValueDirective implements OnInit, OnDestroy { * value to be displayed, if any. */ /* eslint-disable */ - @Input() abstract displayValue?: ReadValue; - /* eslint-enable */ + @Input() abstract displayValue?: ReadValue; + /* eslint-enable */ /** * custom validators for a specific value type. @@ -68,7 +71,19 @@ export abstract class BaseValueDirective implements OnInit, OnDestroy { */ abstract customValidators: ValidatorFn[]; - constructor(protected _fb?: FormBuilder) {} + commentIsVisible$ = this._store.select(ResourceSelectors.showAllComments).pipe( + takeUntil(this.ngUnsubscribe), + map(showAllComments => { + return showAllComments && this.commentFormControl.value && this.commentFormControl.value.length > 0; + }) + ); + + @Select(ResourceSelectors.showAllComments) showAllComments$: Observable; + + constructor( + protected _store: Store, + protected _fb?: FormBuilder + ) {} ngOnInit() { // initialize form control elements @@ -91,6 +106,9 @@ export abstract class BaseValueDirective implements OnInit, OnDestroy { } ngOnDestroy() { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + // unsubscribe to avoid memory leaks if (this.commentChangesSubscription) { this.commentChangesSubscription.unsubscribe(); @@ -173,20 +191,6 @@ export abstract class BaseValueDirective implements OnInit, OnDestroy { } } - /** - * hide comment field by default if in READ mode - */ - updateCommentVisibility(): void { - this.shouldShowComment = this.mode === 'read'; - } - - /** - * toggles visibility of the comment field regardless of the mode - */ - toggleCommentVisibility(): void { - this.shouldShowComment = !this.shouldShowComment; - } - /** * add the value components FormGroup to a parent FormGroup if one is defined */ diff --git a/apps/dsp-app/src/app/system/project-tile/project-tile.component.html b/apps/dsp-app/src/app/system/project-tile/project-tile.component.html index ea6f3d4d18..138206d872 100644 --- a/apps/dsp-app/src/app/system/project-tile/project-tile.component.html +++ b/apps/dsp-app/src/app/system/project-tile/project-tile.component.html @@ -1,38 +1,9 @@ -
-
-
- + diff --git a/apps/dsp-app/src/app/system/project-tile/project-tile.component.scss b/apps/dsp-app/src/app/system/project-tile/project-tile.component.scss index be5be6ffe3..7779f42cbc 100644 --- a/apps/dsp-app/src/app/system/project-tile/project-tile.component.scss +++ b/apps/dsp-app/src/app/system/project-tile/project-tile.component.scss @@ -1,105 +1,50 @@ @use '@angular/material' as mat; -@use "../../../styles/config" as *; -@use "../../../styles/typography" as t; +@use '../../../styles/config' as *; +@use '../../../styles/typography' as t; +@use '../../../styles/responsive' as *; .project-tile { + :hover { + border-radius: 6px; + background-color: $secondary_600; + } + border: 1px solid $cool_gray_300; border-radius: 6px; box-shadow: 0px 4px 4px $cool_gray_300; .project-tile-content { - padding: 10px 5px 10px 10px; - - .top-bar { - display: flex; - justify-content: space-between; - align-items: center; - - .status, - .settings { - display: inline-block; - } - - .status { - .status-badge { - border-radius: 10px; - padding: 2px 10px 4px 10px; - - span { - font-size: 12px; - } - } - - .status-badge.active { - background-color: #99F6E4; - - span { - color: #0F766E; - } - } - - .status-badge.deactivated { - background-color: #E5E7EB; - - span { - color: #6B7280; - } - } - } - - .settings { - mat-icon-button { - width: 28px; - height: 28px; - line-height: 28px; - - mat-icon { - height: 28px; - width: 28px; - font-size: 28px; - line-height: 28px; - } - } - } - } - - .icon { - padding-top: 23px; - - mat-icon { - font-size: 40px; - height: 40px; - width: 40px; - } - } + padding: 10px; + text-align: -webkit-center; + text-align: -moz-center; .title { - padding: 20px; - height: 80px; + padding: 10px; + height: 191px; + vertical-align: middle; + display: table-cell; } .title-text { - font-weight: 700; - font-size: 24px; + font-weight: 500; + font-size: 18px; color: $black; display: -webkit-box; - -webkit-line-clamp: 3; + -webkit-line-clamp: 8; -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: break-word; text-overflow: ellipsis; - line-height: 28px; + line-height: 24px; + margin: 0; } + } +} - .workspace-button-container { - margin: 14% 0%; - - @include mat.button-typography(t.$dsp-typography-config); - - button { +@media (max-width: map-get($grid-breakpoints, notebook)) { + .project-tile { + .title { + p.title-text { font-size: 16px; - line-height: 24px; - font-weight: 400; - padding: 13px 21px; } } } diff --git a/apps/dsp-app/src/app/user/overview/overview.component.html b/apps/dsp-app/src/app/user/overview/overview.component.html index d48e777648..f19c63beae 100644 --- a/apps/dsp-app/src/app/user/overview/overview.component.html +++ b/apps/dsp-app/src/app/user/overview/overview.component.html @@ -9,9 +9,6 @@

Browse Research Projects

-
- -
diff --git a/apps/dsp-app/src/app/user/overview/overview.component.scss b/apps/dsp-app/src/app/user/overview/overview.component.scss index 9681cb5863..1bc3f8b0c0 100644 --- a/apps/dsp-app/src/app/user/overview/overview.component.scss +++ b/apps/dsp-app/src/app/user/overview/overview.component.scss @@ -1,83 +1,82 @@ @use '@angular/material' as mat; -@use "../../../styles/responsive" as *; -@use "../../../styles/typography" as t; +@use '../../../styles/responsive' as *; +@use '../../../styles/typography' as t; .overview { - - .title-bar { - .title { - font-weight: 700; - font-size: 24px; - line-height: 28px; - } + .title-bar { + .title { + font-weight: 700; + font-size: 20px; + line-height: 28px; } + } - .title-bar.admin { - display: flex; - justify-content: space-between; - align-items: center; - - .title, - .create-project-button { - display: inline-block; - } + .title-bar.admin { + display: flex; + justify-content: space-between; + align-items: center; - .create-project-button { - @include mat.button-typography(t.$dsp-typography-config); - } - } - - .project-tiles { - display: grid; - grid-template-columns: 30% 30% 30%; - grid-column-gap: 5%; - padding-bottom: 2%; + .title { + display: inline-block; } + } + + .project-tiles { + display: grid; + grid-template-columns: 22% 22% 22% 22%; + grid-column-gap: 4%; + padding-bottom: 2%; + } + + .project-tile-container { + text-align: center; + padding-bottom: 15%; + } +} - .project-tile-container { - text-align: center; - padding-bottom: 15%; - } +@media (max-width: map-get($grid-breakpoints, notebook)) { + .overview .project-tiles { + grid-template-columns: 30% 30% 30%; + grid-column-gap: 5%; + } } -@media (max-width: 840px) { - .overview { - padding: 5%; +@media (max-width: map-get($grid-breakpoints, tablet)) { + .overview { + padding: 5%; - .project-tiles { - grid-template-columns: 47.5% 47.5%; - grid-column-gap: 5%; - } + .project-tiles { + grid-template-columns: 47.5% 47.5%; + grid-column-gap: 5%; } + } } -@media (max-width: 640px) { - .overview { - padding: 10%; - - .title-bar.admin { - display: block; - padding-bottom: 5%; +@media (max-width: map-get($grid-breakpoints, phone)) { + .overview { + padding: 10%; - .title, - .create-project-button { - display: block; - } + .title-bar.admin { + display: block; + padding-bottom: 5%; - } + .title { + display: block; + } + } - .project-tiles { - grid-template-columns: 100%; - } + .project-tiles { + grid-template-columns: 100%; + } - .project-tile-container { - padding-bottom: 5%; - } + .project-tile-container { + padding-bottom: 5%; } + } } @media (max-width: map-get($grid-breakpoints, phone)) { - .overview { - padding: 5%; - } + .overview { + padding: 5%; + } } diff --git a/apps/dsp-app/src/app/user/overview/overview.component.ts b/apps/dsp-app/src/app/user/overview/overview.component.ts index eb57543032..ae8834e61d 100644 --- a/apps/dsp-app/src/app/user/overview/overview.component.ts +++ b/apps/dsp-app/src/app/user/overview/overview.component.ts @@ -2,7 +2,6 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { Title } from '@angular/platform-browser'; import { Router } from '@angular/router'; import { ReadProject, ReadUser, StoredProject } from '@dasch-swiss/dsp-js'; -import { RouteConstants } from '@dasch-swiss/vre/shared/app-config'; import { LoadProjectsAction, ProjectsSelectors, UserSelectors } from '@dasch-swiss/vre/shared/app-state'; import { Select, Store } from '@ngxs/store'; import { Observable, combineLatest } from 'rxjs'; @@ -36,10 +35,6 @@ export class OverviewComponent implements OnInit { this._loadProjects(); } - createNewProject() { - this._router.navigate([RouteConstants.newProjectRelative]); - } - trackByFn = (index: number, item: StoredProject) => `${index}-${item.id}`; private _loadProjects(): void { diff --git a/apps/dsp-app/src/app/workspace/resource/operations/display-edit/display-edit.component.html b/apps/dsp-app/src/app/workspace/resource/operations/display-edit/display-edit.component.html index c5e51e2ca2..65650527c3 100644 --- a/apps/dsp-app/src/app/workspace/resource/operations/display-edit/display-edit.component.html +++ b/apps/dsp-app/src/app/workspace/resource/operations/display-edit/display-edit.component.html @@ -15,7 +15,8 @@ *ngSwitchCase="'ReadTextValueAsString'" [mode]="mode" [displayValue]="$any(displayValue)" - [textArea]="textArea"> + [textArea]="textArea"> + + "> + {{ displayValue.strval }}
@@ -121,25 +123,17 @@ class="edit" [matTooltip]="'edit'" *ngIf=" - !readOnlyValue && - canModify && - !editModeActive && - projectStatus - " + !readOnlyValue && + canModify && + !editModeActive && + projectStatus + " (click)="activateEditMode()"> edit - save diff --git a/apps/dsp-app/src/app/workspace/resource/operations/display-edit/display-edit.component.scss b/apps/dsp-app/src/app/workspace/resource/operations/display-edit/display-edit.component.scss index e69de29bb2..db728a53be 100644 --- a/apps/dsp-app/src/app/workspace/resource/operations/display-edit/display-edit.component.scss +++ b/apps/dsp-app/src/app/workspace/resource/operations/display-edit/display-edit.component.scss @@ -0,0 +1,10 @@ +::ng-deep .read-mode-view { + display: flex; + box-sizing: border-box; + flex-direction: column; + justify-content: space-between; + + .value-container { + display: inline-block; + } +} diff --git a/apps/dsp-app/src/app/workspace/resource/operations/display-edit/display-edit.component.ts b/apps/dsp-app/src/app/workspace/resource/operations/display-edit/display-edit.component.ts index cce2494798..9d569de560 100644 --- a/apps/dsp-app/src/app/workspace/resource/operations/display-edit/display-edit.component.ts +++ b/apps/dsp-app/src/app/workspace/resource/operations/display-edit/display-edit.component.ts @@ -85,7 +85,6 @@ export class DisplayEditComponent implements OnInit { canModify: boolean; editModeActive = false; submittingValue = false; - shouldShowCommentToggle: boolean; // type of given displayValue // or knora-api-js-lib class representing the value valueTypeOrClass: string; @@ -124,9 +123,6 @@ export class DisplayEditComponent implements OnInit { this.canModify = allPermissions.indexOf(PermissionUtil.Permissions.M) !== -1; - // check if comment toggle button should be shown - this.checkCommentToggleVisibility(); - this.valueTypeOrClass = this._valueService.getValueTypeOrClass(this.displayValue); // get the resource property definition @@ -191,12 +187,6 @@ export class DisplayEditComponent implements OnInit { this.editModeActive = true; this.valueHovered = false; this.mode = 'update'; - - // hide comment toggle button while in edit mode - this.checkCommentToggleVisibility(); - - // hide read mode comment when switching to edit mode - this.displayValueComponent.shouldShowComment = false; } /** @@ -237,12 +227,6 @@ export class DisplayEditComponent implements OnInit { this.displayValue = res2.getValues(this.displayValue.property)[0]; this.mode = 'read'; - // hide comment once back in read mode - this.displayValueComponent.updateCommentVisibility(); - - // check if comment toggle button should be shown - this.checkCommentToggleVisibility(); - // hide the progress indicator this.submittingValue = false; this._cd.markForCheck(); @@ -331,30 +315,6 @@ export class DisplayEditComponent implements OnInit { this.editModeActive = false; this.showActionBubble = false; this.mode = 'read'; - - // hide comment once back in read mode - this.displayValueComponent.updateCommentVisibility(); - - // check if comment toggle button should be shown - this.checkCommentToggleVisibility(); - } - - /** - * show or hide the comment. - */ - toggleComment() { - this.displayValueComponent.toggleCommentVisibility(); - } - - /** - * check if the comment toggle button should be shown. - * Only show the comment toggle button if user is in READ mode and a comment exists for the value. - */ - checkCommentToggleVisibility() { - this.shouldShowCommentToggle = - this.mode === 'read' && - this.displayValue.valueHasComment !== '' && - this.displayValue.valueHasComment !== undefined; } /** diff --git a/apps/dsp-app/src/app/workspace/resource/properties/properties.component.html b/apps/dsp-app/src/app/workspace/resource/properties/properties.component.html index 81e3ede6bf..d2b3b01e2a 100644 --- a/apps/dsp-app/src/app/workspace/resource/properties/properties.component.html +++ b/apps/dsp-app/src/app/workspace/resource/properties/properties.component.html @@ -1,139 +1,22 @@ -
+
-

{{resource.res.label}} (deleted)

+

+ {{resource.res.label}} (deleted) +

- - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
- This resource {{ resource.res.isDeleted ? 'belonged' : 'belongs' }} to the project - - {{project?.shortname}} - open_in_new - - - - - Created - by {{(user.username ? user.username : user.givenName + ' ' + user.familyName)}} - on {{resource.res.creationDate | date}} - + +
@@ -143,7 +26,7 @@

{{resource.res.label}}
0 ) || (prop.propDef.id === hasIncomingLinkIri && numberOffAllIncomingLinkRes > 0)) && ( !prop.propDef['isLinkProperty'] && prop.propDef.objectType !== representationConstants.stillImage && prop.propDef.objectType !== representationConstants.movingImage && @@ -179,15 +62,15 @@

{{resource.res.label}}

-
+
{{resource.res.label}} +
+
diff --git a/apps/dsp-app/src/app/workspace/resource/properties/properties.component.scss b/apps/dsp-app/src/app/workspace/resource/properties/properties.component.scss index 2432c0cad8..1391b7a0a8 100644 --- a/apps/dsp-app/src/app/workspace/resource/properties/properties.component.scss +++ b/apps/dsp-app/src/app/workspace/resource/properties/properties.component.scss @@ -1,18 +1,14 @@ -@use "../../../../styles/config" as *; +@use '../../../../styles/config' as *; -// toolbar -.toolbar, -.infobar { +.toolbar { display: flex; box-sizing: border-box; flex-direction: row; align-items: center; white-space: nowrap; - padding: 0 16px; + padding-left: 16px; width: 100%; color: rgba(0, 0, 0, 0.87); -} -.toolbar { background: $primary_50; &.deleted { @@ -28,16 +24,13 @@ text-overflow: ellipsis; } - .action button { - border-radius: 0; + button { + font-size: 16px; + height: 48px; + padding: 12px; } } -.infobar { - height: 24px; - flex-wrap: wrap; -} - .clipboard-arkurl { width: 264px; height: 32px; @@ -73,6 +66,14 @@ .properties { grid-column: 1 / span 6; + .property:has(.parent-value-component .calendar) .property-label { + margin-block: auto; + } + + .property:has(.parent-value-component.geoname) .property-label h3 { + margin-block-start: 2px; + } + .property { grid-row: 1 / 1; @@ -89,6 +90,11 @@ .property-label { grid-column: 1 / span 1; + h3 { + margin-block-start: 1px; + margin-block-end: 0; + } + .label { text-align: right; } @@ -169,39 +175,46 @@ margin: 0; } -.pagination { +.navigation { display: flex; + box-sizing: border-box; + flex-direction: row; align-items: center; - button { - margin: 0 15px; - padding: 0; - background: transparent; - border: none; - cursor: pointer; - border-radius: 100%; - height: fit-content; - padding: 10px; - display: flex; //centers the icon + white-space: nowrap; + height: 60px; + width: 100%; + .pagination-button:disabled { + span, mat-icon { - color: $primary; + color: $cool_gray_300; } - } - button:hover { - background-color: $cool_gray_300; - } + .pagination-button { + &.previous { + span { + padding-left: 14px; + } + } - button:disabled { - opacity: 0.4; - cursor: not-allowed; - } + &.next { + span { + padding-right: 14px; + } + } - button:disabled:hover { - background-color: transparent; + span, + mat-icon { + color: $primary; + font-weight: 400; + } } + .range { + color: $cool_gray_500; + font-weight: 300; + } } @media screen and (max-width: 768px) { diff --git a/apps/dsp-app/src/app/workspace/resource/properties/properties.component.ts b/apps/dsp-app/src/app/workspace/resource/properties/properties.component.ts index 1bc31b269f..81dc3030d4 100644 --- a/apps/dsp-app/src/app/workspace/resource/properties/properties.component.ts +++ b/apps/dsp-app/src/app/workspace/resource/properties/properties.component.ts @@ -10,15 +10,12 @@ import { OnInit, Output, } from '@angular/core'; -import { MatDialog, MatDialogConfig } from '@angular/material/dialog'; import { PageEvent } from '@angular/material/paginator'; import { ApiResponseError, CardinalityUtil, Constants, CountQueryResponse, - DeleteResource, - DeleteResourceResponse, DeleteValue, IHasPropertyWithPropertyDefinition, KnoraApiConnection, @@ -32,31 +29,12 @@ import { ReadUser, ReadValue, ResourcePropertyDefinition, - StoredProject, - UpdateResourceMetadata, - UpdateResourceMetadataResponse, } from '@dasch-swiss/dsp-js'; import { DspApiConnectionToken } from '@dasch-swiss/vre/shared/app-config'; -import { - Events as CommsEvents, - ComponentCommunicationEventService, - EmitEvent, - OntologyService, - ProjectService, - SortingService, -} from '@dasch-swiss/vre/shared/app-helper-services'; -import { NotificationService } from '@dasch-swiss/vre/shared/app-notification'; -import { - GetAttachedProjectAction, - GetAttachedUserAction, - LoadClassItemsCountAction, - ProjectsSelectors, - ResourceSelectors, -} from '@dasch-swiss/vre/shared/app-state'; -import { Actions, Store, ofActionSuccessful } from '@ngxs/store'; +import { SortingService } from '@dasch-swiss/vre/shared/app-helper-services'; +import { ResourceSelectors } from '@dasch-swiss/vre/shared/app-state'; +import { Select } from '@ngxs/store'; import { Observable, Subject, Subscription, forkJoin } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; -import { ConfirmationWithComment, DialogComponent } from '../../../main/dialog/dialog.component'; import { DspResource } from '../dsp-resource'; import { RepresentationConstants } from '../representation/file-representation'; import { IncomingService } from '../services/incoming.service'; @@ -92,12 +70,6 @@ export class PropertiesComponent implements OnInit, OnChanges, OnDestroy { */ @Input() resource: DspResource; - /** - * input `displayProjectInfo` of properties component: - * display project info or not; "This resource belongs to project XYZ" - */ - @Input() displayProjectInfo = false; - /** * does the logged-in user has system or project admin permissions? */ @@ -111,6 +83,9 @@ export class PropertiesComponent implements OnInit, OnChanges, OnDestroy { @Input() valueUuidToHighlight: string; + @Input() attachedUser: ReadUser; + @Input() attachedProject: ReadProject; + /** * output `referredProjectClicked` of resource view component: * can be used to go to project page @@ -141,9 +116,7 @@ export class PropertiesComponent implements OnInit, OnChanges, OnDestroy { readonly amount_resources = 25; - lastModificationDate: string; - - deletedResource = false; + @Input() lastModificationDate: string; userCanDelete: boolean; cantDeleteReason = ''; @@ -160,30 +133,20 @@ export class PropertiesComponent implements OnInit, OnChanges, OnDestroy { allIncomingLinkResources: ReadResource[] = []; displayedIncomingLinkResources: ReadResource[] = []; hasIncomingLinkIri = Constants.HasIncomingLinkValue; - - project: ReadProject | StoredProject; - user: ReadUser; - pageEvent: PageEvent; loading = false; - showAllProps = false; // show or hide empty properties + @Select(ResourceSelectors.showAllProps) showAllProps$: Observable; constructor( @Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection, - private _dialog: MatDialog, private _incomingService: IncomingService, - private _notification: NotificationService, private _resourceService: ResourceService, private _valueOperationEventService: ValueOperationEventService, private _valueService: ValueService, - private _componentCommsService: ComponentCommunicationEventService, private _sortingService: SortingService, - private _cd: ChangeDetectorRef, - private _store: Store, - private _ontologyService: OntologyService, - private _actions$: Actions + private _cd: ChangeDetectorRef ) {} ngOnInit(): void { @@ -240,15 +203,6 @@ export class PropertiesComponent implements OnInit, OnChanges, OnDestroy { this.deleteValueFromResource(deletedValue.deletedValue); }) ); - - // keep the information if the user wants to display all properties or not - if (localStorage.getItem('showAllProps')) { - this.showAllProps = JSON.parse(localStorage.getItem('showAllProps')); - } else { - localStorage.setItem('showAllProps', JSON.stringify(this.showAllProps)); - } - - this._getResourceAttachedData(); } ngOnChanges(): void { @@ -269,19 +223,6 @@ export class PropertiesComponent implements OnInit, OnChanges, OnDestroy { trackByValuesFn = (index: number, item: any) => `${index}-${item}`; trackByPropertyInfoFn = (index: number, item: PropertyInfoValues) => `${index}-${item.propDef.id}`; - /** - * opens project - * @param project - */ - openProject(project: ReadProject | StoredProject) { - const uuid = ProjectService.IriToUuid(project.id); - window.open(`/project/${uuid}`, '_blank'); - } - - previewProject() { - // --> TODO: pop up project preview on hover - } - /** * goes to the next page of the incoming link pagination * @param page @@ -321,80 +262,6 @@ export class PropertiesComponent implements OnInit, OnChanges, OnDestroy { // --> TODO: pop up resource preview on hover } - openDialog(type: 'delete' | 'erase' | 'edit') { - const dialogConfig: MatDialogConfig = { - width: '560px', - maxHeight: '80vh', - position: { - top: '112px', - }, - data: { mode: `${type}Resource`, title: this.resource.res.label }, - }; - - const dialogRef = this._dialog.open(DialogComponent, dialogConfig); - - dialogRef.afterClosed().subscribe((answer: ConfirmationWithComment) => { - if (!answer) { - // if the user clicks outside of the dialog window, answer is undefined - return; - } - if (answer.confirmed === true) { - if (type !== 'edit') { - const payload = new DeleteResource(); - payload.id = this.resource.res.id; - payload.type = this.resource.res.type; - payload.deleteComment = answer.comment ? answer.comment : undefined; - payload.lastModificationDate = this.lastModificationDate; - switch (type) { - case 'delete': - // delete the resource and refresh the view - this._dspApiConnection.v2.res.deleteResource(payload).subscribe((response: DeleteResourceResponse) => { - this._onResourceDeleted(response); - }); - break; - - case 'erase': - // erase the resource and refresh the view - this._dspApiConnection.v2.res.eraseResource(payload).subscribe((response: DeleteResourceResponse) => { - this._onResourceDeleted(response); - }); - break; - } - } else if (this.resource.res.label !== answer.comment) { - // update resource's label if it has changed - // get the correct lastModificationDate from the resource - this._dspApiConnection.v2.res.getResource(this.resource.res.id).subscribe((res: ReadResource) => { - const payload = new UpdateResourceMetadata(); - payload.id = this.resource.res.id; - payload.type = this.resource.res.type; - payload.lastModificationDate = res.lastModificationDate; - payload.label = answer.comment; - - this._dspApiConnection.v2.res - .updateResourceMetadata(payload) - .subscribe((response: UpdateResourceMetadataResponse) => { - this.resource.res.label = payload.label; - this.lastModificationDate = response.lastModificationDate; - // if annotations tab is active; a label of a region has been changed --> update regions - this._componentCommsService.emit(new EmitEvent(CommsEvents.resourceChanged)); - if (this.isAnnotation) { - this.regionChanged.emit(); - } - this._cd.markForCheck(); - }); - }); - } - } - }); - } - - /** - * display message to confirm the copy of the citation link (ARK URL) - */ - openSnackBar(message: string) { - this._notification.openSnackBar(message); - } - /** * called from the template when the user clicks on the add button */ @@ -548,58 +415,6 @@ export class PropertiesComponent implements OnInit, OnChanges, OnDestroy { } } - toggleAllProps(status: boolean) { - this.showAllProps = !status; - localStorage.setItem('showAllProps', JSON.stringify(this.showAllProps)); - } - - private _getResourceAttachedData() { - this._store.dispatch([ - new GetAttachedUserAction(this.resource.res.id, this.resource.res.attachedToUser), - new GetAttachedProjectAction(this.resource.res.id, this.resource.res.attachedToProject), - ]); - - this.project = this._store - .selectSnapshot(ProjectsSelectors.allProjects) - .find(p => p.id === this.resource.res.attachedToProject); - this._actions$ - .pipe(ofActionSuccessful(GetAttachedProjectAction)) - .pipe(takeUntil(this.ngUnsubscribe)) - .subscribe(() => { - const attachedProjects = this._store.selectSnapshot(ResourceSelectors.attachedProjects); - this.project = attachedProjects[this.resource.res.id].value.find( - u => u.id === this.resource.res.attachedToProject - ); - }); - - this._actions$ - .pipe(ofActionSuccessful(GetAttachedUserAction)) - .pipe(takeUntil(this.ngUnsubscribe)) - .subscribe(() => { - const attachedUsers = this._store.selectSnapshot(ResourceSelectors.attachedUsers); - this.user = attachedUsers[this.resource.res.id].value.find(u => u.id === this.resource.res.attachedToUser); - }); - } - - private _onResourceDeleted(response: DeleteResourceResponse) { - // display notification and mark resource as 'erased' - this._notification.openSnackBar(`${response.result}: ${this.resource.res.label}`); - this.deletedResource = true; - const attachedProject = this._store.selectSnapshot(ResourceSelectors.attachedProjects); - const project = attachedProject[this.resource.res.id].value.find(u => u.id === this.resource.res.attachedToProject); - const ontologyIri = this._ontologyService.getOntologyIriFromRoute(project?.shortcode); - const classId = this.resource.res.entityInfo.classes[this.resource.res.type]?.id; - this._store.dispatch(new LoadClassItemsCountAction(ontologyIri, classId)); - this._componentCommsService.emit(new EmitEvent(CommsEvents.resourceDeleted)); - // if it is an Annotation/Region which has been erases, we emit the - // regionChanged event, in order to refresh the page - if (this.isAnnotation) { - this.regionDeleted.emit(); - } - - this._cd.markForCheck(); - } - /** * gets the number of incoming links and gets the incoming links. * @private diff --git a/apps/dsp-app/src/app/workspace/resource/properties/resource-toolbar/resource-toolbar.html b/apps/dsp-app/src/app/workspace/resource/properties/resource-toolbar/resource-toolbar.html new file mode 100644 index 0000000000..591fb833fb --- /dev/null +++ b/apps/dsp-app/src/app/workspace/resource/properties/resource-toolbar/resource-toolbar.html @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/dsp-app/src/app/workspace/resource/properties/resource-toolbar/resource-toolbar.scss b/apps/dsp-app/src/app/workspace/resource/properties/resource-toolbar/resource-toolbar.scss new file mode 100644 index 0000000000..bfa440847e --- /dev/null +++ b/apps/dsp-app/src/app/workspace/resource/properties/resource-toolbar/resource-toolbar.scss @@ -0,0 +1,12 @@ +.action { + display: inline-flex; + + .toggle-props { + padding: 12px; + height: 48px; + } + + button { + border-radius: 0; + } +} diff --git a/apps/dsp-app/src/app/workspace/resource/properties/resource-toolbar/resource-toolbar.ts b/apps/dsp-app/src/app/workspace/resource/properties/resource-toolbar/resource-toolbar.ts new file mode 100644 index 0000000000..df0bc4fb95 --- /dev/null +++ b/apps/dsp-app/src/app/workspace/resource/properties/resource-toolbar/resource-toolbar.ts @@ -0,0 +1,217 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Inject, + Input, + OnInit, + Output, +} from '@angular/core'; +import { MatDialog, MatDialogConfig } from '@angular/material/dialog'; +import { + DeleteResource, + DeleteResourceResponse, + KnoraApiConnection, + PermissionUtil, + ReadLinkValue, + ReadProject, + ReadResource, + ReadValue, + UpdateResourceMetadata, + UpdateResourceMetadataResponse, +} from '@dasch-swiss/dsp-js'; +import { DspApiConnectionToken } from '@dasch-swiss/vre/shared/app-config'; +import { + Events as CommsEvents, + ComponentCommunicationEventService, + EmitEvent, + OntologyService, +} from '@dasch-swiss/vre/shared/app-helper-services'; +import { NotificationService } from '@dasch-swiss/vre/shared/app-notification'; +import { + LoadClassItemsCountAction, + ResourceSelectors, + ToggleShowAllCommentsAction, + ToggleShowAllPropsAction, +} from '@dasch-swiss/vre/shared/app-state'; +import { Select, Store } from '@ngxs/store'; +import { Observable } from 'rxjs'; +import { ConfirmationWithComment, DialogComponent } from '../../../../main/dialog/dialog.component'; +import { DspResource } from '../../dsp-resource'; +import { ResourceService } from '../../services/resource.service'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + selector: 'app-resource-toolbar', + templateUrl: './resource-toolbar.html', + styleUrls: ['./resource-toolbar.scss'], +}) +export class ResourceToolbarComponent implements OnInit { + @Input() resource: DspResource; + + /** + * does the logged-in user has system or project admin permissions? + */ + @Input() adminPermissions = false; + + /** + * in case properties belongs to an annotation (e.g. region in still images) + * in this case we don't have to display the isRegionOf property + */ + @Input() isAnnotation = false; + + @Input() showToggleProperties = false; + @Input() showEditLabel = false; + + @Input() attachedProject: ReadProject; + + @Input() lastModificationDate: string; + + @Output() regionChanged: EventEmitter = new EventEmitter(); + @Output() regionDeleted: EventEmitter = new EventEmitter(); + + userCanDelete: boolean; + userCanEdit: boolean; + + canReadComments: boolean; + + get hasIconButtonActions() { + return this.isAnnotation || !this.showToggleProperties; + } + + @Select(ResourceSelectors.showAllProps) showAllProps$: Observable; + @Select(ResourceSelectors.showAllComments) showAllComments$: Observable; + + constructor( + @Inject(DspApiConnectionToken) + private _dspApiConnection: KnoraApiConnection, + private _notification: NotificationService, + private _resourceService: ResourceService, + private _cd: ChangeDetectorRef, + private _componentCommsService: ComponentCommunicationEventService, + private _ontologyService: OntologyService, + private _dialog: MatDialog, + private _store: Store + ) {} + + ngOnInit(): void { + if (this.resource.res) { + // get user permissions + const allPermissions = PermissionUtil.allUserPermissions( + this.resource.res.userHasPermission as 'RV' | 'V' | 'M' | 'D' | 'CR' + ); + + this.canReadComments = true; // allPermissions.indexOf(PermissionUtil.Permissions.RV) === -1; // TODO permissions to show comments should be provided + // if user has modify permissions, set addButtonIsVisible to true so the user see's the add button + this.userCanEdit = allPermissions.indexOf(PermissionUtil.Permissions.M) !== -1; + this.userCanDelete = allPermissions.indexOf(PermissionUtil.Permissions.D) !== -1; + } + } + + /** + * opens resource + * @param linkValue + */ + openResource(linkValue: ReadLinkValue | string) { + const iri = typeof linkValue == 'string' ? linkValue : linkValue.linkedResourceIri; + const path = this._resourceService.getResourcePath(iri); + window.open(`/resource${path}`, '_blank'); + } + + toggleShowAllProps() { + this._store.dispatch(new ToggleShowAllPropsAction()); + } + + toggleShowAllComments() { + this._store.dispatch(new ToggleShowAllCommentsAction()); + } + + openDialog(type: 'delete' | 'erase' | 'edit') { + const dialogConfig: MatDialogConfig = { + width: '560px', + maxHeight: '80vh', + position: { + top: '112px', + }, + data: { mode: `${type}Resource`, title: this.resource.res.label }, + }; + + const dialogRef = this._dialog.open(DialogComponent, dialogConfig); + + dialogRef.afterClosed().subscribe((answer: ConfirmationWithComment) => { + if (!answer) { + // if the user clicks outside of the dialog window, answer is undefined + return; + } + if (answer.confirmed === true) { + if (type !== 'edit') { + const payload = new DeleteResource(); + payload.id = this.resource.res.id; + payload.type = this.resource.res.type; + payload.deleteComment = answer.comment ? answer.comment : undefined; + payload.lastModificationDate = this.lastModificationDate; + switch (type) { + case 'delete': + // delete the resource and refresh the view + this._dspApiConnection.v2.res.deleteResource(payload).subscribe((response: DeleteResourceResponse) => { + this._onResourceDeleted(response); + }); + break; + + case 'erase': + // erase the resource and refresh the view + this._dspApiConnection.v2.res.eraseResource(payload).subscribe((response: DeleteResourceResponse) => { + this._onResourceDeleted(response); + }); + break; + } + } else if (this.resource.res.label !== answer.comment) { + // update resource's label if it has changed + // get the correct lastModificationDate from the resource + this._dspApiConnection.v2.res.getResource(this.resource.res.id).subscribe((res: ReadResource) => { + const payload = new UpdateResourceMetadata(); + payload.id = this.resource.res.id; + payload.type = this.resource.res.type; + payload.lastModificationDate = res.lastModificationDate; + payload.label = answer.comment; + + this._dspApiConnection.v2.res + .updateResourceMetadata(payload) + .subscribe((response: UpdateResourceMetadataResponse) => { + this.resource.res.label = payload.label; + this.lastModificationDate = response.lastModificationDate; + // if annotations tab is active; a label of a region has been changed --> update regions + this._componentCommsService.emit(new EmitEvent(CommsEvents.resourceChanged)); + if (this.isAnnotation) { + this.regionChanged.emit(); + } + this._cd.markForCheck(); + }); + }); + } + } + }); + } + + /** + * display message to confirm the copy of the citation link (ARK URL) + */ + openSnackBar(message: string) { + this._notification.openSnackBar(message); + } + + private _onResourceDeleted(response: DeleteResourceResponse) { + // display notification and mark resource as 'erased' + this._notification.openSnackBar(`${response.result}: ${this.resource.res.label}`); + const ontologyIri = this._ontologyService.getOntologyIriFromRoute(this.attachedProject.shortcode); + const classId = this.resource.res.entityInfo.classes[this.resource.res.type]?.id; + this._store.dispatch(new LoadClassItemsCountAction(ontologyIri, classId)); + this._componentCommsService.emit(new EmitEvent(CommsEvents.resourceDeleted)); + // if it is an Annotation/Region which has been erases, we emit the + // regionChanged event, in order to refresh the page + this.regionDeleted.emit(); + + this._cd.markForCheck(); + } +} diff --git a/apps/dsp-app/src/app/workspace/resource/representation/still-image/still-image.component.html b/apps/dsp-app/src/app/workspace/resource/representation/still-image/still-image.component.html index 00752d6469..4cb2818204 100644 --- a/apps/dsp-app/src/app/workspace/resource/representation/still-image/still-image.component.html +++ b/apps/dsp-app/src/app/workspace/resource/representation/still-image/still-image.component.html @@ -32,7 +32,7 @@
-
+
- - - + + + + + + + + + + + @@ -93,37 +120,17 @@ (click)="openPage(compoundNavigation.page - 1)"> navigate_before - - - - - - - - - - - + {{compoundNavigation.page}} of {{compoundNavigation.totalPages}} + + +
+
+ Resource of the project + + {{project?.shortname}}, created + by {{(resourceAttachedUser.username ? resourceAttachedUser.username : resourceAttachedUser.givenName + ' ' + + resourceAttachedUser.familyName)}} + on {{resource.res.creationDate | date}} + +
+
- + - + @@ -134,11 +172,12 @@ [class.active]="annotation.res.id === selectedRegion">
@@ -148,8 +187,9 @@ diff --git a/apps/dsp-app/src/app/workspace/resource/resource.component.scss b/apps/dsp-app/src/app/workspace/resource/resource.component.scss index 8bc72fd0f0..d4cd922cf0 100644 --- a/apps/dsp-app/src/app/workspace/resource/resource.component.scss +++ b/apps/dsp-app/src/app/workspace/resource/resource.component.scss @@ -1,7 +1,9 @@ -@use "../../../styles/config" as *; +@use '../../../styles/config' as *; +@use '../../../styles/responsive' as *; .representation-container { width: 100%; + min-width: 500px; } .previousBtn { @@ -29,8 +31,7 @@ } p { - color: - $yellow_700; + color: $yellow_700; } } @@ -38,7 +39,71 @@ cursor: pointer; } -@media (max-width: 1024px) { +.resource-header { + margin-bottom: 24px; +} + +.resource-label { + display: flex; + box-sizing: border-box; + flex-direction: row; + align-items: left; + justify-content: space-between; + + h4 { + display: inline-block; + font-weight: 500; + font-size: 18px; + line-height: 22px; + margin-bottom: 0; + margin-block-start: 0; + margin-block-end: 0; + padding-top: 16px; + } + + button { + margin-left: 14px; + } +} + +.resource-class-header { + display: flex; + box-sizing: border-box; + flex-direction: row; + align-items: left; + justify-content: space-between; + + h3.label-info { + cursor: help; + } + + h3 { + display: inline-block; + text-transform: uppercase; + font-size: 16px; + font-weight: normal; + letter-spacing: 1.25px; + margin-block-end: 0em; + } + + .action { + display: inline-block; + white-space: nowrap; + + button { + border-radius: 0; + } + } +} + +.infobar { + margin-top: 10px; + box-sizing: border-box; + white-space: nowrap; + color: rgba(0, 0, 0, 0.87); +} + +@media (max-width: map-get($grid-breakpoints, notebook)) { .previousBtn { margin: 12px auto 0 12px; // position: relative; diff --git a/apps/dsp-app/src/app/workspace/resource/resource.component.ts b/apps/dsp-app/src/app/workspace/resource/resource.component.ts index a7541ea3ba..0e11514428 100644 --- a/apps/dsp-app/src/app/workspace/resource/resource.component.ts +++ b/apps/dsp-app/src/app/workspace/resource/resource.component.ts @@ -3,40 +3,59 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + EventEmitter, Inject, Input, OnChanges, OnDestroy, + Output, ViewChild, } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; import { MatTabChangeEvent } from '@angular/material/tabs'; import { Title } from '@angular/platform-browser'; import { ActivatedRoute, Router } from '@angular/router'; import { - ApiResponseError, Constants, CountQueryResponse, IHasPropertyWithPropertyDefinition, KnoraApiConnection, + PermissionUtil, ReadArchiveFileValue, ReadAudioFileValue, ReadDocumentFileValue, ReadLinkValue, ReadMovingImageFileValue, + ReadProject, ReadResource, ReadResourceSequence, ReadStillImageFileValue, ReadTextFileValue, ReadUser, + ReadValue, + ResourceClassDefinitionWithPropertyDefinition, SystemPropertyDefinition, + UpdateResourceMetadata, + UpdateResourceMetadataResponse, } from '@dasch-swiss/dsp-js'; -import { DspApiConnectionToken } from '@dasch-swiss/vre/shared/app-config'; -import { ProjectService } from '@dasch-swiss/vre/shared/app-helper-services'; +import { DspApiConnectionToken, RouteConstants } from '@dasch-swiss/vre/shared/app-config'; +import { + ComponentCommunicationEventService, + EmitEvent, + Events, + ProjectService, +} from '@dasch-swiss/vre/shared/app-helper-services'; import { NotificationService } from '@dasch-swiss/vre/shared/app-notification'; -import { UserSelectors } from '@dasch-swiss/vre/shared/app-state'; -import { Select } from '@ngxs/store'; -import { combineLatest, Observable, Subject, Subscription } from 'rxjs'; -import { map, takeUntil, tap } from 'rxjs/operators'; +import { + GetAttachedProjectAction, + GetAttachedUserAction, + ResourceSelectors, + UserSelectors, +} from '@dasch-swiss/vre/shared/app-state'; +import { Actions, Select, Store, ofActionSuccessful } from '@ngxs/store'; +import { Observable, Subject, Subscription, combineLatest } from 'rxjs'; +import { map, take, takeUntil, takeWhile, tap } from 'rxjs/operators'; +import { ConfirmationWithComment, DialogComponent } from '../../main/dialog/dialog.component'; import { SplitSize } from '../results/results.component'; import { DspCompoundPosition, DspResource } from './dsp-resource'; import { PropertyInfoValues } from './properties/properties.component'; @@ -44,7 +63,11 @@ import { FileRepresentation, RepresentationConstants } from './representation/fi import { Region, StillImageComponent } from './representation/still-image/still-image.component'; import { IncomingService } from './services/incoming.service'; import { ResourceService } from './services/resource.service'; -import { Events, UpdatedFileEventValue, ValueOperationEventService } from './services/value-operation-event.service'; +import { + UpdatedFileEventValue, + ValueOperationEventService, + Events as ValueOperationEvents, +} from './services/value-operation-event.service'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -62,6 +85,11 @@ export class ResourceComponent implements OnChanges, OnDestroy { @Input() splitSizeChanged: SplitSize; + @Output() regionChanged: EventEmitter = new EventEmitter(); + @Output() regionDeleted: EventEmitter = new EventEmitter(); + + @ViewChild('matTabAnnotations') matTabAnnotations; + oldResourceIri: string; // for change detection projectCode: string; @@ -103,8 +131,6 @@ export class ResourceComponent implements OnChanges, OnDestroy { // this will store the current page position information compoundPosition: DspCompoundPosition; - showAllProps = false; - loading = true; refresh: boolean; @@ -117,6 +143,29 @@ export class ResourceComponent implements OnChanges, OnDestroy { attachedToProjectResource = ''; + project$ = this._store.select(ResourceSelectors.attachedProjects).pipe( + takeWhile(attachedProjects => this.resource !== undefined && attachedProjects[this.resource.res.id] !== undefined), + takeUntil(this.ngUnsubscribe), + map(attachedProjects => + attachedProjects[this.resource.res.id].value.find(u => u.id === this.resource.res.attachedToProject) + ) + ); + + resourceAttachedUser: ReadUser; + + notification = this._notification; + + get userCanEdit(): boolean { + if (!this.resource.res) { + return false; + } + + const allPermissions = PermissionUtil.allUserPermissions( + this.resource.res.userHasPermission as 'RV' | 'V' | 'M' | 'D' | 'CR' + ); + return allPermissions.indexOf(PermissionUtil.Permissions.M) !== -1; + } + get isAdmin$(): Observable { return combineLatest([this.user$, this.userProjectAdminGroups$]).pipe( takeUntil(this.ngUnsubscribe), @@ -128,9 +177,12 @@ export class ResourceComponent implements OnChanges, OnDestroy { ); } + get resourceClassType(): ResourceClassDefinitionWithPropertyDefinition { + return this.resource.res.entityInfo.classes[this.resource.res.type]; + } + @Select(UserSelectors.user) user$: Observable; - @Select(UserSelectors.userProjectAdminGroups) - userProjectAdminGroups$: Observable; + @Select(UserSelectors.userProjectAdminGroups) userProjectAdminGroups$: Observable; constructor( @Inject(DspApiConnectionToken) @@ -142,7 +194,11 @@ export class ResourceComponent implements OnChanges, OnDestroy { private _router: Router, private _titleService: Title, private _valueOperationEventService: ValueOperationEventService, - private _cdr: ChangeDetectorRef + private _cdr: ChangeDetectorRef, + private _store: Store, + private _actions$: Actions, + private _dialog: MatDialog, + private _componentCommsService: ComponentCommunicationEventService ) { this._route.params.subscribe(params => { this.projectCode = params.project; @@ -160,13 +216,16 @@ export class ResourceComponent implements OnChanges, OnDestroy { }); this.valueOperationEventSubscriptions.push( - this._valueOperationEventService.on(Events.FileValueUpdated, (newFileValue: UpdatedFileEventValue) => { - if (newFileValue) { - if (this.resourceIri) { - this.initResource(this.resourceIri); + this._valueOperationEventService.on( + ValueOperationEvents.FileValueUpdated, + (newFileValue: UpdatedFileEventValue) => { + if (newFileValue) { + if (this.resourceIri) { + this.initResource(this.resourceIri); + } } } - }) + ) ); } @@ -250,6 +309,7 @@ export class ResourceComponent implements OnChanges, OnDestroy { initResource(iri) { this.oldResourceIri = this.resourceIri; this.getResource(iri).subscribe(dspResource => { + this._getResourceAttachedData(dspResource); this.renderResource(dspResource); }); } @@ -396,6 +456,19 @@ export class ResourceComponent implements OnChanges, OnDestroy { this._cdr.detectChanges(); } + resourceClassLabel = (resource: DspResource): string => resource.res.entityInfo?.classes[resource.res.type].label; + + resourceLabel = (incomingResource: DspResource, resource: DspResource): string => + incomingResource ? `${resource.res.label}: ${incomingResource.res.label}` : resource.res.label; + + openProject(project: ReadProject) { + window.open(`${RouteConstants.projectRelative}/${ProjectService.IriToUuid(project.id)}`, '_blank'); + } + + previewProject() { + // --> TODO: pop up project preview on hover + } + /** * gather resource property information */ @@ -680,4 +753,52 @@ export class ResourceComponent implements OnChanges, OnDestroy { this.stillImageComponent.updateRegions(); } } + + openEditDialog() { + const dialogRef = this._dialog.open(DialogComponent, { + data: { mode: `editResource`, title: this.resource.res.label }, + }); + dialogRef.afterClosed().subscribe((answer: ConfirmationWithComment) => { + if (answer.confirmed === true && this.resource.res.label !== answer.comment) { + // update resource's label if it has changed + // get the correct lastModificationDate from the resource + this._dspApiConnection.v2.res.getResource(this.resource.res.id).subscribe((res: ReadResource) => { + const payload = new UpdateResourceMetadata(); + payload.id = this.resource.res.id; + payload.type = this.resource.res.type; + payload.lastModificationDate = res.lastModificationDate; + payload.label = answer.comment; + + this._dspApiConnection.v2.res + .updateResourceMetadata(payload) + .subscribe((response: UpdateResourceMetadataResponse) => { + this.resource.res.label = payload.label; + this.resource.res.lastModificationDate = response.lastModificationDate; + // if annotations tab is active; a label of a region has been changed --> update regions + this._componentCommsService.emit(new EmitEvent(Events.resourceChanged)); + if (this.matTabAnnotations && this.matTabAnnotations.isActive) { + this.regionChanged.emit(); + } + this._cdr.markForCheck(); + }); + }); + } + }); + } + + private _getResourceAttachedData(resource: DspResource): void { + this._actions$ + .pipe(ofActionSuccessful(GetAttachedUserAction)) + .pipe(take(1)) + .subscribe(() => { + const attachedUsers = this._store.selectSnapshot(ResourceSelectors.attachedUsers); + this.resourceAttachedUser = attachedUsers[resource.res.id].value.find( + u => u.id === resource.res.attachedToUser + ); + }); + this._store.dispatch([ + new GetAttachedUserAction(resource.res.id, resource.res.attachedToUser), + new GetAttachedProjectAction(resource.res.id, resource.res.attachedToProject), + ]); + } } diff --git a/apps/dsp-app/src/app/workspace/resource/values/boolean-value/boolean-value.component.html b/apps/dsp-app/src/app/workspace/resource/values/boolean-value/boolean-value.component.html index db8e82be63..41ca781616 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/boolean-value/boolean-value.component.html +++ b/apps/dsp-app/src/app/workspace/resource/values/boolean-value/boolean-value.component.html @@ -4,7 +4,7 @@ >{{ valueFormControl.value ? 'toggle_on' : 'toggle_off' }} - {{ commentFormControl.value }} + {{ commentFormControl.value }} diff --git a/apps/dsp-app/src/app/workspace/resource/values/boolean-value/boolean-value.component.ts b/apps/dsp-app/src/app/workspace/resource/values/boolean-value/boolean-value.component.ts index d73c673c21..0101bedce2 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/boolean-value/boolean-value.component.ts +++ b/apps/dsp-app/src/app/workspace/resource/values/boolean-value/boolean-value.component.ts @@ -1,6 +1,7 @@ import { Component, Inject, Input, OnChanges, OnDestroy, OnInit } from '@angular/core'; import { FormBuilder } from '@angular/forms'; import { CreateBooleanValue, ReadBooleanValue, UpdateBooleanValue } from '@dasch-swiss/dsp-js'; +import { Store } from '@ngxs/store'; import { BaseValueDirective } from '../../../../main/directive/base-value.directive'; @Component({ @@ -20,8 +21,11 @@ export class BooleanValueComponent extends BaseValueDirective implements OnInit, boolValIsUnset = false; // Whether there is no boolVal set at all - constructor(@Inject(FormBuilder) protected _fb: FormBuilder) { - super(); + constructor( + _store: Store, + @Inject(FormBuilder) protected _fb: FormBuilder + ) { + super(_store, _fb); } // return false as default value if there is no this.displayValue diff --git a/apps/dsp-app/src/app/workspace/resource/values/color-value/color-value.component.html b/apps/dsp-app/src/app/workspace/resource/values/color-value/color-value.component.html index 91dd6e2dfc..912c72ea87 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/color-value/color-value.component.html +++ b/apps/dsp-app/src/app/workspace/resource/values/color-value/color-value.component.html @@ -2,7 +2,7 @@
{{ valueFormControl.value }}
- {{ commentFormControl.value }} + {{ commentFormControl.value }}
diff --git a/apps/dsp-app/src/app/workspace/resource/values/color-value/color-value.component.ts b/apps/dsp-app/src/app/workspace/resource/values/color-value/color-value.component.ts index 5a2d10b662..e24d4074c8 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/color-value/color-value.component.ts +++ b/apps/dsp-app/src/app/workspace/resource/values/color-value/color-value.component.ts @@ -1,6 +1,7 @@ import { Component, Inject, Input, OnChanges, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { FormBuilder, Validators } from '@angular/forms'; import { CreateColorValue, ReadColorValue, UpdateColorValue } from '@dasch-swiss/dsp-js'; +import { Store } from '@ngxs/store'; import { BaseValueDirective } from '../../../../main/directive/base-value.directive'; import { CustomRegex } from '../custom-regex'; import { ValueErrorStateMatcher } from '../value-error-state-matcher'; @@ -22,8 +23,11 @@ export class ColorValueComponent extends BaseValueDirective implements OnInit, O matcher = new ValueErrorStateMatcher(); textColor: string; - constructor(@Inject(FormBuilder) protected _fb: FormBuilder) { - super(); + constructor( + _store: Store, + @Inject(FormBuilder) protected _fb: FormBuilder + ) { + super(_store, _fb); } getInitValue(): string | null { diff --git a/apps/dsp-app/src/app/workspace/resource/values/date-value/date-value.component.html b/apps/dsp-app/src/app/workspace/resource/values/date-value/date-value.component.html index 169f9f40fa..0ec9078c05 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/date-value/date-value.component.html +++ b/apps/dsp-app/src/app/workspace/resource/values/date-value/date-value.component.html @@ -16,7 +16,7 @@ - {{ commentFormControl.value }} + {{ commentFormControl.value }} diff --git a/apps/dsp-app/src/app/workspace/resource/values/date-value/date-value.component.ts b/apps/dsp-app/src/app/workspace/resource/values/date-value/date-value.component.ts index c22e2c50ed..dc64388b0e 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/date-value/date-value.component.ts +++ b/apps/dsp-app/src/app/workspace/resource/values/date-value/date-value.component.ts @@ -1,6 +1,7 @@ import { Component, Inject, Input, OnChanges, OnDestroy, OnInit } from '@angular/core'; import { FormBuilder } from '@angular/forms'; import { CreateDateValue, KnoraDate, KnoraPeriod, ReadDateValue, UpdateDateValue } from '@dasch-swiss/dsp-js'; +import { Store } from '@ngxs/store'; import { BaseValueDirective } from '../../../../main/directive/base-value.directive'; import { ValueErrorStateMatcher } from '../value-error-state-matcher'; @@ -21,8 +22,11 @@ export class DateValueComponent extends BaseValueDirective implements OnInit, On customValidators = []; matcher = new ValueErrorStateMatcher(); - constructor(@Inject(FormBuilder) protected _fb: FormBuilder) { - super(); + constructor( + _store: Store, + @Inject(FormBuilder) protected _fb: FormBuilder + ) { + super(_store, _fb); } /** diff --git a/apps/dsp-app/src/app/workspace/resource/values/decimal-value/decimal-value.component.html b/apps/dsp-app/src/app/workspace/resource/values/decimal-value/decimal-value.component.html index e1f6c29823..4bb4821ab2 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/decimal-value/decimal-value.component.html +++ b/apps/dsp-app/src/app/workspace/resource/values/decimal-value/decimal-value.component.html @@ -1,6 +1,6 @@ {{ valueFormControl.value }} - {{ commentFormControl.value }} + {{ commentFormControl.value }} diff --git a/apps/dsp-app/src/app/workspace/resource/values/decimal-value/decimal-value.component.ts b/apps/dsp-app/src/app/workspace/resource/values/decimal-value/decimal-value.component.ts index d6cd7760f7..9ebdb95e06 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/decimal-value/decimal-value.component.ts +++ b/apps/dsp-app/src/app/workspace/resource/values/decimal-value/decimal-value.component.ts @@ -1,6 +1,7 @@ import { Component, Inject, Input, OnChanges, OnDestroy, OnInit } from '@angular/core'; import { FormBuilder, Validators } from '@angular/forms'; import { CreateDecimalValue, ReadDecimalValue, UpdateDecimalValue } from '@dasch-swiss/dsp-js'; +import { Store } from '@ngxs/store'; import { BaseValueDirective } from '../../../../main/directive/base-value.directive'; import { CustomRegex } from '../custom-regex'; import { ValueErrorStateMatcher } from '../value-error-state-matcher'; @@ -17,8 +18,11 @@ export class DecimalValueComponent extends BaseValueDirective implements OnInit, customValidators = [Validators.pattern(CustomRegex.DECIMAL_REGEX)]; // only allow for decimal values - constructor(@Inject(FormBuilder) protected _fb: FormBuilder) { - super(); + constructor( + _store: Store, + @Inject(FormBuilder) protected _fb: FormBuilder + ) { + super(_store, _fb); } getInitValue(): number | null { diff --git a/apps/dsp-app/src/app/workspace/resource/values/geoname-value/geoname-value.component.html b/apps/dsp-app/src/app/workspace/resource/values/geoname-value/geoname-value.component.html index 48802e9a4a..4610b6bbcc 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/geoname-value/geoname-value.component.html +++ b/apps/dsp-app/src/app/workspace/resource/values/geoname-value/geoname-value.component.html @@ -4,7 +4,7 @@ >{{ ($geonameLabel | async)?.displayName }} - {{ commentFormControl.value }} + {{ commentFormControl.value }} diff --git a/apps/dsp-app/src/app/workspace/resource/values/geoname-value/geoname-value.component.ts b/apps/dsp-app/src/app/workspace/resource/values/geoname-value/geoname-value.component.ts index 8330eb65a9..5545b93178 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/geoname-value/geoname-value.component.ts +++ b/apps/dsp-app/src/app/workspace/resource/values/geoname-value/geoname-value.component.ts @@ -1,6 +1,7 @@ import { ChangeDetectorRef, Component, Inject, Input, OnChanges, OnDestroy, OnInit } from '@angular/core'; import { AbstractControl, FormBuilder, FormControl } from '@angular/forms'; import { CreateGeonameValue, ReadGeonameValue, UpdateGeonameValue } from '@dasch-swiss/dsp-js'; +import { Store } from '@ngxs/store'; import { Observable, Subscription } from 'rxjs'; import { BaseValueDirective } from '../../../../main/directive/base-value.directive'; import { DisplayPlace, GeonameService, SearchPlace } from '../../services/geoname.service'; @@ -38,11 +39,12 @@ export class GeonameValueComponent extends BaseValueDirective implements OnInit, places: SearchPlace[]; constructor( + _store: Store, @Inject(FormBuilder) protected _fb: FormBuilder, private _geonameService: GeonameService, private _cd: ChangeDetectorRef ) { - super(); + super(_store, _fb); } standardValueComparisonFunc(initValue: { id: string }, curValue: { id: string } | null): boolean { diff --git a/apps/dsp-app/src/app/workspace/resource/values/int-value/int-value.component.html b/apps/dsp-app/src/app/workspace/resource/values/int-value/int-value.component.html index e955a1303f..4557b4a584 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/int-value/int-value.component.html +++ b/apps/dsp-app/src/app/workspace/resource/values/int-value/int-value.component.html @@ -1,6 +1,6 @@ {{ valueFormControl.value }} - {{ commentFormControl.value }} + {{ commentFormControl.value }} diff --git a/apps/dsp-app/src/app/workspace/resource/values/int-value/int-value.component.ts b/apps/dsp-app/src/app/workspace/resource/values/int-value/int-value.component.ts index 4ae75f9e2d..a68cdb7bfc 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/int-value/int-value.component.ts +++ b/apps/dsp-app/src/app/workspace/resource/values/int-value/int-value.component.ts @@ -1,6 +1,7 @@ import { Component, Inject, Input, OnChanges, OnDestroy, OnInit } from '@angular/core'; import { FormBuilder, Validators } from '@angular/forms'; import { CreateIntValue, ReadIntValue, UpdateIntValue } from '@dasch-swiss/dsp-js'; +import { Store } from '@ngxs/store'; import { BaseValueDirective } from '../../../../main/directive/base-value.directive'; import { CustomRegex } from '../custom-regex'; import { ValueErrorStateMatcher } from '../value-error-state-matcher'; @@ -19,8 +20,11 @@ export class IntValueComponent extends BaseValueDirective implements OnInit, OnC customValidators = [Validators.pattern(CustomRegex.INT_REGEX)]; // only allow for integer values (no fractions) - constructor(@Inject(FormBuilder) protected _fb: FormBuilder) { - super(); + constructor( + _store: Store, + @Inject(FormBuilder) protected _fb: FormBuilder + ) { + super(_store, _fb); } getInitValue(): number | null { diff --git a/apps/dsp-app/src/app/workspace/resource/values/interval-value/interval-value.component.html b/apps/dsp-app/src/app/workspace/resource/values/interval-value/interval-value.component.html index eb2a7ced39..183c8d8584 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/interval-value/interval-value.component.html +++ b/apps/dsp-app/src/app/workspace/resource/values/interval-value/interval-value.component.html @@ -1,7 +1,7 @@ Start: {{ valueFormControl.value?.start }} End: {{ valueFormControl.value?.end }} - {{ commentFormControl.value }} + {{ commentFormControl.value }} diff --git a/apps/dsp-app/src/app/workspace/resource/values/interval-value/interval-value.component.ts b/apps/dsp-app/src/app/workspace/resource/values/interval-value/interval-value.component.ts index 150d9c2d79..f46082a8d5 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/interval-value/interval-value.component.ts +++ b/apps/dsp-app/src/app/workspace/resource/values/interval-value/interval-value.component.ts @@ -1,6 +1,7 @@ import { Component, Inject, Input, OnChanges, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { FormBuilder } from '@angular/forms'; import { CreateIntervalValue, ReadIntervalValue, UpdateIntervalValue } from '@dasch-swiss/dsp-js'; +import { Store } from '@ngxs/store'; import { BaseValueDirective } from '../../../../main/directive/base-value.directive'; import { ValueErrorStateMatcher } from '../value-error-state-matcher'; import { Interval, IntervalInputComponent } from './interval-input/interval-input.component'; @@ -19,8 +20,11 @@ export class IntervalValueComponent extends BaseValueDirective implements OnInit matcher = new ValueErrorStateMatcher(); - constructor(@Inject(FormBuilder) protected _fb: FormBuilder) { - super(); + constructor( + _store: Store, + @Inject(FormBuilder) protected _fb: FormBuilder + ) { + super(_store, _fb); } standardValueComparisonFunc(initValue: Interval, curValue: Interval | null): boolean { diff --git a/apps/dsp-app/src/app/workspace/resource/values/link-value/link-value.component.html b/apps/dsp-app/src/app/workspace/resource/values/link-value/link-value.component.html index d45ed74b5a..9abb6165da 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/link-value/link-value.component.html +++ b/apps/dsp-app/src/app/workspace/resource/values/link-value/link-value.component.html @@ -1,8 +1,11 @@ - {{ valueFormControl.value?.label }} + {{linkLabelResourceClass}}{{valueFormControl.value?.label}} - {{ commentFormControl.value }} + {{ commentFormControl.value }} diff --git a/apps/dsp-app/src/app/workspace/resource/values/link-value/link-value.component.ts b/apps/dsp-app/src/app/workspace/resource/values/link-value/link-value.component.ts index ddc312825d..c3856ea99f 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/link-value/link-value.component.ts +++ b/apps/dsp-app/src/app/workspace/resource/values/link-value/link-value.component.ts @@ -28,6 +28,7 @@ import { UpdateLinkValue, } from '@dasch-swiss/dsp-js'; import { DspApiConnectionToken } from '@dasch-swiss/vre/shared/app-config'; +import { Store } from '@ngxs/store'; import { Subscription } from 'rxjs'; import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; import { DialogComponent, DialogEvent } from '../../../../main/dialog/dialog.component'; @@ -70,14 +71,21 @@ export class LinkValueComponent extends BaseValueDirective implements OnInit, On loadingResults = false; showNoResultsMessage = false; + get linkLabelResourceClass() { + return this.displayValue.property.includes(Constants.HasStandoffLinkToValue) + ? `${this.valueFormControl.value?.resourceClassLabel}: ` + : ''; + } + constructor( + _store: Store, private _dialog: MatDialog, @Inject(FormBuilder) protected _fb: FormBuilder, @Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection, private _cd: ChangeDetectorRef ) { - super(); + super(_store, _fb); } /** diff --git a/apps/dsp-app/src/app/workspace/resource/values/list-value/list-value.component.html b/apps/dsp-app/src/app/workspace/resource/values/list-value/list-value.component.html index 34bca526df..8ae5e3b159 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/list-value/list-value.component.html +++ b/apps/dsp-app/src/app/workspace/resource/values/list-value/list-value.component.html @@ -51,7 +51,7 @@ {{ item }} - {{ commentFormControl.value }} + {{ commentFormControl.value }} diff --git a/apps/dsp-app/src/app/workspace/resource/values/list-value/list-value.component.ts b/apps/dsp-app/src/app/workspace/resource/values/list-value/list-value.component.ts index 4f6c296ab8..049ed833cb 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/list-value/list-value.component.ts +++ b/apps/dsp-app/src/app/workspace/resource/values/list-value/list-value.component.ts @@ -20,6 +20,7 @@ import { UpdateListValue, } from '@dasch-swiss/dsp-js'; import { DspApiConnectionToken } from '@dasch-swiss/vre/shared/app-config'; +import { Store } from '@ngxs/store'; import { BaseValueDirective } from '../../../../main/directive/base-value.directive'; @Component({ @@ -42,12 +43,13 @@ export class ListValueComponent extends BaseValueDirective implements OnInit, On selectedNodeHierarchy: string[] = []; constructor( + _store: Store, @Inject(FormBuilder) protected _fb: FormBuilder, @Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection, private _cd: ChangeDetectorRef ) { - super(); + super(_store, _fb); } getInitValue(): string | null { diff --git a/apps/dsp-app/src/app/workspace/resource/values/text-value/text-value-as-html/text-value-as-html.component.html b/apps/dsp-app/src/app/workspace/resource/values/text-value/text-value-as-html/text-value-as-html.component.html index 35c968fc48..0f8981f224 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/text-value/text-value-as-html/text-value-as-html.component.html +++ b/apps/dsp-app/src/app/workspace/resource/values/text-value/text-value-as-html/text-value-as-html.component.html @@ -4,4 +4,6 @@ appHtmlLink (internalLinkClicked)="internalLinkClicked.emit($event)" (internalLinkHovered)="internalLinkHovered.emit($event)">
-
{{ comment }}
+
+ {{ comment }} +
diff --git a/apps/dsp-app/src/app/workspace/resource/values/text-value/text-value-as-string/text-value-as-string.component.html b/apps/dsp-app/src/app/workspace/resource/values/text-value/text-value-as-string/text-value-as-string.component.html index 2140b5b3eb..d2e1077a5e 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/text-value/text-value-as-string/text-value-as-string.component.html +++ b/apps/dsp-app/src/app/workspace/resource/values/text-value/text-value-as-string/text-value-as-string.component.html @@ -1,8 +1,10 @@ - +
- - {{ commentFormControl.value }} - +
+ + {{ commentFormControl.value }} +
+
diff --git a/apps/dsp-app/src/app/workspace/resource/values/text-value/text-value-as-string/text-value-as-string.component.spec.ts b/apps/dsp-app/src/app/workspace/resource/values/text-value/text-value-as-string/text-value-as-string.component.spec.ts index 7ba38290b7..2238074ee0 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/text-value/text-value-as-string/text-value-as-string.component.spec.ts +++ b/apps/dsp-app/src/app/workspace/resource/values/text-value/text-value-as-string/text-value-as-string.component.spec.ts @@ -399,7 +399,6 @@ describe('TextValueAsStringComponent', () => { testHostFixture.detectChanges(); testHostComponent.mode = 'read'; - testHostComponent.inputValueComponent.shouldShowComment = true; testHostFixture.detectChanges(); diff --git a/apps/dsp-app/src/app/workspace/resource/values/text-value/text-value-as-string/text-value-as-string.component.ts b/apps/dsp-app/src/app/workspace/resource/values/text-value/text-value-as-string/text-value-as-string.component.ts index 52c33f5020..3532539142 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/text-value/text-value-as-string/text-value-as-string.component.ts +++ b/apps/dsp-app/src/app/workspace/resource/values/text-value/text-value-as-string/text-value-as-string.component.ts @@ -1,6 +1,7 @@ import { Component, Inject, Input, OnChanges, OnDestroy, OnInit } from '@angular/core'; import { FormBuilder } from '@angular/forms'; import { CreateTextValueAsString, ReadTextValueAsString, UpdateTextValueAsString } from '@dasch-swiss/dsp-js'; +import { Store } from '@ngxs/store'; import * as Editor from 'ckeditor5-custom-build'; import { BaseValueDirective } from '../../../../../main/directive/base-value.directive'; import { ValueErrorStateMatcher } from '../../value-error-state-matcher'; @@ -24,8 +25,11 @@ export class TextValueAsStringComponent extends BaseValueDirective implements On editor: Editor; editorConfig; - constructor(@Inject(FormBuilder) protected _fb: FormBuilder) { - super(); + constructor( + _store: Store, + @Inject(FormBuilder) protected _fb: FormBuilder + ) { + super(_store, _fb); } getInitValue(): string | null { diff --git a/apps/dsp-app/src/app/workspace/resource/values/text-value/text-value-as-xml/text-value-as-xml.component.html b/apps/dsp-app/src/app/workspace/resource/values/text-value/text-value-as-xml/text-value-as-xml.component.html index 13e20f8e4a..4eefc613c3 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/text-value/text-value-as-xml/text-value-as-xml.component.html +++ b/apps/dsp-app/src/app/workspace/resource/values/text-value/text-value-as-xml/text-value-as-xml.component.html @@ -1,15 +1,17 @@ -
- - {{ displayValue?.xml }} - - {{ commentFormControl.value }} +
+
+ + {{ displayValue?.xml }} + + {{ commentFormControl.value }} +
diff --git a/apps/dsp-app/src/app/workspace/resource/values/text-value/text-value-as-xml/text-value-as-xml.component.ts b/apps/dsp-app/src/app/workspace/resource/values/text-value/text-value-as-xml/text-value-as-xml.component.ts index 4440cf75c4..c45b9f2ca2 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/text-value/text-value-as-xml/text-value-as-xml.component.ts +++ b/apps/dsp-app/src/app/workspace/resource/values/text-value/text-value-as-xml/text-value-as-xml.component.ts @@ -12,6 +12,7 @@ import { } from '@angular/core'; import { FormBuilder } from '@angular/forms'; import { Constants, CreateTextValueAsXml, ReadTextValueAsXml, UpdateTextValueAsXml } from '@dasch-swiss/dsp-js'; +import { Store } from '@ngxs/store'; import * as Editor from 'ckeditor5-custom-build'; import { BaseValueDirective } from '../../../../../main/directive/base-value.directive'; import { ValueErrorStateMatcher } from '../../value-error-state-matcher'; @@ -26,7 +27,6 @@ export class TextValueAsXMLComponent extends BaseValueDirective implements OnIni @Input() displayValue?: ReadTextValueAsXml; @Output() internalLinkClicked: EventEmitter = new EventEmitter(); - @Output() internalLinkHovered: EventEmitter = new EventEmitter(); readonly standardMapping = Constants.StandardMapping; // todo: define this somewhere else @@ -51,10 +51,11 @@ export class TextValueAsXMLComponent extends BaseValueDirective implements OnIni }; constructor( + _store: Store, @Inject(FormBuilder) protected _fb: FormBuilder, private _cd: ChangeDetectorRef ) { - super(); + super(_store, _fb); } standardValueComparisonFunc(initValue: any, curValue: any): boolean { diff --git a/apps/dsp-app/src/app/workspace/resource/values/time-value/time-value.component.html b/apps/dsp-app/src/app/workspace/resource/values/time-value/time-value.component.html index 8ecf7fe992..1c48583b1e 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/time-value/time-value.component.html +++ b/apps/dsp-app/src/app/workspace/resource/values/time-value/time-value.component.html @@ -1,7 +1,7 @@ Date: {{ valueFormControl.value | date }} Time: {{ valueFormControl.value | date : 'HH:mm' }} - {{ commentFormControl.value }} + {{ commentFormControl.value }} diff --git a/apps/dsp-app/src/app/workspace/resource/values/time-value/time-value.component.ts b/apps/dsp-app/src/app/workspace/resource/values/time-value/time-value.component.ts index 493f0046d9..fba3c6a929 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/time-value/time-value.component.ts +++ b/apps/dsp-app/src/app/workspace/resource/values/time-value/time-value.component.ts @@ -1,6 +1,7 @@ -import { Component, OnInit, OnChanges, OnDestroy, ViewChild, Input, Inject } from '@angular/core'; +import { Component, Inject, Input, OnChanges, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { FormBuilder } from '@angular/forms'; -import { ReadTimeValue, CreateTimeValue, UpdateTimeValue } from '@dasch-swiss/dsp-js'; +import { CreateTimeValue, ReadTimeValue, UpdateTimeValue } from '@dasch-swiss/dsp-js'; +import { Store } from '@ngxs/store'; import { BaseValueDirective } from '../../../../main/directive/base-value.directive'; import { ValueErrorStateMatcher } from '../value-error-state-matcher'; import { TimeInputComponent } from './time-input/time-input.component'; @@ -19,8 +20,11 @@ export class TimeValueComponent extends BaseValueDirective implements OnInit, On matcher = new ValueErrorStateMatcher(); - constructor(@Inject(FormBuilder) protected _fb: FormBuilder) { - super(); + constructor( + _store: Store, + @Inject(FormBuilder) protected _fb: FormBuilder + ) { + super(_store, _fb); } getInitValue(): string | null { diff --git a/apps/dsp-app/src/app/workspace/resource/values/uri-value/uri-value.component.html b/apps/dsp-app/src/app/workspace/resource/values/uri-value/uri-value.component.html index 8020911ae2..7206064978 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/uri-value/uri-value.component.html +++ b/apps/dsp-app/src/app/workspace/resource/values/uri-value/uri-value.component.html @@ -4,7 +4,7 @@ >{{ label ? label : valueFormControl.value }} - {{ commentFormControl.value }} + {{ commentFormControl.value }} diff --git a/apps/dsp-app/src/app/workspace/resource/values/uri-value/uri-value.component.ts b/apps/dsp-app/src/app/workspace/resource/values/uri-value/uri-value.component.ts index caf80554c8..7bc63fd778 100644 --- a/apps/dsp-app/src/app/workspace/resource/values/uri-value/uri-value.component.ts +++ b/apps/dsp-app/src/app/workspace/resource/values/uri-value/uri-value.component.ts @@ -1,6 +1,7 @@ import { Component, Inject, Input, OnChanges, OnDestroy, OnInit } from '@angular/core'; import { FormBuilder, Validators } from '@angular/forms'; import { CreateUriValue, ReadUriValue, UpdateUriValue } from '@dasch-swiss/dsp-js'; +import { Store } from '@ngxs/store'; import { BaseValueDirective } from '../../../../main/directive/base-value.directive'; import { CustomRegex } from '../custom-regex'; import { ValueErrorStateMatcher } from '../value-error-state-matcher'; @@ -18,8 +19,11 @@ export class UriValueComponent extends BaseValueDirective implements OnInit, OnC customValidators = [Validators.pattern(CustomRegex.URI_REGEX)]; - constructor(@Inject(FormBuilder) protected _fb: FormBuilder) { - super(); + constructor( + _store: Store, + @Inject(FormBuilder) protected _fb: FormBuilder + ) { + super(_store, _fb); } getInitValue(): string | null { diff --git a/apps/dsp-app/src/assets/i18n/de.json b/apps/dsp-app/src/assets/i18n/de.json index 0d43bf9503..ea44ba79b6 100644 --- a/apps/dsp-app/src/assets/i18n/de.json +++ b/apps/dsp-app/src/assets/i18n/de.json @@ -137,6 +137,9 @@ "alreadyMember": "Der Benutzer ist bereits in diesem Projekt" } } + }, + "resource": { + "properties": "Eigenschaften" } } } \ No newline at end of file diff --git a/apps/dsp-app/src/assets/i18n/en.json b/apps/dsp-app/src/assets/i18n/en.json index a60412e898..8527f5a16d 100644 --- a/apps/dsp-app/src/assets/i18n/en.json +++ b/apps/dsp-app/src/assets/i18n/en.json @@ -188,6 +188,9 @@ } } } + }, + "resource": { + "properties": "Properties" } } } \ No newline at end of file diff --git a/apps/dsp-app/src/styles/_elements.scss b/apps/dsp-app/src/styles/_elements.scss index 5e931148e7..8817d203c1 100644 --- a/apps/dsp-app/src/styles/_elements.scss +++ b/apps/dsp-app/src/styles/_elements.scss @@ -247,6 +247,10 @@ a, // // resource +.dsp-representation:has(.toolbar .compoundNavigation) { + margin-bottom: 64px; +} + .dsp-representation { width: 100%; height: 70vh; @@ -254,7 +258,7 @@ a, display: block; position: relative; padding: 0; - margin-bottom: 35px; + margin-bottom: 20px; &.video, &.audio, @@ -279,7 +283,7 @@ a, .toolbar { color: $primary_50; background-color: $dark; - height: 64px; + // height: 64px; border-radius: 0px 0px 8px 8px; box-shadow: 0px 4px 4px 0px #888888; } @@ -678,7 +682,7 @@ nav.mat-mdc-tab-nav-bar, // action bubble .action-bubble { position: absolute; - right: 8px; + right: 56px; top: 3px; border: 1px solid #e4e4e4; border-radius: 14px; diff --git a/apps/dsp-app/src/styles/_layout.scss b/apps/dsp-app/src/styles/_layout.scss index f2d95f9fde..80597aeaa6 100644 --- a/apps/dsp-app/src/styles/_layout.scss +++ b/apps/dsp-app/src/styles/_layout.scss @@ -31,7 +31,7 @@ &.medium, &.large, &.extra-large { - padding: 36px 48px; + padding: 36px; } &.small { @@ -119,12 +119,13 @@ padding: 0 16px !important; // override default value height: auto !important; // override default value - .mat-expansion-panel-header-title, .mat-expansion-indicator { + .mat-expansion-panel-header-title, + .mat-expansion-indicator { color: $primary; } .mat-expansion-indicator::after { - color: $primary!important; + color: $primary !important; } } diff --git a/apps/dsp-app/src/styles/_viewer.scss b/apps/dsp-app/src/styles/_viewer.scss index a231db985b..9fcb5650eb 100644 --- a/apps/dsp-app/src/styles/_viewer.scss +++ b/apps/dsp-app/src/styles/_viewer.scss @@ -1,18 +1,39 @@ // css for parent value components +@use 'config' as *; .read-mode-view { font: 400 15px/24px sans-serif; + .show-comment { + color: $dark-text; + + .mat-icon { + font-size: 18px; + display: inline; + vertical-align: text-bottom; + } + } + .rm-value, .rm-comment { display: block; margin: 0px; } + .rm-comment { + margin-top: 6px; + font-size: small; + border-top: 1px solid rgba(33, 33, 33, 0.1); + } + .rm-value.text-value { white-space: pre-wrap; } + .rm-value.list:has(.hierarchy:only-child) .last { + font-weight: normal; + } + .rm-value.list, .rm-value .hierarchy { display: inline-flex; diff --git a/libs/vre/advanced-search/src/lib/data-access/advanced-search-store/advanced-search-store.service.ts b/libs/vre/advanced-search/src/lib/data-access/advanced-search-store/advanced-search-store.service.ts index 290ea1c446..dcccf99cbe 100644 --- a/libs/vre/advanced-search/src/lib/data-access/advanced-search-store/advanced-search-store.service.ts +++ b/libs/vre/advanced-search/src/lib/data-access/advanced-search-store/advanced-search-store.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { Constants, ListNodeV2 } from '@dasch-swiss/dsp-js'; import { ComponentStore } from '@ngrx/component-store'; import { EMPTY, Observable, of } from 'rxjs'; -import { switchMap, tap, catchError, take } from 'rxjs/operators'; +import { catchError, switchMap, take, tap } from 'rxjs/operators'; import { v4 as uuidv4 } from 'uuid'; import { AdvancedSearchService, @@ -180,6 +180,8 @@ export class AdvancedSearchStoreService extends ComponentStore !(ontology || resourceClass || propertyFormList.length) ); + defaultOntology: ApiData | undefined; + constructor( private _advancedSearchService: AdvancedSearchService, private _gravsearchService: GravsearchService @@ -691,7 +693,7 @@ export class AdvancedSearchStoreService extends ComponentStore = {}; attachedProjects: IKeyValuePairs = {}; diff --git a/libs/vre/shared/app-state/src/lib/resource/resource.state.ts b/libs/vre/shared/app-state/src/lib/resource/resource.state.ts index 7234e68b9e..e0c09a4983 100644 --- a/libs/vre/shared/app-state/src/lib/resource/resource.state.ts +++ b/libs/vre/shared/app-state/src/lib/resource/resource.state.ts @@ -5,10 +5,17 @@ import { Action, State, StateContext, Store } from '@ngxs/store'; import { map, take } from 'rxjs/operators'; import { ProjectsSelectors } from '../projects/projects.selectors'; import { UserSelectors } from '../user/user.selectors'; -import { GetAttachedProjectAction, GetAttachedUserAction } from './resource.actions'; +import { + GetAttachedProjectAction, + GetAttachedUserAction, + ToggleShowAllCommentsAction, + ToggleShowAllPropsAction, +} from './resource.actions'; import { ReourceStateModel } from './resource.state-model'; const defaults = { + showAllProps: false, + showAllComments: false, isLoading: false, attachedProjects: {}, attachedUsers: {}, @@ -26,6 +33,16 @@ export class ResourceState { private _projectApiService: ProjectApiService ) {} + @Action(ToggleShowAllPropsAction) + toggleShowAllPropsAction(ctx: StateContext) { + ctx.patchState({ showAllProps: !ctx.getState().showAllProps }); + } + + @Action(ToggleShowAllCommentsAction) + toggleShowAllCommentsAction(ctx: StateContext) { + ctx.patchState({ showAllComments: !ctx.getState().showAllComments }); + } + @Action(GetAttachedUserAction) getAttachedUser(ctx: StateContext, { resourceIri, identifier, idType }: GetAttachedUserAction) { const state = ctx.getState(); diff --git a/package.json b/package.json index 4cd1826a79..9d68617540 100644 --- a/package.json +++ b/package.json @@ -156,4 +156,4 @@ "publishConfig": { "registry": "https://npm.pkg.github.com/dasch-swiss" } -} +} \ No newline at end of file