From 13d03e4459650d95fb7cd1994eeaa10e4378ce30 Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Fri, 20 Jan 2023 14:56:29 +0200 Subject: [PATCH] Change deposit in DMPs: Modal changed to dropdown in deposit action and change DOI display --- dmp-frontend/src/app/ui/dmp/dmp.module.ts | 116 ++++++++------- .../dmp-deposit-dialog.component.html | 21 --- .../dmp-deposit-dialog.component.scss | 0 .../dmp-deposit-dialog.component.ts | 138 ----------------- .../dmp-deposit-dropdown.component.html | 13 ++ .../dmp-deposit-dropdown.component.scss | 36 +++++ .../dmp-deposit-dropdown.component.ts | 140 ++++++++++++++++++ .../dmp/overview/dmp-overview.component.html | 29 ++-- .../dmp/overview/dmp-overview.component.scss | 7 +- .../ui/dmp/overview/dmp-overview.component.ts | 138 +++++++++-------- .../ui/dmp/overview/dmp-overview.module.ts | 27 ++-- dmp-frontend/src/assets/i18n/en.json | 2 +- .../assets/images/repository-placeholder.png | Bin 0 -> 17610 bytes 13 files changed, 351 insertions(+), 316 deletions(-) delete mode 100644 dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dialog/dmp-deposit-dialog.component.html delete mode 100644 dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dialog/dmp-deposit-dialog.component.scss delete mode 100644 dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dialog/dmp-deposit-dialog.component.ts create mode 100644 dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dropdown/dmp-deposit-dropdown.component.html create mode 100644 dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dropdown/dmp-deposit-dropdown.component.scss create mode 100644 dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dropdown/dmp-deposit-dropdown.component.ts create mode 100644 dmp-frontend/src/assets/images/repository-placeholder.png diff --git a/dmp-frontend/src/app/ui/dmp/dmp.module.ts b/dmp-frontend/src/app/ui/dmp/dmp.module.ts index 16867c162..d6060d4b1 100644 --- a/dmp-frontend/src/app/ui/dmp/dmp.module.ts +++ b/dmp-frontend/src/app/ui/dmp/dmp.module.ts @@ -1,56 +1,62 @@ -import { NgModule } from '@angular/core'; -import { FormattingModule } from '@app/core/formatting.module'; -import { AutoCompleteModule } from '@app/library/auto-complete/auto-complete.module'; -import { ConfirmationDialogModule } from '@common/modules/confirmation-dialog/confirmation-dialog.module'; -import { ExportMethodDialogModule } from '@app/library/export-method-dialog/export-method-dialog.module'; -import { UrlListingModule } from '@app/library/url-listing/url-listing.module'; -import { DmpCloneComponent } from '@app/ui/dmp/clone/dmp-clone.component'; -import { DmpRoutingModule } from '@app/ui/dmp/dmp.routing'; -import { AddResearcherComponent } from '@app/ui/dmp/editor/add-researcher/add-researcher.component'; -import { AvailableProfilesComponent } from '@app/ui/dmp/editor/available-profiles/available-profiles.component'; -import { DatasetsTabComponent } from '@app/ui/dmp/editor/datasets-tab/datasets-tab.component'; -import { DmpEditorComponent } from '@app/ui/dmp/editor/dmp-editor.component'; -import { DmpFinalizeDialogComponent } from '@app/ui/dmp/editor/dmp-finalize-dialog/dmp-finalize-dialog.component'; -import { DynamicDmpFieldResolverComponent } from '@app/ui/dmp/editor/dynamic-field-resolver/dynamic-dmp-field-resolver.component'; -import { DynamicFieldGrantComponent } from '@app/ui/dmp/editor/dynamic-fields-grant/dynamic-field-grant/dynamic-field-grant.component'; -import { DynamicFieldsGrantComponent } from '@app/ui/dmp/editor/dynamic-fields-grant/dynamic-fields-grant.component'; -import { GeneralTabComponent } from '@app/ui/dmp/editor/general-tab/general-tab.component'; -import { GrantTabComponent } from '@app/ui/dmp/editor/grant-tab/grant-tab.component'; -import { PeopleTabComponent } from '@app/ui/dmp/editor/people-tab/people-tab.component'; -import { InvitationAcceptedComponent } from '@app/ui/dmp/invitation/accepted/dmp-invitation-accepted.component'; -import { DmpInvitationDialogComponent } from '@app/ui/dmp/invitation/dmp-invitation-dialog.component'; -import { DmpCriteriaComponent } from '@app/ui/dmp/listing/criteria/dmp-criteria.component'; -import { DmpUploadDialogue } from '@app/ui/dmp/listing/upload-dialogue/dmp-upload-dialogue.component'; -import { DmpListingComponent } from '@app/ui/dmp/listing/dmp-listing.component'; -import { DmpListingItemComponent } from '@app/ui/dmp/listing/listing-item/dmp-listing-item.component'; -import { DmpOverviewModule } from '@app/ui/dmp/overview/dmp-overview.module'; -import { DmpWizardComponent } from '@app/ui/dmp/wizard/dmp-wizard.component'; -import { DmpWizardEditorComponent } from '@app/ui/dmp/wizard/editor/dmp-wizard-editor.component'; -import { DmpWizardDatasetListingComponent } from '@app/ui/dmp/wizard/listing/dmp-wizard-dataset-listing.component'; -import { CommonFormsModule } from '@common/forms/common-forms.module'; -import { FormValidationErrorsDialogModule } from '@common/forms/form-validation-errors-dialog/form-validation-errors-dialog.module'; -import { CommonUiModule } from '@common/ui/common-ui.module'; -import { MultipleChoiceDialogModule } from '@common/modules/multiple-choice-dialog/multiple-choice-dialog.module'; -import { AddOrganizationComponent } from './editor/add-organization/add-organization.component'; -import { AddCostComponent } from './editor/cost-editor/add-cost/add-cost.component'; -import { CostListingComponent } from './editor/cost-editor/cost-listing/cost-listing.component'; -import { DmpCriteriaDialogComponent } from './listing/criteria/dmp-criteria-dialog.component'; -import { StartNewDmpDialogComponent } from './start-new-dmp-dialogue/start-new-dmp-dialog.component'; -import { MainInfoComponent } from './editor/main-info/main-info.component'; -import { FundingInfoComponent } from './editor/funding-info/funding-info.component'; -import { DatasetInfoComponent } from './editor/dataset-info/dataset-info.component'; -import { DatasetEditorDetailsModule } from './editor/dataset-editor-details/dataset-editor-details.module'; -import { DatasetEditorDetailsComponent } from './editor/dataset-editor-details/dataset-editor-details.component'; -import { DatasetDescriptionFormModule } from '../misc/dataset-description-form/dataset-description-form.module'; -import { LicenseInfoComponent } from './editor/license-info/license-info.component'; -import { StartNewDatasetDialogComponent } from './start-new-dataset-dialogue/start-new-dataset-dialog.component'; -import { NgxDropzoneModule } from 'ngx-dropzone'; -import { DmpToDatasetDialogComponent } from './dmp-to-dataset/dmp-to-dataset-dialog.component'; -import { FormProgressIndicationComponent } from '../misc/dataset-description-form/components/form-progress-indication/form-progress-indication.component'; -import { FormProgressIndicationModule } from '../misc/dataset-description-form/components/form-progress-indication/form-progress-indication.module'; -import { DatasetPreviewDialogComponent } from './dataset-preview/dataset-preview-dialog.component'; +import {NgModule} from '@angular/core'; +import {FormattingModule} from '@app/core/formatting.module'; +import {AutoCompleteModule} from '@app/library/auto-complete/auto-complete.module'; +import {ConfirmationDialogModule} from '@common/modules/confirmation-dialog/confirmation-dialog.module'; +import {ExportMethodDialogModule} from '@app/library/export-method-dialog/export-method-dialog.module'; +import {UrlListingModule} from '@app/library/url-listing/url-listing.module'; +import {DmpCloneComponent} from '@app/ui/dmp/clone/dmp-clone.component'; +import {DmpRoutingModule} from '@app/ui/dmp/dmp.routing'; +import {AddResearcherComponent} from '@app/ui/dmp/editor/add-researcher/add-researcher.component'; +import {AvailableProfilesComponent} from '@app/ui/dmp/editor/available-profiles/available-profiles.component'; +import {DatasetsTabComponent} from '@app/ui/dmp/editor/datasets-tab/datasets-tab.component'; +import {DmpEditorComponent} from '@app/ui/dmp/editor/dmp-editor.component'; +import {DmpFinalizeDialogComponent} from '@app/ui/dmp/editor/dmp-finalize-dialog/dmp-finalize-dialog.component'; +import { + DynamicDmpFieldResolverComponent +} from '@app/ui/dmp/editor/dynamic-field-resolver/dynamic-dmp-field-resolver.component'; +import { + DynamicFieldGrantComponent +} from '@app/ui/dmp/editor/dynamic-fields-grant/dynamic-field-grant/dynamic-field-grant.component'; +import {DynamicFieldsGrantComponent} from '@app/ui/dmp/editor/dynamic-fields-grant/dynamic-fields-grant.component'; +import {GeneralTabComponent} from '@app/ui/dmp/editor/general-tab/general-tab.component'; +import {GrantTabComponent} from '@app/ui/dmp/editor/grant-tab/grant-tab.component'; +import {PeopleTabComponent} from '@app/ui/dmp/editor/people-tab/people-tab.component'; +import {InvitationAcceptedComponent} from '@app/ui/dmp/invitation/accepted/dmp-invitation-accepted.component'; +import {DmpInvitationDialogComponent} from '@app/ui/dmp/invitation/dmp-invitation-dialog.component'; +import {DmpCriteriaComponent} from '@app/ui/dmp/listing/criteria/dmp-criteria.component'; +import {DmpUploadDialogue} from '@app/ui/dmp/listing/upload-dialogue/dmp-upload-dialogue.component'; +import {DmpListingComponent} from '@app/ui/dmp/listing/dmp-listing.component'; +import {DmpListingItemComponent} from '@app/ui/dmp/listing/listing-item/dmp-listing-item.component'; +import {DmpOverviewModule} from '@app/ui/dmp/overview/dmp-overview.module'; +import {DmpWizardComponent} from '@app/ui/dmp/wizard/dmp-wizard.component'; +import {DmpWizardEditorComponent} from '@app/ui/dmp/wizard/editor/dmp-wizard-editor.component'; +import {DmpWizardDatasetListingComponent} from '@app/ui/dmp/wizard/listing/dmp-wizard-dataset-listing.component'; +import {CommonFormsModule} from '@common/forms/common-forms.module'; +import { + FormValidationErrorsDialogModule +} from '@common/forms/form-validation-errors-dialog/form-validation-errors-dialog.module'; +import {CommonUiModule} from '@common/ui/common-ui.module'; +import {MultipleChoiceDialogModule} from '@common/modules/multiple-choice-dialog/multiple-choice-dialog.module'; +import {AddOrganizationComponent} from './editor/add-organization/add-organization.component'; +import {AddCostComponent} from './editor/cost-editor/add-cost/add-cost.component'; +import {CostListingComponent} from './editor/cost-editor/cost-listing/cost-listing.component'; +import {DmpCriteriaDialogComponent} from './listing/criteria/dmp-criteria-dialog.component'; +import {StartNewDmpDialogComponent} from './start-new-dmp-dialogue/start-new-dmp-dialog.component'; +import {MainInfoComponent} from './editor/main-info/main-info.component'; +import {FundingInfoComponent} from './editor/funding-info/funding-info.component'; +import {DatasetInfoComponent} from './editor/dataset-info/dataset-info.component'; +import {DatasetEditorDetailsModule} from './editor/dataset-editor-details/dataset-editor-details.module'; +import {DatasetEditorDetailsComponent} from './editor/dataset-editor-details/dataset-editor-details.component'; +import {DatasetDescriptionFormModule} from '../misc/dataset-description-form/dataset-description-form.module'; +import {LicenseInfoComponent} from './editor/license-info/license-info.component'; +import {StartNewDatasetDialogComponent} from './start-new-dataset-dialogue/start-new-dataset-dialog.component'; +import {NgxDropzoneModule} from 'ngx-dropzone'; +import {DmpToDatasetDialogComponent} from './dmp-to-dataset/dmp-to-dataset-dialog.component'; +import { + FormProgressIndicationModule +} from '../misc/dataset-description-form/components/form-progress-indication/form-progress-indication.module'; +import {DatasetPreviewDialogComponent} from './dataset-preview/dataset-preview-dialog.component'; import {RichTextEditorModule} from "@app/library/rich-text-editor/rich-text-editor.module"; -import { DmpDepositDialogComponent } from './editor/dmp-deposit-dialog/dmp-deposit-dialog.component'; @NgModule({ imports: [ @@ -104,8 +110,7 @@ import { DmpDepositDialogComponent } from './editor/dmp-deposit-dialog/dmp-depos FundingInfoComponent, DatasetInfoComponent, LicenseInfoComponent, - DatasetPreviewDialogComponent, - DmpDepositDialogComponent + DatasetPreviewDialogComponent ], entryComponents: [ DmpInvitationDialogComponent, @@ -121,8 +126,7 @@ import { DmpDepositDialogComponent } from './editor/dmp-deposit-dialog/dmp-depos StartNewDmpDialogComponent, StartNewDatasetDialogComponent, DatasetEditorDetailsComponent, - DatasetPreviewDialogComponent, - DmpDepositDialogComponent + DatasetPreviewDialogComponent ] }) export class DmpModule { } diff --git a/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dialog/dmp-deposit-dialog.component.html b/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dialog/dmp-deposit-dialog.component.html deleted file mode 100644 index 1ab72528e..000000000 --- a/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dialog/dmp-deposit-dialog.component.html +++ /dev/null @@ -1,21 +0,0 @@ -
-
{{ data.message }}
-
- close -
-
-
- - - -
-
{{'DMP-OVERVIEW.DEPOSIT.NO-REPOSITORIES' | translate}}
- -
-
- -
- -
\ No newline at end of file diff --git a/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dialog/dmp-deposit-dialog.component.scss b/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dialog/dmp-deposit-dialog.component.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dialog/dmp-deposit-dialog.component.ts b/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dialog/dmp-deposit-dialog.component.ts deleted file mode 100644 index 5254b633b..000000000 --- a/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dialog/dmp-deposit-dialog.component.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { Component, Inject, OnInit } from '@angular/core'; -import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { DepositConfigurationStatus } from '@app/core/common/enum/deposit-configuration-status'; -import { DepositConfigurationModel } from '@app/core/model/deposit/deposit-configuration'; -import { DmpOverviewModel } from '@app/core/model/dmp/dmp-overview'; -import { DoiModel } from '@app/core/model/doi/doi'; -import { DepositRepositoriesService } from '@app/core/services/deposit-repositories/deposit-repositories.service'; -import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service'; -import { Oauth2DialogService } from '@app/ui/misc/oauth2-dialog/service/oauth2-dialog.service'; -import { BaseComponent } from '@common/base/base.component'; -import { MultipleChoiceDialogComponent } from '@common/modules/multiple-choice-dialog/multiple-choice-dialog.component'; -import { TranslateService } from '@ngx-translate/core'; -import { takeUntil } from 'rxjs/operators'; - -@Component({ - selector: 'app-dmp-deposit-dialog', - templateUrl: './dmp-deposit-dialog.component.html', - styleUrls: ['./dmp-deposit-dialog.component.scss'] -}) -export class DmpDepositDialogComponent extends BaseComponent implements OnInit { - - inputRepos: DepositConfigurationModel[]; - outputRepos: DoiModel[]; - dmp: DmpOverviewModel; - private oauthLock: boolean; - - constructor( - private depositRepositoriesService: DepositRepositoriesService, - public dialogRef: MatDialogRef, - private dialog: MatDialog, - private language: TranslateService, - private translate: TranslateService, - private uiNotificationService: UiNotificationService, - private oauth2DialogService: Oauth2DialogService, - @Inject(MAT_DIALOG_DATA) public data: any - ) { - super(); - this.inputRepos = data['depositRepos'][0]; - this.dmp = data['depositRepos'][1]; - for(var i = 0; i < this.dmp.dois.length; i++){ - this.inputRepos = this.inputRepos.filter(r => this.hasDoi(r, this.dmp.dois, i)); - } - this.outputRepos = []; - } - - hasDoi(repo, dois, i){ - return repo.repositoryId !== dois[i].repositoryId; - } - - ngOnInit(): void { - } - - onSubmit() { - this.dialogRef.close(this.outputRepos); - } - - close() { - this.dialogRef.close(this.outputRepos); - } - - deposit(repo: DepositConfigurationModel) { - - if(repo.depositType == DepositConfigurationStatus.BothSystemAndUser){ - - const dialogRef = this.dialog.open(MultipleChoiceDialogComponent, { - maxWidth: '600px', - restoreFocus: false, - data: { - message: this.language.instant('DMP-OVERVIEW.DEPOSIT.ACCOUNT-LOGIN'), - titles: [this.language.instant('DMP-OVERVIEW.DEPOSIT.LOGIN', { 'repository': repo.repositoryId }), this.language.instant('DMP-OVERVIEW.MULTIPLE-DIALOG.USE-DEFAULT')] - } - }); - dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => { - switch (result) { - case 0: - this.showOauth2Dialog(repo.repositoryAuthorizationUrl + '?client_id=' + repo.repositoryClientId - + '&response_type=code&scope=deposit:write+deposit:actions+user:email&state=astate&redirect_uri=' - + repo.redirectUri, repo, this.dmp); - break; - case 1: - this.depositRepositoriesService.createDoi(repo.repositoryId, this.dmp.id, null) - .pipe(takeUntil(this._destroyed)) - .subscribe(doi =>{ - this.onDOICallbackSuccess(); - this.outputRepos.push(doi); - this.close(); - }, error => this.onDOICallbackError(error)); - break; - } - }); - - } - else if(repo.depositType == DepositConfigurationStatus.System){ - this.depositRepositoriesService.createDoi(repo.repositoryId, this.dmp.id, null) - .pipe(takeUntil(this._destroyed)) - .subscribe(doi =>{ - this.onDOICallbackSuccess(); - this.outputRepos.push(doi); - this.close(); - }, error => this.onDOICallbackError(error)); - } - } - - onDOICallbackSuccess(): void { - this.uiNotificationService.snackBarNotification(this.language.instant('DMP-EDITOR.SNACK-BAR.SUCCESSFUL-DOI'), SnackBarNotificationLevel.Success); - } - - onDOICallbackError(error) { - this.uiNotificationService.snackBarNotification(error.error.message ? error.error.message : this.language.instant('DMP-EDITOR.SNACK-BAR.UNSUCCESSFUL-DOI'), SnackBarNotificationLevel.Error); - } - - showOauth2Dialog(url: string, repo: DepositConfigurationModel, dmp: DmpOverviewModel) { - this.oauth2DialogService.login(url) - .pipe(takeUntil(this._destroyed)) - .subscribe(result => { - if (result !== undefined) { - if (result.oauthCode !== undefined && result.oauthCode !== null && !this.oauthLock) { - this.depositRepositoriesService.getAccessToken(repo.repositoryId, result.oauthCode) - .pipe(takeUntil(this._destroyed)) - .subscribe(token => { - this.depositRepositoriesService.createDoi(repo.repositoryId, dmp.id, token) - .pipe(takeUntil(this._destroyed)) - .subscribe(doi =>{ - this.onDOICallbackSuccess(); - this.outputRepos.push(doi); - this.close(); - }, error => this.onDOICallbackError(error)); - }); - this.oauthLock = true; - } - } - else{ - this.oauthLock = false; - } - }); - } - -} diff --git a/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dropdown/dmp-deposit-dropdown.component.html b/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dropdown/dmp-deposit-dropdown.component.html new file mode 100644 index 000000000..08206f330 --- /dev/null +++ b/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dropdown/dmp-deposit-dropdown.component.html @@ -0,0 +1,13 @@ +
+ +

{{ 'DMP-LISTING.ACTIONS.DEPOSIT' | translate }}

+
+ + + diff --git a/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dropdown/dmp-deposit-dropdown.component.scss b/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dropdown/dmp-deposit-dropdown.component.scss new file mode 100644 index 000000000..4d8681e21 --- /dev/null +++ b/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dropdown/dmp-deposit-dropdown.component.scss @@ -0,0 +1,36 @@ +.frame-btn { + border: 1px solid #212121; + color: black; + background: #ffffff; + box-shadow: 0px 2px 6px #00000029; + display: flex; + justify-content: center; + align-items: center; +} + +.frame-txt { + color: #000000; + font-size: 0.75em; + font-weight: bold; + letter-spacing: 0px; + text-transform: uppercase; + cursor: pointer; +} + +.mat-mini-fab { + width: 2.5em; + height: 2.5em; +} + +.mat-mini-fab-icon { + font-size: 1.2em; + display: flex; + justify-content: center; + align-items: center; +} + +.logo { + margin-right: 16px; + max-width: 24px; + max-height: 24px; +} diff --git a/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dropdown/dmp-deposit-dropdown.component.ts b/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dropdown/dmp-deposit-dropdown.component.ts new file mode 100644 index 000000000..aecfb4399 --- /dev/null +++ b/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dropdown/dmp-deposit-dropdown.component.ts @@ -0,0 +1,140 @@ +import {Component, EventEmitter, Inject, Input, OnInit, Output} from '@angular/core'; +import {MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog'; +import {DepositConfigurationStatus} from '@app/core/common/enum/deposit-configuration-status'; +import {DepositConfigurationModel} from '@app/core/model/deposit/deposit-configuration'; +import {DmpOverviewModel} from '@app/core/model/dmp/dmp-overview'; +import {DoiModel} from '@app/core/model/doi/doi'; +import {DepositRepositoriesService} from '@app/core/services/deposit-repositories/deposit-repositories.service'; +import { + SnackBarNotificationLevel, + UiNotificationService +} from '@app/core/services/notification/ui-notification-service'; +import {Oauth2DialogService} from '@app/ui/misc/oauth2-dialog/service/oauth2-dialog.service'; +import {BaseComponent} from '@common/base/base.component'; +import {MultipleChoiceDialogComponent} from '@common/modules/multiple-choice-dialog/multiple-choice-dialog.component'; +import {TranslateService} from '@ngx-translate/core'; +import {takeUntil} from 'rxjs/operators'; +import {DomSanitizer, SafeHtml, SafeResourceUrl} from "@angular/platform-browser"; + +@Component({ + selector: 'app-dmp-deposit-dropdown', + templateUrl: './dmp-deposit-dropdown.component.html', + styleUrls: ['./dmp-deposit-dropdown.component.scss'] +}) +export class DmpDepositDropdown extends BaseComponent implements OnInit { + @Input() + inputRepos: DepositConfigurationModel[]; + @Input() + dmp: DmpOverviewModel; + outputRepos = []; + logos: Map = new Map(); + @Output() + outputReposEmitter: EventEmitter = new EventEmitter(); + private oauthLock: boolean; + + constructor( + private depositRepositoriesService: DepositRepositoriesService, + private dialog: MatDialog, + private language: TranslateService, + private translate: TranslateService, + private uiNotificationService: UiNotificationService, + private oauth2DialogService: Oauth2DialogService, + private sanitizer: DomSanitizer + ) { + super(); + } + + hasDoi(repo, dois, i) { + return repo.repositoryId !== dois[i].repositoryId; + } + + ngOnInit(): void { + for (var i = 0; i < this.dmp.dois.length; i++) { + this.inputRepos = this.inputRepos.filter(r => this.hasDoi(r, this.dmp.dois, i)); + } + this.inputRepos.forEach(repo => { + if(repo.hasLogo) { + this.depositRepositoriesService.getLogo(repo.repositoryId).subscribe(logo => { + console.log(this.sanitizer.bypassSecurityTrustResourceUrl('data:image/png;base64,' + logo)); + this.logos.set(repo.repositoryId, this.sanitizer.bypassSecurityTrustResourceUrl('data:image/png;base64, ' + logo)); + }) + } + }) + } + + deposit(repo: DepositConfigurationModel) { + + if (repo.depositType == DepositConfigurationStatus.BothSystemAndUser) { + + const dialogRef = this.dialog.open(MultipleChoiceDialogComponent, { + maxWidth: '600px', + restoreFocus: false, + data: { + message: this.language.instant('DMP-OVERVIEW.DEPOSIT.ACCOUNT-LOGIN'), + titles: [this.language.instant('DMP-OVERVIEW.DEPOSIT.LOGIN', {'repository': repo.repositoryId}), this.language.instant('DMP-OVERVIEW.MULTIPLE-DIALOG.USE-DEFAULT')] + } + }); + dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => { + switch (result) { + case 0: + this.showOauth2Dialog(repo.repositoryAuthorizationUrl + '?client_id=' + repo.repositoryClientId + + '&response_type=code&scope=deposit:write+deposit:actions+user:email&state=astate&redirect_uri=' + + repo.redirectUri, repo, this.dmp); + break; + case 1: + this.depositRepositoriesService.createDoi(repo.repositoryId, this.dmp.id, null) + .pipe(takeUntil(this._destroyed)) + .subscribe(doi => { + this.onDOICallbackSuccess(); + this.outputRepos.push(doi); + this.outputReposEmitter.emit(this.outputRepos); + }, error => this.onDOICallbackError(error)); + break; + } + }); + + } else if (repo.depositType == DepositConfigurationStatus.System) { + this.depositRepositoriesService.createDoi(repo.repositoryId, this.dmp.id, null) + .pipe(takeUntil(this._destroyed)) + .subscribe(doi => { + this.onDOICallbackSuccess(); + this.outputRepos.push(doi); + this.outputReposEmitter.emit(this.outputRepos); + }, error => this.onDOICallbackError(error)); + } + } + + onDOICallbackSuccess(): void { + this.uiNotificationService.snackBarNotification(this.language.instant('DMP-EDITOR.SNACK-BAR.SUCCESSFUL-DOI'), SnackBarNotificationLevel.Success); + } + + onDOICallbackError(error) { + this.uiNotificationService.snackBarNotification(error.error.message ? error.error.message : this.language.instant('DMP-EDITOR.SNACK-BAR.UNSUCCESSFUL-DOI'), SnackBarNotificationLevel.Error); + } + + showOauth2Dialog(url: string, repo: DepositConfigurationModel, dmp: DmpOverviewModel) { + this.oauth2DialogService.login(url) + .pipe(takeUntil(this._destroyed)) + .subscribe(result => { + if (result !== undefined) { + if (result.oauthCode !== undefined && result.oauthCode !== null && !this.oauthLock) { + this.depositRepositoriesService.getAccessToken(repo.repositoryId, result.oauthCode) + .pipe(takeUntil(this._destroyed)) + .subscribe(token => { + this.depositRepositoriesService.createDoi(repo.repositoryId, dmp.id, token) + .pipe(takeUntil(this._destroyed)) + .subscribe(doi => { + this.onDOICallbackSuccess(); + this.outputRepos.push(doi); + this.outputReposEmitter.emit(this.outputRepos); + }, error => this.onDOICallbackError(error)); + }); + this.oauthLock = true; + } + } else { + this.oauthLock = false; + } + }); + } + +} diff --git a/dmp-frontend/src/app/ui/dmp/overview/dmp-overview.component.html b/dmp-frontend/src/app/ui/dmp/overview/dmp-overview.component.html index ef03de5fe..3fd9c3472 100644 --- a/dmp-frontend/src/app/ui/dmp/overview/dmp-overview.component.html +++ b/dmp-frontend/src/app/ui/dmp/overview/dmp-overview.component.html @@ -112,13 +112,19 @@
-

{{'DMP-EDITOR.TITLE.SUBTITLE' | translate}}

-
- - - {{doi.repositoryId}} - - +
+ {{'DMP-EDITOR.TITLE.SUBTITLE' | translate}}: + + + + {{doi.repositoryId}} + + + +
+ +
+ {{selectedModel.doi}}
-
- -

{{ 'DMP-LISTING.ACTIONS.DEPOSIT' | translate }}

-
-
+ +
diff --git a/dmp-frontend/src/app/ui/dmp/overview/dmp-overview.component.scss b/dmp-frontend/src/app/ui/dmp/overview/dmp-overview.component.scss index 200ae686d..19f35902f 100644 --- a/dmp-frontend/src/app/ui/dmp/overview/dmp-overview.component.scss +++ b/dmp-frontend/src/app/ui/dmp/overview/dmp-overview.component.scss @@ -195,8 +195,7 @@ .doi-label { font-size: 1em; - color: #212121; - opacity: 0.6; + color: rgba(33, 33, 33, 0.6); margin-bottom: 0.3em; } @@ -337,6 +336,10 @@ padding: 0rem 0rem 0.4rem 0rem !important; } +.select-repo { + border-bottom: 1px solid #212121; +} + // .card-content { // display: flex; // justify-content: center; diff --git a/dmp-frontend/src/app/ui/dmp/overview/dmp-overview.component.ts b/dmp-frontend/src/app/ui/dmp/overview/dmp-overview.component.ts index 6848e176e..0778658bf 100644 --- a/dmp-frontend/src/app/ui/dmp/overview/dmp-overview.component.ts +++ b/dmp-frontend/src/app/ui/dmp/overview/dmp-overview.component.ts @@ -1,51 +1,52 @@ - -import { Component, OnInit, ViewChild, ElementRef } from '@angular/core'; -import { MatDialog } from '@angular/material/dialog'; -import { ActivatedRoute, Params, Router } from '@angular/router'; -import { DatasetStatus } from '@app/core/common/enum/dataset-status'; -import { DmpStatus } from '@app/core/common/enum/dmp-status'; -import { Principal } from '@app/core/model/auth/principal'; -import { DatasetOverviewModel } from '@app/core/model/dataset/dataset-overview'; -import { DatasetsToBeFinalized } from '@app/core/model/dataset/datasets-toBeFinalized'; -import { DmpOverviewModel } from '@app/core/model/dmp/dmp-overview'; -import { UserInfoListingModel } from '@app/core/model/user/user-info-listing'; -import { AuthService } from '@app/core/services/auth/auth.service'; -import { DmpService } from '@app/core/services/dmp/dmp.service'; -import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service'; -import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component'; -import { DmpFinalizeDialogComponent, DmpFinalizeDialogInput, DmpFinalizeDialogOutput } from '@app/ui/dmp/editor/dmp-finalize-dialog/dmp-finalize-dialog.component'; -import { BreadcrumbItem } from '@app/ui/misc/breadcrumb/definition/breadcrumb-item'; -import { BaseComponent } from '@common/base/base.component'; -import { TranslateService } from '@ngx-translate/core'; +import {Component, ElementRef, OnInit, ViewChild} from '@angular/core'; +import {MatDialog} from '@angular/material/dialog'; +import {ActivatedRoute, Params, Router} from '@angular/router'; +import {DatasetStatus} from '@app/core/common/enum/dataset-status'; +import {DmpStatus} from '@app/core/common/enum/dmp-status'; +import {Principal} from '@app/core/model/auth/principal'; +import {DatasetOverviewModel} from '@app/core/model/dataset/dataset-overview'; +import {DatasetsToBeFinalized} from '@app/core/model/dataset/datasets-toBeFinalized'; +import {DmpOverviewModel} from '@app/core/model/dmp/dmp-overview'; +import {UserInfoListingModel} from '@app/core/model/user/user-info-listing'; +import {AuthService} from '@app/core/services/auth/auth.service'; +import {DmpService} from '@app/core/services/dmp/dmp.service'; +import { + SnackBarNotificationLevel, + UiNotificationService +} from '@app/core/services/notification/ui-notification-service'; +import {ConfirmationDialogComponent} from '@common/modules/confirmation-dialog/confirmation-dialog.component'; +import { + DmpFinalizeDialogComponent, + DmpFinalizeDialogInput, + DmpFinalizeDialogOutput +} from '@app/ui/dmp/editor/dmp-finalize-dialog/dmp-finalize-dialog.component'; +import {BreadcrumbItem} from '@app/ui/misc/breadcrumb/definition/breadcrumb-item'; +import {BaseComponent} from '@common/base/base.component'; +import {TranslateService} from '@ngx-translate/core'; import * as FileSaver from 'file-saver'; -import { Observable, of as observableOf } from 'rxjs'; -import { takeUntil, map } from 'rxjs/operators'; -import { Role } from "@app/core/common/enum/role"; -import { DmpInvitationDialogComponent } from '../invitation/dmp-invitation-dialog.component'; -import { ConfigurationService } from '@app/core/services/configuration/configuration.service'; -import { Oauth2DialogService } from '@app/ui/misc/oauth2-dialog/service/oauth2-dialog.service'; -import { UserService } from '@app/core/services/user/user.service'; -import { Location } from '@angular/common'; -import { FormGroup } from '@angular/forms'; -import { LockService } from '@app/core/services/lock/lock.service'; -import { VersionListingModel } from '@app/core/model/version/version-listing.model'; -import { CloneDialogComponent } from '../clone/clone-dialog/clone-dialog.component'; -import { DmpModel } from '@app/core/model/dmp/dmp'; -import { DmpEditorModel } from '../editor/dmp-editor.model'; -import { FunderFormModel } from '../editor/grant-tab/funder-form-model'; -import { ProjectFormModel } from '../editor/grant-tab/project-form-model'; -import { GrantTabModel } from '../editor/grant-tab/grant-tab-model'; -import { ExtraPropertiesFormModel } from '../editor/general-tab/extra-properties-form.model'; -import { StartNewDmpDialogComponent } from '../start-new-dmp-dialogue/start-new-dmp-dialog.component'; -import { HttpClient } from '@angular/common/http'; -import { MatomoService } from '@app/core/services/matomo/matomo-service'; -import { PopupNotificationDialogComponent } from '@app/library/notification/popup/popup-notification.component'; -import { DepositRepositoriesService } from '@app/core/services/deposit-repositories/deposit-repositories.service'; -import { DmpDepositDialogComponent } from '../editor/dmp-deposit-dialog/dmp-deposit-dialog.component'; -import { DepositConfigurationModel } from '@app/core/model/deposit/deposit-configuration'; -import { DoiModel } from '@app/core/model/doi/doi'; -import { MatSelect } from '@angular/material/select'; -import { isNullOrUndefined } from '@app/utilities/enhancers/utils'; +import {Observable, of as observableOf} from 'rxjs'; +import {map, takeUntil} from 'rxjs/operators'; +import {Role} from "@app/core/common/enum/role"; +import {DmpInvitationDialogComponent} from '../invitation/dmp-invitation-dialog.component'; +import {ConfigurationService} from '@app/core/services/configuration/configuration.service'; +import {Location} from '@angular/common'; +import {FormGroup} from '@angular/forms'; +import {LockService} from '@app/core/services/lock/lock.service'; +import {VersionListingModel} from '@app/core/model/version/version-listing.model'; +import {CloneDialogComponent} from '../clone/clone-dialog/clone-dialog.component'; +import {DmpModel} from '@app/core/model/dmp/dmp'; +import {DmpEditorModel} from '../editor/dmp-editor.model'; +import {FunderFormModel} from '../editor/grant-tab/funder-form-model'; +import {ProjectFormModel} from '../editor/grant-tab/project-form-model'; +import {GrantTabModel} from '../editor/grant-tab/grant-tab-model'; +import {ExtraPropertiesFormModel} from '../editor/general-tab/extra-properties-form.model'; +import {StartNewDmpDialogComponent} from '../start-new-dmp-dialogue/start-new-dmp-dialog.component'; +import {MatomoService} from '@app/core/services/matomo/matomo-service'; +import {PopupNotificationDialogComponent} from '@app/library/notification/popup/popup-notification.component'; +import {DepositRepositoriesService} from '@app/core/services/deposit-repositories/deposit-repositories.service'; +import {DepositConfigurationModel} from '@app/core/model/deposit/deposit-configuration'; +import {DoiModel} from '@app/core/model/doi/doi'; +import {isNullOrUndefined} from '@app/utilities/enhancers/utils'; @Component({ selector: 'app-dmp-overview', @@ -68,6 +69,7 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit { textMessage: any; versions: VersionListingModel[]; version: VersionListingModel; + selectedModel: DoiModel; @ViewChild('doi') doi: ElementRef; @@ -109,6 +111,9 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit { .pipe(takeUntil(this._destroyed)) .subscribe(data => { this.dmp = data; + if(!this.hasDoi()) { + this.selectedModel = this.dmp.dois[0]; + } this.checkLockStatus(this.dmp.id); this.setIsUserOwner(); this.getAllVersions(this.dmp); @@ -133,6 +138,9 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit { .pipe(takeUntil(this._destroyed)) .subscribe(data => { this.dmp = data; + if(!this.hasDoi()) { + this.selectedModel = this.dmp.dois[0]; + } // this.checkLockStatus(this.dmp.id); this.getAllVersions(this.dmp); const breadCrumbs = []; @@ -454,8 +462,8 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit { return (dmp.status == DmpStatus.Finalized && dmp.isPublic); } - hasDoi(dmp: DmpOverviewModel) { - return (this.dmp.dois == null || this.dmp.dois.length == 0) ? true : false; + hasDoi(dmp: DmpOverviewModel = null) { + return (this.dmp.dois == null || this.dmp.dois.length == 0); } getAllVersions(dmp: DmpOverviewModel) { @@ -494,29 +502,15 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit { }); } - deposit(){ - const dialogRef = this.dialog.open(DmpDepositDialogComponent, { - maxWidth: '600px', - disableClose: true, - restoreFocus: false, - autoFocus: false, - data: { - depositRepos: [this.depositRepos, this.dmp], - message: this.language.instant('DMP-OVERVIEW.DEPOSIT.SELECT-REPOSITORIES'), - confirmButton: this.language.instant('DMP-OVERVIEW.DEPOSIT.AUTHORIZE'), - cancelButton: this.language.instant('DMP-OVERVIEW.DEPOSIT.CANCEL'), - } - }); - dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe((result: DoiModel[]) => { - if (result.length > 0) { - this.dmp.dois.push(...result); - this.hasDOIToken = true; - } - }); + afterDeposit(result: DoiModel[]) { + if (result.length > 0) { + this.dmp.dois.push(...result); + this.hasDOIToken = true; + } } moreDeposit(){ - return (this.dmp.dois.length < this.depositRepos.length) ? true : false; + return (this.dmp.dois.length < this.depositRepos.length); } finalize(dmp: DmpOverviewModel) { @@ -537,7 +531,7 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit { }), accessRights: extraProperties.visible } - + const dialogRef = this.dialog.open(DmpFinalizeDialogComponent, { maxWidth: '500px', restoreFocus: false, @@ -642,8 +636,8 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit { this.router.navigate(['/datasets', 'new', this.dmp.id]); } - selectDoi(s: MatSelect, doiModel: DoiModel){ - s.placeholder = doiModel.doi; + selectDoi(doiModel: DoiModel){ + this.selectedModel = doiModel; const foundIdx = this.dmp.dois.findIndex(el => el == doiModel); this.dmp.dois.splice(foundIdx, 1); this.dmp.dois.unshift(doiModel); diff --git a/dmp-frontend/src/app/ui/dmp/overview/dmp-overview.module.ts b/dmp-frontend/src/app/ui/dmp/overview/dmp-overview.module.ts index dc73b4f76..53561af58 100644 --- a/dmp-frontend/src/app/ui/dmp/overview/dmp-overview.module.ts +++ b/dmp-frontend/src/app/ui/dmp/overview/dmp-overview.module.ts @@ -10,21 +10,24 @@ import { CommonUiModule } from '@common/ui/common-ui.module'; import { RouterModule } from '@angular/router'; import { CloneDialogModule } from '../clone/clone-dialog/clone-dialog.module'; import { NgDialogAnimationService } from 'ng-dialog-animation'; +import {DmpModule} from "@app/ui/dmp/dmp.module"; +import {DmpDepositDropdown} from "@app/ui/dmp/editor/dmp-deposit-dropdown/dmp-deposit-dropdown.component"; @NgModule({ - imports: [ - CommonUiModule, - CommonFormsModule, - UrlListingModule, - ConfirmationDialogModule, - CloneDialogModule, - ExportMethodDialogModule, - FormattingModule, - AutoCompleteModule, - RouterModule - ], + imports: [ + CommonUiModule, + CommonFormsModule, + UrlListingModule, + ConfirmationDialogModule, + CloneDialogModule, + ExportMethodDialogModule, + FormattingModule, + AutoCompleteModule, + RouterModule + ], declarations: [ - DmpOverviewComponent + DmpOverviewComponent, + DmpDepositDropdown ], providers: [ NgDialogAnimationService diff --git a/dmp-frontend/src/assets/i18n/en.json b/dmp-frontend/src/assets/i18n/en.json index 0e7336915..da9ec99c6 100644 --- a/dmp-frontend/src/assets/i18n/en.json +++ b/dmp-frontend/src/assets/i18n/en.json @@ -1017,7 +1017,7 @@ "CLONE-DMP": "Clone", "NEW-VERSION": "New Version", "CREATE-DATASET": "Creating Dataset", - "SUBTITLE": "DOI", + "SUBTITLE": "DOI provided by", "PREVIEW-DATASET": "Previewing Dataset" }, "FIELDS": { diff --git a/dmp-frontend/src/assets/images/repository-placeholder.png b/dmp-frontend/src/assets/images/repository-placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..c5f84947a0d0ccb798ba392ac8bc915f12b0bc01 GIT binary patch literal 17610 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